Unity 事件入門:5 種 C# 委派與事件用法全面解析

Ted Liou 2025.09.08 Unity 最後更新 2026.03.17

快速摘要

Unity 裡沒有哪一種事件寫法能通吃所有情境。本文用同一組範例比較 delegateActionUnityActionUnityEventEventHandler<TEventArgs>,並直接給出實務選型。

Unity 裡能用來傳遞事件的寫法很多,但實務上不需要把它們當成五個平行選項來背。一般程式邏輯大多用 Action 就夠了;要讓事件能在 Inspector 直接綁定,才輪到 UnityEvent;當事件資料開始變多、後面還可能繼續擴充時,再考慮 EventHandler<TEventArgs> 會比較穩。

真正麻煩的地方,在於我們常在還沒分清楚情境時,就先選了工具。結果專案一大,事件越掛越多,訂閱點越來越散,後面要讀、要改、要除錯都開始卡。本文用同一組範例把五種常見寫法擺在一起,先看差異,再談怎麼選。

先把選型原則講清楚

如果事件只是用來通知程式內部某件事發生了,例如血量改變、按鈕按下、狀態切換,Action 通常是最乾脆的選擇。它是標準 C# 委派,寫法短,也不會把程式邏輯綁在 Unity 特定 API 上。

UnityEvent 的位置很不一樣。它最有價值的地方,在於可以序列化到場景與 Inspector,讓設計師、企劃或我們自己在編輯器裡直接綁事件。這點是一般 Actionevent 做不到的,Unity 官方文件也正是從這個角度在介紹它。

EventHandler<TEventArgs> 則是另一個方向。它的用途,是把事件資料整理成一個明確型別。當參數開始變多,或未來很可能還要再加欄位時,這種寫法通常比一路往 Action<int, GameObject, int> 疊參數更好讀。

五種寫法放在同一個範例裡

下面這份腳本故意把五種常見寫法寫在一起,參數都維持同一組:idplayerscore。這樣看差異會比較清楚。

 1using System;
 2using UnityEngine;
 3using UnityEngine.Events;
 4
 5public class DelegateExample : MonoBehaviour
 6{
 7    // A: 最基本的自訂委派
 8    public delegate void GameEventDelegate(int id, GameObject player, int score);
 9    public event GameEventDelegate OnGameEventA;
10
11    // B: C# Action
12    public event Action<int, GameObject, int> OnGameEventB;
13
14    // C: UnityAction
15    public event UnityAction<int, GameObject, int> OnGameEventC;
16
17    // D: UnityEvent
18    [Serializable]
19    public class GameUnityEvent : UnityEvent<int, GameObject, int> { }
20
21    public GameUnityEvent OnGameEventD;
22
23    // E: EventHandler<TEventArgs>
24    public class GameEventArgs : EventArgs
25    {
26        public int Id { get; }
27        public GameObject Player { get; }
28        public int Score { get; }
29
30        public GameEventArgs(int id, GameObject player, int score)
31        {
32            Id = id;
33            Player = player;
34            Score = score;
35        }
36    }
37
38    public event EventHandler<GameEventArgs> OnGameEventE;
39
40    private void Start()
41    {
42        GameObject playerObject = new GameObject("Player");
43
44        OnGameEventA += HandleGameEvent;
45        OnGameEventA += (id, player, score) =>
46            Debug.Log($"Event A Triggered. ID: {id}, Player: {player.name}, Score: {score}");
47        OnGameEventA?.Invoke(1, playerObject, 100);
48
49        OnGameEventB += HandleGameEvent;
50        OnGameEventB += (id, player, score) =>
51            Debug.Log($"Event B Triggered. ID: {id}, Player: {player.name}, Score: {score}");
52        OnGameEventB?.Invoke(2, playerObject, 200);
53
54        OnGameEventC += HandleGameEvent;
55        OnGameEventC += (id, player, score) =>
56            Debug.Log($"Event C Triggered. ID: {id}, Player: {player.name}, Score: {score}");
57        OnGameEventC?.Invoke(3, playerObject, 300);
58
59        OnGameEventD ??= new GameUnityEvent();
60        OnGameEventD.AddListener(HandleGameEvent);
61        OnGameEventD.AddListener((id, player, score) =>
62            Debug.Log($"Event D Triggered. ID: {id}, Player: {player.name}, Score: {score}"));
63        OnGameEventD.Invoke(4, playerObject, 400);
64
65        OnGameEventE += HandleGameEventE;
66        OnGameEventE += (sender, args) =>
67            Debug.Log($"Event E Triggered. ID: {args.Id}, Player: {args.Player.name}, Score: {args.Score}");
68        OnGameEventE?.Invoke(this, new GameEventArgs(5, playerObject, 500));
69    }
70
71    private void HandleGameEvent(int id, GameObject player, int score)
72    {
73        Debug.Log($"Handled Game Event. ID: {id}, Player: {player.name}, Score: {score}");
74    }
75
76    private void HandleGameEventE(object sender, GameEventArgs args)
77    {
78        Debug.Log($"Handled Game Event E. ID: {args.Id}, Player: {args.Player.name}, Score: {args.Score}");
79    }
80}

五種寫法各自適合什麼情境

如果只看語法,這五種寫法很像都能做到「通知別人」。但拉回 Unity 專案的日常工作,它們各自站的位置其實很不一樣。

類型優點缺點適用情境
基本 delegate完全自訂,想怎麼定義方法簽名都可以。需要額外宣告型別,通常比 Action 冗長。想保留明確語意,或需要客製化委派型別時。
C# Action簡潔、標準、可移植性高。無法在 Inspector 直接配置。最常見的程式邏輯事件。
UnityAction和 Unity 事件系統關係密切,很多 Unity API 直接使用它。若沒有和 Unity API 互動,和 Action 的差異不大。已經在用 UnityEvent,或 API 明確要求 UnityAction 時。
UnityEvent可序列化到 Inspector,能加持久化回呼。程式碼側的註冊寫法較重,也更依賴 Unity 環境。UI 按鈕、動畫事件、設計師會直接配置的場合。
EventHandler事件資料可集中管理,欄位擴充時比較穩。寫法比 Action 長。參數多、事件資料會成長,或想走標準 .NET 事件模式時。

如果想看官方對 UnityEventUnityAction 的定位,請參考:Unity Manual: UnityEventsUnity Scripting API: UnityActionEventHandler<TEventArgs> 的標準事件模式則可參考:Microsoft Learn: Standard .NET event patterns

參數一多,EventHandler<TEventArgs> 開始有優勢

範例裡所有事件都傳了三個參數。這時候 Action<int, GameObject, int> 還看得懂,但已經開始接近臨界點了。再多一兩個欄位,或其中一個欄位要改型別,訂閱端和呼叫端都會一起變得很吵。

把事件資料包進 EventArgs 類別後,讀程式的人不必一直背參數順序。看到 args.Playerargs.Score,意圖就很直接。後面如果還要加欄位,也是在 GameEventArgs 上擴充,而不是把每個訂閱方法全部改一輪。

這也是為什麼我在專案裡的習慣很明確:少參數、單純通知型事件,用 Action;事件資料開始成形,甚至已經像一筆獨立訊息時,就改成 EventHandler<TEventArgs>。這樣後面比較不會後悔。

總結

Unity 事件寫法很多,但真正需要先做的只有一件事:分清楚這個事件到底要服務誰。一般程式邏輯優先用 Action;需要 Inspector 綁定時用 UnityEvent;事件資料開始變複雜時,再改成 EventHandler<TEventArgs>。把這個順序抓穩,後面就不太會為了語法選型反覆重寫。

常見問題

如果是少參數或一般程式邏輯事件,Action 通常是最直觀也最好移植的做法。它夠簡潔,也不會把程式邏輯綁在 Unity 特定 API 上。

當事件參數變多、未來還可能擴充時,EventHandler<TEventArgs> 會比較穩。因為資料可以包進 EventArgs 類別,可讀性和維護性都會比一長串參數更好。

不一定。UnityEvent 的優勢在於可以在編輯器中配置,適合 UI、動畫事件或設計師會直接操作的情境;如果只是一般程式邏輯,Action 往往更乾脆。

作者

Ted Liou

現職 Unity C# 工程師,主要分享 Unity、C# 與 Vibe Coding 相關技術教學。

上一篇 Unity Android 開發環境起手式:先把 Editor、SDK、NDK、JDK 準備好 下一篇 Unity 專案太大怎麼辦?3 步驟搞定備份、打包與瘦身