> For the complete documentation index, see [llms.txt](/ja/llms.txt)


# Photon からの移行ガイド

Photon（PUN2）で作ったマルチプレイを GS2-Realtime の sync / stateMachine へ移す手順と対応表



このページは、Photon PUN2 で作ったマルチプレイを、GS2-Realtime のオブジェクト同期（`sync` / `stateMachine`）へ移すための手順と対応表です。各 API の詳細は [Unity クライアント]()を参照してください。

## 考え方の違い {#differences}

| 観点 | Photon PUN2 | GS2-Realtime |
| --- | --- | --- |
| ルームを探す | Photon のロビーとマッチメイキング | GS2-Matchmaking でメンバーを集め、GS2-Realtime のルームを作る |
| ルームへの接続 | `PhotonNetwork.ConnectUsingSettings` / `JoinRoom` | 発行された IP アドレス・ポート・暗号鍵へ `RealtimeSession` で接続する |
| 誰が値を決めるか | 各クライアント（マスタークライアントがまとめ役） | ルームのサーバーが、書き込みの権限と RPC の許可を検証する。`stateMachine` では、ゲームの進行もサーバーのルールが決める |
| 同期するデータ | `OnPhotonSerializeView` で自由なデータを送る | オブジェクトごとに最大 32 個の整数フィールド。位置・回転は刻み幅に丸めて送る |
| 通信 | UDP（reliable / unreliable） | WebSocket（確実に届けるもの）と UDP（定期的な状態の更新） |
| 課金 | 同時接続数 | ルームのサーバーの起動時間と通信量 |

いちばん大きな違いは、ルームのサーバーが権限を検証できることです。ほかのプレイヤーのオブジェクトの値は書き換えられません。さらに[オブジェクトカタログ](#catalog)を設定すると、登録した Prefab と RPC だけを通すように検査させられます。マスタークライアントに任せていたゲームの進行（敵の HP、得点、勝敗）は、`stateMachine` のルールに移すと、クライアントの改造の影響を受けなくなります。

## 対応表 {#mapping}

### コンポーネント {#mapping-components}

| Photon PUN2 | GS2-Realtime |
| --- | --- |
| `PhotonView` | `NetworkEntity` |
| `PhotonTransformView` | `NetworkTransform` |
| `IPunObservable` / `OnPhotonSerializeView` | `INetworkObservable`（`OnNetworkWrite` / `OnNetworkRead`） |
| `PhotonNetwork.PrefabPool` / `Resources` の Prefab | Prefab Catalog と `NetworkWorld` |
| シーンに置いた `PhotonView`、`InstantiateRoomObject` | ルームが作る共有オブジェクト（`stateMachine` のルール、または操作権を貸し出すオブジェクト） |

### オブジェクト {#mapping-objects}

| 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 とイベント {#mapping-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` |

### ルームとプレイヤー {#mapping-room}

| 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 で送る） |

## 移行の手順 {#steps}

1. **ルームの準備を置き換える**: Photon のロビーとマッチメイキングを、GS2-Matchmaking と GS2-Realtime のルームの作成に置き換えます。Realtime のネームスペースは `serverType` を `sync`（ゲームの進行もサーバーで決めるなら `stateMachine`）にします。流れは [GS2-Realtime の紹介]()と[チュートリアル]()を参照してください。
2. **接続を置き換える**: `RealtimeSession` で接続し、`NetworkWorld.Attach` と `world.Receive` をつなぎます。[ルームに接続する](#connection)のとおりに書きます。
3. **Prefab を置き換える**: `PhotonView` を `NetworkEntity` に、`PhotonTransformView` を `NetworkTransform` にします。Prefab Catalog を作り、Prefab ID を割り当てて登録します。
4. **同期する値を置き換える**: `OnPhotonSerializeView` で送っていた値は、同期スキーマのフィールドにして `INetworkObservable` で読み書きします。値は 32 個までの整数（float は刻み幅に丸める）なので、文字列や配列は RPC かカスタムプロパティで送ります。
5. **RPC を置き換える**: `[PunRPC]` を `[NetworkRpc]` に、`photonView.RPC` を `entity.Rpc` にします。ほかのプレイヤーのオブジェクトに対して呼ぶ RPC には `AnyCaller = true` を付けます。引数の型が[使える型](#rpc)に入っているかを確かめます。
6. **生成と削除を置き換える**: `PhotonNetwork.Instantiate` を `world.Instantiate` に、`PhotonNetwork.Destroy` を `entity.Destroy()` にします。シーンに置いていたオブジェクトは、ルームが作る共有オブジェクトにします。
7. **検査したい場合はオブジェクトカタログを設定する**: 生成や RPC をルームに検査させたい場合は、Prefab Catalog のアセットを右クリックし、**Copy Object Catalog JSON** で生成した JSON を、Realtime のネームスペースの `syncSetting.objectCatalog` に設定します。RPC の名前や Prefab を変えたら、生成し直します。
8. **イベント・プロパティ・マスタークライアントを置き換える**: 対応表のとおりに置き換えます。
9. **UDP を使う**: WebGL 以外では、`RealtimeUdpChannel` で定期的な状態の更新を UDP で送ります。

## コードの書き換え例 {#examples}

### RPC {#example-rpc}

```csharp
// 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);
```

### 生成と所有者の判定 {#example-instantiate}

```csharp
// 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** に登録すると、書き込める間だけ自動で有効になります。

### 値の同期 {#example-serialize}

```csharp
// 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");
}
```

### イベントとプロパティ {#example-room}

```csharp
// 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");
```

## 注意すること {#caveats}

- **確定は非同期**: `world.Instantiate` はすぐに使えますが、ルームが拒否すると `SpawnFailed` が呼ばれて削除されます。カスタムプロパティは、ルームを経由して全員に届いた時点で反映されます。
- **値は丸められる**: 位置は 0.01、回転は 360/4096 度の刻み幅で送ります。精度を変えるには、同期スキーマで指定します。
- **権限はサーバーが検証する**: 所有者以外の書き込みは拒否されます。オブジェクトカタログを設定すると、登録していない Prefab と RPC、許可していない所有権の取得も拒否されます。
- **RPC の引数の上限**: 合計 4096 バイトまでです。大きなデータは分けて送ります。
- **送信の順序**: UDP で送る定期的な更新と、WebSocket で送る RPC やイベントの間の順序は保証しません。RPC の直前に書き換えた値は、RPC より先に届きます。
- **ルームの終了条件**: 参加者全員から 1 分間通信がないとルームは終了します。[ルームの終了条件との関係](#idle)を参照してください。
- **WebGL**: UDP を使えないので、すべて WebSocket で送ります。
- **Photon の別製品**: Photon Voice や Photon Chat に当たる機能は、GS2-Realtime には含まれません。テキストチャットには GS2-Chat を使えます。



