C#のFuncおよびActionの理解ガイド ~初学者向け技術資料~
プログラミングにおいて、デリゲートはメソッドへの参照を保持し、柔軟なコード設計を可能にする強力な機能です。C#では、Func
やAction
といった汎用デリゲートが用意されており、これらを活用することでコードの再利用性と可読性を大幅に向上させることができます。また、Unityのようなゲームエンジンにおいては、UnityEvent
を使用することで、ゲームオブジェクト間の効率的な通信やイベントハンドリングが実現されます。
本資料では、C#のデリゲートの基本概念から始まり、Func
およびAction
の具体的な使い方、さらにUnityにおけるUnityEvent
の活用方法までを包括的に解説します。初学者の方々がデリゲートの力を理解し、実際のプロジェクトで効果的に活用できるよう、わかりやすい例とともに説明を進めていきます。
デリゲートの理解は、イベント処理や非同期プログラミング、LINQなど、C#およびUnityのさまざまな機能を深く理解し、効果的に活用するための基盤となります。本資料が、皆様のプログラミングスキル向上に寄与することを願っています。
1. はじめに
1.1 目的
本資料は、C#におけるFunc
およびAction
デリゲートの基本的な概念から実践的な使用方法、さらにUnityにおける活用例までを、初学者向けにわかりやすく解説することを目的としています。デリゲートの理解は、イベント処理やLINQ、そしてUnityのスクリプティングなど、多くの機能を効果的に活用する上で不可欠です。
1.2 対象読者
- C#プログラミングを学び始めたばかりの初心者
- デリゲート、特に
Func
およびAction
の概念を理解したい方 - Unityを使用してゲーム開発を行っている開発者
- より効率的なコードを書くための知識を深めたい開発者
2. C#のデリゲートとは
2.1 デリゲートの基本概念
デリゲートは、メソッドへの参照を保持する型安全なオブジェクトです。簡単に言えば、「メソッドへのポインタ」として機能します。これにより、メソッドを他のメソッドの引数として渡したり、後で実行したりすることが可能になります。
2.2 デリゲートの宣言と使用例
デリゲートの宣言
public delegate int MyDelegate(string s);
上記の例では、MyDelegate
という名前のデリゲートを宣言しています。このデリゲートは、string
型の引数を1つ取り、int
型の戻り値を返すメソッドを参照します。
デリゲートの使用例
public class DelegateExample
{
// デリゲートの宣言
public delegate int MyDelegate(string s);
// デリゲートを使用するメソッド
public void ExecuteDelegate()
{
MyDelegate del = new MyDelegate(StringLength);
int length = del("Hello, World!");
Console.WriteLine($"文字列の長さ: {length}");
}
// デリゲートが参照するメソッド
public int StringLength(string s)
{
return s.Length;
}
}
この例では、MyDelegate
デリゲートを使用して、StringLength
メソッドを呼び出しています。
3. Actionデリゲート
3.1 Actionとは何か
Action
は、戻り値を持たないメソッドを参照するための汎用デリゲートです。System
名前空間に定義されており、最大16個のパラメータを取ることができます。
3.2 Actionの使い方
Actionの宣言
Action<string> action = PrintMessage;
ここでは、string
型の引数を1つ取るAction
デリゲートを宣言しています。
3.3 例: Actionの実装例
public class ActionExample
{
// Actionデリゲートを使用するメソッド
public void ExecuteAction()
{
Action<string> action = PrintMessage;
action("こんにちは、Actionデリゲート!");
}
// Actionが参照するメソッド
public void PrintMessage(string message)
{
Console.WriteLine(message);
}
}
この例では、Action<string>
を使用して、PrintMessage
メソッドを呼び出しています。Action
は戻り値がないため、void
メソッドに適しています。
4. Funcデリゲート
4.1 Funcとは何か
Func
は、戻り値を持つメソッドを参照するための汎用デリゲートです。System
名前空間に定義されており、最大16個のパラメータを取ることができます。最後の型引数が戻り値の型として扱われます。
4.2 Funcの使い方
Funcの宣言
Func<string, int> func = GetStringLength;
ここでは、string
型の引数を1つ取り、int
型の戻り値を返すFunc
デリゲートを宣言しています。
4.3 例: Funcの実装例
public class FuncExample
{
// Funcデリゲートを使用するメソッド
public void ExecuteFunc()
{
Func<string, int> func = GetStringLength;
int length = func("こんにちは、Funcデリゲート!");
Console.WriteLine($"文字列の長さ: {length}");
}
// Funcが参照するメソッド
public int GetStringLength(string s)
{
return s.Length;
}
}
この例では、Func<string, int>
を使用して、GetStringLength
メソッドを呼び出しています。Func
は戻り値があるため、結果を受け取ることができます。
5. ActionとFuncの違い
5.1 戻り値の有無
- Action: 戻り値がありません。
void
メソッドに適しています。 - Func: 戻り値があります。最後の型引数が戻り値の型となります。
5.2 使用シナリオの比較
- Action:
- イベントハンドラ
- 何らかの処理を実行するが結果を返さない場合
- Func:
- データを処理して結果を返す場合
- LINQクエリ内での条件や変換処理
6. ラムダ式との組み合わせ
6.1 ラムダ式の基本
ラムダ式は、匿名メソッドを簡潔に表現する方法です。Func
やAction
と組み合わせて、インラインで処理を定義する際に非常に便利です。
6.2 FuncとActionでのラムダ式の使用例
Funcとラムダ式
Func<int, int, int> add = (x, y) => x + y;
int result = add(5, 3);
Console.WriteLine($"結果: {result}"); // 出力: 結果: 8
Actionとラムダ式
Action<string> greet = name => Console.WriteLine($"こんにちは、{name}!");
greet("太郎"); // 出力: こんにちは、太郎!
7. 実践的な使用例
7.1 イベントハンドリング
Action
はイベントハンドラとしてよく使用されます。例えば、ボタンがクリックされたときに特定の処理を実行する場合などです。
public class Button
{
public event Action OnClick;
public void Click()
{
OnClick?.Invoke();
}
}
public class EventExample
{
public void Run()
{
Button button = new Button();
button.OnClick += () => Console.WriteLine("ボタンがクリックされました!");
button.Click();
}
}
7.2 LINQとの連携
Func
はLINQクエリ内での条件や変換処理に頻繁に使用されます。
using System;
using System.Linq;
using System.Collections.Generic;
public class LinqExample
{
public void Run()
{
List<int> numbers = new List<int> {1, 2, 3, 4, 5};
Func<int, bool> isEven = x => x % 2 == 0;
var evenNumbers = numbers.Where(isEven);
Console.WriteLine("偶数:");
foreach(var num in evenNumbers)
{
Console.WriteLine(num);
}
}
}
7.3 他のシナリオ
- 非同期処理: デリゲートを使用してバックグラウンドでの処理を実行
- コールバック: 処理完了時に呼び出すメソッドを指定
8. Unityでの活用
UnityはC#を主要なスクリプティング言語として使用しており、Func
やAction
デリゲートはさまざまなシナリオで活用されています。ここでは、UnityにおけるFunc
とAction
の具体的な活用方法をいくつか紹介します。
8.1 イベントシステムの構築
Unityでは、ゲーム内のイベント(例えば、プレイヤーの死亡、アイテムの取得など)を処理するために、Action
デリゲートを使用することが一般的です。これにより、柔軟で拡張性の高いイベントハンドリングが可能になります。
例: プレイヤーの死亡イベント
using UnityEngine;
using System;
public class Player : MonoBehaviour
{
// プレイヤー死亡時のイベント
public event Action OnDeath;
public void Die()
{
// 死亡処理のロジック
Debug.Log("プレイヤーが死亡しました。");
// イベントの発火
OnDeath?.Invoke();
}
}
public class GameManager : MonoBehaviour
{
public Player player;
void Start()
{
if (player != null)
{
player.OnDeath += HandlePlayerDeath;
}
}
void HandlePlayerDeath()
{
Debug.Log("ゲームマネージャー: プレイヤーの死亡を検知しました。ゲームオーバー画面を表示します。");
// ゲームオーバー画面の表示などの処理
}
}
この例では、Player
クラスが死亡時にOnDeath
イベントを発火し、GameManager
がそのイベントを購読して適切な処理を実行しています。
8.2 コールバックによる非同期処理
Unityでは、非同期処理やコルーチンを使用して時間のかかる処理を実行することがよくあります。Action
やFunc
を使用して、処理完了時にコールバックを実行することが可能です。
例: データのロードとコールバック
using UnityEngine;
using System;
using System.Collections;
public class DataLoader : MonoBehaviour
{
// データロード完了時のコールバック
public void LoadData(Action<string> onDataLoaded)
{
StartCoroutine(LoadDataCoroutine(onDataLoaded));
}
private IEnumerator LoadDataCoroutine(Action<string> onDataLoaded)
{
// データロードのシミュレーション
yield return new WaitForSeconds(2f);
string data = "ロードされたデータ";
// コールバックの実行
onDataLoaded?.Invoke(data);
}
}
public class DataManager : MonoBehaviour
{
public DataLoader dataLoader;
void Start()
{
if (dataLoader != null)
{
dataLoader.LoadData(OnDataLoaded);
}
}
void OnDataLoaded(string data)
{
Debug.Log($"データがロードされました: {data}");
// ロードされたデータを使用した処理
}
}
この例では、DataLoader
がデータを非同期でロードし、ロード完了後にOnDataLoaded
コールバックを実行しています。
8.3 UI要素の操作
UnityのUIシステムでは、ボタンのクリックやスライダーの値変更などのイベントに対してAction
デリゲートを使用して処理を割り当てることができます。
例: ボタンのクリックイベント
using UnityEngine;
using UnityEngine.UI;
using System;
public class UIButtonHandler : MonoBehaviour
{
public Button myButton;
void Start()
{
if (myButton != null)
{
// リスナーにActionデリゲートを追加
myButton.onClick.AddListener(() => OnButtonClicked());
}
}
void OnButtonClicked()
{
Debug.Log("ボタンがクリックされました!");
// ボタンクリック時の処理
}
}
変更点の説明
- ラムダ式の省略:
- 元のコードでは、
AddListener
にラムダ式() => OnButtonClicked()
を渡していましたが、これは冗長です。 - 代わりに、直接メソッド名
OnButtonClicked
を渡すことで同じ効果を得られます。
- 元のコードでは、
// 省略前
myButton.onClick.AddListener(() => OnButtonClicked());
// 省略後
myButton.onClick.AddListener(OnButtonClicked);
この例では、Button
のonClick
イベントにAction
デリゲートを使用してOnButtonClicked
メソッドを割り当てています。
8.4 Func
を使用した条件付きロジック
Func
デリゲートは、条件付きロジックやフィルタリング処理に役立ちます。例えば、敵キャラクターのAIで特定の条件を満たす場合にのみ行動を起こすようなシナリオで使用できます。
例: 敵の攻撃条件の評価
using UnityEngine;
using System;
public class EnemyAI : MonoBehaviour
{
// 攻撃条件を評価するFuncデリゲート
public Func<bool> CanAttack;
void Update()
{
if (CanAttack != null && CanAttack())
{
Attack();
}
}
void Attack()
{
Debug.Log("敵が攻撃を開始しました!");
// 攻撃処理の実装
}
}
public class GameController : MonoBehaviour
{
public EnemyAI enemy;
void Start()
{
if (enemy != null)
{
enemy.CanAttack = EvaluateAttackCondition;
}
}
bool EvaluateAttackCondition()
{
// 攻撃条件のロジック(例: プレイヤーが一定範囲内にいる)
float distanceToPlayer = Vector3.Distance(transform.position, FindObjectOfType<Player>().transform.position);
return distanceToPlayer < 10f;
}
}
この例では、EnemyAI
がCanAttack
というFunc<bool>
デリゲートを使用して攻撃条件を評価し、条件が満たされた場合に攻撃を実行します。
8.5 イベントベースのメッセージングシステム
大規模なプロジェクトでは、イベントベースのメッセージングシステムを導入することで、コンポーネント間の通信を効率化できます。Action
デリゲートを活用して、柔軟なメッセージングを実現します。
例: シンプルなイベントマネージャー
using UnityEngine;
using System;
using System.Collections.Generic;
public class EventManager : MonoBehaviour
{
private Dictionary<string, Action> eventDictionary = new Dictionary<string, Action>();
public static EventManager Instance { get; private set; }
void Awake()
{
// シングルトンパターンの実装
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
// イベントの登録
public void StartListening(string eventName, Action listener)
{
if (eventDictionary.TryGetValue(eventName, out Action thisEvent))
{
thisEvent += listener;
eventDictionary[eventName] = thisEvent;
}
else
{
thisEvent += listener;
eventDictionary.Add(eventName, thisEvent);
}
}
// イベントの発火
public void TriggerEvent(string eventName)
{
if (eventDictionary.TryGetValue(eventName, out Action thisEvent))
{
thisEvent.Invoke();
}
}
// イベントの解除
public void StopListening(string eventName, Action listener)
{
if (Instance == null) return;
if (eventDictionary.TryGetValue(eventName, out Action thisEvent))
{
thisEvent -= listener;
eventDictionary[eventName] = thisEvent;
}
}
}
public class Player : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
// スペースキーが押されたら「PlayerJumped」イベントを発火
EventManager.Instance.TriggerEvent("PlayerJumped");
}
}
}
public class UIManager : MonoBehaviour
{
void OnEnable()
{
EventManager.Instance.StartListening("PlayerJumped", OnPlayerJumped);
}
void OnDisable()
{
EventManager.Instance.StopListening("PlayerJumped", OnPlayerJumped);
}
void OnPlayerJumped()
{
Debug.Log("UIManager: プレイヤーがジャンプしました!UIを更新します。");
// UI更新処理の実装
}
}
この例では、EventManager
がAction
デリゲートを使用してイベントの登録、発火、解除を管理しています。Player
がスペースキーを押すと「PlayerJumped」イベントが発火し、UIManager
がそのイベントを受け取ってUIを更新します。
8.6 UnityEventの活用
UnityEvent
は、Unity独自のイベントシステムであり、インスペクター上でイベントのリスナーを設定できるなど、エディタとの統合が特徴です。UnityEvent
はSystem.Serializable
であり、C#のAction
やFunc
と同様にメソッドの参照を保持しますが、エディタ上での柔軟な操作が可能です。
8.6.1 UnityEventとは何か
UnityEvent
は、UnityEngine.Events
名前空間に属するクラスで、イベントのリスニングと発火を簡単に行うための仕組みです。主に以下の特徴を持っています:
- インスペクターから直接イベントリスナーを追加可能
- 引数を持つイベントをサポート
- デザインタイムでイベントの設定が可能
8.6.2 UnityEventの基本的な使い方
例: シンプルなUnityEventの使用
using UnityEngine;
using UnityEngine.Events;
public class UnityEventExample : MonoBehaviour
{
// UnityEventの宣言(publicフィールド)
public UnityEvent onActionPerformed;
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
PerformAction();
}
}
void PerformAction()
{
Debug.Log("アクションが実行されました!");
// UnityEventの発火
onActionPerformed.Invoke();
}
}
このスクリプトをGameObjectにアタッチすると、インスペクター上でonActionPerformed
イベントにリスナーを追加できます。例えば、別のメソッドをドラッグ&ドロップで追加し、特定のアクションを実行させることができます。
UnityEventのインスペクター設定
- スクリプトのアタッチ:
UnityEventExample
スクリプトを任意のGameObjectにアタッチします。
- イベントリスナーの追加:
- アタッチしたGameObjectを選択します。
- インスペクターに表示される
UnityEventExample
コンポーネント内のOn Action Performed
イベントセクションに移動します。 +
ボタンをクリックして新しいリスナーを追加します。- リスナーとして使用したいGameObject(例えば、別のスクリプトがアタッチされているGameObject)をドラッグ&ドロップします。
- ドロップダウンメニューから実行したいメソッドを選択します。
8.6.3 UnityEventとAction/Funcの比較
特徴 | UnityEvent | Action/Func |
---|---|---|
インスペクター対応 | あり | なし |
引数のサポート | 型指定された引数をサポート(UnityEvent) | 型指定された引数をサポート(Func, Action) |
エディタとの統合 | 高い | 低い |
コードによる柔軟性 | 制限あり(インスペクター経由が主) | 高い(コード内で自由に使用可能) |
8.6.4 UnityEventの実践的な使用例
例: ゲーム開始イベントの設定
using UnityEngine;
using UnityEngine.Events;
public class GameStarter : MonoBehaviour
{
// UnityEventの宣言(publicフィールド)
public UnityEvent onGameStart;
void Update()
{
if (Input.GetKeyDown(KeyCode.S))
{
StartGame();
}
}
void StartGame()
{
Debug.Log("ゲームが開始されました!");
onGameStart.Invoke();
}
}
public class GameListener : MonoBehaviour
{
public GameStarter gameStarter;
void OnEnable()
{
if (gameStarter != null)
{
gameStarter.onGameStart.AddListener(OnGameStartHandler);
}
}
void OnDisable()
{
if (gameStarter != null)
{
gameStarter.onGameStart.RemoveListener(OnGameStartHandler);
}
}
void OnGameStartHandler()
{
Debug.Log("GameListener: ゲーム開始イベントを受信しました。");
// ゲーム開始時の処理を実装
}
}
この例では、GameStarter
がonGameStart
イベントを発火し、GameListener
がそのイベントをリッスンして処理を実行します。インスペクターを使用してイベントのリスナーを設定することも可能です。
例: UIボタンとUnityEventの連携
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
public class UIButtonUnityEvent : MonoBehaviour
{
public Button myButton;
public UnityEvent onButtonClicked;
void Start()
{
if (myButton != null)
{
myButton.onClick.AddListener(() => OnButtonClicked());
}
// UnityEventのインスタンス化は不要(publicフィールドとして宣言されているため)
}
void OnButtonClicked()
{
Debug.Log("UIButtonUnityEvent: ボタンがクリックされました!");
onButtonClicked.Invoke();
}
}
このスクリプトをGameObjectにアタッチし、myButton
にUIのボタンを設定すると、ボタンがクリックされた際にonButtonClicked
イベントが発火します。インスペクター上で追加のリスナーを設定することで、クリック時に実行したい処理を柔軟に設定できます。
8.6.5 UnityEventのカスタム引数(Unity 2019.3以降)
Unity 2019.3以降では、UnityEvent
がジェネリック化されており、カスタム引数を持つイベントをより簡単に実装できるようになりました。これにより、カスタムクラスや複数の引数を直接UnityEvent
に渡すことが可能です。
例: カスタム引数を持つUnityEvent(Unity 2019.3以降)
using UnityEngine;
using UnityEngine.Events;
// カスタム引数クラス
[System.Serializable]
public class PlayerData
{
public string playerName;
public int playerScore;
}
public class Player : MonoBehaviour
{
// ジェネリックUnityEventの宣言(publicフィールド)
public UnityEvent<PlayerData> onPlayerScored;
void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
PlayerScored("太郎", 100);
}
}
void PlayerScored(string name, int score)
{
Debug.Log($"プレイヤー {name} が {score} 点を獲得しました!");
PlayerData data = new PlayerData { playerName = name, playerScore = score };
onPlayerScored.Invoke(data);
}
}
public class ScoreDisplay : MonoBehaviour
{
public Player player;
void OnEnable()
{
if (player != null)
{
player.onPlayerScored.AddListener(OnPlayerScoredHandler);
}
}
void OnDisable()
{
if (player != null)
{
player.onPlayerScored.RemoveListener(OnPlayerScoredHandler);
}
}
void OnPlayerScoredHandler(PlayerData data)
{
Debug.Log($"ScoreDisplay: プレイヤー {data.playerName} のスコアは {data.playerScore} 点です。");
// スコア表示の更新処理
}
}
説明:
- カスタム引数クラスの定義
PlayerData
クラスを定義し、プレイヤーの名前とスコアを保持します。
- ジェネリックUnityEventの宣言
Player
クラス内でUnityEvent<PlayerData>
を宣言し、プレイヤーがスコアを獲得したときにイベントを発火します。
- イベントの発火
PlayerScored
メソッドでスコアが更新された際にonPlayerScored.Invoke(data)
を呼び出し、イベントを発火します。
- イベントのリッスン
ScoreDisplay
クラスはPlayer
のonPlayerScored
イベントをリッスンし、イベントが発火された際にスコア表示を更新します。
Unity 2019.3以降のポイント:
- ジェネリックUnityEventの拡張:
UnityEvent<T0>
を使用することで、カスタムクラスや特定の引数を持つイベントを直接定義できます。 - インスペクターでのサポート強化: インスペクター上で
UnityEvent<PlayerData>
を設定し、リスナーとして任意のメソッドを追加できます。
例: 複数の引数を持つUnityEvent(Unity 2019.3以降)
using UnityEngine;
using UnityEngine.Events;
public class GameEventManager : MonoBehaviour
{
// ジェネリックUnityEvent<string, int>の宣言(publicフィールド)
public UnityEvent<string, int> onEnemyDefeated;
void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
EnemyDefeated("Goblin", 50);
}
}
void EnemyDefeated(string enemyType, int damage)
{
Debug.Log($"敵 {enemyType} が倒され、{damage} ダメージを受けました!");
onEnemyDefeated.Invoke(enemyType, damage);
}
}
public class EnemyUI : MonoBehaviour
{
public GameEventManager eventManager;
void OnEnable()
{
if (eventManager != null)
{
eventManager.onEnemyDefeated.AddListener(OnEnemyDefeatedHandler);
}
}
void OnDisable()
{
if (eventManager != null)
{
eventManager.onEnemyDefeated.RemoveListener(OnEnemyDefeatedHandler);
}
}
void OnEnemyDefeatedHandler(string enemyType, int damage)
{
Debug.Log($"EnemyUI: 敵 {enemyType} が倒され、{damage} ダメージを受けました。UIを更新します。");
// UI更新処理の実装
}
}
説明:
- ジェネリックUnityEventの宣言
GameEventManager
クラス内でUnityEvent<string, int>
を宣言し、敵が倒された際にイベントを発火します。
- イベントの発火
EnemyDefeated
メソッドで敵が倒された際にonEnemyDefeated.Invoke(enemyType, damage)
を呼び出し、イベントを発火します。
- イベントのリッスン
EnemyUI
クラスはGameEventManager
のonEnemyDefeated
イベントをリッスンし、イベントが発火された際にUIを更新します。
Unity 2019.3以降のポイント:
- 複数の引数を持つ
UnityEvent<T0, T1>
などを直接使用でき、柔軟なイベントハンドリングが可能です。 - インスペクター上で複数の引数を設定し、リスナーとして任意のメソッドを追加できます。
8.7 UnityEventの注意点
8.7.1 パフォーマンスへの影響
UnityEvent
は非常に便利ですが、頻繁に発火させるイベントや大量のリスナーを持つイベントではパフォーマンスに影響を与える可能性があります。必要に応じてAction
やFunc
を使用することを検討してください。
8.7.2 型の整合性
ジェネリックUnityEvent
を使用する際は、リスナーが正しい型の引数を受け取るように注意してください。型が一致しないと、実行時エラーが発生する可能性があります。
9. よくある質問
Q1: デリゲートとFunc/Actionの違いは何ですか?
A1: Func
およびAction
は、汎用デリゲートの特殊な形式であり、特定のシグネチャに基づいています。Func
は戻り値を持ち、Action
は戻り値を持たないデリゲートです。デリゲートはより一般的な概念で、独自のシグネチャを持つデリゲートを定義することもできます。
Q2: どちらを使用すべきですか、FuncとAction?
A2: 処理に戻り値が必要な場合はFunc
を使用し、戻り値が不要な場合はAction
を使用します。目的に応じて使い分けることが重要です。
Q3: ラムダ式と匿名メソッドの違いは何ですか?
A3: ラムダ式は匿名メソッドの簡潔な表現方法です。機能的には同じですが、ラムダ式の方がコードが短く読みやすい場合が多いです。
Q4: UnityでFunc
やAction
を使用するときの注意点はありますか?
A4: Unityのメインスレッド以外でデリゲートを使用する場合、スレッドセーフであることを確認する必要があります。また、イベントの購読解除を忘れるとメモリリークの原因になるため、OnDestroy
やOnDisable
などで適切に解除することが重要です。
Q5: UnityEventとAction/Funcの違いは何ですか?
A5: UnityEvent
はUnity専用のイベントシステムであり、インスペクターからイベントリスナーを設定できる点が大きな特徴です。一方、Action
やFunc
はC#の汎用デリゲートで、コード内で柔軟に使用できます。用途に応じて、エディタ上での設定が必要な場合はUnityEvent
を、コード内での柔軟な処理が必要な場合はAction
やFunc
を選択すると良いでしょう。
10. まとめ
本資料では、C#におけるFunc
およびAction
デリゲートの基本的な概念から実践的な使用方法、さらにUnityにおける活用例までを解説しました。UnityEvent
の紹介を通じて、Unity特有のイベントシステムとの連携方法も学びました。Unity 2019.3以降では、ジェネリックなUnityEvent<T>
を活用することで、より柔軟かつ強力なイベントハンドリングが可能となっています。デリゲートを理解することで、より柔軟で再利用可能なコードを記述することが可能となります。Func
とAction
を適切に使い分け、C#およびUnityの強力な機能を活用しましょう。
11. 参考資料
- Microsoft Docs – Delegates (C# Programming Guide)
- Microsoft Docs – Func Delegate
- Microsoft Docs – Action Delegate
- C# のラムダ式とLINQのチュートリアル
- Unity公式ドキュメント – イベントシステム
- Unity公式ドキュメント – コルーチン
- Unity公式ドキュメント – UnityEvent クラス
- Unity公式ドキュメント – UnityEvent クラス
- Unity リリースノート
付録: 修正箇所の詳細
UnityEventのジェネリック化のサポートバージョン
- ジェネリック
UnityEvent<T0>
,UnityEvent<T0, T1>
, などはUnity 2019.3以降からサポートされています。それ以前のバージョンでは、ジェネリックなUnityEvent
は利用できず、引数なしのUnityEvent
や、特定のシグネチャに基づくカスタムデリゲートを使用する必要がありました。
new UnityEvent<T>()
の必要性
- パブリックフィールドの場合 (
public UnityEvent<T>
): - Unityのシリアライゼーションシステムにより、インスペクター上で自動的にインスタンス化されます。
- そのため、
Start()
メソッド内でnew UnityEvent<T>()
を呼び出す必要は不要です。 - インスペクターを通じてイベントリスナーを追加できるため、コード内でのインスタンス化は冗長となります。
- プライベートフィールドまたはシリアライズされていない場合 (
private UnityEvent<T>
または[SerializeField] private UnityEvent<T>
): - シリアライズされていない場合、インスペクターから設定されないため、
new UnityEvent<T>()
での明示的なインスタンス化が必要です。 [SerializeField]
を使用してプライベートフィールドをシリアライズする場合、インスペクターで設定されるため通常はインスタンス化は不要ですが、確実を期すためにインスタンス化を行うこともあります。
修正後のコード例
修正前
using UnityEngine;
using UnityEngine.Events;
// カスタム引数クラス
[System.Serializable]
public class PlayerData
{
public string playerName;
public int playerScore;
}
public class Player : MonoBehaviour
{
// ジェネリックUnityEventの宣言
public UnityEvent<PlayerData> onPlayerScored;
void Start()
{
if (onPlayerScored == null)
onPlayerScored = new UnityEvent<PlayerData>();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
PlayerScored("太郎", 100);
}
}
void PlayerScored(string name, int score)
{
Debug.Log($"プレイヤー {name} が {score} 点を獲得しました!");
PlayerData data = new PlayerData { playerName = name, playerScore = score };
onPlayerScored.Invoke(data);
}
}
public class ScoreDisplay : MonoBehaviour
{
public Player player;
void OnEnable()
{
if (player != null)
{
player.onPlayerScored.AddListener(OnPlayerScoredHandler);
}
}
void OnDisable()
{
if (player != null)
{
player.onPlayerScored.RemoveListener(OnPlayerScoredHandler);
}
}
void OnPlayerScoredHandler(PlayerData data)
{
Debug.Log($"ScoreDisplay: プレイヤー {data.playerName} のスコアは {data.playerScore} 点です。");
// スコア表示の更新処理
}
}
修正後
using UnityEngine;
using UnityEngine.Events;
// カスタム引数クラス
[System.Serializable]
public class PlayerData
{
public string playerName;
public int playerScore;
}
public class Player : MonoBehaviour
{
// ジェネリックUnityEventの宣言(publicフィールド)
public UnityEvent<PlayerData> onPlayerScored;
void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
PlayerScored("太郎", 100);
}
}
void PlayerScored(string name, int score)
{
Debug.Log($"プレイヤー {name} が {score} 点を獲得しました!");
PlayerData data = new PlayerData { playerName = name, playerScore = score };
onPlayerScored.Invoke(data);
}
}
public class ScoreDisplay : MonoBehaviour
{
public Player player;
void OnEnable()
{
if (player != null)
{
player.onPlayerScored.AddListener(OnPlayerScoredHandler);
}
}
void OnDisable()
{
if (player != null)
{
player.onPlayerScored.RemoveListener(OnPlayerScoredHandler);
}
}
void OnPlayerScoredHandler(PlayerData data)
{
Debug.Log($"ScoreDisplay: プレイヤー {data.playerName} のスコアは {data.playerScore} 点です。");
// スコア表示の更新処理
}
}
変更点:
Start()
メソッド内でのnew UnityEvent<PlayerData>()
によるインスタンス化を削除。onPlayerScored
がpublic
フィールドとして宣言されているため、Unityのシリアライゼーションシステムが自動的にインスタンス化します。
理由:
public
フィールドとして宣言されているため、Unityのシリアライゼーションシステムが自動的にインスタンス化します。これにより、Start()
メソッド内でのインスタンス化が不要となり、コードがよりシンプルかつ明瞭になります。
インスペクター上での設定手順
- スクリプトのアタッチ:
Player
およびScoreDisplay
スクリプトをそれぞれのGameObjectにアタッチします。
- イベントリスナーの追加:
Player
スクリプトがアタッチされたGameObjectを選択します。- インスペクターに表示される
Player
コンポーネント内のOn Player Scored
イベントセクションに移動します。 +
ボタンをクリックして、新しいリスナーを追加します。- リスナーとして使用するGameObject(
ScoreDisplay
がアタッチされているオブジェクト)をドラッグ&ドロップします。 - ドロップダウンメニューから
ScoreDisplay.OnPlayerScoredHandler(PlayerData)
メソッドを選択します。
ディスカッション
コメント一覧
まだ、コメントがありません