見出し画像

clusterでそれなりに同期するストップウォッチを作った

この記事は Cluster Creator Kit Advent Calender 2020 23日目の記事です。
昨日は なんで屋 さんの 「 ClusterGAMEJAM2020 in WINTERで風呂敷広げて溢れた話 」でした。見積もりは難しく、補正するのはもっと難しいですね。タイトルが溢れるに掛かってるのに今気づきました。

今回は †††cluster用アイテムギミック Stopwatch††† なるものを作ったので、それの解説的なことをしていきます。
やはり変なものを作った話なので、Cluster Creator KitUnityについての知識 がいくらか必要です。

ちなみにサンプルのワールドは ここ に上げてあります。別に楽しくはないです。

Why NOT Timeline?

(この項目も難しめなので読み飛ばして大丈夫です)

cluster のワールドとして動きのあるものを表現する方法として PlayTimelineGimmick があります。これはSignalの時刻と現在時刻との差分で再生位置を決定しているため、 SetAnimatorValueGimmick でアニメーションを開始するのと違い、途中入室等でもタイミングが同期されるという便利な特徴があります。しかし、これはSignalの時刻からスタートするため、途中から再生ということが"そのままでは"できません。

上級者の方なら「過去のSignalをLogicで作ればいいんじゃない?」と思ったかも知れません (実際、自分もやってみるまではそう思ってました)。しかし 、Signalの実値は要するに UNIXTime × 1000 なので、intの範囲外であり、floatだと精度が足りません(1000秒刻みぐらい?)。
そういうわけで、TimelineではなくAnimatorを使っての実装を考えることにしました。

※もしかしたらAnimator等でPlayableDirectorのInitialTimeを操作して動く場合があるかもしれませんが、普通はそうできませんし、少なくとも同期先でのGimmickの動作順番も未定義です。
TimelineをLoopにしてModuloでSignalを捏造することも出来るかもしれませんが、精度は保証しませんし、せっかくなので非ループにも対応したいですよね。

時間を計る

ストップウォッチを作るには、当然時間を計ることが必要です。ドキュメントに明記されている仕様ではありませんが、Signalの値はとある時刻からの秒数*1000が入っていることが知られています。そのため、2つのSignalの差を取って1000で割ることによって、その2事象の差の時間(秒)を得ることが(今の所)できます。一時停止と再生を繰り返すことも、「最後の再生時刻」と「最後の再生時刻での総再生時間」を保存することで引き続き計算が可能です。

途中で止める

(途中から再生する話はこの次に書きます)

ストップウォッチを作るからには、アニメーションを途中で止めることができなければいけません。これは AnimatorState の MotionTime にParameterを指定することで可能です。ここにParameterを指定すると、そのStateは時間経過で動作するのではなく、そのParameterの位置 (アニメーションの長さに対しての比率) のアニメーションをサンプリングする挙動になります。例えば、長さが10秒のアニメーションの3秒の位置で止めたい場合はParameterを0.3にすることで実現できます。

AnimatorState、 ドキュメントは更新されてないしAnimationStateだし草しか生えない(AnimationStateはまた別の概念)。

途中から再生する

アニメーションを指定位置で止めるのはMotionTimeでできたのですが、アニメーションを途中から再生する"イイ"方法は残念ながら見つかりませんでした。 (SetSliderValueGimmick?その話はまだ早い)

今回は同じくAnimatorStateのCycleOffsetを使う方法で実装しました。CycleOffsetとはMotionTimeと同じようにアニメーションの再生位置を変えるパラメータなのですが、これはループ再生時の開始位置を指定するパラメータです。アニメーションをループにする必要がありますが、CycleOffsetを指定してアニメーションを開始することで、途中からアニメーションを再生するGimmickを作ることができました。

(もっといい方法があれば教えて下さい)

最後で止める

途中から再生するためにアニメーションをループに設定したため、ループにしたくない場合でもループするようになってしまいました。アニメーションの止め方自体はもう分かっているので、アニメーションの最後まで行ったという遷移を作ればよいですね。

CycleOffsetとHasExitTimeなTransitionで作れれば楽だったのですが、残念ながらそうではありませんでした。指定した時間で終了するStateができればいいので、Speedに残り時間の逆数を指定するという方法を思いつきました。読者のほとんどは内容に追いつけてないと思いますが、そういうもんです(だから配布してる)。

アニメーションの最後で止めたStateはMotionTimeの指定で作れますね。ParameterのLastには1.0が入っているように見えますが、こいつには実は0.999が入っています。いじらないでね。もしかしたらSpeedが-1のStateでも作れるのかもしれませんが試してません。

再生途中を同期する

アニメーターは複雑なので、再生途中の状態を同期するのは難しく(というか無理)、実際CreatorKitでも対応されていません(これの話も書こうと思ったらつよそうな記事が爆誕する)。しかし、今回は再生時間のみで決まり、途中から再生する機構も準備したので、だいたいはなんとかなりそうですね。

誰かが入室した時、現在の再生時間を再計算して送ればその人の状態は概ね正しくなりそうです。ただし、単にCycleOffsetを更新するだけだと元から再生していた人のところでズレてしまうので、CanTransitToSelfな遷移をSignalで同時に実行する必要があります。

できました!

というわけで、再生・一時停止ができ、ループ・非ループに両対応し、途中入室でも大きくはズレないストップウォッチを(めっちゃ頑張れば)作れることが分かりました。

めっちゃ頑張りたくない人は これ を使ってください。タダです。

明日は kanonji さんの 「 Cluster Creator Kitのトリガー、ギミック、オペレーションの関係について調べたメモ 」 です。クラス名で特徴が結構はっきり分かれてますね。OnCollideItemTriggerのOwnerが難しいのはそのとおりなので、実はItemTimerは……?

この記事が気に入ったらサポートをしてみませんか?