This document is about: PUN 1
SWITCH TO

PUN Classic (v1)、PUN 2、Boltはメンテナンスモードとなっております。Unity2022についてはPUN 2でサポートいたしますが、新機能が追加されることはありません。お客様のPUNプロジェクトおよびBoltプロジェクトが停止することはなく、将来にわたってパフォーマンス性能が落ちることはありません。 今後の新しいプロジェクトについては、Photon FusionまたはQuantumへ切り替えていただくようよろしくお願いいたします。

同期と状態

すべてのプレイヤーを更新し、同じ状態を維持することがゲームの基本です。 他のプレイヤーが誰であるか、何をしているか、どこにいるか、ゲームの世界がどのように見えているかを把握する必要があります。

PUN(およびPhoton全般)は、更新や状態維持をおこなうためのいくつかのツールを提供しています。 このページでは、その選択肢とそれぞれを使用する状況について説明します。

オブジェクトの同期

PUNでは、簡単に特定のゲームオブジェクトを「ネットワーク対応」させることができます。 PhotonViewコンポーネントを割り当てれば、オブジェクトは位置、回転やその他の値をリモート複製と同期することができます。 Transformや(より一般的には)スクリプトの1つのようなコンポーネントを「監視」するには、PhotonViewを設定する必要があります。

ほとんどのデモがオブジェクトの同期を使用しています。 一部のスクリプトはOnPhotonSerializeView()``を実装し、PhotonViewの監視されたコンポーネントとなります。 OnPhotonSerializeView()では位置やその他の値はストリームに書き込まれ、そこから読み取られます。

リモートプロシージャコール(RPC)

ルーム内の任意のクライアントからメソッドが呼び出し可能になるよう、マークすることができます。 属性[PunRPC]でChangeColorToRed()を実装すると、リモートプレイヤーは以下を呼ぶことでゲームオブジェクトの色を赤に変更できます :photonView.RPC("ChangeColorToRed", PhotonTargets.All);

呼び出しは常にGameObject上の特定のPhotonViewを対象とします。 'ChangeColorToRed()'が呼ばれる場合、そのPhotonViewを持つGameObject上でのみ実行されます。 これは、特定のオブジェクトに影響を与えたい場合に便利です。

もちん、ターゲットの無いメソッドの場合は空のGameObjectを「ダミー」としてシーンに入れることができます。 たとえば、RPCを使用して特定のGameObjectに関連しないチャットを実装することもできます。

RPCは「バッファリング」することができます。 サーバーは呼び出しを記憶しRPCが呼ばれてから、参加したユーザーに送信します。 これによって、動作を保存してPhotonNetwork.Instantiate()の代替を実装することができます。 手動でインスタンス化を参照してください。 上記の欠点は、注意しない限りバッファが増え続ける点です。

カスタムプロパティ

Photonのカスタムプロパティには必要に応じて埋めることができる、キー値のハッシュテーブルがあります。 値はクライアントで同期およびキャッシュされるので、使用前に取得する必要はありません。 変更はSetCustomProperties()で他のプレイヤーにプッシュされます。

これはどのように役立つのでしょうか?通常、ルームやプレイヤーにはGameObjectに関連していない次のような属性があります: 最新のマップや、プレイヤーのキャラクターの色(2d jump and runを想像してください)。 これらは、オブジェクトの同期またはRPCで送信することができますが、カスタムプロパティを使用する方が便利な場合があります。

プレイヤーにカスタムプロパティを設定するには、PhotonPlayer.SetCustomProperties(Hashtable propsToSet)を使用して、追加や更新をおこなうキーとなる値を追加します。ローカルプレイヤーオブジェクトへのショートカットはPPhotonNetwork.playerです。 同様にPhotonNetwork.room.SetCustomProperties(Hashtable propsToSet)を使用して、入室しているルームを更新します。

更新の配布には少し時間がかかりますが、それに応じてすべてのクライアントがroom.customPropertiesplayer.customPropertiesを更新します。 プロパティが変更された場合のコールバックとして、PUNはOnPhotonCustomRoomPropertiesChanged(Hashtable propertiesThatChanged)または OnPhotonPlayerPropertiesChanged(object[] playerAndUpdatedProps)をそれぞれ呼び出します。

新しいルームを作成する際にプロパティを設定することもできます。 特に、ルームプロパティをマッチメイキングに使用できる点が便利です。 参加可能なルームをフィルタリングするためにプロパティ・ハッシュテーブルを使用するJoinRandomRoom()オーバーロードがあります。 ルームを作成する場合、RoomOptions.customRoomPropertiesForLobbyを設定して、ロビーでフィルタリング可能なルームプロパティを定義してください。

マッチメイキングに関するドキュメントでは、マッチメイキングにカスタムプロパティを使用する方法を説明しています。

プロパティのCheck And Swap(CAS)

SetCustomPropertiesを使用すると、サーバーは通常、任意のクライアントから新しい値を受け入れます。これは、少し複雑になる場合があります。

たとえば、プロパティはルーム内の固有アイテムを誰が拾ったかの保存に使用できます。 つまり、プロパティのキーがアイテムで、値がそれを拾った者を定義します。 どのクライアントも、そのactorNumberにプロパティをいつでも設定することができます。 全員がほぼ同時にそれを行うと、最後のSetCustomProperties呼び出しがアイテムを獲得(最終値を設定)します。 この方法は反直感的で、好ましくありません。

SetCustomPropertiesには条件として使用できる任意のexpectedValuesパラメータがあります。 expectedValuesではサーバーは、現在のキーとなる値が expectedValues内のものと一致した場合にのみプロパティを更新します。 期限の切れたexpectedValuesの更新は無視されます(クライアントは結果としてエラーを取得し、他の者は更新の失敗を通知されません)。

この例では、固有のアイテムを受け取るオーナーを expectedValuesに含めることができます。全員がアイテムを取ろうとしても、他のすべての更新リクエストのexpectedValuesに期限切れのオーナーが含まれているので、最初のリクエストのみが成功します。

SetCustomProperties,に条件としてexpectedValues​​を使用することをCheck and Swap(CAS)と呼びます。 これは、並行性の問題を回避するのに便利ですが、他にもクリエイティブな方法で使用することができます。

注:SetCustomPropertiesはCASで失敗するかも知れないので、すべてのクライアントはサーバーから送信されるイベントのみで、それらのカスタムプロパティを更新します。これは、新しい値を設定しようとするクライアントを含みます。これは、CASを使用せずに値を設定する場合とは異なるタイミングです。

同期、RPC、プロパティを最大限に活用

値に最適な同期方法を決めるには、更新が必要な頻度と、値の「履歴」の必要性を確認することをお勧めします。

頻度の高い更新(位置、キャラクターの状態)

頻繁な更新にはObject Synchronizationを使用します。 任意の更新数に対して、ストリームに何も書かないことにより、独自のスクリプトで更新をスキップすることができます。 キャラクターの位置は頻繁に変更されます。それぞれの更新は重要ですが、新しいものとすぐに置換される確率が高い状態です。 PhotonViewを設定して、「Unreliable」または「Unreliable On Change」を送信することができます。 前者は、キャラクターが移動しない場合でも一定の周波数で更新を送信します。 後者はゲームオブジェクト(キャラクター、単位)が停止すると更新の送信を停止します。

頻度

これらはユーザーの入力にもとづいており、RPCとして送信するのが最適です。

Object Synchronizationを使用するかどうかの区別は、あまり明確ではありません。 Object Synchronizationをおこなう場合は、より頻繁な更新の動作の場合は「インライン」にした方が良いかも知れません。 例:キャラクターの位置を送信する場合は、「ジャンプ」の状態を送信するために簡単に値を追加することができます。 これは独立したRPCにする必要はありません!

photonView.RPC("rpcName", ...)を呼び出す際、RPCは即時には送信されません。 代わりに、Object Synchronizationの周波数が更新を送信するまでバッファリングされます。 これは、より少ないパッケージにRPCを集約しますが(オーバーヘッドを防ぐため)、変数のラグを生みます。 このローカルラグを防ぐには、PhotonNetwork.SendOutgoingCommands()を呼び出すことで、RPCの更新ループを終えることができます。 これは、ゲームがターンの送信などでRPCに依存している場合などに有効です。

Object Synchronizationとは異なり、RPCはバッファリングされることがあります。 バッファリングされたRPCは、後から参加するプレイヤーに送信されます。これは動作を次々に再生する必要がある場合に便利です。 たとえば、参加するクライアントはシーン内で誰がどのようにツールを配置して、誰がどのようにそれをアップグレードしたかを再生することができます。 後者は、最初の動作に依存します。

バッファリングされたRPCを新しいプレイヤーに送信するにはトラフィックが必要で、クライアントは「ライブ」のゲームプレイに入る前に、各アクションを再生および適用する必要があります。 これによって問題が生じる場合があり、過剰なバッファリングは弱いクライアントを壊す可能性があります。このためバッファリングは注意して使用する必要があります。

頻度の低い更新や状態(ドアの開閉、地図、キャラクター装備)

頻度が非常に少ない変更は通常、カスタムプロパティに保存するのが最適です。 バッファリングされたRPCとは異なり、Hashtableプロパティには現在のキーとなる値のみが含まれます。 これは、ドアの状態を「オープン」(またはクローズ)にする場合に最適です。プレイヤーは、以前ドアがどのように開閉されたかを気にする必要はありません。

上記のRPCの例では誰かがシーン内に道具を置き、それがアップグレードされます。 少ない動作にRPCを使用することは問題ありません。修正が多い場合は、プロパティの単一の値に現在の状態を集約する方が簡単です。 複数の「防衛+10」の更新は多くのRPCではなく、単一の値に簡単に保存することができます。

繰り返しになりますが、カスタムプロパティとRPCの使用に明確な区分はありません。

カスタムプロパティを使用する適切な例として、ルームの「開始時刻」の保存があります。 ゲームが始まったら、プロパティとしてPhotonNetwork.timeを保存します。 この値は、ルーム内のすべてのクライアントで(ほぼ)同じで、開始時間を使用するとクライアントはゲームが実行されている時間の長さや誰のターンかを計算することができます。 また、各ターンの開始時間を保存することもできます。 これはゲームを一時停止できる場合に適しています。PUNパッケージのInRoomRoundTimerクラスを参照してください。

Back to top