Instantiation
ほとんどのマルチプレイヤーゲームではゲームオブジェクトの作成と同期が必要です。
ここでいうゲームオブジェクトとは、ルーム内の全クライアントに表示されるべきキャラクターであったり、ユニット、モンスターのことです。
PUNではこれらを作成、同期するのに便利な機能を用意しています。
いつも通りUnityでは、Instantiate
とDestroy
はゲームオブジェクトの存続期間を管理するために使用します。
PUN 2ではプーリングを使用してオブジェクトの作成(および回復)が可能です。
ネットワーク化された各GameObjectには、ネットワーク経由の識別子としてPhotonViewコンポーネント(およびViewID)が必要です。
このページと次のページではネットワークゲームオブジェクトの作成、同期、および使用について説明しています。
PhotonNetwork.Instantiate
ネットワークゲームオブジェクトの作成には、UnityのObject.Instantiate
ではなくPhotonNetwork.Instantiate
を使用します。
ルーム内のクライアントは全て制御することになるオブジェクトを呼び出して作成できます。
C#
PhotonNetwork.Instantiate("MyPrefabName", new Vector3(0, 0, 0), Quaternion.identity, 0);
PhotonNetwork.Instantiate
の最初のパラメータは、インスタンス化する「プレハブ」を決める文字列です。
PUNは内部でGameObjectをPhotonNetwork.PrefabPool
からフェッチし、ネットワークに設定して有効化します。
オブジェクトを作成する位置と回転の設定は必須です。
後から参加するプレイヤーは、既に動かされていたとしても最初にこの場所でインスタンス化を開始します。
全てのプレハブはPhotonView
コンポーネントを備えていなければなりません。
これにはViewID(ネットワークメッセージの識別子)、オブジェクトを所有する人、ネットワークアップデートを書き込みおよび読み込みを行うスクリプト(「監視」リスト)とこれらのアップデートの送信方法(「監視オプション」)も含まれます。
インスペクターを確認してエディターを介してPhotonViewを設定します。
ネットワーク化されたオブジェクトには複数のPhotonViewを含めることができますが、パフォーマンス上の理由から、1つのみを使用することをお勧めします。
デフォルトでPUN instantiateは、DefaultPoolを使用します。これはResourcesフォルダからプレハブを読み込んで後でGameObjectを破壊します。
より洗練されたIPunPrefabPool
実装はオブジェクトをDestroy
のプーリングに返し、Instantiate
で再利用します。
その場合、実際にInstantiateでGameObjectが作成されるわけではありません。つまりUnityによるStart()
の呼び出しは行われないということです。
そのため、ネットワークゲームオブジェクトのスクリプトはOnEnable
およびOnDisable
のみを実装します。
インスタンス化の際にGameObjectsを設定では、スクリプトにIPunInstantiateMagicCallback
を実装することもできます。
PUNはインターフェースがコンポーネントに実装されたかどうか確認し、そのインスタンスが利用されるときにOnPhotonInstantiate(PhotonMessageInfo info)
を呼び出します。
ここには誰がいつそのゲームオブジェクトをインスタンス化したのかについての情報も含まれています。
注:IPunInstantiateMagicCallback
実装を検索するのは手間がかかりますので、PUNは、インターフェースのどのプレハブを 使わない のかキャッシュして再度プレハブを使用するときにはこの検索を省きます。
例えば、以下のようにインスタンス化したGameObjectをプレイヤーのTag
オブジェクトとして設定できます。
C#
void OnPhotonInstantiate(PhotonMessageInfo info)
{
// 例えば このゲームオブジェクトをこのプレイヤーのキャラクターとしてPlayer.TagObjectに保存します
info.sender.TagObject = this.GameObject;
}
裏で、PhotonNetwork.Instantiate
は、後から参加するプレイヤーのためにイベントをサーバーに格納します。
Networked Objectsのライフタイム
デフォルトで、PhotonNetwork.Instantiate
で作成されたGameObjectは、作成者がルームにいる限り存在しています。
ルームを換えると、Unityでシーンを換えたときと同じでオブジェクトは継承されません。
クライアントがルームを退出する際、残っているプレイヤーは、去っていくプレイヤーの作成したオブジェクトを破壊します。
もしこれがご自身のゲームロジックに適切でない場合は、この動作を無効化することも可能です。無効化するにはルームの作成時にRoomOptions.CleanupCacheOnLeave
をfalseに設定してください。
マスタークライアントはPhotonNetwork.InstantiateRoomObject()
を使用してルームのライフタイムをもつゲームオブジェクトをを作成できます。
注:オブジェクトはマスタークライアントとは関連性がなく、ルームと関連づいています。
デフォルトでは、マスタークライアントがオブジェクトを制御していますが、photonView.TransferOwnership()
を使用することでその制御を引き渡すことができます。
また、ネットワーク化されたオブジェクトを手動で明示的に破棄することもできます。PhotonNetwork.Destroyをご確認ください。
ネットワークシーンオブジェクト
シーンのオブジェクトにPhotonViewを配置することは全く問題ありません。
これはデフォルトでマスタークライアントに制御されることになります。ルームに関連するRPCを送信するための「ニュートラル」なオブジェクトを用意するのに便利です。
重要:ルームに入室する前にネットワークオブジェクトとシーンを読み込むと、まだ使えないPhotonViewの値があります。
例えば、ルームに入っていないと、Awake()
のisMine
は使えません!
シーンの切り替え
シーンを読み込むと、Unityは通常現在hierarchyにある全てのGameObjectを破壊します。
ネットワークオブジェクトも含まれるので混同しやすいです。
例:メニューのシーンで、ルームに参加して別のシーンを読み込みます。
ルームに入室するのが少し速くなって、ルームから最初のメッセージが来るかもしれません。
PUNはネットワークオブジェクトのインスタンス化を開始しますが、ロジックは他のシーンを読み込むため、インスタンス化されたオブジェクトは無くなってしまいます。
シーンの読み込みに関する問題を避けるため、PhotonNetwork.AutomaticallySyncScene
をtrueに設定しPhotonNetwork.LoadLevel()
を使って、シーンを切り替えます。
RPCのタイミングと読み込みレベルを参照してください。
カスタムインスタンス化データ
インスタンス化の呼び出し時に、いくつかの初期カスタムデータを送信できます。
PhotonNetwork.Instantiate *
メソッドの最後のパラメーターを利用するだけです。
これには2つの主な利点があります。
- 余分なメッセージを避けてトラフィックを節約します: この種の情報を同期するために、別個のRPCまたはRaiseEvent呼び出しを使用する必要はありません。
- タイミング:交換されたデータは、プレハブのインスタンス化の時点で利用可能であり、何らかの初期化を行うのに役立ちます
インスタンス化データは、Photonがシリアル化できるもののオブジェクト配列( object []
)です。
例:
カスタムデータでインスタンス化する:
C#
object[] myCustomInitData = GetInitData();
PhotonNetwork.Instantiate("MyPrefabName", new Vector3(0, 0, 0), Quaternion.identity, 0, myCustomInitData);
カスタムデータを受け取る:
C#
public void OnPhotonInstantiate(PhotonMessageInfo info)
{
object[] instantiationData = info.photonView.InstantiationData;
// ...
}
PhotonNetwork.Destroy
通常、ルームを出るとGameObjectは自動的に破壊されます。
ネットワーク化されたオブジェクトの寿命を参照してください。
しかし、接続されていてルームに参加していて、PhotonNetwork.Instantiate呼び出しで作成されたGameObjectを「ネットワーク破壊」したい場合は、PhotonNetwork.Destroyを使用してください。
これには以下が含まれます。
- サーバー上のルームからキャッシュされたInstantiateイベントを削除する。
- 破壊するGameObjectの階層にあるPhotonView用にバッファリングされたRPCの削除
- 他のクライアントにもGameObjectを削除するようにメッセージを送信する(ネットワークラグの影響を受ける)
これを成功させるためには、破壊するGameObjectが以下の条件を満たす必要があります。
- GameObjectは実行時に
PhotonNetwork.Instantiate*
メソッドコールを使ってインスタンス化されます。 - クライアントがオンラインルームに参加している場合、GameObjectのPhotonViewは同じクライアントが所有または制御している必要があります。
PhotonNetwork.InstantiateRoomObject
で作成したGameObjectはマスタークライアントのみが破棄できます。 - クライアントがルームに参加していない場合やオフラインのルームに参加していない場合は、ローカルでGameObjectを破棄することができます。
PrefabPoolの使用
デフォルトで、PUNでは簡単なDefaultPool
を使用してゲームオブジェクトのインスタンス化と破壊を行っています。
これはResourcesフォルダを使用してプレハブを読み込みを行いますが、破壊されるオブジェクトのプーリングは行いません。(使用方法を簡単にするためです。)
ゲームのパフォーマンスに悪い影響を与える場合は、カスタムPrefabPoolを設定するタイミングです。
カスタムプールクラスは2つのメソッドによるIPunPrefabPool
インターフェースの実装が必須です。
GameObject Instantiate(string prefabId, Vector3 position, Quaternion rotation)
はプレハブのインスタンスを取得します。
この関数では正当な、PhotonViewで無効化されたGameObjectを返します。
Destroy(GameObject gameObject)
がプレハブのインスタンスを破壊(もしくは回復)するために呼び出されます。
ゲームオブジェクトは既に無効化されてプールは後でインスタンス化の際に利用するためにリセットしてキャッシュすることもあります。
注:カスタムIPunPrefabPool
が使用されると、PhotonNetwork.Instantiate
はGameObjectを作成せず、Start()
も呼び出されません。
必要に応じてOnEnable
やOnDisable
を使用して、いまだに実行している物理コンポーネントや他のコンポーネントを無効化してください。
手動でインスタンス化
もし、PUNの備え付けインスタンス化とプールを使用したくない場合は、以下の手順でRPCもしくはRaiseEventを使用して挙動を再実装することができます。
インスタンス化するオブジェクト(プレハブの名前)と識別の方法(ViewID)をリモートクライアントに指定する必要があります。
ネットワークメッセージを適切なゲームオブジェクト/スクリプトにルーティングするにはPhotonView.ViewID
が鍵です。
もし手動でインスタンス化する場合は、新しいViewIDをPhotonNetwork.AllocateViewID()
に割り当て、送信します。
ルームにいるメンバーは全員、新しいオブジェクトに同じIDを設定しなければなりません。
Manual Instantiationイベントはバッファリングされる必要があります。
後から接続するクライアントはスポーンインストラクションも受信しなければいけません。
C#
public void SpawnPlayer()
{
GameObject player = Instantiate(PlayerPrefab);
PhotonView photonView = player.GetComponent<PhotonView>();
if (PhotonNetwork.AllocateViewID(photonView))
{
object[] data = new object[]
{
player.transform.position, player.transform.rotation, photonView.ViewID
};
RaiseEventOptions raiseEventOptions = new RaiseEventOptions
{
Receivers = ReceiverGroup.Others,
CachingOption = EventCaching.AddToRoomCache
};
SendOptions sendOptions = new SendOptions
{
Reliability = true
};
PhotonNetwork.RaiseEvent(CustomManualInstantiationEventCode, data, raiseEventOptions, sendOptions);
}
else
{
Debug.LogError("Failed to allocate a ViewId.");
Destroy(player);
}
}
はじめにプレイヤープレハブをローカルでインスタンス化します。
これはオブジェクトのPhotonViewコンポーネントにリファレンスが求められるので、必要な動作です。
正常にPhotonViewにIDを割り振れていた場合は、他のクライアントに送信して、オブジェクトの配列の中に保管したい全てのデータを集めます。
この例では、インスタンス化されたオブジェクトの位置と回転、そして 一番大事な 割り振られたViewID
を送信します。
その後で、RaiseEventOptions
とSendOptions
を作成します。
RaiseEventOptions
では、このイベントがルームのキャッシュに追加され、自分以外のクライアントにのみ送信されたことを確認します。自分のオブジェクトはローカルで既にインスタンス化しているので必要ありません。
SendOptions
ではこのイベントが信頼性をもって送信されたことを明示します。
最後はPhotonNetwork.RaiseEvent(...)
で、これを使ってカスタムイベントをサーバーに送信します。
この場合は、単にこの特定のイベントを表すbyte値であるCustomManualInstantiationEventCode
を使用します。
もしもPhotonViewにIDが正常に割り振れていなければ、エラーメッセージをログして以前にインスタンス化したオブジェクトを破壊します。
PhotonNetwork.RaiseEvent
を使用しているので、OnEvent
コールバックハンドラーの使用が必要です。
これは適切に登録するようにしてください。
どのように作動するかについては、技術資料のページのRPCとRaiseEventを参照してください。
この例でのOnEvent
ハンドラーは以下のように見えます。
C#
public void OnEvent(EventData photonEvent)
{
if (photonEvent.Code == CustomManualInstantiationEventCode)
{
object[] data = (object[]) photonEvent.CustomData;
GameObject player = (GameObject) Instantiate(PlayerPrefab, (Vector3) data[0], (Quaternion) data[1]);
PhotonView photonView = player.GetComponent<PhotonView>();
photonView.ViewID = (int) data[2];
}
}
ここで確認するのは受信したイベントがカスタムManual Instantiationイベントであるかです。
もしそうであれば、受信した位置と回転の情報でプレイヤープレハブをインスタンス化します。
その後で、オブジェクトのPhotonViewコンポーネントにリファレンスを取得し受信したViewID
を割り当てます。
アセットバンドルを使ってネットワークオブジェクトを読み込む場合、アセットバンドルの読み込みコードを追加して、サンプルの「playerPrefab」を自分のアセットバンドルのものと置き換えます。
ViewIDの制限
ほとんどのゲームでは、プレイヤーごとに数個のPhotonViewしか必要ありません。 キャラクター用に1つか2つ、それが通常です。
さらに必要な場合は、何かが間違っている可能性があります。
たとえば、ネットワークでインスタンス化して武器が発射するすべての弾丸にPhotonViewを割り当てるのは非常に非効率的です。代わりにRPCを介してプレイヤーまたは武器のPhotonViewで発射された弾丸を追跡します。
デフォルトでは、PUNはプレイヤーごとに最大1000のPhotonViewに対応しています。プレイヤーの理論上の最大数は2,147,483になります。
プレイヤーごとに簡単にPhotonViewを追加できます。
これは次のように機能します:
PhotonViewsは、ネットワークメッセージごとにviewIDを送信します。
このviewIDは整数で、アクター番号とプレイヤーのviewIDで構成されます。
intの最大サイズは2,147,483,647( int.MaxValue
)です。これを200万人以上のプレイヤーが許可する PhotonNetwork.MAX_VIEW_IDS
(1000)で割ったものです。
したがって、 MAX_VIEW_IDS
のデフォルトのケースが1000に等しい場合:
MAX_VIEW_IDS
の倍数のViewIDは使われません。2,147,483,000までは、0、1000、2000などとなります。- ViewID 0は予約されています。 これは、viewIDがまだ割り当てられていないことを意味します。
- シーンオブジェクトのviewIDは1〜999です。
- アクター番号1のオブジェクトのviewIDは1000〜1999です。
- アクター番号xのオブジェクトは、
x * MAX_VIEW_IDS
と(x + 1)* MAX_VIEW_IDS-1
の間のviewIDを持ちます。 - アクター番号2,147,483のオブジェクトには、2,147,483,000〜2,147,483,647のviewIDがあります。
int.MaxValue
の制限に達したため、可能な値は648のみです。
ViewIDは一度割り当てられると予約され、解放されるとリサイクルされます。
つまり、対応するネットワーク上のGameObjectをネットワークで破壊した後、ViewIDを再利用することができます。