VRChatワールドで、ボタンを押すと状態は変わるのに、見た目のアニメーションが進まないことがありました。調べてみると、アニメーションの更新が途中で止まっていました。
今回は、遅延イベントのタイミングが原因の候補でした。
1フレーム後でも同じフレーム番号になることがある
UdonSharpでは、次のようにイベントを遅延実行できます。
SendCustomEventDelayedFrames(nameof(_Step), 1);
ただし、Updateより前の入力イベントなどから予約すると、Time.frameCount上の遅延が想定より1フレーム短くなる場合があります。同じフレーム番号のまま _Step が呼ばれることもあります。
参考:VRChat公式ドキュメント — SendCustomEventDelayedFrames
同じフレームの重複防止で更新が止まる
次の図は、更新が止まる可能性のある流れと、今回試した修正を並べたものです。修正前に同一フレームの判定が実際に発生したかは、ログで確認できていません。
| 修正前:止まる場合 | 修正後:初回を遅らせる |
|---|---|
① 開始時に _Step をすぐ実行 | ① 初回の _Step を2フレーム後に予約 |
| ② 次の更新を1フレーム後に予約 | ② フレームが進んでから初回の更新を実行 |
| ③ 同じフレーム番号で届くと、重複防止で終了 | ③ 次の更新を1フレーム後に予約 |
| ④ 次の予約がなくなり、更新が停止 | ④ アニメーションの更新が進む |
例えば、次のようなコードです。アニメーション中だけ _Step を繰り返す処理の抜粋です。
private bool _running;
private int _lastFrame = -1;
private void StartLoop()
{
if (_running) return;
_running = true;
_lastFrame = -1;
_Step(); // 最初はすぐ実行
}
public void _Step()
{
if (!_running || _lastFrame == Time.frameCount) return;
_lastFrame = Time.frameCount;
// ここでアニメーションを更新する
SendCustomEventDelayedFrames(nameof(_Step), 1);
}
予約した _Step が同じフレーム番号で呼ばれると、重複防止の return で抜けます。この例では次の予約も行われないため、_running が true のまま更新が止まることがあります。
最初の更新を2フレーム後に予約する
今回は、開始時に _Step() を直接呼ばず、最初の更新を2フレーム後に予約するようにしました。
private void StartLoop()
{
if (_running) return;
_running = true;
_lastFrame = -1;
// 入力直後でも、最初の更新を後のフレームに送る
SendCustomEventDelayedFrames(nameof(_Step), 2);
}
変更後、VRChatの実クライアントでボタンを繰り返し操作しても、アニメーションが進むようになりました。
ただし、修正前にフレーム番号までは記録していなかったため、実際に同じフレームの判定で止まったかは確定できていません。また、2フレーム遅らせる方法は、古い予約との重複や非表示からの復帰など、すべてのケースを解決するものではありません。
同じ症状が出たら、状態だけでなく、進行値、Time.frameCount、前回処理したフレーム、次のイベントを予約したかも確認すると、原因を絞りやすくなります。