Unity项目中泛型与非泛型UnityEvent集合实现技术咨询
ItemAction in Unity Item Class Hey there! Let's break down how to solve this problem neatly. Since you've got two flavors of ItemAction—one wrapping a plain UnityEvent and another wrapping UnityEvent<T>—the key is to give them a common base type so your abstract Item class can hold both in a single collection. Here's a step-by-step implementation:
1. Create a Shared Abstract Base Class
First, define an abstract base class that both ItemAction variants will inherit from. This gives us a unified type to use in our collection, plus core functionality for invoking actions and type safety checks:
using System; using UnityEngine; public abstract class BaseItemAction : ScriptableObject // Remove ScriptableObject if your actions are plain C# classes { // Returns the required parameter type (null for non-generic actions) public abstract Type ParameterType { get; } // Generic invoke method to handle both non-generic and generic cases public abstract void Invoke(object parameter = null); }
2. Update Your Existing ItemAction Classes
Modify your non-generic and generic ItemAction classes to inherit from BaseItemAction, implementing the required methods to fit the unified interface:
Non-Generic ItemAction (Wraps UnityEvent)
using UnityEngine; using UnityEngine.Events; public class ItemAction : BaseItemAction { [SerializeField] private UnityEvent _onTriggered; public override Type ParameterType => null; public override void Invoke(object parameter = null) { if (parameter != null) { Debug.LogWarning("Non-generic ItemAction received an unexpected parameter; ignoring it."); } _onTriggered?.Invoke(); } // Optional: Helper method for cleaner parameterless calls public void Invoke() => _onTriggered?.Invoke(); }
Generic ItemAction<T> (Wraps UnityEvent<T>)
using System; using UnityEngine; using UnityEngine.Events; public class ItemAction<T> : BaseItemAction { [SerializeField] private UnityEvent<T> _onTriggered; public override Type ParameterType => typeof(T); public override void Invoke(object parameter = null) { if (parameter is T typedParameter) { _onTriggered?.Invoke(typedParameter); } else if (parameter != null) { Debug.LogWarning($"Invalid parameter type for ItemAction<{typeof(T).Name}>: expected {typeof(T).Name}, got {parameter.GetType().Name}"); } else { Debug.LogWarning($"ItemAction<{typeof(T).Name}> requires a parameter of type {typeof(T).Name}, but received null."); } } // Optional: Strongly-typed helper method for safer, cleaner calls public void Invoke(T parameter) => _onTriggered?.Invoke(parameter); }
3. Add the Unified Collection to Your Abstract Item Class
Now you can use a List<BaseItemAction> in your Item class to hold both types of actions. We'll also add flexible helper methods to trigger actions in different scenarios:
using System.Collections.Generic; using System.Linq; using UnityEngine; public abstract class Item : ScriptableObject // Adjust if your Item is a plain C# class { [SerializeField] private List<BaseItemAction> _itemActions = new List<BaseItemAction>(); // Trigger all actions (non-generic run, generic ones warn if no parameter is passed) public void TriggerAllActions() { foreach (var action in _itemActions) { action.Invoke(); } } // Trigger all actions with a parameter (only matching generic actions run correctly) public void TriggerAllActions<T>(T parameter) { foreach (var action in _itemActions) { action.Invoke(parameter); } } // Trigger only non-generic actions public void TriggerNonGenericActions() { foreach (var action in _itemActions.OfType<ItemAction>()) { action.Invoke(); } } // Trigger only generic actions of a specific type public void TriggerGenericActions<T>(T parameter) { foreach (var action in _itemActions.OfType<ItemAction<T>>()) { action.Invoke(parameter); } } // Smart trigger: Automatically match parameters to compatible action types public void TriggerActionsWithParameter(object parameter) { if (parameter == null) { TriggerNonGenericActions(); return; } foreach (var action in _itemActions) { if (action.ParameterType == null) { action.Invoke(); } else if (action.ParameterType.IsAssignableFrom(parameter.GetType())) { action.Invoke(parameter); } else { Debug.LogWarning($"Skipping action {action.name} - parameter type mismatch."); } } } }
Key Notes
- Type Safety: The strongly-typed
Invoke(T)method inItemAction<T>lets you call generic actions directly without casting, while the baseInvoke(object)handles unified collection calls. - Debugging: Built-in warning messages help catch mismatched parameters or missing inputs during development.
- Extensibility: You can add more functionality to the
BaseItemActionclass (like enabling/disabling actions) that both variants will inherit automatically.
内容的提问来源于stack exchange,提问作者J. Doe

