Unity学习90天-第7天-学习委托与事件(简化版)

欢迎回来! 今天我们来搞定理解委托和事件的核心概念,用"受伤、得分、游戏结束"三个游戏场景掌握解耦思路!

一、为什么要用事件?先看"耦合"的痛点

假设玩家受伤时,需要同时做三件事:、

玩家受伤

├── UI 血条减少

├── 音效播放"受伤音"

└── 相机震动

不用事件的写法(强耦合):

cs 复制代码
// 玩家脚本里直接调用所有系统
void TakeDamage(float damage)
{
    health -= damage;
    
    UIManager.Instance.UpdateHealthBar(health);   // 直接调用UI
    AudioManager.Instance.PlayHurtSound();         // 直接调用音效
    CameraShake.Instance.Shake();                  // 直接调用相机
}

问题出在哪?

问题 后果
玩家脚本要知道 UI、音效、相机的存在 改一个系统可能连锁修改多处
删掉音效系统 → 玩家脚本报错 牵一发而动全身
新增"受伤特效"→ 要改玩家脚本 功能越多,玩家脚本越臃肿

类比 :强耦合就像公司老板直接给每个员工打电话布置任务------老板要记住所有员工的电话,换人了就要改号码。事件就是设一个"广播电台"------老板只管播报消息,员工自己决定要不要收听,互不依赖。

二、委托------函数的"类型"

委托(delegate)本质是:用来存储"函数"的变量类型

类比 :普通变量存的是数据(int 存数字,string 存文字),委托变量存的是方法(函数)

cs 复制代码
// 声明一个委托类型(规定:无参数,无返回值)
delegate void OnDamageHandler();

// 这个委托变量可以"存"符合格式的方法
OnDamageHandler myDelegate;

// 把方法赋给委托
void PlaySound() { Debug.Log("播放音效"); }
myDelegate = PlaySound;  // 注意不加括号

// 调用委托 = 调用它存的方法
myDelegate();  // 输出:"播放音效"

三、Action------最常用的内置委托

Unity 开发中,不需要自己声明 delegate,直接用 C# 内置的 Action 就够了:

类型 说明 示例
Action 无参数、无返回值 游戏结束通知
Action<T> 一个参数、无返回值 传递受伤数值
Action<T1, T2> 两个参数 传坐标、传名字+分数
cs 复制代码
Action onGameOver;              // 无参
Action<float> onDamage;         // 带一个float参数(伤害值)
Action<string, int> onScore;    // 带两个参数(玩家名、分数)

四、event 关键字------给委托加保护锁

光用 Action 有个隐患:任何地方都可以直接调用或清空它。加上 event 关键字,可以限制只有声明它的类能触发,外部只能订阅/取消订阅。

cs 复制代码
// 没有 event:任何脚本都能 GameManager.OnGameOver() 直接触发
public static Action OnGameOver;

// 加了 event:外部只能 += 或 -=,不能直接调用
public static event Action OnGameOver;
操作 有 event 无 event
订阅(+= 允许 允许
取消订阅(-= 允许 允许
从外部触发(() 报错 允许(危险!)
从外部清空(= null 报错 允许(危险!)

五、完整三步走------事件的标准用法

  • 第一步:声明事件(在"广播站"里)
  • 第二步:订阅事件(在"听众"里)
  • 第三步:触发事件(事情发生时)

六、完成一个得分事件委托

那么我们来完成一个小小的实列吧!

cs 复制代码
using UnityEngine;

// ── 得分管理器(发布者)──────────────────
public class ScoreManager : MonoBehaviour
{
    public static event Action<int> OnScoreChanged;  // 携带当前总分

    private static int totalScore = 0;

    // 静态方法,任何地方可以调用
    public static void AddScore(int points)
    {
        totalScore += points;
        OnScoreChanged?.Invoke(totalScore);
        Debug.Log("得分!当前总分:" + totalScore);
    }
}
cs 复制代码
// ── 分数UI(订阅者)──────────────────────
public class ScoreUI : MonoBehaviour
{
    void OnEnable()
    {
        ScoreManager.OnScoreChanged += UpdateScoreDisplay;
    }

    void OnDisable()
    {
        ScoreManager.OnScoreChanged -= UpdateScoreDisplay;
    }

    void UpdateScoreDisplay(int score)
    {
        Debug.Log("UI显示分数:" + score);
        // GetComponent<Text>().text = "分数:" + score;
    }
}

使用方式 :敌人死亡时,调用 ScoreManager.AddScore(10) 即可,不需要引用任何 UI 脚本。

七、一定要记住:订阅了就要取消订阅

cs 复制代码
// 错误写法:只订阅不取消
void Start()
{
    GameManager.OnGameOver += DoSomething;
}
// 这个对象销毁了,但 OnGameOver 还记着它 → 报空引用错误!

// 正确写法:成对出现
void OnEnable()
{
    GameManager.OnGameOver += DoSomething;
}

void OnDisable()
{
    GameManager.OnGameOver -= DoSomething;
}
场景 订阅位置 取消位置
场景内始终存在的对象 Awake / Start OnDestroy
会被禁用/启用的对象 OnEnable OnDisable

八、知识点总结

核心语法 作用
public static event Action OnXxx 声明静态事件
OnXxx?.Invoke() 安全触发事件
OnXxx += 方法名 订阅(开始监听)
OnXxx -= 方法名 取消订阅(停止监听)

今天的内容就到这里! 接下来我将连续更新90天的Untiy教程从基础到一个网络部分,有兴趣的朋友们可以收藏关注,谢谢!如果有疑问,评论区见。

相关推荐
.小小陈.2 小时前
深度拆解 Linux 进程间通信(IPC):从管道到 System V 全链路详解
linux·服务器·网络·学习
君莫愁。2 小时前
【Unity】解决UGUI的Button无法点击/点击无反应的排查方案
unity·c#·游戏引擎·解决方案·ugui·按钮·button
Sss_Ass2 小时前
跟着老师不迷路系列---跟着李述铜老师学习汇编语言之基本汇编程序指令集分类
开发语言·学习·学习方法·汇编语言·李述铜
爱上好庆祝2 小时前
移动端适配
前端·css·学习·html·css3
寒秋花开曾相惜11 小时前
(学习笔记)第四章 处理器体系结构
linux·网络·数据结构·笔记·学习
低代码布道师13 小时前
微搭低代码MBA 培训管理系统实战 30——学习卡
学习·低代码·rxjava
南無忘码至尊13 小时前
Unity学习90天 - 第 6天 - 学习协程 Coroutine并实现每隔 2 秒生成一波敌人
学习·unity·c#·游戏引擎
LN花开富贵13 小时前
【ROS】鱼香ROS2学习笔记二
linux·笔记·python·学习·嵌入式
檬柠wan13 小时前
MySQL-数据库增删改查学习
数据库·学习·mysql