Unity设计模式-发布者订阅者模式

Unity中的发布者-订阅者模式与事件系统

1. 基本概念

1.1 发布者-订阅者模式

  • 定义:一种消息通知机制,发布者发送消息,订阅者接收消息

  • 角色构成:

    • Publisher(发布者):负责定义和发布事件
    • Subscriber(订阅者):接收并处理事件
    • Event(事件):消息的载体
    • EventBus(事件总线):可选,用于集中管理事件
  • 工作流程:

    1. 发布者定义事件
    2. 订阅者注册(订阅)事件
    3. 发布者触发事件
    4. 订阅者接收并处理事件
    5. 订阅者可以取消订阅事件
  • 优点:

    • 解耦发布者和订阅者,实现松耦合设计
    • 支持一对多的消息传递
    • 提高代码的可维护性和扩展性
    • 支持动态的订阅和取消订阅
  • 缺点:

    • 可能导致内存泄漏(如果忘记取消订阅)
    • 事件流程不易跟踪和调试
    • 过度使用可能导致代码难以理解

1.2 C#中的事件系统实现方式

  1. 基于委托(delegate):
// 1. 定义委托类型
public delegate void MessageHandler(string message);

// 2. 声明事件
public event MessageHandler OnMessageReceived;

// 3. 触发事件
OnMessageReceived?.Invoke("Hello");

// 4. 订阅事件
OnMessageReceived += HandleMessage;
  1. 基于EventHandler:
// 1. 定义事件参数
public class MessageEventArgs : EventArgs
{
    public string Message { get; }
    public MessageEventArgs(string message) => Message = message;
}

// 2. 声明事件
public event EventHandler<MessageEventArgs> OnMessageReceived;

// 3. 触发事件
OnMessageReceived?.Invoke(this, new MessageEventArgs("Hello"));

// 4. 订阅事件
OnMessageReceived += (sender, args) => Debug.Log(args.Message);
  1. 基于Action:
// 最简单的方式,适用于简单场景
public event Action<string> OnMessageReceived;

2. 项目中的实现分析

2.1 武器切换系统分析

// WeaponSwitcher.cs - 发布者
public class WeaponSwitcher : MonoBehaviour
{
    // 1. 定义委托类型 - 指定事件的签名
    public delegate void WeaponSwitchedHandler(GunController newWeapon);
    
    // 2. 声明事件 - 创建事件实例
    public event WeaponSwitchedHandler OnWeaponSwitched;
    
    // 3. 在适当时机触发事件
    public void SwitchWeapon(int direction)
    {
        // ... 武器切换逻辑 ...
        
        // 使用null条件运算符安全地触发事件
        OnWeaponSwitched?.Invoke(weapons[currentIndex]);
    }
}

// PlayerControllerNew.cs - 订阅者
public class PlayerControllerNew : MonoBehaviour
{
    private void Start()
    {
        // 4. 订阅事件
        weaponSwitcher.OnWeaponSwitched += OnWeaponSwitched;
    }

    // 5. 实现事件处理方法
    public void OnWeaponSwitched(GunController newWeapon)
    {
        currentGun = newWeapon;
    }

    private void OnDisable()
    {
        // 6. 取消订阅事件,防止内存泄漏
        weaponSwitcher.OnWeaponSwitched -= OnWeaponSwitched;
    }
}

2.2 输入系统事件分析

public class PlayerControllerNew : MonoBehaviour
{
    // 1. 声明输入动作引用
    private InputAction fireAction;
    
    private void OnEnable()
    {
        // 2. 订阅输入事件
        // performed: 当输入动作完成时触发
        // canceled: 当输入动作取消时触发
        fireAction.performed += OnFirePerformed;
        fireAction.canceled += OnFireCanceled;
    }
    
    // 3. 实现事件处理方法
    private void OnFirePerformed(InputAction.CallbackContext context)
    {
        currentGun.isShooting = true;
    }

    private void OnFireCanceled(InputAction.CallbackContext context)
    {
        currentGun.isShooting = false;
    }
    
    private void OnDisable()
    {
        // 4. 取消订阅输入事件
        fireAction.performed -= OnFirePerformed;
        fireAction.canceled -= OnFireCanceled;
    }
}

2.3 事件参数的使用分析

在武器切换系统中,我们可以扩展事件参数以传递更多信息:

// 1. 定义武器切换事件参数
public class WeaponSwitchEventArgs : EventArgs
{
    public GunController PreviousWeapon { get; }
    public GunController NewWeapon { get; }
    public float SwitchTime { get; }

    public WeaponSwitchEventArgs(GunController previous, GunController next, float time)
    {
        PreviousWeapon = previous;
        NewWeapon = next;
        SwitchTime = time;
    }
}

// 2. 修改事件声明
public class WeaponSwitcher : MonoBehaviour
{
    public event EventHandler<WeaponSwitchEventArgs> OnWeaponSwitched;

    private void SwitchWeapon(int direction)
    {
        var previousWeapon = weapons[currentIndex];
        // ... 切换武器逻辑 ...
        
        // 3. 使用事件参数触发事件
        OnWeaponSwitched?.Invoke(this, new WeaponSwitchEventArgs(
            previousWeapon,
            weapons[currentIndex],
            Time.time
        ));
    }
}

// 4. 修改事件处理
public class PlayerControllerNew : MonoBehaviour
{
    private void HandleWeaponSwitch(object sender, WeaponSwitchEventArgs e)
    {
        // 可以访问更多信息
        Debug.Log($"Switched from {e.PreviousWeapon.name} to {e.NewWeapon.name} at {e.SwitchTime}");
        currentGun = e.NewWeapon;
    }
}

3. 实际应用场景分析

3.1 UI交互系统

// UI事件系统展示了发布者-订阅者模式的典型应用
public class UIManager : MonoBehaviour
{
    [SerializeField] private Button fireButton;
    [SerializeField] private Toggle autoFireToggle;
    [SerializeField] private Slider ammoSlider;

    private void SetupUIEvents()
    {
        // UI组件作为发布者,UIManager作为订阅者
        fireButton.onClick.AddListener(HandleFireButtonClick);
        autoFireToggle.onValueChanged.AddListener(HandleAutoFireToggle);
        ammoSlider.onValueChanged.AddListener(HandleAmmoSliderChange);
    }

    private void HandleFireButtonClick()
    {
        // 处理开火按钮点击
    }

    private void HandleAutoFireToggle(bool isOn)
    {
        // 处理自动开火切换
    }

    private void HandleAmmoSliderChange(float value)
    {
        // 处理弹药量变化
    }
}

3.2 游戏状态管理

// 使用静态事件实现全局状态管理
public class GameEvents
{
    // 游戏状态相关事件
    public static event Action OnGameStart;
    public static event Action OnGamePause;
    public static event Action OnGameResume;
    public static event Action OnGameOver;

    // 玩家状态相关事件
    public static event Action<int> OnHealthChanged;
    public static event Action<int> OnScoreChanged;
    public static event Action<Vector3> OnPlayerPositionChanged;

    // 安全的事件触发方法
    public static void TriggerGameStart() => OnGameStart?.Invoke();
    public static void TriggerHealthChanged(int newHealth) => OnHealthChanged?.Invoke(newHealth);
    public static void TriggerScoreChanged(int newScore) => OnScoreChanged?.Invoke(newScore);
}

// 使用示例
public class GameManager : MonoBehaviour
{
    private void Start()
    {
        // 订阅游戏事件
        GameEvents.OnGameStart += HandleGameStart;
        GameEvents.OnGameOver += HandleGameOver;
    }

    private void HandleGameStart()
    {
        // 处理游戏开始逻辑
    }

    private void HandleGameOver()
    {
        // 处理游戏结束逻辑
    }

    private void OnDestroy()
    {
        // 取消订阅
        GameEvents.OnGameStart -= HandleGameStart;
        GameEvents.OnGameOver -= HandleGameOver;
    }
}

4. 最佳实践

4.1 事件命名规范

  • 事件命名通常以"On"开头
  • 委托类型名通常以"Handler"或"EventHandler"结尾
  • 事件参数类名通常以"EventArgs"结尾

4.2 事件参数传递

// 自定义事件参数
public class WeaponEventArgs : EventArgs
{
    public GunController Weapon { get; private set; }
    public float SwitchTime { get; private set; }

    public WeaponEventArgs(GunController weapon, float switchTime)
    {
        Weapon = weapon;
        SwitchTime = switchTime;
    }
}

// 使用自定义事件参数
public event EventHandler<WeaponEventArgs> OnWeaponSwitched;

4.3 生命周期管理

  • 在适当的时机订阅和取消订阅事件
  • 避免在不需要时保持订阅状态
  • 防止内存泄漏
private void OnEnable()
{
    // 订阅事件
    weaponSwitcher.OnWeaponSwitched += OnWeaponSwitched;
}

private void OnDisable()
{
    // 取消订阅事件
    weaponSwitcher.OnWeaponSwitched -= OnWeaponSwitched;
}

4.4 性能考虑

  • 避免在频繁调用的方法中创建委托实例
  • 使用缓存的委托引用而不是每次都创建新的
  • 考虑使用对象池来管理事件参数对象

5. 注意事项

5.1 线程安全

  • 事件触发时注意线程同步
  • 使用线程安全的事件触发方式
private readonly object eventLock = new object();

protected virtual void OnWeaponSwitched(WeaponEventArgs e)
{
    lock (eventLock)
    {
        OnWeaponSwitched?.Invoke(this, e);
    }
}

5.2 空引用处理

  • 使用null条件运算符(?.)
  • 在多线程环境中使用线程安全的事件触发方式
  • 注意事件订阅者的生命周期

5.3 内存管理

  • 及时取消事件订阅
  • 避免循环引用
  • 使用弱引用(WeakReference)处理长期存在的事件订阅

6. 实际应用示例

6.1 武器系统示例

// 发布者(WeaponSwitcher)
public class WeaponSwitcher : MonoBehaviour
{
    public event Action<GunController> OnWeaponSwitched;
    
    public void SwitchWeapon(int direction)
    {
        // ... 武器切换逻辑 ...
        OnWeaponSwitched?.Invoke(weapons[currentIndex]);
    }
}

// 订阅者(PlayerController)
public class PlayerController : MonoBehaviour
{
    private void Start()
    {
        weaponSwitcher.OnWeaponSwitched += HandleWeaponSwitch;
    }

    private void HandleWeaponSwitch(GunController newWeapon)
    {
        // ... 处理武器切换 ...
    }

    private void OnDisable()
    {
        weaponSwitcher.OnWeaponSwitched -= HandleWeaponSwitch;
    }
}

6.2 游戏状态管理示例

public class GameManager : MonoBehaviour
{
    public static event Action<GameState> OnGameStateChanged;
    private GameState currentState;

    public void SetGameState(GameState newState)
    {
        currentState = newState;
        OnGameStateChanged?.Invoke(currentState);
    }
}

public class UIManager : MonoBehaviour
{
    private void OnEnable()
    {
        GameManager.OnGameStateChanged += UpdateUI;
    }

    private void UpdateUI(GameState state)
    {
        // 更新UI显示
    }

    private void OnDisable()
    {
        GameManager.OnGameStateChanged -= UpdateUI;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值