前言
一、委托(Delegate)基础
对惹,这里有一 个游戏开发交流小组,希望大家可以点击进来一起交流一下开发经验呀!
1.1 委托的定义与使用
// 1. 定义委托类型
public delegate void SimpleDelegate();
public delegate void ParameterDelegate(string message);
public delegate int CalculateDelegate(int a, int b);
// 2. 使用委托
public class DelegateExample : MonoBehaviour
{
private SimpleDelegate myDelegate;
private ParameterDelegate paramDelegate;
void Start()
{
// 3. 赋值方法
myDelegate = PrintHello;
paramDelegate = PrintMessage;
// 4. 调用委托
myDelegate?.Invoke();
paramDelegate?.Invoke("Hello World");
// 5. 多播委托(多个方法)
myDelegate += PrintWorld;
myDelegate += () => Debug.Log("Lambda表达式");
myDelegate?.Invoke();
// 6. 移除方法
myDelegate -= PrintHello;
}
void PrintHello()
{
Debug.Log("Hello");
}
void PrintWorld()
{
Debug.Log("World");
}
void PrintMessage(string msg)
{
Debug.Log(msg);
}
}
1.2 Unity内置委托类型
public class UnityBuiltInDelegates : MonoBehaviour
{
// UnityAction - 无返回值委托
private UnityAction unityAction;
private UnityAction<int, float> paramAction;
// UnityEvent - 序列化的事件
public UnityEvent onEventTriggered;
public UnityEvent<string> onMessageEvent;
void Start()
{
// UnityAction用法
unityAction = () => Debug.Log("Action triggered");
paramAction = (x, y) => Debug.Log($"x={x}, y={y}");
// 添加监听
onEventTriggered.AddListener(OnTriggered);
onMessageEvent.AddListener(OnMessageReceived);
// 触发
onEventTriggered?.Invoke();
onMessageEvent?.Invoke("Test Message");
}
void OnTriggered()
{
Debug.Log("事件被触发");
}
void OnMessageReceived(string msg)
{
Debug.Log($"收到消息: {msg}");
}
void OnDestroy()
{
// 重要:清理监听
onEventTriggered.RemoveAllListeners();
onMessageEvent.RemoveAllListeners();
}
}
二、事件(Event)高级用法
2.1 标准事件模式
// 1. 定义事件参数类
public class GameEventArgs : EventArgs
{
public string EventName { get; }
public object Data { get; }
public DateTime Timestamp { get; }
public GameEventArgs(string name, object data)
{
EventName = name;
Data = data;
Timestamp = DateTime.Now;
}
}
// 2. 事件发布者
public class EventPublisher : MonoBehaviour
{
// 定义事件(使用EventHandler标准模式)
public event EventHandler<GameEventArgs> OnGameEvent;
public event EventHandler OnSimpleEvent;
// 触发事件的保护方法
protected virtual void RaiseGameEvent(string eventName, object data)
{
OnGameEvent?.Invoke(this, new GameEventArgs(eventName, data));
}
// 示例:触发事件
public void PlayerDied()
{
RaiseGameEvent("PlayerDied", new { score = 100, position = transform.position });
}
public void LevelCompleted()
{
OnSimpleEvent?.Invoke(this, EventArgs.Empty);
}
}
2.2 事件订阅者
public class EventSubscriber : MonoBehaviour
{
[SerializeField] private EventPublisher publisher;
void OnEnable()
{
if (publisher != null)
{
publisher.OnGameEvent += HandleGameEvent;
publisher.OnSimpleEvent += HandleSimpleEvent;
}
}
void OnDisable()
{
// 重要:避免内存泄漏
if (publisher != null)
{
publisher.OnGameEvent -= HandleGameEvent;
publisher.OnSimpleEvent -= HandleSimpleEvent;
}
}
void HandleGameEvent(object sender, GameEventArgs e)
{
Debug.Log($"事件: {e.EventName}, 数据: {e.Data}, 时间: {e.Timestamp}");
switch (e.EventName)
{
case "PlayerDied":
HandlePlayerDeath(e.Data);
break;
case "ItemCollected":
HandleItemCollection(e.Data);
break;
}
}
void HandleSimpleEvent(object sender, EventArgs e)
{
Debug.Log("简单事件触发");
}
void HandlePlayerDeath(object data)
{
// 处理玩家死亡逻辑
Debug.Log("玩家死亡处理");
}
void HandleItemCollection(object data)
{
// 处理物品收集
}
}
三、Unity中的实际应用场景
3.1 UI事件系统
public class UIEventHandler : MonoBehaviour
{
// 自定义UI事件
public event Action<Button> OnButtonClicked;
public event Action<Slider, float> OnSliderValueChanged;
public event Action<Toggle, bool> OnToggleChanged;
[SerializeField] private Button playButton;
[SerializeField] private Slider volumeSlider;
[SerializeField] private Toggle soundToggle;
void Start()
{
// Unity UI组件事件转换为自定义事件
playButton.onClick.AddListener(() => OnButtonClicked?.Invoke(playButton));
volumeSlider.onValueChanged.AddListener(value =>
OnSliderValueChanged?.Invoke(volumeSlider, value));
soundToggle.onValueChanged.AddListener(isOn =>
OnToggleChanged?.Invoke(soundToggle, isOn));
}
}
3.2 游戏状态管理
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
// 游戏状态事件
public event Action<GameState> OnGameStateChanged;
public event Action<int> OnScoreChanged;
public event Action<int> OnPlayerHealthChanged;
public enum GameState { Menu, Playing, Paused, GameOver }
private GameState currentState;
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
public void ChangeGameState(GameState newState)
{
currentState = newState;
OnGameStateChanged?.Invoke(newState);
switch (newState)
{
case GameState.Playing:
StartGame();
break;
case GameState.GameOver:
EndGame();
break;
}
}
public void AddScore(int points)
{
// 分数逻辑...
OnScoreChanged?.Invoke(currentScore);
}
// 其他方法...
}
3.3 成就系统
public class AchievementSystem : MonoBehaviour
{
// 成就事件
public event Action<string> OnAchievementUnlocked;
public event Action<int> OnTotalAchievementsChanged;
private Dictionary<string, bool> achievements = new Dictionary<string, bool>();
public void UnlockAchievement(string achievementId)
{
if (!achievements.ContainsKey(achievementId) || !achievements[achievementId])
{
achievements[achievementId] = true;
OnAchievementUnlocked?.Invoke(achievementId);
OnTotalAchievementsChanged?.Invoke(achievements.Count(a => a.Value));
// 显示成就UI等
Debug.Log($"成就解锁: {achievementId}");
}
}
}
// 成就触发器
public class AchievementTrigger : MonoBehaviour
{
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
AchievementSystem.Instance?.OnAchievementUnlocked?.Invoke("First_Secret_Found");
}
}
}
四、高级模式与最佳实践
4.1 事件总线模式
public class EventBus : MonoBehaviour
{
private static EventBus instance;
public static EventBus Instance => instance;
// 事件字典
private Dictionary<Type, Delegate> eventTable = new Dictionary<Type, Delegate>();
void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
}
// 订阅事件
public void Subscribe<T>(Action<T> handler) where T : struct
{
Type eventType = typeof(T);
if (!eventTable.ContainsKey(eventType))
{
eventTable[eventType] = handler;
}
else
{
eventTable[eventType] = Delegate.Combine(eventTable[eventType], handler);
}
}
// 取消订阅
public void Unsubscribe<T>(Action<T> handler) where T : struct
{
Type eventType = typeof(T);
if (eventTable.ContainsKey(eventType))
{
eventTable[eventType] = Delegate.Remove(eventTable[eventType], handler);
}
}
// 发布事件
public void Publish<T>(T eventData) where T : struct
{
Type eventType = typeof(T);
if (eventTable.ContainsKey(eventType) && eventTable[eventType] != null)
{
(eventTable[eventType] as Action<T>)?.Invoke(eventData);
}
}
}
// 使用事件总线
public struct PlayerDiedEvent
{
public Vector3 DeathPosition;
public int RemainingLives;
public string KillerName;
}
public class PlayerController : MonoBehaviour
{
void Die()
{
var deathEvent = new PlayerDiedEvent
{
DeathPosition = transform.position,
RemainingLives = currentLives,
KillerName = "Enemy"
};
EventBus.Instance.Publish(deathEvent);
}
}
public class DeathEffectManager : MonoBehaviour
{
void OnEnable()
{
EventBus.Instance.Subscribe<PlayerDiedEvent>(OnPlayerDied);
}
void OnDisable()
{
EventBus.Instance.Unsubscribe<PlayerDiedEvent>(OnPlayerDied);
}
void OnPlayerDied(PlayerDiedEvent evt)
{
// 播放死亡特效
Instantiate(deathEffect, evt.DeathPosition, Quaternion.identity);
}
}
4.2 观察者模式实现
// 可观察对象接口
public interface IObservable<T>
{
void AddObserver(IObserver<T> observer);
void RemoveObserver(IObserver<T> observer);
void NotifyObservers(T data);
}
// 观察者接口
public interface IObserver<T>
{
void OnNotify(T data);
}
// 具体实现
public class HealthSystem : MonoBehaviour, IObservable<int>
{
private List<IObserver<int>> observers = new List<IObserver<int>>();
private int currentHealth = 100;
public void AddObserver(IObserver<int> observer)
{
if (!observers.Contains(observer))
{
observers.Add(observer);
}
}
public void RemoveObserver(IObserver<int> observer)
{
observers.Remove(observer);
}
public void NotifyObservers(int health)
{
foreach (var observer in observers)
{
observer.OnNotify(health);
}
}
public void TakeDamage(int damage)
{
currentHealth -= damage;
currentHealth = Mathf.Max(0, currentHealth);
NotifyObservers(currentHealth);
}
}
public class HealthUI : MonoBehaviour, IObserver<int>
{
[SerializeField] private Slider healthSlider;
[SerializeField] private Text healthText;
void Start()
{
var healthSystem = FindObjectOfType<HealthSystem>();
if (healthSystem != null)
{
healthSystem.AddObserver(this);
}
}
public void OnNotify(int health)
{
healthSlider.value = health / 100f;
healthText.text = $"HP: {health}";
if (health < 30)
{
// 低血量警告
StartCoroutine(FlashWarning());
}
}
IEnumerator FlashWarning()
{
// 闪烁效果
healthText.color = Color.red;
yield return new WaitForSeconds(0.5f);
healthText.color = Color.white;
}
void OnDestroy()
{
var healthSystem = FindObjectOfType<HealthSystem>();
if (healthSystem != null)
{
healthSystem.RemoveObserver(this);
}
}
}
五、性能优化与注意事项
5.1 性能优化技巧
public class OptimizedEventSystem : MonoBehaviour
{
// 1. 使用缓存减少委托分配
private UnityAction cachedAction;
void Start()
{
cachedAction = DoSomething;
// 避免在循环中创建新委托
for (int i = 0; i < 100; i++)
{
someEvent.AddListener(cachedAction); // 好
// someEvent.AddListener(() => DoSomething()); // 不好,每次创建新委托
}
}
// 2. 使用对象池管理事件参数
public class EventDataPool
{
private static ObjectPool<GameEventArgs> pool =
new ObjectPool<GameEventArgs>(() => new GameEventArgs(), 10);
public static GameEventArgs Get(string name, object data)
{
var args = pool.Get();
// 初始化...
return args;
}
public static void Release(GameEventArgs args)
{
pool.Release(args);
}
}
// 3. 避免频繁的事件触发
private float lastEventTime;
private const float EVENT_COOLDOWN = 0.1f;
public void TryTriggerEvent()
{
if (Time.time - lastEventTime > EVENT_COOLDOWN)
{
lastEventTime = Time.time;
OnEvent?.Invoke();
}
}
}
5.2 常见陷阱与解决方案
public class EventPitfalls : MonoBehaviour
{
// 陷阱1:忘记取消订阅(内存泄漏)
private void SubscribeAndForget()
{
SomeClass.Instance.OnEvent += HandleEvent;
// 如果不在适当时候取消订阅,即使对象被销毁,委托仍然持有引用
}
// 解决方案:在OnDisable或OnDestroy中取消订阅
void OnEnable()
{
SomeClass.Instance.OnEvent += HandleEvent;
}
void OnDisable()
{
SomeClass.Instance.OnEvent -= HandleEvent;
}
// 陷阱2:空引用检查
public event Action OnUnsafeEvent;
void TriggerUnsafeEvent()
{
OnUnsafeEvent(); // 如果没有订阅者会抛出NullReferenceException
}
// 解决方案:使用空条件运算符
void TriggerSafeEvent()
{
OnUnsafeEvent?.Invoke();
}
// 陷阱3:线程安全问题
private event Action OnThreadEvent;
private readonly object lockObject = new object();
public void ThreadSafeSubscribe(Action handler)
{
lock (lockObject)
{
OnThreadEvent += handler;
}
}
public void ThreadSafeInvoke()
{
Action localCopy;
lock (lockObject)
{
localCopy = OnThreadEvent;
}
localCopy?.Invoke();
}
// 陷阱4:事件链导致无限递归
private bool isHandlingEvent = false;
void HandleEventA()
{
if (isHandlingEvent) return;
isHandlingEvent = true;
// 处理事件...
OnEventB?.Invoke(); // 可能触发其他事件
isHandlingEvent = false;
}
}
总结
委托与事件的选择指南
- 使用委托的情况:
- 需要回调函数
- 简单的方法传递
- 需要多播功能
- 在类内部使用
- 使用事件的情况:
- 公开的接口,需要封装
- 观察者模式实现
- 组件间通信
- 需要更安全的访问控制
- UnityEvent的特殊优势:
- 支持序列化,可在Inspector中配置
- 可视化编辑
- 适合非程序员使用
最佳实践
- 始终使用空条件运算符(
?.Invoke()) - 及时清理订阅(OnDisable/OnDestroy)
- 考虑使用事件总线解耦复杂系统
- 为频繁触发的事件添加节流机制
- 使用结构体作为事件参数以减少GC
- 避免在事件处理中抛出异常
- 文档化事件的使用方式和期望行为
掌握委托和事件是Unity开发中的核心技能,合理使用可以创建出松耦合、可维护性高的代码架构。
更多教学视