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


# Migration Guide from Photon

Steps and mapping tables for moving multiplayer built with Photon (PUN2) to GS2-Realtime sync / stateMachine



This page provides the steps and mapping tables for moving multiplayer built with Photon PUN2 to GS2-Realtime object sync (`sync` / `stateMachine`). For details on each API, see [Unity Client]().

## Differences in Approach {#differences}

| Aspect | Photon PUN2 | GS2-Realtime |
| --- | --- | --- |
| Finding a room | Photon's lobby and matchmaking | Gather members with GS2-Matchmaking and create a GS2-Realtime room |
| Connecting to a room | `PhotonNetwork.ConnectUsingSettings` / `JoinRoom` | Connect with `RealtimeSession` to the issued IP address, port, and encryption key |
| Who decides values | Each client (the master client coordinates) | The room server validates write permissions and RPC permissions. With `stateMachine`, the server's rules also decide game progression |
| Synced data | Send arbitrary data with `OnPhotonSerializeView` | Up to 32 integer fields per object. Position and rotation are rounded to a step size before sending |
| Transport | UDP (reliable / unreliable) | WebSocket (for things that must arrive) and UDP (for periodic state updates) |
| Billing | Concurrent connections | Room server uptime and communication volume |

The biggest difference is that the room server can validate permissions. You cannot rewrite the values of other players' objects, and if you set an [object catalog](#catalog), the room lets only the registered Prefabs and RPCs through. If you move game progression that you left to the master client (enemy HP, score, win/loss) into `stateMachine` rules, it is no longer affected by modified clients.

## Mapping Tables {#mapping}

### Components {#mapping-components}

| Photon PUN2 | GS2-Realtime |
| --- | --- |
| `PhotonView` | `NetworkEntity` |
| `PhotonTransformView` | `NetworkTransform` |
| `IPunObservable` / `OnPhotonSerializeView` | `INetworkObservable` (`OnNetworkWrite` / `OnNetworkRead`) |
| `PhotonNetwork.PrefabPool` / Prefabs in `Resources` | Prefab Catalog and `NetworkWorld` |
| `PhotonView` placed in the scene, `InstantiateRoomObject` | Shared objects created by the room (`stateMachine` rules, or objects that lend control) |

### Objects {#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` (connection ID) |
| `photonView.TransferOwnership(player)` | `entity.TransferOwnership(connectionId)` |
| `photonView.RequestOwnership()` (`OwnershipOption.Takeover`) | `entity.RequestOwnership()` (`"ownership": "takeover"` when you set an object catalog) |
| `PhotonNetwork.CleanupCacheOnLeave = false` | `"keepOnLeave": true` in the catalog (handed over to the master client) |
| `photonView.Group` / `PhotonNetwork.SetInterestGroups` | `entity.SetGroup` / `world.SetInterestGroups`, `SubscribeGroup` |

### RPC and Events {#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` with the same name |
| `RpcTarget.AllViaServer` | `RpcTarget.All` (always goes through the server) |
| `RpcTarget.MasterClient` | `entity.RpcTo(name, world.MasterClientId, args)` |
| `photonView.RPC(name, player, args)` | `entity.RpcTo(name, connectionId, args)` |
| `PhotonMessageInfo` | `NetworkRpcInfo` (`Sender` is the caller's connection ID) |
| `PhotonNetwork.RaiseEvent` | `world.RaiseEvent` / `RaiseEventTo` (handlers use `[NetworkEvent]`) |
| `IOnEventCallback.OnEvent` | `[NetworkEvent]` methods registered with `world.AddEventTarget`, or `world.EventReceived` |

### Rooms and Players {#mapping-room}

| Photon PUN2 | GS2-Realtime |
| --- | --- |
| `PhotonNetwork.LocalPlayer.ActorNumber` | `session.MyConnectionId` |
| `PhotonNetwork.PlayerList`, `OnPlayerEnteredRoom` / `OnPlayerLeftRoom` | `RealtimeSession`'s `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 sending | `RealtimeUdpChannel` (sends periodic state updates over UDP) |

## Migration Steps {#steps}

1. **Replace room preparation**: Replace Photon's lobby and matchmaking with GS2-Matchmaking and GS2-Realtime room creation. Set `serverType` of the Realtime namespace to `sync` (or `stateMachine` if the server also decides game progression). For the flow, see [Introduction to GS2-Realtime]() and the [Tutorial]().
2. **Replace the connection**: Connect with `RealtimeSession` and wire up `NetworkWorld.Attach` and `world.Receive`. Write it as described in [Connect to a Room](#connection).
3. **Replace Prefabs**: Change `PhotonView` to `NetworkEntity` and `PhotonTransformView` to `NetworkTransform`. Create a Prefab Catalog, assign Prefab IDs, and register them.
4. **Replace synced values**: Turn values you sent in `OnPhotonSerializeView` into sync schema fields and read and write them with `INetworkObservable`. Values are up to 32 integers (floats are rounded to a step size), so send strings and arrays with RPCs or custom properties.
5. **Replace RPCs**: Change `[PunRPC]` to `[NetworkRpc]` and `photonView.RPC` to `entity.Rpc`. Add `AnyCaller = true` to RPCs called on other players' objects. Check that the argument types are among the [supported types](#rpc).
6. **Replace spawning and destroying**: Change `PhotonNetwork.Instantiate` to `world.Instantiate` and `PhotonNetwork.Destroy` to `entity.Destroy()`. Turn objects that were placed in the scene into shared objects created by the room.
7. **Set an object catalog if you want checks**: To have the room check spawns and RPCs, right-click the Prefab Catalog asset and set the JSON generated with **Copy Object Catalog JSON** in `syncSetting.objectCatalog` of the Realtime namespace. Regenerate it whenever you change RPC names or Prefabs.
8. **Replace events, properties, and the master client**: Replace them as shown in the mapping tables.
9. **Use UDP**: On platforms other than WebGL, send periodic state updates over UDP with `RealtimeUdpChannel`.

## Code Rewrite Examples {#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);
```

### Spawning and Checking the Owner {#example-instantiate}

```csharp
// Photon PUN2
var player = PhotonNetwork.Instantiate("Player", spawnPoint, Quaternion.identity);
if (player.GetComponent<PhotonView>().IsMine) { /* Enable input */ }

// GS2-Realtime (register Prefab ID 1 in the Prefab Catalog beforehand)
var player = world.Instantiate(1, spawnPoint, Quaternion.identity);
if (player.IsMine) { /* Enable input */ }
```

If you register scripts that accept input in the `NetworkEntity`'s **Controlled Behaviours**, they are automatically enabled only while you can write.

### Syncing Values {#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 (declare a float Health field in the sync schema beforehand)
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");
}
```

### Events and Properties {#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);   // Register [NetworkEvent] void Goal(int team) beforehand
world.SetRoomProperty("stage", "forest");
```

## Caveats {#caveats}

- **Confirmation is asynchronous**: You can use `world.Instantiate` immediately, but if the room rejects it, `SpawnFailed` is called and the object is destroyed. Custom properties take effect when they have gone through the room and reached everyone.
- **Values are rounded**: Position is sent with a step size of 0.01 and rotation with 360/4096 degrees. To change the precision, specify it in the sync schema.
- **The server validates permissions**: Writes by non-owners are rejected. With an object catalog, unregistered Prefabs and RPCs and ownership takeovers that are not allowed are rejected as well.
- **RPC argument limit**: Up to 4096 bytes in total. Split large data and send it in parts.
- **Send order**: Ordering between periodic updates sent over UDP and RPCs or events sent over WebSocket is not guaranteed. Values modified right before an RPC arrive before the RPC.
- **Room termination conditions**: A room terminates when no participant has communicated for 1 minute. See [Relation to Room Termination Conditions](#idle).
- **WebGL**: UDP is not available, so everything is sent over WebSocket.
- **Other Photon products**: GS2-Realtime does not include features equivalent to Photon Voice or Photon Chat. You can use GS2-Chat for text chat.



