[Unity] C#でのActionとFunc、ラムダ式の使い方の基本

ActionとFuncの違いは?

ActionFuncは、C#でよく使われるデリゲート(委譲)型ですが、目的に応じて使い分けます。

Action

戻り値がないメソッドを参照するために使います。例えば、ボタンが押された時やイベントが発生した時に何かを実行したい場合に使用します。

C#
Action myAction = () => Console.WriteLine("Action Executed");
myAction();  // コンソールに「Action Executed」が出力される
C#
Func

戻り値があるメソッドを参照します。Funcを使うと、入力を受け取り、結果を返す処理を簡潔に記述できます。最後の型パラメータが戻り値の型になります。

C#
Func<int, int, int> add = (a, b) => a + b;
int result = add(3, 5);  // 8が返る
C#

デリゲートは、メソッドの参照を持つ型です。ActionFuncもデリゲートをベースにしており、基本的にはデリゲートの一部として考えられます。

手動でデリゲートを定義することもできますが、ActionFuncはそれを簡略化するために用意されています。

カスタムデリゲートの例:

C#
public delegate void CustomDelegate(string message);

CustomDelegate myDelegate = (msg) => Console.WriteLine(msg);
myDelegate("This is a custom delegate!");
C#

しかし、ActionFuncを使えば、カスタムデリゲートを定義する必要がなく、簡単に同様の処理が可能です。

メソッドの登録

ActionFuncにメソッドを登録するときには、+=演算子を使います。これは、イベントやデリゲートに対してメソッドを追加する際の基本的な操作です。なお、メソッドに引数が必要な場合は、ラムダ式を使用して引数を渡す必要があります。以下にActionを例に説明します。

+= で直接メソッドを追加する場合

Actionに対してメソッドを登録する際、直接メソッドを参照できる場合は、+=だけで問題ありません。このケースでは、メソッドのシグネチャがActionと一致している必要があります(つまり、戻り値がなく、引数が同じであること)。

:

C#
public class GameManager {
    public void DecreasePlayerLives() {
        Console.WriteLine("Lives Decreased");
    }
}

enemy.onEnemyReachedGoal += gameManager.DecreasePlayerLives;
C#

ここでは、DecreasePlayerLivesは引数を取らないメソッドなので、そのままActionに追加できます。

+= () => でラムダ式を使う場合

引数を持つメソッドや、複雑な処理を登録したい場合は、+=に加えてラムダ式を使う必要があります。ラムダ式を使うことで、Actionが呼び出された時に、その場でパラメータを指定したり、処理を動的に追加することができます。

:

C#
enemy.onEnemyReachedGoal += () => waveSpawner.OnEnemyDeactivated(enemyGameObject);
C#

この場合、OnEnemyDeactivatedenemyGameObjectという引数が必要なので、ラムダ式でActionの中でこの引数を設定しています。

ラムダ式について

ラムダ式の基本

ラムダ式は、無名関数とも呼ばれ、関数名を持たずにその場で関数を定義して処理を行う方法です。簡潔に関数を記述でき、ActionFuncに直接登録できます。

ラムダ式の構文
C#
(parameters) => { function body }
C#
  • parameters: 関数が受け取る引数(ない場合は()
  • function body: 実行されるコード

例:

C#
Action greet = () => Console.WriteLine("Hello, World!");
greet();
C#

引数ありの例:

C#
Func<int, int, int> multiply = (x, y) => x * y;
Console.WriteLine(multiply(3, 4));  // 12
C#

ActionとFuncの応用シナリオ

イベントハンドリング

例えば、プレイヤーが特定のエリアに到達したとき、ポイントを加算したり、次のレベルへ進めるための処理を追加したい場合、Actionを使ったイベントハンドリングが有効です。

C#
// プレイヤーがエリアに到達したときのイベント
player.onPlayerReachedArea += () => gameManager.IncreaseScore();
C#

この例では、プレイヤーが特定のエリアに入ったときにスコアを加算する処理が登録されています。Actionは動的に処理を追加できるので、他のエリアに到達した場合に違う処理を追加したり、複数の処理を同じイベントに紐付けることが簡単です。

複数の処理を追加する場合も、+=で次々にイベントに処理を追加できます。

C#
player.onPlayerReachedArea += () => gameManager.IncreaseScore();
player.onPlayerReachedArea += () => levelManager.UnlockNextLevel();
C#

これにより、1つのイベントで複数の処理を同時に行うことができます。

コールバック関数

非同期処理や、ゲームの特定のタイミングで何かを行う必要がある場合にも、ActionFuncは便利です。たとえば、アイテム収集型のゲームにおいて、アイテムの収集が完了したら報酬を与える処理を実行するために、Actionをコールバックとして使えます。

C#
public void CollectItems(Action onComplete)
{
    // アイテムを収集する処理
    Console.WriteLine("Collecting items...");

    // アイテム収集が完了したらコールバックを実行
    onComplete?.Invoke();
}

// アイテム収集が完了した時点で、報酬を与える処理
CollectItems(() => Console.WriteLine("Reward granted!"));
C#

この例では、CollectItemsメソッドの完了後に、指定したコールバック関数が呼び出されます。コールバック関数を使用すると、非同期処理や順次処理が必要な場面で、処理が完了した後に動的に次の処理を追加できます。

補足: ActionとFuncの連携

特定の状況下では、ActionFuncを組み合わせることも非常に効果的です。例えば、一般的なRPGゲームのバトルシステムで、ターゲットの選定をFuncで行い、その結果に基づいて攻撃を行うActionを実行するパターンが考えられます。

C#
// 敵が攻撃可能かどうかを判断するためのFunc
Func<Enemy, bool> canAttack = (enemy) => enemy.IsInAttackRange();

// 敵に攻撃を行うためのAction
Action<Enemy> attack = (enemy) => enemy.TakeDamage(10);

// 条件に基づいて攻撃を実行
if (canAttack(targetEnemy)) {
    attack(targetEnemy);
}
C#

この例では、Func<Enemy, bool>を使ってターゲットが攻撃範囲内にいるかどうかを判断し、攻撃可能な場合にAction<Enemy>でダメージを与える処理を実行しています。このように、FuncActionを組み合わせることで、複雑な条件判定と処理を分離し、簡潔に管理できます。

最後に

2Dタワーディフェンスゲーム制作中にかなり重要そうな機能を知ったのでまとめてみました。ゲーム制作中にどんどん新しいことを知り、その度に内容をまとめているとなかなか時間が奪われますが、きっといつか自分の役に立つはず!

コメント

タイトルとURLをコピーしました