知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【Unity】UniTask 【2】#91

前回の成果

UniTaskの大まかな概要と静的なメソッド群について学んだ。

soramamenatan.hatenablog.com


今回やること

前回に引き続き、UniTaskについて学んでいきます。


Awaiter

Awaiterとは、オブジェクトのawaitに必要なものとなっています。
あるオブジェクトにGetAwaiterメソッドが実装されています。
そこからAwaiterを取得することができれば、コンパイラがawaitできると判断してくれます。
UniTaskでは、このAwaiterを利用して様々な機能のAwaiterを用意してくれています。


コルーチン

コルーチンをawaitすることができます。
これにより待機、実行が容易にできるようになります。

コルーチンのAwaiter
/// <summary>
/// コルーチンのAwaiter
/// </summary>
/// <returns></returns>
private async void AwaitCoroutine() {
    await WaitTime(1, "Awaiterでの1回目の待機");
    await WaitTime(2, "Awaiterでの2回目の待機");
    await WaitTime(3, "Awaiterでの3回目の待機");
}

/// <summary>
/// 指定時間待つコルーチン
/// </summary>
/// <param name="time"></param>
/// <param name="message"></param>
/// <returns></returns>
private IEnumerator WaitTime(float time, string message) {
    yield return new WaitForSeconds(time);
    Debug.Log("Finished. Message is : " + message);
}
結果

1回目のコルーチンが終了したのを待ってから2回目のコルーチンが実行されています。
次も同様に、2回目のコルーチンが終了したのを待ってから3回目のコルーチンが実行されています。

f:id:soramamenatan:20210211133617g:plain

StartCoroutineとの比較

通常のコルーチンの呼び出し方であるStartCoroutineと比較してみます。

StartCoroutineでの呼び出し
/// <summary>
/// コルーチンでの呼び出し
/// </summary>
private void OnlyCoroutine() {
    StartCoroutine(WaitTime(1, "コルーチンでの1回目の待機"));
    StartCoroutine(WaitTime(2, "コルーチンでの2回目の待機"));
    StartCoroutine(WaitTime(3, "コルーチンでの3回目の待機"));
}
結果

StartCoroutineですと前のコルーチンの処理を待たずに処理を実行しているのがわかります。

f:id:soramamenatan:20210211134119g:plain


uGUI

uGUIをawaitすることもできます。
uGUIの変更を待つものですので、複数回の変更には以下のソースコードでは対応していません。

uGUIのAwaiter
using UnityEngine;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;
using System.Threading;

public class UniTaskAwaiterUGUI : MonoBehaviour {

    [SerializeField]
    private Button _button;

    [SerializeField]
    private Toggle _toggle;

    [SerializeField]
    private InputField _inputField;

    [SerializeField]
    private Slider _slider;

    void Start() {
        UGUIAwaiterMethods();
    }

    /// <summary>
    /// uGUIのAwaiter
    /// </summary>
    private void UGUIAwaiterMethods() {
        // CancellationTokenの取得
        CancellationToken token = this.GetCancellationTokenOnDestroy();
        AwaitButton(token);
        AwaitToggle(token);
        AwaitInputField(token);
        AwaitSlider(token);
    }

    /// <summary>
    /// ボタンが押されるのを待つ
    /// </summary>
    /// <returns></returns>
    private async void AwaitButton(CancellationToken token) {
        IAsyncClickEventHandler handler = _button.GetAsyncClickEventHandler(token);
        await handler.OnClickAsync();
        Debug.Log("onClickButton");
    }

    /// <summary>
    /// トグルの値が変更されるのを待つ
    /// </summary>
    /// <param name="token"></param>
    /// <returns></returns>
    private async void AwaitToggle(CancellationToken token) {
        IAsyncValueChangedEventHandler<bool> handler = _toggle.GetAsyncValueChangedEventHandler(token);
        bool isOn = await handler.OnValueChangedAsync();
        Debug.Log("Toggle State : " + isOn);
    }

    /// <summary>
    /// InputFieldが入力されるのを待つ
    /// </summary>
    /// <param name="token"></param>
    /// <returns></returns>
    private async void AwaitInputField(CancellationToken token) {
        IAsyncEndEditEventHandler<string> handler = _inputField.GetAsyncEndEditEventHandler(token);
        string input = await handler.OnEndEditAsync();
        Debug.Log("InputField入力文字 : " + input);
    }

    /// <summary>
    /// Sliderの値が変更されるのを待つ
    /// </summary>
    /// <param name="token"></param>
    /// <returns></returns>
    private async void AwaitSlider(CancellationToken token) {
        IAsyncValueChangedEventHandler<float> handler = _slider.GetAsyncValueChangedEventHandler(token);
        float value = await handler.OnValueChangedAsync();
        Debug.Log("Slider Value : " + value);
    }
}
Buttonの結果

ボタンが押下されたときにログを出しています。

f:id:soramamenatan:20210211142816g:plain

Toggleの結果

トグルの値が変化したときにログを出しています。

f:id:soramamenatan:20210211143716g:plain

InputFieldの結果

文字列が入力され、決定されたときにログを出しています。

f:id:soramamenatan:20210211204914g:plain

Sliderの結果

スライダーの値が変化したときにログを出しています。

f:id:soramamenatan:20210211143821g:plain


AsyncOperation

非同期操作の有効期間を追跡します。
イベントベースの非同期パターンの概要に従ってクラスを実装する場合、使用するクラスのインスタンスで呼び出される各非同期操作の有効期間を追跡することが必要な場合があります。
AsyncOperation クラスを使用すると、非同期タスクの進行状況を追跡して報告できます。

AsyncOperationとは何? Weblio辞書:より引用

AsyncOperationを使用することで、非同期でロードをすることができます。
シーンや画像などの事前ロードに使用されることが多いです。

AsyncOperationのAwaiter
/// <summary>
/// AsyncOperationのAwaiter
/// </summary>
/// <returns></returns>
private async void WaitSceneLoad() {
    await SceneManager.LoadSceneAsync("Scenes/CoroutineToObservable");
    Debug.Log("Sceneの読み込み成功");
}
結果

シーンのロードが完了したときにログを出しています。

f:id:soramamenatan:20210211150507p:plain


また、以下のようにすることでロードの進捗を取得することもできます。

ロードの進捗を取得
/// <summary>
/// 進捗を表示
/// </summary>
/// <returns></returns>
private async void WaitSceneLoadProgress() {
    await SceneManager
            .LoadSceneAsync("Scenes/CoroutineToObservable")
            .ToUniTask(Progress.Create<float>(x => {
                Debug.Log("進捗 : " + x);
            }));
    Debug.Log("Sceneの読み込み成功");
}


UnityCallback

MonoBehaviourで使用されるイベントコールバックをawaitすることもできます。
例えば、OnCollisionEnterやStartが挙げられます。
今回は一部紹介します。

UnityCallbackのAwaiter
using UnityEngine;
using Cysharp.Threading.Tasks;
using Cysharp.Threading.Tasks.Triggers;

public class UniTaskAwaiterUnityCallback : MonoBehaviour {

    [SerializeField]
    private UniTaskLogObject _logObject;

    private UniTaskLogObject _createdLogObject;

    void Start() {
        CreateGameObject();
        UnityCallbackAwaiterMethods();
    }

    /// <summary>
    /// ログ出し用オブジェクトの生成
    /// </summary>
    private void CreateGameObject() {
        _createdLogObject = null;
        _createdLogObject = Instantiate(_logObject, parent: transform);
    }

    /// <summary>
    /// AwaiterのUnityCallbackのメソッド群
    /// </summary>
    private void UnityCallbackAwaiterMethods() {
        AwaitAwake();
        AwaitStart();
        AwaitOnDestroy();
    }

    /// <summary>
    /// Awakeを待つ
    /// </summary>
    /// <returns></returns>
    private async void AwaitAwake() {
        await _createdLogObject.AwakeAsync();
        Debug.LogError("AwaitAwake");
    }

    /// <summary>
    /// Startを待つ
    /// </summary>
    /// <returns></returns>
    private async void AwaitStart() {
        await _createdLogObject.StartAsync();
        Debug.LogError("StartAwake");

    }

    /// <summary>
    /// OnDestroyを待つ
    /// </summary>
    /// <returns></returns>
    private async void AwaitOnDestroy() {
        await _createdLogObject.OnDestroyAsync();
        Debug.LogError("OnDestroyAwake");
    }
}
Logを出すだけのオブジェクト
using UnityEngine;

public class UniTaskLogObject : MonoBehaviour {

    void Awake() {
        Debug.Log("Awake");
    }

    void Start() {
        Debug.Log("Start");
    }

    void OnDestroy() {
        Debug.Log("OnDestroy");
    }
}
結果

Logを出すオブジェクトはDebug.Logで、
AwaiterはDebug.LogErrorでログを出しています。

AwakeとStartのタイミングでAwaiterが呼ばれているのがわかります。

f:id:soramamenatan:20210211161456p:plain

Destroy時も同様です。

f:id:soramamenatan:20210211161508g:plain


今回は以上となります。
ここまでご視聴ありがとうございました。