見出し画像

君たちはどうきいるか(ワ-ルド編)(後編)

前半では、何が同期できるかを見てきました。ここからは、どうやって同期していくかを考えていきます。
まずは、同期したいものを「事象」「状態」の二つに分けます。有り体に言うと、イベント同期にすべきか、同期変数にすべきか…later-joinerにも同期する必要があるか否かです。

事象を同期する

「事象」にあたるのは、一時的なもの…効果音や、パーティクルがあります。これらを状態同期してしまうとどうなるかというと…later-joinerがJoinした瞬間に、同期してきたあらゆる効果音やパーティクルが一斉に実行される、というような事態が起きます。
例えば、トロッコが岩にぶつかって衝突エフェクトが出るギミックを考えます。衝突後のトロッコ位置は、later-joinerにも同期されて欲しい「状態」にあたりますね。たいていはVRC Object Syncで同期する事になるでしょう。
一方トロッコが岩に衝突した時の衝突エフェクトや効果音は、その場限りのものなので、「事象」と言えるでしょう。

「事象」と「状態」を区別する

ところで、全員が同時実行するため間接的に同期イベントとして扱えるイベントがある事を説明しました。わざわざSendCustomNetworkEvent()を使う事は無いのです。
先述のトロッコの例で言うと、衝突判定はOnCollisionEnter()で取れますが、トロッコが「移動して衝突する」部分はVRC Object Sync等で既に同期済みの情報です。これにより、OnCollisionEnter()は間接的同期イベントとして扱えるといえます。衝突してから衝突エフェクトや衝突音にタイムラグが起こるのもおかしいので、こういった処理にはSendCustomNetworkEvent()を採用しない方が良いでしょう。

間接的同期イベント

ただし、ローカル処理であるが故に実行抜けが発生するプレイヤーが出る可能性があるので、それを避けたい場合は誰か一人 ―たいていはオブジェクトオーナーー にSendCustomNetworkEvent()を「All」で実行させると良いでしょう。
他には、オブジェクトオーナーにしか出来ない処理を依頼…ピックアップオブジェクトの一括位置リセット等が該当します。こちらは 依頼者→Owner→変数同期 と2回同期リレーする事になるためタイムラグが大きいですが、むやみにOwner切替をしない安定性が高い手法です。

こうしてみると、事象の同期に該当するものは少ない事が分かります。

状態を同期する

次に「状態」を見ていきましょう。例えば誰かがオブジェクトをピックアップしている"状態"、オブジェクトのオンオフ"状態"、ゲームの進行"状態"…
かなり多岐に渡りますね。同期の本懐とは、状態の統一化にあると言えるかも知れません。
まずはこれら、やろうとしているギミックから、必要な"状態"を抜き出してみましょう。

ここで例題に、ミラースイッチを考えます。
最初はミラーはオフが良いですね。そして軽量/フル表示ミラーの2種類を、スイッチを押す度に切り替わるようにもしたい。
この要件からは、ミラーというギミックに、 オフ/軽量/フル表示 の3つの"状態"があると言えます。真偽型(bool)には収まらないので、整数型(int)で同期する事にしましょう。
ここで重要なのは、軽量ミラーのオンオフとフル表示ミラーのオンオフでboolが二つじゃないの?という事ですが、今回の例で言えば実際そうです。
しかし、これがミラーの表示に合わせてマテリアルの色を差し替えたりだとか、一緒に別のオブジェクトもオンオフを切り替えたりだとか、状態が変わった際に変更する物が増えれば増えるほど、同期すべきパラメーターの量が増えてややこしくなっていきます。この例のように"ミラーの状態"として定義しておけば、あとは状態に合わせた処理を羅列していくだけ、となります。
同期すべきは、個々のオブジェクトの状態ではなく、より抽象的な"状態"、という事です。コツとしては、「どうなって欲しいか」「どうあるべきか」で考えると良いかと思います。
さて、この"状態"を変化させる要因は、勿論プレイヤーのインタラクトなので、こう書く事になります。

同期するミラースイッチ

では次に、簡単なゲーム…三目並べを作ることを考えてみます。
三目並べは3×3の9マスに、交互に○×を埋めていく事になる訳ですが、「マスに何が埋まっているか」が今回同期すべき「状態」と言えそうです。
1つのマスが「無」「○」「×」の状態を持ち、それが9つになるので、整数型の配列(byte[])として同期するのが良さそうです。
マスを埋めたら直ぐに反映されて欲しいので、マスを埋めるプレイヤーがSetOwnerでオーナー権を取得して、マスを埋める。勝敗判定がしたいならここでついでにやっちゃうと良いでしょう。あとはその結果を同期して反映してやれば…

三目並べゲーム

…おや、これはミラースイッチの時と同じですね。ギミック自体が多少複雑になっても、状態の同期に関しては、基本この書き方の延長で良さそうです。

最後に、AnimatorでNPC的な何かをワールドに置くことを考えてみましょう。
Animatorとの同期に関しては、こちらの記事が考え方の参考になります。

そうです。Animator自体が"状態"遷移図そのものに見立てた機構なのです。
この記事中にあるように、Animatorのパラメーターから同期が必要なもの(あるいは全部)を抽出し、それをUdonで同期してその結果をAnimatorに流し込むだけ。あとはAnimatorの状態遷移の作り込み次第…となります。
この考え方は、実はAvatars3.0のExpressionParametersと同じです。そのため、アバターを触ったことがある人ならば馴染みやすいと思います。

良き同期設計を!

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