阅读优秀代码
本文最后更新于3 天前,其中的信息可能已经过时,如有错误请留言

为什么把 Awake(和 Update)声明为 virtual

如果你不写 virtual,仍然可以在子类里写自己的 Awake。但这会用 方法隐藏(new) 的方式覆盖父类版本,而 Unity 仍然分别调用两份 Awake——父类的、子类的。
一旦你的父类 Awake 里有 必须执行的初始化(拿 CharacterBase 来说:缓存 Animator 和 CharacterController),就需要确认子类永远不会忘记这一步。把它声明为 virtual + 要求子类 override 并手动 base.Awake(),可以在编译期强制提醒。

protected override void Awake()
{
    base.Awake();          // 保证父类初始化
    /* 子类自己的初始化 */
}

如果不用 virtual 而让子类写 new void Awake(),你稍不注意就会漏掉 base.Awake()——编译器不会给任何警告,运行时才发现 controller = null 之类的问题

Lambda表达式

参数列表规则:

无参数必须写空括号

() => DoSomething();

一个参数:可以省略括号(但显式写类型/属性时不能省略

x => x * 2
(int x) => x * 2          // 显式类型 → 需要括号
([NotNull] string s) => s.Length  // 带属性 → 需要括号(C# 10+ 支持参数属性)

多个参数必须加括号

(x, y) => x + y
(int x, int y) => x + y

方法体规则

单表达式:可以省略花括号与 return

Func<int,int> f = x => x + 1; // 返回值 = 表达式的值
Action a = () => Console.WriteLine("Hi"); // 表达式为 void → 推断为 Action

语句块:用 { ... }返回非 void 时必须写 return

Func g = x => { var y = x + 1; return y; };

=>

1. 方法定义的简写语法

包括普通方法/属性/构造函数/析构函数 的方法体简写
=> 右侧必须是单个表达式;需要多语句/校验就回到 { … } 写法

void A() => Console.Write("A");

//等价于
void A()
{
    Console.Write("A");
}

有返回值时,表达式的值就是返回值:

int Sq(int x) => x * x;

//等价于传统写法:
int Sq(int x)
{
    return x * x;
}

属性的访问器get/set

int Count => _list.Count; // 只读属性
int Count { get => _count; set => _count = value; } // 访问器

2.委托和Lambda表达式的关系

首先记住Lambda表达式的上一个版本叫匿名方法,所以本质还是一个没有名字的函数,是函数

委托(delegate)就是“带有签名的类型安全函数指针

所以两者的关系是:lambda表达式作为委托的简易实现
编译器把 x => x + 1 这种语法转换成与某个委托类型匹配的可调用对象。

注意第一种用法 =>的左侧是函数签名而不是参数列表,而用在委托的简化实现时,左侧是参数列表

属性的简单写法

private int _health;

public int Health
{
get { return _health; }
set { _health = value; }
}

写法类型示例说明
自动属性public int HP { get; set; }最常用,编译器自动处理字段
只读属性public int Level { get; }只能在构造器赋值
表达式属性public float Speed => 10f;适合只读并简单返回值
私有设置public int Score { get; private set; }外部只读,内部可写
手动属性见完整写法可在 get/set 中写逻辑

表达式属性的等价写法:

public float Speed
{
get { return 10f; }
}
public float Speed
{
    get => 10f; //其实是有返回值的方法的简写(见上)
}

带默认值的自动属性(auto-property initializer)语法:

public BindableProperty<Transform> lockTarget { get; set; } 
    = new BindableProperty<Transform>();

委托

调用

直接调用(像方法一样)

Action<string> log = Console.WriteLine;
log("hello");           // 直接像方法那样调用 —— 最常用、最快

空条件调用 ?.Invoke(...)(防空)

Action<string> log = null;
log?.Invoke("safe");    // log 为 null 时什么也不做;常用于触发事件

常见内置委托类型

委托签名形态作用典型用法/API
Actionvoid 返回;Action()Action<T1> ... Action<T1,...,T16>做事但不返回结果(副作用)事件/回调、List.ForEach(Action<T>)、按钮点击等
Func最后一个泛型参数是返回值Func<TResult>Func<T1,TResult>有输入、有返回值(变换/计算)Select(Func<T,R>)Aggregate、工厂方法等
Predicate等价于 Func<T,bool>断言/条件判断(返回布尔)List.Find/Exists/RemoveAll(Predicate<T>)Where

委托的Target属性

委托的Target指“委托要调用到的那个对象”——也就是接收者/响应者(receiver)。
不是事件的拥有者/发布者(publisher)。发布者通常会作为事件回调的第一个参数 sender 传进来,而不是 delegate.Target

  • 实例方法回调delegate.Target == 方法所属实例(响应者)。
  • 静态方法回调delegate.Target == null(无接收者)。
  • 捕获变量的 lambdadelegate.Target == 闭包对象(编译器生成的隐藏对象)。
  • 扩展方法:本质静态方法,Target == null
  • 多播委托:每个订阅各有自己的 Target

委托之间的 ==

  • 闭包对象:编译器生成、承载被捕获变量的隐藏对象,是委托的 Target
  • 委托相等:必须 MethodTarget 同时相同;捕获 lambda 往往方法不同(即使 Target 相同),或作用域不同导致 Target 不同,所以比较常为 false

Action/委托虽然是引用类型,但 .NET 专门为委托实现了 ==/!=(在 MulticastDelegate 里)。
比较规则不是“同一引用”,而是比较调用列表(invocation list)

  • 单播委托:相等当且仅当方法相同 + 目标对象(Target)相同
    • 静态方法:Target == null,只要方法相同就相等。
    • 实例方法:方法相同但目标不同就不相等。
  • 多播委托:两边的调用列表长度相同且逐项(方法+目标)一一相等且顺序一致才相等。
  • 类型必须相同Action<object> 不能和 Action<string>== 比。
  • null 规则:两个都是 null → 相等;一边 null 一边不是 → 不相等。
// 静态方法:方法相同 => 相等
Action a1 = Console.Beep;
Action a2 = Console.Beep;
Console.WriteLine(a1 == a2); // True

// 实例方法:方法相同但目标不同 => 不相等
var s1 = "hi";
var s2 = "hi";
Func<int> f1 = s1.GetHashCode;
Func<int> f2 = s2.GetHashCode;
Console.WriteLine(f1 == f2); // False(目标 s1 vs s2)

// 捕获的 lambda:通常不同(各自有不同的闭包对象)
int n = 0, m = 0;
Action l1 = () => Console.WriteLine(n);
Action l2 = () => Console.WriteLine(n);
Action l3 = () => Console.WriteLine(m);
Console.WriteLine(l1 == l2); // False(闭包对象不同)
Console.WriteLine(l1 == l3); // False(方法体不同)

// 不捕获的 lambda/方法组:编译成同一静态方法 => 相等
Action p1 = () => Console.WriteLine("X");
Action p2 = () => Console.WriteLine("X");
Console.WriteLine(p1 == p2); // True(方法相同、Target 为 null)

// 多播委托:调用列表要完全一致
Action x = Console.Beep;
Action y = Console.Beep;
Action xy1 = x + y;
Action xy2 = x + y;
Action yx  = y + x;
Console.WriteLine(xy1 == xy2); // True
Console.WriteLine(xy1 == yx);  // False(顺序不同)

闭包对象

当 lambda/匿名方法捕获了外部变量(如 n)时,编译器会生成一个隐藏的“显示类/闭包类”(display class),把被捕获的局部变量变成这个类的字段;该类的一个实例就是“闭包对象”。捕获的 lambda 会被编译成这个类上的实例方法,因此委托的 Target 就是这个闭包对象;

int n = 0;

// 两个捕获同一外部变量 n 的 lambda
Action l1 = () => Console.WriteLine(n);
Action l2 = () => Console.WriteLine(n);

Console.WriteLine(object.ReferenceEquals(l1.Target, l2.Target)); // True:同一闭包对象
Console.WriteLine(l1.Method == l2.Method);                       // False:方法不同
Console.WriteLine(l1 == l2);                                     // False:Method 不同→不相等

如果来自不同作用域/不同调用(闭包对象也不同):

Action Make()
{
    int x = 0;
    return () => Console.WriteLine(x);   // 捕获 x → 生成新的闭包对象
}

var a = Make();
var b = Make();
Console.WriteLine(object.ReferenceEquals(a.Target, b.Target)); // False:不同闭包对象
Console.WriteLine(a.Method == b.Method);                       // True/False 取决于编译器实现
Console.WriteLine(a == b);                                     // False:至少 Target 不同

多播委托

多播委托指的是“一个委托实例里绑定了多个响应方法,调用该委托时会按顺序依次调用这些方法

构造与调用顺序

void A() => Console.Write("A");
void B() => Console.Write("B");

Action d = null;   // 空委托
d += A;            // 现在是单播: [A]
d += B;            // 多播: [A, B]
d += A;            // 多播: [A, B, A]

d();               // 输出:ABA   (按添加顺序依次调用)

相等性:多播委托相等当且仅当调用列表长度相同,且每一项(方法+目标)顺序一致

Action d1 = A; d1 += B;    // [A, B]
Action d2 = A; d2 += B;    // [A, B]
Action d3 = B; d3 += A;    // [B, A]

Console.WriteLine(d1 == d2); // True  列表完全一致
Console.WriteLine(d1 == d3); // False 顺序不同

移除监听

Action d = A; d += B; d += A;   // [A, B, A]
d -= A;                         // 移除尾部那个 A → [A, B]

全局唯一单例(Mono Singleton)模板

泛型单例基类,用于保证某个组件在整个游戏生命周期中只存在一个实例,并且自动持久化到场景之间。

// 约束 T 必须是继承自 MonoSingleton<T> 的类型本身
public class MonoSingleton<T>:MonoBehaviour where T : MonoSingleton<T> 
{
    private static T instance;
    public static T Instance
    {
        get 
        {
            if (instance == null)
            {
                //先尝试在场景中查找现有的 T 类型对象;
                instance = GameObject.FindAnyObjectByType<T>();
                if (instance == null)
                {
                    //如果找不到,就创建一个新的空物体并挂上该组件,这保证了程序中一定有一个有效的 T
                    //适合XXmanager,毕竟一般我们也是创建一个空物体放置在原点然后挂载上XXmanager.cs
                    var go = new GameObject(typeof(T).Name);
                    instance = go.AddComponent<T>();
                }
            }
            return instance; 
        }
    }
    protected virtual void Awake()
    {
        //默认放到DontDestroyScene中
        //如果当前实例是第一个,就设置为单例,并且让它在场景切换时不销毁(DontDestroyOnLoad)
        if (instance == null)
        {
            instance = (T)this;
            DontDestroyOnLoad(instance);
        }
        else
        {
            //否则销毁这个重复对象(保持唯一性)
            //在物体销毁时访问次对象最好加一个Null的判断,防止程序退出次物体被销毁
            Destroy(instance);
        }
    }
}

使用类单例

public class XXManager : MonoSingleton<AudioManager>
{
    public void PlaySound(string name) { ... }
}

然后你可以在任何地方使用:

XXManager.Instance.PlaySound("click");

where关键字

where 用于规定泛型类型参数 T 必须具备某些特性,如继承某个类、实现某接口、有无参构造函数等。

where T : class

public class MyClass<T> where T : class

限制 T 必须是引用类型(比如 stringGameObject、自定义类等)。

where T : struct

限制 T 必须是值类型(如 int, float, Vector3,不能为 Nullable)

where T : new()

public class MyClass<T> where T : new()

限制 T 必须有一个公共的无参构造函数,这样你可以 new T()

where T : SomeBaseClass

public class MyClass<T> where T : Enemy

限制 T 必须是 Enemy 类或其子类。这样你可以使用 Enemy 中的方法。

where T : IMyInterface

public class MyClass<T> where T : IAttackable

限制 T 必须实现某个接口,如 IAttackable

多个约束组合使用

public class MyClass<T> where T : MonoBehaviour, new()

限制 T 必须继承自 MonoBehaviour 并且有无参构造函数

单例模板类的递归泛型约束(CRTP,Curiously Recurring Template Pattern)

保证从 MonoSingleton<T> 中返回的 Instance 是派生类类型,不是基类

I

当你希望一个类型能够被比较(用于排序、查找等),你可以让它实现这个接口

namespace System
{
    public interface IComparable<in T>
    {
        int CompareTo(T other);
    }
}

可观察属性(ObservableProperty)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

/// <summary>
///  让一个变量属性的值发生变化时,自动触发 ValueChanged 事件
/// </summary>
/// <typeparam name="T"></typeparam>
public class BindableProperty<T> 
{
    // 事件委托:当 Value 值发生变化时,会调用所有订阅的方法,并传入新的值
    public Action<T> ValueChanged;
    private T m_value;
    // 公共属性,外部可以读写它的值
    public T Value
    {
        // 当设置新值时(Value = xxx)
        set
        {
            // 如果新值和当前值不相等
            if (!Equals(m_value,value))
            {
                // 更新内部存储的值
                m_value = value;
                // 触发事件通知
                ValueChanged?.Invoke(m_value);
            }
        }
       get { return m_value; }
    }
}

它是一个可观察的泛型值容器:把某个值包装成 Value 属性,外部读写;当你给 Value 赋新值时,如果新旧值不相等(用 Equals 比较),就更新内部值并触发 ValueChanged 事件,把新值通知给所有订阅者。
适合做数据绑定/状态同步(值变→UI/逻辑自动响应),并避免重复通知。

public BindableProperty<Transform> lockTarget { get; set; } = new BindableProperty<Transform>();

当你给 lockTarget.Value 赋值时,如果新旧引用不相等,就触发 ValueChanged(newValue)

Private & Readonly Readonly | const

private 只是限制可见性(只能在这个类里访问),不限制你在类内部重新赋值
readonly 则是限制赋值次数/时机:这个字段只能在声明处构造函数里赋值;离开构造函数后就不能再被重新指到别的对象

private readonly Dictionary<T, List<Action<object, object>>> eventDic = new();
  • 这张字典只能在本类里用(private);
  • 它的引用一经建立就不允许被换成另一张字典(readonly)。
    你仍然可以对内容增删改(Add/Remove/Clear),但不能写 eventDic = new Dictionary<...>()eventDic = null

为什么有用(即使已经 private):

  • 防误操作:避免以后维护时在某个方法里把 eventDic 重新 new/置空,导致已注册的监听丢失。
  • 意图表达:告诉读代码的人“这个集合的身份固定不变,只有内容会变”
特性constreadonly
何时确定编译期运行期(构造时/静态构造时)
是否内联到调用方✅ 是(有版本兼容风险❌ 否
是否隐式 static✅ 是(不能再写 static❌ 否(可选:可写成 static readonly
允许的类型数值/bool/char/string/enum/decimal,或引用类型的 null任意类型
赋值位置只能在声明处声明处构造函数static readonly 可在静态构造函数)
是否可因实例不同而不同❌ 否(所有地方同一值)✅ 可以(每个实例的只读字段可不同)
能否指向可变对象并修改其内容不适用(必须是常量)引用不能换,但对象能改(内容可变)

内联

内联(Inlining)就是编译器/运行时把“对某个东西的引用”直接替换为它的内容,从而省掉一次间接访问或函数调用。

两种常见语境:

  1. 常量内联(C# const:调用方在编译时把 Lib.Pi 直接写成数值 3.1415…,以后即使库把常量改了,未重新编译的调用方仍用旧值
  2. 函数内联(JIT/优化):把一次小函数调用替换成函数体本身,减少调用开销(是否内联由 JIT/优化器决定,可受 MethodImplOptions.AggressiveInlining 等影响)。

const编译期常量(隐式 static、会被内联,只能是编译期可确定的字面量/枚举/字符串/数字等)
readonly运行期只读字段(仅能在声明或构造函数/静态构造函数赋值,不会内联,可用于任意类型——引用不可换但对象内容可变)

事件系统

EventHandler<T>

首先需要知道事件的ID,依此订阅者类在OnEnable中调用AddEventListening,在DisEnable中调用RemoveEventListeningByTarget

成员功能函数中创建EventMessage类对象,传递并调用 SendMessage / SentMessageByQue,两者都最终会触发TriggerEvent,一个立刻调用TriggerEvent(eventMessage),另一个会先将消息放入Queue中,在Updata中只要检测存放EventMessage的Queue.Count大于0才调用TriggerEvent(eventMessage)

总的来说EventHandler定义了事件系统给外界提供了订阅,解订阅,发送信息,创建信息之间的逻辑,而底层实现放在了EventHandlerBase类中,下面我们来分析这个类

using System; // 引用系统基础命名空间
using System.Collections.Generic; // 使用泛型集合(List、Dictionary、Queue 等)
using System.Linq; // (未使用)LINQ 扩展
using System.Text; // (未使用)文本相关
using System.Threading.Tasks; // (未使用)任务并行库
using Unity.VisualScripting; // (未使用)Bolt/Visual Scripting 相关

public class EventHandler<T> // 事件派发器(带队列,可同步或分帧派发)
{
    private readonly EventHandlerBase<T> eventHandlerBase = new EventHandlerBase<T>(); // 底层注册表:事件ID -> 监听列表
    private readonly Queue<EventMessage> eventQue = new Queue<EventMessage>(); // 事件消息队列(分帧派发用),用queue是为了防止谁先发谁先处理

    public void OnEventInit() // 初始化(通常在系统启动时调用)
    {
        eventQue.Clear(); // 清空队列,避免残留消息
    }

    public void OnEventUpdate() // 每帧驱动(由外部 Update 调用)
    {
        if (eventQue.Count > 0) // 若队列中有待处理事件
        {
            var eventMessage = eventQue.Dequeue(); // 出队一个事件消息
            TriggerEvent(eventMessage); // 立即触发该消息绑定的回调
        }
        // 提示:当前一帧只处理一条,如事件量大可能积压;可按需改为 while 循环 + 帧预算
    }

    /// <summary>
    /// 比如UI类中:void OnEnable()  => EventService.Instance.AddEventListening(EventID.HpChanged, OnHpChanged);
    /// </summary>
    /// <param name="id"></param>
    /// <param name="action"></param>
    public void AddEventListening(T id, Action<object, object> action) // 对外:按事件ID注册监听
    {
        eventHandlerBase.AddEventHandler(id, action); // 委托给底层注册表
    }

    public void RemoveEventListeningByID(T id) // 对外:按事件ID移除所有监听
    {
        eventHandlerBase.RemoveEventByID(id); // 委托给底层注册表
    }

    /// <summary>
    /// 比如UI类中:void OnDisable() => EventService.Instance.RemoveEventListeningByTarget(this);
    /// </summary>
    /// <param name="target"></param>
    public void RemoveEventListeningByTarget(object target) // 对外:按对象(回调所属目标)批量移除
    {
        eventHandlerBase.RemoveEventByTarget(target); // 委托给底层注册表
    }

    //在修改数据,事件系统外需要通知时调用,订阅方(UI/音效/逻辑都可各自订阅
    /*
     * public void TakeDamage(int dmg)
    {
    int old = hp;
    hp = Mathf.Max(0, hp - dmg);
    
    EventService.Instance.SendMessage(
        EventID.HpChanged,
        sender: this,
        param2: new HpChangedArgs(old, hp)
    );
    }*/
public void SentMessage(T t, object param1, object param2) // 同步派发:立刻触发所有监听
{
    List<Action<object, object>> actions = eventHandlerBase.GetEvent(t); // 取出该事件ID的监听列表
    if (actions == null) { return; } // 若无人监听,直接返回                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
    foreach (var action in actions) // 遍历所有监听回调
    {
        EventMessage eventMessage = new EventMessage(action, param1, param2); // 封装一条事件消息
        TriggerEvent(eventMessage); // 立即触发(同步执行)
    }
}

public void SentMessageByQue(T t, object param1, object param2) // 入队派发:分帧处理
{
    List<Action<object, object>> actions = eventHandlerBase.GetEvent(t); // 取出监听列表
    // 风险提示:actions 可能为 null,这里没有判空;可按需加 if (actions == null) return;
    foreach (var action in actions) // 遍历所有监听
    {
        EventMessage eventMessage = new EventMessage(action, param1, param2); // 封装消息
        //new EventMessage(...) 创建的是托管堆上的对象。你把它 Enqueue 到 Queue 后,队列持有它的引用,所以即使方法返回、局部变量离开作用域,
        //它也不会被回收;等到后来 OnEventUpdate() 里 Dequeue 出来并用完、且不再被任何地方引用时,它才变成 GC 可回收(至于什么时候回收,由 GC 决定、不可预测)
        eventQue.Enqueue(eventMessage); // 入队,等待 OnEventUpdate 在后续帧触发
    }
}

private void TriggerEvent(EventMessage eventMessage) // 实际触发回调
{
    eventMessage.action(eventMessage.param1, eventMessage.param2); // 执行委托,把两个参数传入
    // 建议:为了健壮性,可 try/catch 捕获异常,避免一个监听崩溃影响后续
}
}

public class EventMessage // 单条事件消息(绑定一个委托与两个参数)
{
public Action<object, object> action; // 回调委托(签名固定为两个 object 参数)
public object param1; // 第一个参数(任意类型,外部按约定强转)
public object param2; // 第二个参数(任意类型)
public EventMessage(Action<object, object> action, object param1, object param2) // 构造函数
{
    this.action = action; // 赋值回调
    this.param1 = param1; // 赋值参数1
    this.param2 = param2; // 赋值参数2
}
}

EventHandlerBase

只负责“谁监听了什么” 的存储与管理,不负责触发。是EventHandler的底层实现

using System;
using System.Collections.Generic;

// 事件注册表:管理“事件ID <-> 监听回调”与“目标对象 <-> 事件ID”映射
public class EventHandlerBase<T>
{
    // 事件ID -> 回调函数列表,支持一个事件名(ID)下挂多个监听;监听的回调签名统一为 Action<object, object>且事件的传参依据各类事件函数来选择或者不使用
    private readonly Dictionary<T, List<Action<object, object>>> eventDic = new Dictionary<T, List<Action<object, object>>>();

    //目标对象 -> 注册过的事件ID列表 (反向表)
    private readonly Dictionary<object,List<T>> targetEventDic = new Dictionary<object, List<T>>();
    /// <summary>
    /// 注册监听
    /// </summary>
    /// <param name="t">事件的“ID/键</param>
    /// <param name="action"></param>
    public void AddEventHandler(T t, Action<object, object> action) 
    {
        if (!eventDic.ContainsKey(t)) // 如果该事件ID还没有列表
        {
            //eventDic.Add(t, new List<Action<object, object>>());使用索引器:
            eventDic[t]=new List<Action<object, object>>(); // 创建一个新的回调列表
        }

        //避免从重复注册相同的事件(这就是自己实现时间系统的好处,可以规避Unity事件系统的问题)
        List<Action<object,object>> eventList = eventDic[t]; // 取出回调列表
        //Predicate<T>是参数类型T,返回bool 的函数的委托类型 public delegate bool Predicate<in T>(T obj);
        Action<object, object> checkAction = eventList.Find(i => i == action); // 查找是否已存在该委托
        if (checkAction != null)
        {
            //说明该事件已绑定过该回调函数,则不给该事件重复添加回调函数
            return;
        }
        //事件未绑定过该回调函数,则给事件绑定该回调函数
        eventList.Add(action);

        //更新从属者信息
        // 获取委托的响应者(实例方法为对象,静态方法为 null) - Unity自带
        object target = action.Target; 
        if (!targetEventDic.ContainsKey(target)) 
        {
            //如果响应者不在反向表中,则为该响应者在反向表中创建一个列表
            //targetEventDic.Add(target, new List<T>());
            targetEventDic[target] = new List<T>();
        }   
        //把事件名添加到这个从属者列表中
        targetEventDic[target].Add(t);
    }

    // 按事件ID移除所有监听
    public void RemoveEventByID(T t)
    {
        if (eventDic.ContainsKey(t))
        {
            // 先不着急移除 eventDic 的键,先更新 targetEventDic
            //更新target
            // 在事件ID - 回调函数列表字典中根据键也就是事件ID,取出对应的回调函数列表
            List<Action<object,object>> actions = eventDic[t];
            // 遍历该列表
            foreach (var action in actions)
            {
                //找到该回调函数的所属者
                object target = action.Target;
                if (target != null && targetEventDic.ContainsKey(target))
                {
                    //这里可能存在一个位置注册多个同名的事件(list会存在重复元素)
                    List<T> idList = targetEventDic[target];
                    idList.RemoveAll(id => id.Equals(t));//可以用拉姆达表达式
                    if (idList.Count == 0)
                    {
                        targetEventDic.Remove(target);
                    }
                }
            }
            eventDic.Remove(t);
        }
    }
    public void RemoveEventByTarget(object target)
    {
        if (targetEventDic.ContainsKey(target))
        {
            List<T> idList = targetEventDic[target];
            foreach (var id in idList)
            {
                if (eventDic.ContainsKey(id))
                {
                    List<Action<object, object>> actions = eventDic[id];
                    actions.RemoveAll(action => action.Target == target);
                    if (actions.Count == 0)
                    {
                        eventDic.Remove(id);
                    }
                }
            }
            targetEventDic.Remove(target);
        }
    }
    public List<Action<object, object>> GetEvent(T t)
    {
        if (eventDic.ContainsKey(t))
        {
            return eventDic[t];
        }
        return null;
    }
}

EventID 就是在一个公共的枚举里集中定义/分配,要用的事件名加到这个枚举里,编译后任何脚本都能用 EventID.XXX 调用。对Event Handler的第二层封装,只是该类是单例。外层通过单例访问Event Handler

using System;
public enum EventID//这里添加事件ID
{
OnDoneStateUIInit
}

public class EventService : MonoSingleton
{
private EventHandler EventHandler = new EventHandler();

protected override void Awake()
{
    //不同ID的Event都有自己唯一的EventHandler
    base.Awake();
    //初始化,清空queen
    EventHandler.OnEventInit();
}

private void Update()
{
   //如果queen中的事件消息数量大于0则调用
   EventHandler?.OnEventUpdate();
}
private void OnDestroy()
{
    EventHandler = null;
}

/// <summary>
/// 添加事件,参数超过2个的,封装个类传递参数
/// </summary>
/// <param name="eventID"></param>
/// <param name="action"></param>
public void AddEventListening(EventID eventID, Action<object, object> action)
{
    //先回调Event Handler,再回调给Event HandlerBase给事件ID绑定响应方法
    EventHandler.AddEventListening(eventID, action);
}

/// <summary>
/// 通过ID注销某一类事件:这会将所有地方注册的该类事件都给注销
/// </summary>
/// <param name="eventID"></param>
public void RemoveEventListeningByID(EventID eventID)
{
    EventHandler.RemoveEventListeningByID(eventID);
}
/// <summary>
/// 清除该对象所有注册的事件
/// </summary>
/// <param name="target"></param>
public void RemoveEventListeningByTarget(object target)
{
    EventHandler.RemoveEventListeningByTarget(target);
}
/// <summary>
/// 立即处理事件
/// </summary>
/// <param name="eventID"></param>
/// <param name="param1"></param>
/// <param name="param2"></param>
public void SendMessage(EventID eventID, object param1, object param2)
{
    EventHandler.SentMessage(eventID, param1, param2);
}
/// <summary>
/// 分帧处理事件
/// </summary>
/// <param name="eventID"></param>
/// <param name="param1"></param>
/// <param name="param2"></param>
public void SendMessageByQue(EventID eventID, object param1, object param2)
{
    EventHandler.SentMessageByQue(eventID, param1, param2);
}

}

field:SerializeField

[field: SerializeField] public StringAsset standValueParameter { get; set; }

Unity 只能序列化字段;这些是自动属性{ get; set; }),因此用 [field: SerializeField] 标注到幕后字段,让它们在 Inspector 可见/可保存。

学习笔记如有侵权,请提醒我,我会马上删除
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇