Photon からの移行ガイド
Photon(PUN2)で作ったマルチプレイを GS2-Realtime の sync / stateMachine へ移す手順と対応表
このページは、Photon PUN2 で作ったマルチプレイを、GS2-Realtime のオブジェクト同期(sync / stateMachine)へ移すための手順と対応表です。各 API の詳細は Unity クライアントを参照してください。
考え方の違い
| 観点 | Photon PUN2 | GS2-Realtime |
|---|
| ルームを探す | Photon のロビーとマッチメイキング | GS2-Matchmaking でメンバーを集め、GS2-Realtime のルームを作る |
| ルームへの接続 | PhotonNetwork.ConnectUsingSettings / JoinRoom | 発行された IP アドレス・ポート・暗号鍵へ RealtimeSession で接続する |
| 誰が値を決めるか | 各クライアント(マスタークライアントがまとめ役) | ルームのサーバーが、書き込みの権限と RPC の許可を検証する。stateMachine では、ゲームの進行もサーバーのルールが決める |
| 同期するデータ | OnPhotonSerializeView で自由なデータを送る | オブジェクトごとに最大 32 個の整数フィールド。位置・回転は刻み幅に丸めて送る |
| 通信 | UDP(reliable / unreliable) | WebSocket(確実に届けるもの)と UDP(定期的な状態の更新) |
| 課金 | 同時接続数 | ルームのサーバーの起動時間と通信量 |
いちばん大きな違いは、ルームのサーバーが権限を検証できることです。ほかのプレイヤーのオブジェクトの値は書き換えられません。さらにオブジェクトカタログを設定すると、登録した Prefab と RPC だけを通すように検査させられます。マスタークライアントに任せていたゲームの進行(敵の HP、得点、勝敗)は、stateMachine のルールに移すと、クライアントの改造の影響を受けなくなります。
対応表
コンポーネント
| Photon PUN2 | GS2-Realtime |
|---|
PhotonView | NetworkEntity |
PhotonTransformView | NetworkTransform |
IPunObservable / OnPhotonSerializeView | INetworkObservable(OnNetworkWrite / OnNetworkRead) |
PhotonNetwork.PrefabPool / Resources の Prefab | Prefab Catalog と NetworkWorld |
シーンに置いた PhotonView、InstantiateRoomObject | ルームが作る共有オブジェクト(stateMachine のルール、または操作権を貸し出すオブジェクト) |
オブジェクト
| Photon PUN2 | GS2-Realtime |
|---|
PhotonNetwork.Instantiate(name, pos, rot) | world.Instantiate(prefabId, pos, rot) |
PhotonNetwork.Destroy(go) | entity.Destroy() |
photonView.IsMine | entity.IsMine |
photonView.Owner | entity.OwnerId(接続 ID) |
photonView.TransferOwnership(player) | entity.TransferOwnership(connectionId) |
photonView.RequestOwnership()(OwnershipOption.Takeover) | entity.RequestOwnership()(オブジェクトカタログを設定する場合は "ownership": "takeover") |
PhotonNetwork.CleanupCacheOnLeave = false | カタログで "keepOnLeave": true(マスタークライアントへ引き継ぐ) |
photonView.Group / PhotonNetwork.SetInterestGroups | entity.SetGroup / world.SetInterestGroups、SubscribeGroup |
RPC とイベント
| Photon PUN2 | GS2-Realtime |
|---|
[PunRPC] | [NetworkRpc] |
photonView.RPC(name, RpcTarget.All, args) | entity.Rpc(name, RpcTarget.All, args) |
RpcTarget.Others / AllBuffered / OthersBuffered | 同名の RpcTarget |
RpcTarget.AllViaServer | RpcTarget.All(常にサーバーを経由する) |
RpcTarget.MasterClient | entity.RpcTo(name, world.MasterClientId, args) |
photonView.RPC(name, player, args) | entity.RpcTo(name, connectionId, args) |
PhotonMessageInfo | NetworkRpcInfo(Sender は呼び出し元の接続 ID) |
PhotonNetwork.RaiseEvent | world.RaiseEvent / RaiseEventTo(ハンドラーは [NetworkEvent]) |
IOnEventCallback.OnEvent | world.AddEventTarget で登録した [NetworkEvent] メソッド、または world.EventReceived |
ルームとプレイヤー
| Photon PUN2 | GS2-Realtime |
|---|
PhotonNetwork.LocalPlayer.ActorNumber | session.MyConnectionId |
PhotonNetwork.PlayerList、OnPlayerEnteredRoom / OnPlayerLeftRoom | RealtimeSession の OnJoinPlayer / OnLeavePlayer |
PhotonNetwork.MasterClient / IsMasterClient | world.MasterClientId / IsMasterClient |
PhotonNetwork.SetMasterClient / OnMasterClientSwitched | world.SetMasterClient / MasterClientChanged |
Room.SetCustomProperties / OnRoomPropertiesUpdate | world.SetRoomProperty / RoomPropertyChanged |
Player.SetCustomProperties / OnPlayerPropertiesUpdate | world.SetPlayerProperty / PlayerPropertyChanged |
PhotonNetwork.Time | world.ServerTime |
PhotonNetwork.SendRate / SerializationRate | world.SendRate |
| unreliable な送信 | RealtimeUdpChannel(定期的な状態の更新を UDP で送る) |
移行の手順
- ルームの準備を置き換える: Photon のロビーとマッチメイキングを、GS2-Matchmaking と GS2-Realtime のルームの作成に置き換えます。Realtime のネームスペースは
serverType を sync(ゲームの進行もサーバーで決めるなら stateMachine)にします。流れは GS2-Realtime の紹介とチュートリアルを参照してください。 - 接続を置き換える:
RealtimeSession で接続し、NetworkWorld.Attach と world.Receive をつなぎます。ルームに接続するのとおりに書きます。 - Prefab を置き換える:
PhotonView を NetworkEntity に、PhotonTransformView を NetworkTransform にします。Prefab Catalog を作り、Prefab ID を割り当てて登録します。 - 同期する値を置き換える:
OnPhotonSerializeView で送っていた値は、同期スキーマのフィールドにして INetworkObservable で読み書きします。値は 32 個までの整数(float は刻み幅に丸める)なので、文字列や配列は RPC かカスタムプロパティで送ります。 - RPC を置き換える:
[PunRPC] を [NetworkRpc] に、photonView.RPC を entity.Rpc にします。ほかのプレイヤーのオブジェクトに対して呼ぶ RPC には AnyCaller = true を付けます。引数の型が使える型に入っているかを確かめます。 - 生成と削除を置き換える:
PhotonNetwork.Instantiate を world.Instantiate に、PhotonNetwork.Destroy を entity.Destroy() にします。シーンに置いていたオブジェクトは、ルームが作る共有オブジェクトにします。 - 検査したい場合はオブジェクトカタログを設定する: 生成や RPC をルームに検査させたい場合は、Prefab Catalog のアセットを右クリックし、Copy Object Catalog JSON で生成した JSON を、Realtime のネームスペースの
syncSetting.objectCatalog に設定します。RPC の名前や Prefab を変えたら、生成し直します。 - イベント・プロパティ・マスタークライアントを置き換える: 対応表のとおりに置き換えます。
- UDP を使う: WebGL 以外では、
RealtimeUdpChannel で定期的な状態の更新を UDP で送ります。
コードの書き換え例
RPC
// Photon PUN2
[PunRPC]
void Fire(Vector3 direction, PhotonMessageInfo info) { /* ... */ }
photonView.RPC(nameof(Fire), RpcTarget.Others, direction);
// GS2-Realtime
[NetworkRpc]
void Fire(Vector3 direction, NetworkRpcInfo info) { /* ... */ }
entity.Rpc(nameof(Fire), RpcTarget.Others, direction);
生成と所有者の判定
// Photon PUN2
var player = PhotonNetwork.Instantiate("Player", spawnPoint, Quaternion.identity);
if (player.GetComponent<PhotonView>().IsMine) { /* 入力を有効にする */ }
// GS2-Realtime(Prefab ID 1 を Prefab Catalog に登録しておく)
var player = world.Instantiate(1, spawnPoint, Quaternion.identity);
if (player.IsMine) { /* 入力を有効にする */ }
入力を受け付けるスクリプトは、NetworkEntity の Controlled Behaviours に登録すると、書き込める間だけ自動で有効になります。
値の同期
// Photon PUN2
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting) stream.SendNext(health);
else health = (float)stream.ReceiveNext();
}
// GS2-Realtime(同期スキーマに float の Health フィールドを宣言しておく)
public void OnNetworkWrite(NetworkEntity entity) => entity.SetFloat("Health", health);
public void OnNetworkRead(NetworkEntity entity, uint changed)
{
if (entity.HasChanged("Health", changed)) health = entity.GetFloat("Health");
}
イベントとプロパティ
// Photon PUN2
PhotonNetwork.RaiseEvent(GoalCode, team, new RaiseEventOptions { Receivers = ReceiverGroup.All }, SendOptions.SendReliable);
PhotonNetwork.CurrentRoom.SetCustomProperties(new Hashtable { { "stage", "forest" } });
// GS2-Realtime
world.RaiseEvent("Goal", RpcTarget.All, team); // [NetworkEvent] void Goal(int team) を登録しておく
world.SetRoomProperty("stage", "forest");
注意すること
- 確定は非同期:
world.Instantiate はすぐに使えますが、ルームが拒否すると SpawnFailed が呼ばれて削除されます。カスタムプロパティは、ルームを経由して全員に届いた時点で反映されます。 - 値は丸められる: 位置は 0.01、回転は 360/4096 度の刻み幅で送ります。精度を変えるには、同期スキーマで指定します。
- 権限はサーバーが検証する: 所有者以外の書き込みは拒否されます。オブジェクトカタログを設定すると、登録していない Prefab と RPC、許可していない所有権の取得も拒否されます。
- RPC の引数の上限: 合計 4096 バイトまでです。大きなデータは分けて送ります。
- 送信の順序: UDP で送る定期的な更新と、WebSocket で送る RPC やイベントの間の順序は保証しません。RPC の直前に書き換えた値は、RPC より先に届きます。
- ルームの終了条件: 参加者全員から 1 分間通信がないとルームは終了します。ルームの終了条件との関係を参照してください。
- WebGL: UDP を使えないので、すべて WebSocket で送ります。
- Photon の別製品: Photon Voice や Photon Chat に当たる機能は、GS2-Realtime には含まれません。テキストチャットには GS2-Chat を使えます。