UniTask の GitHub に質問を投げたら Tim Cassell さんが答えてくれて、解決しました。私が誤解していました。記事の最後に追記します。(見出し「概要」~「考察」が最初の疑問で、「解決」が追記です)
概要
UniTask.Yield() の引数に enum PlayerLoopTiming を与えると、そのタイミングまで待ってから動作を再開する…と一般には理解されています。例えば
await UniTask.Yield(PlayerLoopTiming.Update);
Debug.Log("OK");
と書けば、Update のタイミングで OK が Console に表示されるはずです。
ところが実験したところ、そうでもないような気がします。しかも実行するたびにタイミングが変わるようです。
環境
Unity 2023.2.11f1
UniTask 2.5.4
実験
Scene に "Create Empty" で GameObject を作り、これに MyUniTaskTest コンポーネント (後述のソースコード) を加えてあります。
MyUniTaskTest コンポーネントの実装は以下のようになっています。
_phase に現在のタイミングを記録しています。
Update() の中で最初の1回だけ MyTaskAsync() を起動しています。
using Cysharp.Threading.Tasks;
using System.Threading;
using UnityEngine;
public class MyUniTaskTest : MonoBehaviour
{
string _phase;
bool _done;
void OnEnable()
{
_done = false;
}
void FixedUpdate()
{
_phase = "FixedUpdate_Begin";
_phase = "FixedUpdate_End";
}
void Update()
{
_phase = "Update_Begin";
if (!_done)
{
_done = true;
MyTaskAsync(destroyCancellationToken).Forget();
}
_phase = "Update_End";
}
void LateUpdate()
{
_phase = "LateUpdate_Begin";
_phase = "LateUpdate_End";
}
async UniTaskVoid MyTaskAsync(CancellationToken cancel)
{
print($"MyTaskAsync");
for (int i = 0; i<30; i++)
{
print($"{i}: {Time.frameCount} {_phase}");
await UniTask.Yield(PlayerLoopTiming.Update, cancellationToken: cancel);
}
}
}
結果
これを実行し、 Console に表示されたものを貼り付けます。
1回目
2回目
3回目
表にすると
1回目 | 2回目 | 3回目 | |
---|---|---|---|
0 | Update_Begin | Update_Begin | Update_Begin |
1 | FixedUpdate_End | FixedUpdate_End | FixedUpdate_End |
2 | FixedUpdate_End | FixedUpdate_End | FixedUpdate_End |
3 | FixedUpdate_End | FixedUpdate_End | FixedUpdate_End |
4 | FixedUpdate_End | LateUpdate_End | LateUpdate_End |
5 | FixedUpdate_End | LateUpdate_End | LateUpdate_End |
6 | LateUpdate_End | LateUpdate_End | FixedUpdate_End |
7 | FixedUpdate_End | FixedUpdate_End | LateUpdate_End |
8 | LateUpdate_End | LateUpdate_End | LateUpdate_End |
9 | LateUpdate_End | FixedUpdate_End | LateUpdate_End |
考察
わかりません。Update のタイミングであれば、_phase の内容は FixedUpdate_End, Update_Begin, Update_End のいずれかだと思います。なぜ LateUpdate_End が表示されることがあるのか。
追記
Tim Cassell さんによると、 PlayerLoopTiming.Update は MonoBehaviour の Update よりも前に実行されるそうです。また FixedUpdate は毎フレーム実行されるわけではないので、
第 n フレームの最後まで実行する。変数 _phase には LateUpdate_End が入る。
第 n+1 フレームの実行が始まる。このフレームでは FixedUpdate が呼ばれないとする。MyUniTaskTest.Update
よりも前に、MyTaskAsync
の実行が再開される。だから (第 n フレームで変数 _phaseに代入された) LateUpdate_End が表示される。
という順に実行される、ということでした。