#概要
VRChatのワールドギミックを作るうえで避けては通れない同期問題。
同期処理を組む際に私が重要だと思うポイントを解説していきたいと思います。
#1.基礎知識
##BroadCastTypeとVRC_ObjectSync
まずは同期を行う際に最も重要なこの二つの要素を理解しましょう。
VRChatではプレイヤーの位置以外は、特別な処理を行わない限り同期されることはありません。
ほとんどの同期処理は、この二要素によって行われます。
VRC_TriggerのBroadCastTypeをLocal以外に設定する事で、条件を満たした際Actionsをワールド内の他のプレイヤーに反映することができます。
ここで重要なのは、**"条件を満たしたプレイヤーからActionsのみが伝播する"**ということです。
他のプレイヤーが条件を満たしているかどうかはActionsの伝播に関係が無いことを覚えておいてください。
BroadCastTypeによる同期とは、
全てのプレイヤーが同じActionsを行う ≒ 全てのプレイヤーで同じ状態になる
という理屈な訳ですが、Actionsの伝播には通信ラグがあり、重力などの外部的な影響があるとずれが生じてしまいます。
そこで使われるのがVRC_ObjectSyncであり、名前の通りオブジェクトの位置をリアルタイムで同期するコンポーネントです。
一見非常に便利なVRC_ObjectSyncですが、ここで一つ質問です。
『BroadCastTypeは条件を満たしたプレイヤーを基準にActionsの同期を行う物でしたが、
VRC_ObjectSyncは誰を基準に位置を同期させているのでしょうか?』
答えはオーナーシップを持っているプレイヤーです。
オーナーシップはPickupした時や、TakeOwnerShip関数を実行した時等に移動します。
SpawnObjectアクションで発生したオブジェクトは、それを実行したプレイヤーがオーナーシップを持ちます。
スクリプトによる移動が絡んだオブジェクトを同期させる際は、誰がオーナーシップを所有しているかを意識して処理を組みます。
#2.同期のTips
##処理させるプレイヤーは一人に絞る
前章で解説した通り、同期処理は必ず一人のプレイヤーを基準に行われます。
複数のプレイヤーが処理に関わると、通信ラグも相まって同期が非常に難しくなるため、基本的に一人のプレイヤーに処理させるようにします。
##Localを活用する
BroadCastTypeのLocalはActionsを他のプレイヤーに伝播させません。
LocalでコンポーネントのEnableや、Animatorのパラメータを変更する事で特定のプレイヤーだけで処理を行う事ができます。
また、一部のVRC_Triggerの条件は複数のプレイヤーが同時に満たす場合があるため、その際もLocalを使いActionsが余計に伝播されるのを防ぎます。
必ず全てのプレイヤーで満たされる条件
OnPlayerJoined OnPlayerLeft OnSpawn OnDestroy OnStationEntered OnStationExited
状況によって複数のプレイヤーで満たされる条件
Custom OnEnable OnTimer OnEnterTrigger OnExitTrigger OnEnterCollider OnExitCollider OnAvatarHit
##Masterで処理する
特定のプレイヤーに処理させる必要が無い場合、ワールドのMaster上で処理するのが効果的です。
BroadCastTypeのMasterはその条件を満たしたのがワールドのMasterの場合のみActionsの実行、伝播を行います。
AlwaysのActivateCustomTriggerからMasterのCustomを発火させる、等の方法で全ての処理をMasterに集約します。
##見た目だけを同期する
VRC_ObjectSyncが入ったオブジェクトは、親子構造が複雑になるとオーナーシップの取得に問題が発生しやすいです。
そういう場合は実際に動かすオブジェクトは同期させず、親子関係外にある、外見とコライダだけのオブジェクトをAutoCamや、FollowTarget+LookatTargetで追従させてVRC_ObjectSyncを追加する事で同期処理を単純化します。
VRC_Stationでプレイヤーを移動させる際、ピックアップが荒ぶる現象の対策にもなったりします。
#3.実用例
GoogleDriveにUnityPackageをアップロードしているので、そちらをインポートしてからご覧ください。
https://drive.google.com/open?id=1927gFc3JDWZjh-U2soV9BOB-dkz-D4SG
##同期ドア
Prefab名**"SyncDoor"**
アニメーションで開閉するドアです。
ドアの開閉を切り替える程度のアニメーションなら、Animatorのパラメータ変更をAlwaysで行う事で十分同期させる事ができます。
Toggleは使わず、オブジェクトを分けてTrueとFalseで処理します。
最近ではVRC_SyncAnimationを使った同期も確立されているので、アニメーションの種類が多い場合等はそちらの方が安定するでしょう。
##タイマー
Prefab名**"TimerSystem"**
簡易的なカウントダウンタイマーです。
タイマーがスタートすると、1秒毎にAnimatorからアニメーションイベントによって"Timer"というCustomトリガーが起動します。
これをそのままLocalで処理してしまうと、各自のPC処理速度によってタイマーの経過に差が出来てしまいます。
最初のタイマー起動をAlwaysで行い、時間の進行処理をMasterにする事で、全てのプレイヤーの時間経過処理をワールドMasterに一本化し同期させています。
##銃弾再利用銃
Prefab名**"ResetPositionGun"**
VRC_SceneResetPositionを使って、弾をスポーンさせず使いまわす銃です。
銃をピックアップした際の処理が最も重要で、まず全ての銃弾のオーナーシップを銃の所持者に取得させます。
そしてAlwaysで全てのプレイヤーからColliderとFollowTargetをDisableさせてから、Localで同じ要素を逆にEnableにしています。
これは、同期の基準となるプレイヤーと、それ以外のプレイヤーの処理を分けるためのものでとても大事です。
FollowTargetは同期の基準となるプレイヤー以外でEnableになっているとVRC_ObjectSyncの位置同期を上書きしてしまうので切っておき、基準プレイヤーでは銃弾が使用されていない時ワールド上空に格納しておく役割があるためLocalで変更を行います。
後は弾が発射されたらアニメーションイベントからResetPosition関数とAddForceのActionsが設定されたCustomトリガーを呼び出しています。
#4.おわりに
同期のアプローチ方法に正解は無く、手段は無限にあります。
行いたい処理によって最適な手段は変わってくるので、私が行っている手段もあくまで参考の一つとして、自分なりに最適な方法を探ってみて下さい。いい方法が見つかったらシェアしてくれると嬉しい。
VRChat 技術メモ帳
http://vrcworld.wiki.fc2.com/