[[Roomの作成または参加|unity_photon_cloud_room_create_join]] > [[キャラクタの同期(位置と回転のみ)|unity_photon_cloud_char_pos_rot]] ---- !!![Photon Cloud] キャラクタの同期(Demoのmonsterprefab) PlayerがLobby/Roomへの接続と、サンプルにあるmonsterprefabのキャラクタのマルチプレイの同期は確認できました。 次に「monsterprefab」はどうやって同期を取ってるのだろう?というのを調査。 Inspectorを見てみると、いくつかScriptが割り当てられています。 {{ref_image unity_photon_char.png}} myThirdPersonController/PhotonView/NetworkCharacter/ClickDetector/AudioRpcのスクリプトが存在。 このうち「PhotonView」はPhotonで提供されているスクリプト(Photon Unity Networking/Plugins/PhotonNetwork内)。 それ以外が、Demoプロジェクトのスクリプト。 !!monsterprefabで使っているScript !myThirdPersonController Assetの「Character Controller」の「ThirdPersonController」を派生させたクラス。 idle/walk/run/jumpのアニメーションを割り当て、キー操作で移動制御する汎用のスクリプトです。 PC用で、モバイルでは操作できず。 !NetworkCharacter Photon.MonoBehaviour派生クラス。 キャラクタの位置と回転を保持している。 OnPhotonSerializeView関数のコールバックで、他のプレイヤーとキャラクタデータを送受信している模様。 この実装がないと、他のプレイヤーと位置や回転の同期ができない。 徐々に目標の位置と回転に移行させることで、いきなりワープしてくるといった挙動を抑えている。 !ClickDetector MonoBehaviour派生クラス。 キャラクタの攻撃時の判定を行う? なくても動作。 !AudioRpc Photon.MonoBehaviour派生クラス。 音データをRPCを使って他のプレイヤーで再生するクラス。 !!同期処理に最低限必要な実装 Photonでの最小限の同期を行うためには、 *Resourcesフォルダにキャラクタのprefabを配置する *prefabに、ComponentとしてPhotonViewを追加 *prefabに、ComponentとしてNetworkCharacter(キャラクタ情報の送受信)を追加 *prefabのInspector上で、NetworkCharacterをPhotonViewのObserveにドラッグして割り当て {{ref_image unity_photon_serialize_drag.png}} を行い、 カラのGameObjectでPhoton.MonoBehaviour 派生クラスをComponentとして追加。 OnJoinedRoomでRoomに参加したときに、 GameObject monster = PhotonNetwork.Instantiate("monsterprefab", Vector3.zero, Quaternion.identity, 0); としてprefabをInstantiateで実体化することにより、キャラクタの位置と回転が同期しました。 !!同期を取るための通信手段 *[[OnPhotonSerializeView関数コールバックを使う|unity_photon_cloud_char_pos_rot]] *[[PhotonViewのRPCを使う|unity_photon_cloud_rpc]] の2つが代表的。他にもあるようですが、とりあえずこの2つの手段でキャラクタの同期はなんとかなりそうです。 !OnPhotonSerializeView関数コールバックを使う DemoでのNetworkCharacter.csは以下のようになっています(日本語コメントを追加)。 using UnityEngine; public class NetworkCharacter : Photon.MonoBehaviour { private Vector3 correctPlayerPos = Vector3.zero; // We lerp towards this private Quaternion correctPlayerRot = Quaternion.identity; // We lerp towards this // Update is called once per frame void Update() { // photonViewが自分自身ではない場合、位置と回転を反映. if (!photonView.isMine) { transform.position = Vector3.Lerp(transform.position, this.correctPlayerPos, Time.deltaTime * 5); transform.rotation = Quaternion.Lerp(transform.rotation, this.correctPlayerRot, Time.deltaTime * 5); } } /** * プレイヤー同士の位置/回転情報の同期をとる. * 自分のキャラクタの位置と回転を送信、自分以外のキャラクタの位置と回転を受信. */ void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) { if (stream.isWriting) { // We own this player: send the others our data stream.SendNext(transform.position); stream.SendNext(transform.rotation); myThirdPersonController myC = GetComponent(); stream.SendNext((int)myC._characterState); } else { // Network player, receive data this.correctPlayerPos = (Vector3)stream.ReceiveNext(); this.correctPlayerRot = (Quaternion)stream.ReceiveNext(); myThirdPersonController myC = GetComponent(); myC._characterState = (CharacterState)stream.ReceiveNext(); } } } Update関数内で「!photonView.isMine」により自分自身でない場合、位置と回転を徐々に目標の値に移行していってます。 OnPhotonSerializeViewコールバック関数は定期的に呼ばれ、 「stream.isWriting」がtrueの場合は、自分自身の位置と回転を他のプレイヤーに対して送信。 falseの場合は、他のプレイヤーの情報を受信し、privateに保持している位置と回転の値を更新。 !PhotonViewのRPCを使う 音を鳴らすのにRPCを使ってイベントを送信。 カラのGameObjectにRandomMatchmaker(Photon.MonoBehaviour派生クラス)をComponentに追加して、OnJoinedRoom関数内でmyPhotonViewとしてPhotonViewの参照を保持。 OnGUI関数内で、 myPhotonView.RPC("Marco", PhotonTargets.All); としている箇所でRPCを使っています。 第一引数の"Marco"は、prefabのComponentに追加されているAudioRpcスクリプト(Photon.MonoBehaviour派生クラス)で記載。 [RPC] void Marco() { if (!this.enabled) return; Debug.Log("Marco"); this.audio.clip = marco; this.audio.Play(); } ---- [[Roomの作成または参加|unity_photon_cloud_room_create_join]] > [[キャラクタの同期(位置と回転のみ)|unity_photon_cloud_char_pos_rot]] ---- {{lastmodified}}