【DOTween/UniTask】Forgetした後にDestroyすると警告が出る

UniTask + DOTween で UniTask を Forget した後、そのメソッド内でゲームオブジェクトを触ってる最中にゲームオブジェクトを Destroy すると警告が出る対応方法です。限定的な状況ですが、解消方法を紹介したいと思います。

実装は以下の通り

// こんな感じでForgetした後GameObjectのメンバーを触ってる
private void Start()
{
    Exec().Forget();
}

private async UniTask Exec()
{
    await transform.DOLocalMove(new Vector3(1.2f, 1.2f), 0.5f);
}

Forget した後 GameObject のメンバーを触ってると別スレッドからの操作になるので設計的には少々微妙かもしれませんが Start で色々タスクを起動して待機させているケースもあると思います。

急にDestroyすると警告が出る

Forget したメソッド中で UniTask の GetCancellationTokenOnDestroy してもゲームオブジェクトを急に Destory すると DOTween で警告メッセージが出ます。DOTween の処理を await 中に Play モードから抜けたり、Scene を遷移しても似たような問題が起きます。ちゃんと設定しているはずなのにあれ?という状況です。

await transform.DOMove(1.0f, 1.0f); // Forgetした状態で待機中にDestoryするとエラー発生

// とか、こういうの
CancellationToken token = this.GetCancellationTokenOnDestroy();
 
await this.transform.DOLocalMoveX(0.5f, 0.5f) // 待機中にDestoryするとエラー
        .SetRelative()
        .ToUniTask(TweenCancelBehaviour.KillAndCancelAwait, token);

上記実装のように Forget して放り投げたスレッド上で await している最中に急に gameObiect を Destory したり、Playモードから抜けると DOTween 側で以下のようなメッセージが出ます。

DOTWEEN ► Target or field is missing/null () ► 
The object of type 'Transform' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.

// 「Tween が生きてる最中にオブジェクトがなくなっちゃったよ」的な意味です

エディタの再生を終了するときは以下のメッセージが出ます。

DOTWEEN ►  SAFE MODE ► DOTween's safe mode captured 1 errors. This is usually ok
(it's what safe mode is there for) but if your game is encountering issues you
should set Log Behaviour to Default in DOTween Utility Panel in
order to get detailed warnings when an error is captured
(consider that these errors are always on the user side).

UniTask の GetCancellationTokenOnDestroy 呼び出し時にゲームオブジェクトに追加される AsyncDestroyTrigger は OnDestry を正しく実行しているようですが、Forget して別のスレッドで実行していると問題となるようです。

解決方法

このメッセージの回避方法ですが、DOTween のオブジェクトが破棄された時に自分も破棄する SetLink メソッドを追加することで解決できます。

CancellationToken token = this.GetCancellationTokenOnDestroy();
await this.transform.DOLocalMoveX(0.5f, 0.5f)
      .SetRelative()
      .ToUniTask(TweenCancelBehaviour.KillAndCancelAwait, token);
      
// ↓

CancellationToken token = this.GetCancellationTokenOnDestroy();
await this.transform.DOLocalMoveX(0.5f, 0.5f)
      .SetRelative()
      .SetLink(this.gameObject) // ★★★これを追加
      .ToUniTask(TweenCancelBehaviour.KillAndCancelAwait, token);

DOTween のオブジェクトが Destory されたら Tween も破棄する SetLink の指定を追加します。これだけで警告は消えます。

AsyncWaitForCompletionは使用しないこと

余談ですが、UniTask と DOTween を併用する場合 DOTween にある「AsyncWaitForCompletion」などは使用してはいけません。これは UniTask ではなく C# の標準機能の Task で await するための構文です。

using Cysharp.Threading.Tasks;

private async void Start()
{
    // C#標準機能のTaskでawaitする
    await transform.DOLocalMoveX(1.0f, 1.0f).AsyncWaitForCompletion();
 
    // UniTaskのawait、usingに「Cysharp.Threading.Tasks」があればそのままawaitできる。
    await transform.DOLocalMoveX(1.0f, 1.0f);
}

UniTask を導入済みの場合、わざわざゲーム向けでない非効率な標準機能を使用する必要はないと思うので上記は気を付けましょう。