🕌

[PlayFab + UniTask(UniRx)] OnApplicationQuitAsync の実装例

2022/01/09に公開


困ったこと

Unity にてゲームを終了した時刻を PlayFab のプレイヤーデータに保持したいと思っていました。

※ PlayFab については、こちらの記事がとってもわかりやすいです。
https://zenn.dev/nekojoker/articles/38f1654ee254f482dfce

MonoBehaviour にある OnApplicationQuit メソッドを利用したのですが、
サーバー間のやり取りを行っている間の処理が終わるまで待ってくれる訳もなく、あえなく失敗。


調べてみたところ

UniTask や UniRx + UniTask を使うことで OnApplicationQuit メソッドにおいて、
非同期処理が行える便利な機能があることを見つけました。

※ UniTask については、こちらの記事がとってもわかりやすいです。
https://learning.unity3d.jp/2974/

今回のケースでは具体的な実装例を発見できなかったため、
備忘録も兼ねて記事に残しておこうと思います。

PlayFab の SDK については、非同期処理を書きやすくする(async / await 利用の)ため、
PlayFab C# SDK を利用しています。


UniTask での実装例

読みやすいように行間を空けています。

using Cysharp.Threading.Tasks;
using Cysharp.Threading.Tasks.Triggers;
using PlayFab;
using PlayFab.ClientModels;
using System;
using System.Collections.Generic;
using UnityEngine;

public class OnlineTimeManager : MonoBehaviour
{
    private const string FORMAT = "yyyy/MM/dd HH:mm:ss";

    private async UniTaskVoid Start() {

        // キャンセレーショントークンの作成
        var ct = this.GetCancellationTokenOnDestroy();

        // OnApplicationQuit メソッド内で非同期処理の待機が出来る状態にする
        await this.GetAsyncApplicationQuitTrigger().OnApplicationQuitAsync(ct);
    }

    /// <summary>
    /// ゲームが終了したときに自動的に呼ばれる
    /// </summary>
    private async UniTask OnApplicationQuit() {  // async UniTask に変更

        // PlayFab にゲーム終了時の時間を保存
        await UpdateLogOffTimeAsync();
  
        // 複数の await がある場合には WhenAll で対応
        
        Debug.Log("ゲームを終了します。");
    }

    /// <summary>
    /// OnApplicationQuit 時に実行するメソッド。今回はログオフ時間をサーバーにセーブ
    /// </summary>
    /// <returns></returns>
    public static async UniTask<bool> UpdateLogOffTimeAsync() {

        string dateTimeString = DateTime.Now.ToString(FORMAT);

        var request = new UpdateUserDataRequest {

            Data = new Dictionary<string, string> { { "LogOffTime", dateTimeString } }
        };

        var response = await PlayFabClientAPI.UpdateUserDataAsync(request);

        if (response.Error!= null) {
            Debug.Log("エラー");

            // 要エラーハンドリング

            return false;
        }

        Debug.Log("ログオフ時の時刻セーブ完了");
        return true;
    }
}

処理の説明

簡単にはなりますが、スクリプトの説明を書いておきます。

用意したメソッドは3つです。

Start メソッド内で UniTask.Triggers に含まれる GetAsyncApplicationQuitTrigger().OnApplicationQuitAsync() メソッドを実行します。
このメソッドを実行することにより、MonoBehaviour にある OnApplicationQuit() メソッドに
おいて非同期処理の待機処理が実装出来るようになります。

OnApplicationQuitAsync() メソッドの引数にはキャンセレーショントークンの設定を行い、
ゲームオブジェクトが破壊された場合には、このトリガー機能も一緒に終了するように紐づけしています。

元々 MonoBehaviour にある OnApplicationQuit() メソッドには aysnc を追加して、
戻り値を UniTask に変更します。
await を利用して非同期メソッドを実行することで、非同期処理が終了するまで処理を
待機できますので、ゲーム内の情報をサーバー(PlayFab)にセーブしてから、
OnApplicationQuit() メソッドが終了する制御を実装できます。

UpdateLogOffTimeAsync() メソッドには bool 型の戻り値を設定していますが、
この例では利用していません。
await 時の戻り値として利用することが出来ますので、
非同期処理の結果を受けてから分岐などを作る際に使ってください。

メソッド内の処理の内容自体は、日時の情報を文字列に置き換えて、
それを PlayFab のプレイヤーデータ内に更新するものです。
この処理自体はいつもの PlayFab とのやりとりになりますので、特別なことはしていません。


UniRx + UniTask での実装例

処理の挙動は、UniTask のみの場合と同じです。
UniRx の OnceApplicationQuit (MainThreadDispatcher の機能に内包)を利用しています。

using Cysharp.Threading.Tasks;
using PlayFab;
using PlayFab.ClientModels;
using System;
using System.Collections.Generic;
using UnityEngine;
using UniRx;

public class OnlineTimeManager : MonoBehaviour
{
    private const string FORMAT = "yyyy/MM/dd HH:mm:ss";

    private void Start() {

        // OnApplicationQuit の実行タイミングでメッセージを発行する UniRx の機能
        Observable.OnceApplicationQuit()
            .Subscribe(_ => {
                QuitGameAsync().Forget();
            });
    }

    /// <summary>
    /// ゲームが終了したときに実行
    /// </summary>
    private async UniTask QuitGameAsync() {

        // 1つの処理を行う場合
        await  UpdateLogOffTimeAsync();

        Debug.Log("セーブ完了①。");

        // 複数の処理を行う場合(適宜な処理に書き換えてください)
        await UniTask.WhenAll(
            UpdateLogOffTimeAsync(),
            UpdateLogOffTimeAsync(),
            UpdateLogOffTimeAsync(),
            UpdateLogOffTimeAsync()
            );

        Debug.Log("セーブ完了②。");     
        Debug.Log("ゲームを終了します。");
    }

    /// <summary>
    /// OnApplicationQuit 時に実行するメソッド。今回はログオフ時間をサーバーにセーブ
    /// </summary>
    /// <returns></returns>
    public static async UniTask<bool> UpdateLogOffTimeAsync() {

        // UniTask のみの実装例と同じ内容なので省略します。
    }
}

準備

スクリプトが完成したら、確認するための準備を行います。

ヒエラルキーに新しいゲームオブジェクトを作成します。
名称は任意です。(ここではスクリプト名と同じ名称にしています)

作成した OnlineTimeManager スクリプトをゲームオブジェクトにアタッチします。

完成です。


完成

ゲームを実行してデバッグしていきます。

UniTask のみでの実装の場合、ゲームの実行に合わせて先ほどのゲームオブジェクトに
新しいコンポーネントが2つ、自動的にアタッチされます。
このうち、AsyncApplicationQuitTrigger がアタッチされていれば、
OnApplicationQuit メソッドが非同期処理で実行されるようになります。

altテキスト


あとは、ゲームを終了するだけです。
Debug.Log メソッドにより、Console ビューに処理の流れが確認できますので、
非同期処理が待機されているかどうかを確認しておきます。

altテキスト

複数の処理を行った場合のログです。
altテキスト

PlayFab のゲームマネージャー画面も確認して、
プレイヤーデータ(タイトル)が更新されているかを確認しておきます。

altテキスト

以上になります。

GitHubで編集を提案

Discussion