【Unity】コールバックとは?処理が終わった後に呼び出されるメソッド

コールバックとは? Unity

こんにちは、ともくんのゲーム作り部屋にようこそ!

このページでは、

「コールバック関数ってなに?」

「コールバックはどうやって使うの?」

というお悩みの方に向けた内容となっています。

Unityなどのプログラミングでは、コールバックと呼ばれる仕組みが使われることがあります。

このコールバックとは、外部のクラスの関数を呼び出す際に、クラス内の関数を引数で渡してあげることで、その処理が完了したタイミングで、渡した関数を実行してくれるという仕組みです。

コールバックを使うことで、ファイルの読み込みが完了したら処理を実行したり、アニメーションの再生が終わったら別の処理を行うといったことができるようになります。

そこで、このページでは、Unityで使われるコールバックについて、どんな仕組みなのか、また基本的な使い方や作り方までをまとめていきます。

この記事を書いた人

ゲーム作りを学び始めた一児のパパです。
このブログは、子供から「ゲームを作ってみたい!」と言われ、非プログラマーでゲーム作りをしたことない僕が、ゲーム作りの本を読んで独学でゲーム開発を学んでいるブログです。
同じように初めてゲーム作りをしている方と一緒に学んでいけるようなブログに出来たらいいなと思っています。
また、「このコードはおかしい」とか「もっと良い書き方があるよ!」などあれば、どんどん指摘して頂けると助かります。

コールバックとは?

まずは、コールバックがどういうものなのか紹介していきます。

処理が完了したら呼び出し元の関数が実行される仕組み

コールバックとは、ある関数の処理が完了した際に、呼び出し元のクラスの関数を実行することができる仕組みのことです。

もう少し詳しく言うと、外部のクラスの関数を呼び出す際に、引数を使ってクラス内で定義した関数を渡してあげることで、その関数の処理が終わった際などの特定のタイミングで、クラス内の関数を実行してくれる仕組みのことをコールバックと言い、そこで呼び出される関数のことをコールバック関数と言います。

例えば、時間を計測するTimerクラスを作り、その中で5秒間の計測を行うメソッドを定義します。

次に、爆弾を操作するBombクラスを作り、その中で爆弾を爆発させるメソッドを定義しておきます。

そして、BombクラスからTimerクラスのメソッドを呼び出す際に、爆発させるメソッドを引数で渡してあげることで、5秒間の計測が行われた後に、爆発させるという処理を行うことができます。

このように、外部のメソッドを呼び出して処理が行われた後に、今度は呼び出し元となるクラスのメソッドを呼び返してもらう仕組みのことをコールバックと言います。

非同期処理を作る際などに使われる

コールバックの仕組みは、非同期処理を作る際やUIによる操作などに使われることがあります。

例えば、サーバーからのデータの読み込み処理などを行う時に、時間がかかってしまうと、その間、次の処理が行えずゲーム画面が止まったままになってしまいます。

そこで、読み込み処理の際にコールバック関数を設定しておくことで、処理が終わるまでの間は別の処理を行い、処理が完了したらゲームに表示させるといった仕組みを作ることができます。

他にも、オブジェクトを移動させたり、アニメーションを再生させるような場合にコールバック関数を設定しておくことで、その処理が終わった後に特定の処理を実行させるということができます。

コールバックの基本的な作り方・使い方

ここからは、コールバックの基本的な作り方や使い方を紹介していきます。

Unityでのコールバックの作り方として、UnityEventUnityActionでも作れますが、ここではC#で一般的に使われるデリゲート(delegate)というものを使っていきます。

デリゲート(delegate)を使ったコールバックの作り方

デリゲートを使ったコールバックの基本的な作り方として、

  • delegate型を定義する(呼び出し先)
  • 引数でメソッドを受け取る(呼び出し先)
  • 引数にコールバックのメソッドを登録する(呼び出し元)

の3つの流れで紹介していきます。

delegate型を定義する(呼び出し先)

まず、呼び出し先となるクラスの中にdelegate型を定義していきます。

このdelegate型とは、メソッドを代入して変数のように使うことができる型となっていて、以下のように定義してあげます。

delegate 戻り値の型 デリゲート名(引数);

ここでは、以下の5行目のように戻り値は無しにして、引数で文字列を受け取るデリゲートを定義しています。

using UnityEngine;

public class Test : MonoBehaviour
{
    delegate void Sample(string str);   // Sampleというデリゲートを定義している
}

引数でメソッドを受け取る(呼び出し先)

次に、同じく呼び出し先のクラスのメソッドの引数に、先ほどのdelegate型の変数を設定して、呼び出し元からメソッドを受け取り、特定のタイミングでコールバックできるようにしていきます。

delegate型通常の変数の型と同様に扱うことができるので、

using UnityEngine;

public class Test : MonoBehaviour
{
    delegate void Sample(string str);

    void SampleMethod(Sample sample)    // 引数にデリゲートの変数を指定する
    {
        Debug.Log("処理を実行");
        sample("処理完了!");    // メソッドを呼び出してコールバック処理
    }
}

7行目のようにメソッドの引数に「デリゲート名 変数名」と書いてあげることで、呼び出し元からその変数でメソッドを受け取ることができます。

そして、この変数をメソッドの処理が完了した後など任意のタイミングで、10行目のように呼び出してあげることで、コールバックが行われます。

引数にコールバックのメソッドを登録する(呼び出し元)

最後に、呼び出し元からメソッドを呼び出す際に、引数にコールバックで実行したいメソッドを登録しておきます。

using UnityEngine;

public class Test2 : MonoBehaviour
{
    public Test test;   // インスペクターウィンドウから代入

    void Start()
    {
        test.SampleMethod(CallBack);    // メソッドの引数にコールバックで実行したいメソッドを記述
    }

    void CallBack(string str)
    {
        Debug.Log("コールバックの" + str); // コールバックの処理を記述
    }
}

9行目で呼び出し先のメソッドを記述していて、引数でCallBackというメソッドを指定しています。

これで、呼び出し先ではCallBackのメソッドをdelegate型の変数で受け取ることができるので、コールバック処理が実行されるようになります。

実際にゲームを開始してみると、

処理が実行された後に、コールバックの処理が行われているのが分かります。

オブジェクトの移動が終わったら処理を呼び出す

今度は、Unityでオブジェクトとボタンを使って、

  • ボタンを押したらオブジェクトが移動
  • 移動が完了したらボタンの内容が変更する(コールバック)

という処理を簡単に作ってみます。

まずは、オブジェクトに対して、以下のスクリプトをアタッチしておきます。

using UnityEngine;

public class BoxController : MonoBehaviour
{
    public delegate void OnComplete();  // デリゲート型を定義
    OnComplete onComplete;  // デリケート型のメンバ変数を宣言
    Vector3 startPos;
    bool isMoving;

    void Start()
    {
        startPos = transform.position;  // 初期位置を取得
    }

    void Update()
    {
        if (isMoving)
        {
            transform.Translate(0.02f, 0, 0);   // オブジェクトの移動処理
            if (transform.position.x > 7.0f)
            {
                isMoving = false;   // オブジェクトの移動を終了する
                onComplete();       // コールバック処理
            }
        }
    }

    public void BoxMove(OnComplete onComplete)  // 引数でメソッドを受け取る
    {
        this.onComplete = onComplete;   // メンバ変数に引数の値を代入
        isMoving = true;    // オブジェクトの移動を開始する
    }

    public void BoxInit(OnComplete onComplete)  // 引数でメソッドを受け取る
    {
        transform.position = startPos;  // 初期位置にオブジェクトを戻す処理
        onComplete();   // コールバック処理
    }
}

このBoxControllerが、呼び出し先のクラスとなります。

5行目でdelegate型OnCompleteという名前で定義していて、6行目でOnComplete型の変数を宣言しています。

28行目のBoxMoveメソッドは、bool型の値を変更してUpdate内でオブジェクトの移動処理を行うようにしていて、引数で受け取ったメソッドを30行目でメンバ変数のonCompleteに代入しています。

そして、Update内でオブジェクトの移動処理が終わった23行目で、onComplete();と記述することで、コールバック処理を行っています。

また、34行目で初期化するためのBoxInitメソッドを定義していて、ここでも引数でメソッドを受け取り、オブジェクトを初期位置に戻した後、37行目でコールバック処理を実行しています。

次に、UIのボタンに対して、以下のスクリプトをアタッチしておきます。

using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class ButtonController : MonoBehaviour
{
    public BoxController box;
    public TextMeshProUGUI tmp;
    public Button button;

    // 現在のボタンの状況を指定
    enum ButtonState
    {
        Start,
        Moving,
        Reset
    }
    ButtonState buttonState;

    // ボタンをクリックした場合
    public void ButtonClick()
    {
        switch(buttonState)
        {
            // スタートボタンの場合
            case ButtonState.Start:
                SetMoving();
                box.BoxMove(SetReset);  // 引数でコールバック関数を登録する
                break;
            // リセットボタンの場合
            case ButtonState.Reset:
                box.BoxInit(SetInit);  // 引数でコールバック関数を登録する
                break;
        }
    }

    // スタートボタンに変更する処理
    void SetInit()
    {
        tmp.text = "Start";
        buttonState = ButtonState.Start;
    }

    // 移動中の表示に変更する処理
    void SetMoving()
    {
        tmp.text = "Moving...";
        buttonState = ButtonState.Moving;
        button.interactable = false;
    }

    // リセットボタンに変更する処理
    void SetReset()
    {
        tmp.text = "Reset";
        buttonState = ButtonState.Reset;
        button.interactable = true;
    }
}

このButtonControllerが、呼び出し元のクラスとなります。

ここでは以下のメソッドを使って、それぞれの状況に応じて、ボタンの表示や操作を変更するように制御しています。

  • SetInitメソッド(38行目) ⇒ Startボタンに変更
  • SetMovingメソッド(45行目) ⇒ 移動中に表示するボタンに変更
  • SetResetメソッド(53行目) ⇒ Resetボタンに変更

SetInit

SetMoving

SetReset

そして、20行目でボタンをクリックした際に実行されるButtonClickメソッドを定義しています。

この中でスタートボタンの状態でクリックした場合、28行目でBoxControllerBoxMoveメソッドを呼び出していて、引数でSetResetを指定することで、移動処理が終わったらリセットボタンに変更する処理が行われるようにコールバック関数として登録しています。

また、リセットボタンをクリックした場合は、32行目で同じくBoxControllerBoxInitメソッドを呼び出していて、引数でSetInitを指定することで、オブジェクトが初期位置に戻ったらスタートボタンに変わるようにコールバック関数として登録しています。

ちなみに、ボタンのクリックイベントは、ButtonコンポーネントのOnClickイベントButtonClickメソッドを指定しています。

これでゲームを実行してみると、

ボタンをクリックしたらオブジェクトが移動するようになり、止まったらResetボタンに変更されるようになっています。

また、Resetボタンを押すと、オブジェクトが元の位置に戻り、ボタンもStartボタンに切り替わっているのが分かります。

まとめ

このページでは、Unityで使えるコールバックという仕組みについて、どういうものなのか、また作り方や使い方までをまとめていきましたが、いかがでしたでしょうか?

コールバックとは、外部のクラスのメソッドを呼び出す際に、引数にクラス内のメソッドを渡してあげることで、処理が終わった後にそのメソッドを実行してくれる仕組みです。

このコールバックを使うことで、特定の処理が終わった後に呼び出すことができたり、非同期処理などを作ることができます。

Unityでコールバックを作るやり方は、delegateを使う以外にも、UnityEventUnityActionを使って作ることができます。

最後までお読みいただきまして、ありがとうございました!

コメント