This is the documentation and reference for the Photon Client Library for .NET and Unity.
- Photon Workflow
- Custom Operations
- Fragmentation and Channels
- Using TCP
- Network Simulation
- The Photon Server
- LoadBalancing API
Photon is a development framework to build real-time multiplayer games and applications for various platforms. It consists of a Server SDK and Client SDKs for several platforms. Photon provides a low-latency communication-layer based on UDP (or alternatively TCP). It enables reliable and unreliable transfer of data in "commands". On top of this, an operation- and event-framework is established to ease development of your own games.
Each game is different, so we developed several "server applications" which provide a basic logic and included them in the server SDK as example and code base.
• The "Lite" Application offers the basic operations that we felt useful for most room-based multiplayer games. We included its operations and events are part of the client API.
• The "LoadBalancing" Application allows you to run multiple servers for your game. A master server coordinates the games creation and joining. There is a special API in the SDK that makes use of this application.
• The "MMO" Application.
Photon Cloud is a hosted service for your Photon games. To use it, register at https://www.photonengine.com and use the LoadBalancing API and demos to start your development.
To get an impression of how to work on the client, we will use the server's LoadBalancing API logic. This application defines rooms which are created when users try to join them. Each user in a room becomes an actor with her own number.
A simplified client workflow looks like this:
- create a
- from now on: regularly call
Serviceto get events and send commands (e.g. ten times a second)
- call one of the
Connectmethods to connect the server
- wait until the library calls
- the returned status int should equal
OpJoinRoomto get into a game
- wait until the library calls
- send data in the game by calling
- receive events in
- The LoadBalancing API defines several useful events for common situations: Someone joins or leaves the room.
- In LoadBalancing API, events created by calling
OpRaiseEventwill be received by others in the same room in this method.
- when you are done: call
LoadBalancingClient.OpLeaveto quit/leave the game
- wait for "leave" return in
- disconnect with
- check "disconnect" return in
StatusCode.DisconnectCombined with the server's LoadBalancing application, this simple workflow would allow you to use rooms and send your game's events. The methods used could be broken down into three layers:
- Low Level:
Service, Connect, Disconnectand the
OnStatusChangedare directly referring to the connection to the server. This level works with UDP/TCP packets which transport commands (which in turn carry your operations). It keeps your connection alive and organizes your RPC calls and events into packages.
- Logic Level: Operations, results and events make up the logical level in Photon. Any operation defined on the server (think RPC call) and can have a result. Events are incoming from the server and update the client with some data.
- Application Level: Made up by a specific application and its features. In this case we use the operations and logic of the LoadBalancing API. In this specific case, we have rooms and actors and more.
The LoadBalancingClient is matching the server side implementation and wraps it up for you. You don't have to manage the low level communication in most cases. However, it makes sense to know that everything that goes from client to server (and the other way round) is put into "commands". Internally, commands are also used to establish and keep the connection between client and server alive (without carrying additional data).
All methods that are operations (RPC calls) are prefixed with "Op" to tell them apart from anything else.
Other server side applications (like MMO or your own) will define different operations.
These will have different parameters and return values.
These operations are not part of the client library but can be implemented by calling
The interface IPhotonPeerListener must be implemented for callbacks.
OnStatusChangedis for peer state-changes (connect, disconnect, errors, compare with
OnOperationResponseis the callback for operations (join, leave, etc.)
OnEventas callback for events coming in
DebugReturnas callback to debug output (less frequently used by release builds) The following properties in PhotonPeer are of special interest:
TimePingIntervalsets the time between ping-operations
RoundTripTimeof reliable operations to the server and back
RoundTripTimeVarianceshows the variability of the roundtrip time
ServerTimeInMilliSecondsis the continuously approximated server's time
Operation is our term for remote procedure calls (RPC) on Photon.
This in turn can be described as methods that are implemented on the server-side and called by clients.
As any method, they have parameters and return values.
The Photon development framework takes care of getting your RPC calls from clients to server and results back.
Server-side, operations are part of an application running on top of Photon.
The default application provided by Exit Games is called "LoadBalancing API".
LoadBalancingPeer class extends the
PhotonPeer by methods for each of the LoadBalancing API Operations.
LoadBalancingClient is a wrapper of the
Examples for LaodBalancing API Operations are "Join" and "RaiseEvent".
On the client side, they can be found in the
LoadBalancingClient class as methods:
They can be used right away with the default implementation of Photon and the LoadBalancing API.
Photon is extendable with features that are specific to your game. You could persist world states or double check information from the clients. Any operation that is not in Lite, LoadBalancing API or the MMO application logic is called Custom Operation. Creating those is primarily a server-side task, of course, but the clients have to use new functions / operations of the server.
So Operations are methods that can be called from the client side.
They can have any number of parameters and any name.
To preserve traffic, we assign byte-codes for every operation and each parameter.
The definition is done server side.
Each Operation has its own, unique number to identify it, known as the operation code (
An operation class defines the expected parameters and assigns a parameter code for each.
With this definition, the client side only has to fill in the values and let the server know the
OperationCode of the Operation.
Photon uses Dictionaries to aggregate parameters for operation requests, responses and events.
OpCustom to call any operation, providing the parameters in a Dictionary.
OperationCode and parameter-codes are currently of type byte (to minimize overhead).
They need to match the definition of the server side to successfully call your operation.
Recommended for further reading:
Unlike operations, events are "messages" that are rarely triggered by the client that receives them.
Events come from outside: the server or other clients.
They are created as side effect of operations (e.g. when you join a room) or raised as main purpose of the operation Raise Event.
Most events carry some form of data but in rare cases the type of event itself is the message.
Events are (once more) Dictionaries with arbitrary content.
In the "top-level" of an event, bytes are used as keys for values.
The values can be of any serializable type.
The LoadBalancing API, e.g., uses a
Hashtable for custom event content in its operation
Recommended for further reading:
Fragmentation and Channels
Bigger data chunks of data won't fit into a single package, so they are fragmented and reassembled automatically. Depending on the data size, a single operation or event can be made up of multiple packages.
Be aware that this might stall other commands.
SendOutgoingCommands more often than absolutely necessary.
You should check that
PhotonPeer.QueuedOutgoingCommands is becoming zero regularly to make sure everything gets out.
You can also check the debug output for "UDP package is full", which can happen from time to time but should not happen permanently.
Maximum Transfer Unit
The maximum size for any UDP package can be configured by setting
By default, this is 1200 bytes.
Some routers will fragment even this UDP package size.
If you don't need bigger sizes, go for 512 bytes per package, which is more overhead per command but potentially safer.
This setting is ignored by TCP connections, which negotiate their MTU internally.
The sequencing of the protocol makes sure that any receiving client will Dispatch your actions in the order you sent them. Unreliable data is considered replaceable and can be lost. Reliable events and operations will be repeated several times if needed but they will all be dispatched in order without gaps. Unreliable actions are also related to the last reliable action and not dispatched before that reliable data was dispatched first. This can be useful, if the events are related to each other.
Example: Your FPS sends out unreliable movement updates and reliable chat messages. A lost package with movement updates would be left out as the next movement update is coming fast. On the receiving end, this would maybe show as a small jump. If a package with a chat message is lost, this is repeated and would introduce lag, even to all movement updates after the message was created. In this case, the data is unrelated and should be put into different channels.
The .NET clients and server are now supporting "channels". This allows you to separate information into multiple channels, each being sequenced independently. This means, that Events of one channel will not be stalled because events of another channel are not available. By default an PhotonPeer has two channels and channel zero is the default to send operations. The operations join and leave are always sent in channel zero (for simplicity). There is a "background" channel 255 used internally for connect and disconnect messages. This is ignored for the channel count. Channels are prioritized: the lowest channel number is put into a UDP package first. Data in a higher channel might be sent later when a UDP package is already full.
Example: The chat messages can now be sent in channel one, while movement is sent in channel zero. They are not related and if a chat message is delayed, it will no longer affect movement in channel zero. Also, channel zero has higher priority and is more likely to be sent (in case packages get filled up).
A PhotonPeer could be instanced with TCP as underlying protocol if necessary. This is not best practice but some platforms don't support UDP sockets. This is why Silverlight (e.g.) uses TCP in all cases. The Photon Client API is the same for both protocols but there are some differences in what goes on under the hood. Everything sent over TCP is always reliable, even if you call your operations as unreliable! If you use only TCP clients simply send any operation unreliable. It saves some work (and traffic) in the underlying protocols. If you have TCP and UDP clients anything you send between the TCP clients will always be transferred reliable. But as you communicate with some clients that use UDP these will get your events reliable or unreliable.
Example: A Silverlight client might send unreliable movement updates in channel # 1. This will be sent via TCP, which makes it reliable. Photon however also has connections with UDP clients (like a 3D downloadable game client). It will use your reliable / unreliable settings to forward your movement updates accordingly.
During development, most tests will be done in a local network. Once released, the clients will communicate through the internet, which has a higher delay per message and in some cases even drops messages entirely. To prepare a game for real-life conditions, the Photon client libraries let you simulate some effects of internet-communication: lag, jitter and packet loss.
- Lag / Latency: a more or less constant delay of messages between client and server. Either direction can be affected in a different way but usually the values are close to another. Affects the roundtrip time.
- Jitter: Is randomizes the Lag in the simulation. This affects the variance of the roundtrip time. Udp packages can get out of order this way, which also is simulated. The new lag will be: Lag + [-JitterValue..+JitterValue]. This keeps the mean Lag at the setting and some packages are actually faster than the Lag value implies.
- Packet Loss: UPD packages can become lost. In the Photon protocol, commands that are flagged as reliable will be repeated while other commands (operations) might get lost this way.
The lag simulation is running in its own Thread which tries to meet delays defined in the settings. In most cases, they can be met but actual delays will have a variance of up to +/- 20ms.
Using Network Simulation
By default, Network Simulation is turned off.
It can be turned on by setting
PhotonPeer.IsSimulationEnabled and the settings are aggregated into a NetworkSimulationSet, also accessible by the peer class (e.g. the LoadBalancingClient).
The Photon Server
The Photon Server is the central hub for communication for all your clients. It is a service that can be run on any Windows machine, handling the UDP and TCP connections of clients and hosting a .NET runtime layer with your own business logic, called application. The Photon Server SDK includes several applications in source and pre-built. You can run them out of the box or develop your own server logic. Get the Photon Server SDK here
The LoadBalancing API is the example application for room-based games on Photon and (hopefully) a flexible basis for your own games. It offers rooms, joining and leaving them, sending events to the other players in a room and handles properties. The LoadBalancing API is tightly integrated with the client libraries and used as example throughout most documentation.
Properties on Photon
The LoadBalancing API implements a general purpose mechanism to set and fetch key/value pairs on the server side (in memory). They are associated to a room/game or a player within a room and can be fetched or updated by anyone in that game. Each entry in the properties Hashtable is considered a separate property and can be overwritten independently. The value of a property can be of any serializable datatype. The keys must be either of type string or byte. Bytes are preferred, as they mean the less overhead. To avoid confusion, don't mix string and byte as key-types. Mixed types of keys, require separate requests to fetch them. Property broadcasting and events Property changes in a game can be "broadcasted", which triggers events for the other players to update them. The player who changed the property does not get the update (again). Any change that uses the broadcast option will trigger a property update event. This event carries the changed properties (only), which changed the properties and where the properties belong to. Your clients need to "merge" the changes (if properties are cached at all). Properties can be set by these methods:
LoadBalancingClient.OpSetPropertiesOfActorsets actor properties.
LoadBalancingClient.OpSetPropertiesOfRoomsets room properties.
LoadBalancingClient.OpJoinOrCreateRoomsets initial room properties if the room did not exist before and sets joining actor properties.
LoadBalancingClient.OpCreateRoomsets initial room properties and sets joining actor properties.
LoadBalancingClient.OpJoinRoomsets joining actor properties.
Any change that uses the broadcast option will trigger a property update event
This event carries the properties as value of key
Additionally, there is information about who changed the properties in key
ParameterCode.TargetActorNr will only be available if the property-set belongs to a certain player.
If it's not present, the properties are room properties.