コガネブログ

平日更新を目標に Unity や C#、Visual Studio、ReSharper などのゲーム開発アレコレを書いていきます

【Unity】非同期処理を簡単に記述できる「UniTask」の使い方をいくつか紹介

はじめに

「UniTask」を Unity プロジェクトに導入することで
非同期処理を簡単に記述できるようになります

この記事では「UniTask」の使い方をいくつか紹介していきます

目次

検証環境

  • Unity 2018.4.8f1
  • UniTask 1.1.0

導入方法

https://github.com/Cysharp/UniTask/releases

上記のページから「UniRx.Async.unitypackage」をダウンロードして
Unity プロジェクトにインポートします

使用例

Resources.Load を非同期で待つ

using UniRx.Async;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private async void Start()
    {
        var sprite = await Resources.LoadAsync<Sprite>( "" );
        
        Debug.Log( sprite.name );
    }
}

シーンの読み込みを非同期で待つ

using UniRx.Async;
using UnityEngine;
using UnityEngine.SceneManagement;

public sealed class Example : MonoBehaviour
{
    private async void Start()
    {
        await SceneManager.LoadSceneAsync( "" );
        
        Debug.Log( "完了" );
    }
}

指定したフレーム数待つ

using UniRx.Async;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private async void Start()
    {
        await UniTask.DelayFrame( 25 );
        
        Debug.Log( "完了" );
    }
}

指定した秒数待つ

using System;
using UniRx.Async;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private async void Start()
    {
        await UniTask.Delay( TimeSpan.FromSeconds( 3 ) );

        // Time.timeScale を無視する場合
        await UniTask.Delay( TimeSpan.FromSeconds( 3 ), true );
        
        Debug.Log( "完了" );
    }
}

コルーチンを待つ

using System.Collections;
using UniRx.Async;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private async void Start()
    {
        await Hoge();
        
        Debug.Log( "完了" );
    }

    private IEnumerator Hoge()
    {
        yield break;
    }
}

指定した条件が true になるまで待つ

using UniRx.Async;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private bool m_isStart;

    private async void Start()
    {
        await UniTask.WaitUntil( () => m_isStart );
        
        Debug.Log( "完了" );
    }

    private void Update()
    {
        if ( Input.GetKeyDown( KeyCode.Space ) )
        {
            m_isStart = true;
        }
    }
}

指定した条件が false になるまで待つ

using UniRx.Async;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private bool m_isStop = true;

    private async void Start()
    {
        await UniTask.WaitWhile( () => m_isStop );
        
        Debug.Log( "完了" );
    }

    private void Update()
    {
        if ( Input.GetKeyDown( KeyCode.Space ) )
        {
            m_isStop = false;
        }
    }
}

自作の非同期メソッドを待つ

using System;
using UniRx.Async;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private async void Start()
    {
        var result = await Hoge();

        Debug.Log( result );
    }

    private async UniTask<string> Hoge()
    {
        // 3 秒待つ
        await UniTask.Delay( TimeSpan.FromSeconds( 3 ) );

        return await UniTask.Run( () => "完了" );
    }
}

UnityWebRequest を待つ

using UniRx.Async;
using UnityEngine;
using UnityEngine.Networking;

public sealed class Example : MonoBehaviour
{
    private async void Start()
    {
        var url     = "http://baba-s.hatenablog.com/";
        var request = UnityWebRequest.Get( url );

        await request.SendWebRequest();

        Debug.Log( request.downloadHandler.text );
    }
}

進捗を取得する

using System;
using UniRx.Async;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private async void Start()
    {
        var sprite = await Resources
            .LoadAsync<Sprite>( "" )
            .ConfigureAwait( new Progress<float>( p => Debug.Log( p ) ) )
        ;
        
        Debug.Log( sprite.name );
    }
}

ConfigureAwait を使用することで非同期処理の進捗を取得できます

3D の当たり判定を待つ

using UniRx.Async.Triggers;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private async void Start()
    {
        var trigger = this.GetAsyncCollisionTrigger();

        // OnCollisionEnter を待つ
        var result = await trigger.OnCollisionEnterAsync();

        // OnCollisionStay を待つ
        //var result = await trigger.OnCollisionStayAsync();

        // OnCollisionExit を待つ
        //var result = await trigger.OnCollisionExitAsync();

        Debug.Log( result.gameObject.name );
    }
}

2D の当たり判定を待つ

using UniRx.Async.Triggers;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    private async void Start()
    {
        var trigger = this.GetAsyncCollision2DTrigger();

        // OnCollisionEnter2D を待つ
        var result = await trigger.OnCollisionEnter2DAsync();

        // OnCollisionStay2D を待つ
        //var result = await trigger.OnCollisionStay2DAsync();

        // OnCollisionExit2D を待つ
        //var result = await trigger.OnCollisionExit2DAsync();

        Debug.Log( result.gameObject.name );
    }
}

ボタンが押されるのを待つ

using UniRx.Async;
using UnityEngine;
using UnityEngine.UI;

public sealed class Example : MonoBehaviour
{
    public Button m_button;

    private async void Start()
    {
        await m_button.OnClickAsync();
        
        Debug.Log( "完了" );
    }
}

Awake、Start、OnDestroy を待つ

using UniRx.Async.Triggers;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    public GameObject m_target;

    private async void Start()
    {
        await m_target.AwakeAsync();

        Debug.Log( "Awake" );
        
        await m_target.StartAsync();

        Debug.Log( "Start" );
        
        await m_target.OnDestroyAsync();

        Debug.Log( "OnDestroy" );
    }
}

OnEnable、OnDisable を待つ

using UniRx.Async.Triggers;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    public GameObject m_target;

    private async void Start()
    {
        var trigger = m_target.GetAsyncEnableDisableTrigger();

        await trigger.OnEnableAsync();

        Debug.Log( "OnEnable" );

        await trigger.OnDisableAsync();

        Debug.Log( "OnDisable" );
    }
}

AsyncLazy(非同期遅延初期化)を書く

using UniRx.Async;
using UnityEngine;

public sealed class Example : MonoBehaviour
{
    public UniTask<TextAsset> TextAsset { get; }
        = UniTask.Lazy( () => LoadTextAsset() );

    private async void Start()
    {
        var textAsset1 = await TextAsset; // 1回目は非同期処理が実行される
        var textAsset2 = await TextAsset; // 2回目以降は結果のみが取得される
        var textAsset3 = await TextAsset;
    }

    private static async UniTask<TextAsset> LoadTextAsset()
    {
        // 1回だけログが出力される
        Debug.Log( "初期化" );
        var textAsset = await Resources.LoadAsync<TextAsset>( "text" );
        return textAsset as TextAsset;
    }
}

すべての非同期処理が終わるまで待つ

戻り値なし

using UniRx.Async;
using UnityEngine;
using UnityEngine.UI;

public sealed class Example : MonoBehaviour
{
    public Button m_button1;
    public Button m_button2;
    public Button m_button3;

    private async void Start()
    {
        var onClick1 = m_button1.OnClickAsync();
        var onClick2 = m_button2.OnClickAsync();
        var onClick3 = m_button3.OnClickAsync();

        // すべてのボタンが押されるまで待つ
        await UniTask.WhenAll( onClick1, onClick2, onClick3 );
        
        Debug.Log( "完了" );
    }

}

戻り値あり

using UniRx.Async;
using UnityEngine;
using UnityEngine.Networking;

public sealed class Example : MonoBehaviour
{
    private async void Start()
    {
        async UniTask<string> GetTextAsync( UnityWebRequest request )
        {
            var operation = await request.SendWebRequest();
            return operation.downloadHandler.text;
        }

        var task1 = GetTextAsync( UnityWebRequest.Get( "http://google.com" ) );
        var task2 = GetTextAsync( UnityWebRequest.Get( "http://bing.com" ) );
        var task3 = GetTextAsync( UnityWebRequest.Get( "http://yahoo.com" ) );

        // すべての通信が完了するまで待つ
        var (unity, github, yahoo) = await UniTask.WhenAll( task1, task2, task3 );
        //var result = await UniTask.WhenAll( task1, task2, task3 );
        
        Debug.Log( "完了" );
    }
}

いずれかの非同期処理が終わるまで待つ

戻り値なし

using UniRx.Async;
using UnityEngine;
using UnityEngine.UI;

public sealed class Example : MonoBehaviour
{
    public Button m_button1;
    public Button m_button2;
    public Button m_button3;

    private async void Start()
    {
        var onClick1 = m_button1.OnClickAsync();
        var onClick2 = m_button2.OnClickAsync();
        var onClick3 = m_button3.OnClickAsync();

        // いずれかのボタンが押されるまで待つ
        await UniTask.WhenAny( onClick1, onClick2, onClick3 );
        
        Debug.Log( "完了" );
    }

}

戻り値あり

using UniRx.Async;
using UnityEngine;
using UnityEngine.Networking;

public sealed class Example : MonoBehaviour
{
    private async void Start()
    {
        async UniTask<string> GetTextAsync( UnityWebRequest request )
        {
            var operation = await request.SendWebRequest();
            return operation.downloadHandler.text;
        }

        var task1 = GetTextAsync( UnityWebRequest.Get( "http://google.com" ) );
        var task2 = GetTextAsync( UnityWebRequest.Get( "http://bing.com" ) );
        var task3 = GetTextAsync( UnityWebRequest.Get( "http://yahoo.com" ) );

        // いずれかの通信が完了するまで待つ
        var (unity, github, yahoo) = await UniTask.WhenAll( task1, task2, task3 );
        //var result = await UniTask.WhenAny( task1, task2, task3 );
        
        Debug.Log( "完了" );
    }
}