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

AspectPhoton PUN2GS2-Realtime
Finding a roomPhoton’s lobby and matchmakingGather members with GS2-Matchmaking and create a GS2-Realtime room
Connecting to a roomPhotonNetwork.ConnectUsingSettings / JoinRoomConnect with RealtimeSession to the issued IP address, port, and encryption key
Who decides valuesEach 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 dataSend arbitrary data with OnPhotonSerializeViewUp to 32 integer fields per object. Position and rotation are rounded to a step size before sending
TransportUDP (reliable / unreliable)WebSocket (for things that must arrive) and UDP (for periodic state updates)
BillingConcurrent connectionsRoom 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, 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

Components

Photon PUN2GS2-Realtime
PhotonViewNetworkEntity
PhotonTransformViewNetworkTransform
IPunObservable / OnPhotonSerializeViewINetworkObservable (OnNetworkWrite / OnNetworkRead)
PhotonNetwork.PrefabPool / Prefabs in ResourcesPrefab Catalog and NetworkWorld
PhotonView placed in the scene, InstantiateRoomObjectShared objects created by the room (stateMachine rules, or objects that lend control)

Objects

Photon PUN2GS2-Realtime
PhotonNetwork.Instantiate(name, pos, rot)world.Instantiate(prefabId, pos, rot)
PhotonNetwork.Destroy(go)entity.Destroy()
photonView.IsMineentity.IsMine
photonView.Ownerentity.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.SetInterestGroupsentity.SetGroup / world.SetInterestGroups, SubscribeGroup

RPC and Events

Photon PUN2GS2-Realtime
[PunRPC][NetworkRpc]
photonView.RPC(name, RpcTarget.All, args)entity.Rpc(name, RpcTarget.All, args)
RpcTarget.Others / AllBuffered / OthersBufferedRpcTarget with the same name
RpcTarget.AllViaServerRpcTarget.All (always goes through the server)
RpcTarget.MasterCliententity.RpcTo(name, world.MasterClientId, args)
photonView.RPC(name, player, args)entity.RpcTo(name, connectionId, args)
PhotonMessageInfoNetworkRpcInfo (Sender is the caller’s connection ID)
PhotonNetwork.RaiseEventworld.RaiseEvent / RaiseEventTo (handlers use [NetworkEvent])
IOnEventCallback.OnEvent[NetworkEvent] methods registered with world.AddEventTarget, or world.EventReceived

Rooms and Players

Photon PUN2GS2-Realtime
PhotonNetwork.LocalPlayer.ActorNumbersession.MyConnectionId
PhotonNetwork.PlayerList, OnPlayerEnteredRoom / OnPlayerLeftRoomRealtimeSession’s OnJoinPlayer / OnLeavePlayer
PhotonNetwork.MasterClient / IsMasterClientworld.MasterClientId / IsMasterClient
PhotonNetwork.SetMasterClient / OnMasterClientSwitchedworld.SetMasterClient / MasterClientChanged
Room.SetCustomProperties / OnRoomPropertiesUpdateworld.SetRoomProperty / RoomPropertyChanged
Player.SetCustomProperties / OnPlayerPropertiesUpdateworld.SetPlayerProperty / PlayerPropertyChanged
PhotonNetwork.Timeworld.ServerTime
PhotonNetwork.SendRate / SerializationRateworld.SendRate
Unreliable sendingRealtimeUdpChannel (sends periodic state updates over UDP)

Migration 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.
  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.
  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

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);

Spawning and Checking the Owner

// 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

// 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

// 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

  • 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.
  • 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.