Skip to content

Instantly share code, notes, and snippets.

@ssshake
Last active September 29, 2023 06:15
Show Gist options
  • Star 97 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save ssshake/86b4da6c31258a7188f7fef3dbaf1d26 to your computer and use it in GitHub Desktop.
Save ssshake/86b4da6c31258a7188f7fef3dbaf1d26 to your computer and use it in GitHub Desktop.
Photon PUN Cheat Sheet
## Photon Methods
**public class Blank : Photon.PunBehaviour**
instead of mono behavior, use this to receive photon callbacks in your script.
**public override void OnLeftRoom()**
An example of overriding a punbehavior callback
**if (!photonView.isMine)**
Since all scripts attached to any game object, whether yours or theirs, runs locally. You need to check if the instance of the script is yours if you want to perform an actions. Example: when hit with laser beam, on trigger enter, check if you are the one who was hit, and if so take damage. All other players will as well do this processing and take their own damage.
```csharp
void OnTriggerEnter(Collider other)
{
// we dont' do anything if we are not the local player.
if (!photonView.isMine)
{
return;
}
}
```
**if (PhotonNetwork.isMasterClient)**
One client will be the master (automatically negotiated). The rest of the clients are slaves. If you wanted to detect some event and perform some action, it should be encapsulated in isMasterClient so that only the master performs the action, not every client connected.
Example: Load next level after match has been won. Every client will receive some event that the match has been won, but only the master will load the next level.
```csharp
if (PhotonNetwork.isMasterClient)
{
LoadArena();
}
```
**if (photonView.isMine == false && PhotonNetwork.connected == true)**
Restricts input to your local instance of a player game object. Put this as an early return in Update()
```csharp
if (photonView.isMine == false && PhotonNetwork.connected == true)
{
return;
}
```
**PhotonNetwork.Instantiate**
This is how you load an object in photon.
```csharp
PhotonNetwork.Instantiate(this.playerPrefab.name, new Vector3(0f, 5f, 0f), Quaternion.identity, 0);
```
## Photon GameObject Scripts
**PhotonView**
A [PhotonView](https://doc.photonengine.com/en-us/pun/current/getting-started/feature-overview#_viewpun) is what connects together the various instances on each computers, and define what components to observe and how to observe these components.
**Photon Transform View**
Used in combination with PhotonView to sync transform properties of an objects across clients (player avatar would want this attached)
*Needs to be dragged into PhotonView as an Observed Component*
https://doc.photonengine.com/en-us/pun/current/demos-and-tutorials/pun-basics-tutorial/player-networking
**PhotonAnimatorView**
Sync animations on an object, for example player avatar. You need to opted in every animation listed in "Synchronize Parameters" and set them to Discrete by default.
*Needs to be dragged into PhotonView as an Observed Component*
> `Discrete synchronization` means that a value gets sent 10 times a
> second (in `OnPhotonSerializeView`). The receiving clients pass the
> value on to their local Animator.
>
> `Continuous` synchronization means that the `PhotonAnimatorView`
> runs every frames. When `OnPhotonSerializeView` is called (10 times
> per second), the values recorded since the last call are sent
> together. The receiving client then applies the values in sequence to
> retain smooth transitions. While this mode is smoother, it also sends
> more data to achieve this effect.
## Remote Procedure Calls
Allows a player to call a method on all or some other players.
```csharp
public class MyCustomRPC : Monobevaiour
{
public void CallRemoteMethod (){
if (PhotonNetwork.offlineMode == true){ //use this you need to support offline mode.
MyRemoteMethod(PhotonTargets.Others, new object [] { 42, true });
return;
}
GetComponent<PhotonView>().RPC("MyRemoteMethod", PhotonTargets.Others, new object [] { 42, true })
//Target Types
//PhotonTargets.Others
//PhotonTargets.All //triggered instantly
//PhotonTargets.AllViaServer //local client gets even through server just like everyone else
//PhotonTargets.MasterClient
//PhotonNetwork.playerList[0]
//PhotonTargets.AllBuffered
//PhotonTargets.AllBufferedViaServer //used in item pickups where could be contested which client got it first
//An important use is also when a new player connects later, they will recieve this
//buffered event that the item has been picked up and should be removed from scene
}
[PunRPC]
void MyRemoteMethod(int someNumber, bool someBool) {
Debug.Log(someNumber);
Debug.Log(someBool);
}
}
```
## Scene Objects
Objects that need to be synchronized across clients but aren't owned by anyone. Technically they are owned by the master client.
The are created two ways:
1) Objects placed into the scene by editor and have a PhotonView
2) By script using PhotonNetwork.InstantiateSceneObject
```csharp
void PickupObject ( Health health ){
PhotonView.RPC("OnPickup", PhotonTargets.AllBufferedViaServer, new object [] { health.PhotonView.viewId }
}
[RPC]
protected void OnPickup( int viewId ){
PhotonView view = PhotonView.Find( viewId);
}
```
## PhotonNetwork.time
Used to syncronize the timing of things like projectiles across all clients.
```csharp
void Update (){
float timePassed = (float)( PhotonNetwork.time - CreationTime ); //CreationTime also was set using PhotonNetwork.Time
transform.position = StartPosition + transform.forward * Speed * timePassed;
if (timePassed > LifeTime){
Destroy (gameObject);
}
if (transform.position.y < 0f){
Destroy(gameObject);
CreateHitFx();
}
}
void OnCollisionEnter ( Collision collision) {
if (collsion.collider.tag == "ObjectWithStaticPositionRotationSize"){
OnProjectileHit();
}
else if ( collsion.collider.tag == "MovingObjectThatMayNotBeInPerfectSyncAcrossClients){
//Lets the client who owns the thing that was hit make the call
Ship ship = collsion.collider.GetComponent<Ship>();
if (ship.Team != ProjectileOwner.Team && ship.PhotonView.isMine){
ship.ShipCollision.OnProjectileHit(this);
OnProjectileHit(); //RPC to let everyone know what happened
ProjectileOwner.ShipShooting.SendPorjectileHit( ProjectileId);
}
}
}
```
## IPunObservable (streams and manual synchronization)
This lets you sync data in your hand rolled scripts. For example knowing when an enemy has fired and rendering their laser. ***You need to add your script in the Observed Components list in Photon View***
Add to class:
```csharp
public class PlayerManager : Photon.PunBehaviour, IPunObservable{}
```
Use in script:
```csharp
void IPunObservable.OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.IsWriting)
{
// We own this player: send the others our data
//This script is local, you write to stream
stream.SendNext(IsFiring);
stream.SendNext(17.8f); //the scructure of send and receive will match so you can send multiple things
//and receive them in that order
}
else
{
// Network player, receive data
//This script is receiving data from remote players script
this.IsFiring = (bool)stream.ReceiveNext();
this.SomethingElse = (float)stream.ReceiveNext();
LastNetworkDataReceivedTime = info.timestamp; //the stream is outdated due to lag so we need to know the time past since
//it was sent. This is used to modify time based things on local instances
//This is used for prediction models like position
//This should also be used in combination with PhotonNetwork.GetPing();
}
}
```
This is confusing but remember to think of this as an instance not a singleton. If there are 2 players in the game, one is you and one is the remote player.
Your instance of this script is always going to write. Example: you're firing or your health decreased, the instance of the script on YOUR avatar will write this data.
If a remote player loses health, the instance of this script on THEIR avatar will receive data that was written by them on their client.
The If/else conditions arent both hit in the same instance... one will always be hit in your instance and the other will always be hit in their instance.
## Prefabs
Always make sure Prefabs that are supposed to be instantiated over the network are within a [Resources](http://docs.unity3d.com/ScriptReference/Resources.html) folder, this is a Photon Requirement.
**Preserve instance of network game object**
PlayerManger.cs
```csharp
// #Important
// used in GameManager.cs: we keep track of the localPlayer instance to prevent instantiation when levels are synchronized
if (photonView.isMine)
{
PlayerManager.LocalPlayerInstance = this.gameObject;
}
// #Critical
// we flag as don't destroy on load so that instance survives level synchronization, thus giving a seamless experience when levels load.
DontDestroyOnLoad(this.gameObject);
```
GameManager.cs
```csharp
if (PlayerManager.LocalPlayerInstance==null)
{
Debug.Log("We are Instantiating LocalPlayer from "+Application.loadedLevelName);
// we're in a room. spawn a character for the local player. it gets synced by using PhotonNetwork.Instantiate
PhotonNetwork.Instantiate(this.playerPrefab.name, new Vector3(0f,5f,0f), Quaternion.identity, 0);
}
else
{
Debug.Log("Ignoring scene load for "+Application.loadedLevelName);
}
```
## Lobby Management
```csharp
PhotonNetwork.JoinRoom("Room Name");
PhotonNetwork.JoinRandomRoom();
PhotonNetwork.room.PlayerCount
PhotonNetwork.LoadLevel("RoomName");
PhotonNetwork.CreateRoom(RoomName, new RoomOptions() { MaxPlayers = MaxPlayersPerRoom }, null);
PhotonNetwork.ConnectUsingSettings(_gameVersion);
PhotonNetwork.JoinRandomRoom();
PhotonNetwork.connected;
// this makes sure we can use PhotonNetwork.LoadLevel() on the master client and all clients in the same room sync their level automatically
PhotonNetwork.automaticallySyncScene = true;
// we don't join the lobby. There is no need to join a lobby to get the list of rooms.
PhotonNetwork.autoJoinLobby = false;
PhotonNetwork.logLevel = Loglevel;
```
```csharp
using UnityEngine;
namespace Com.ResultsGrid.Simulator
{
public class Launcher : Photon.PunBehaviour
{
#region Public Variables
public PhotonLogLevel Loglevel = PhotonLogLevel.Informational;
[Tooltip("The maximum number of players per room. When a room is full, it can't be joined by new players, and so new room will be created")]
public byte MaxPlayersPerRoom = 4;
[Tooltip("The Ui Panel to let the user enter name, connect and play")]
public GameObject controlPanel;
[Tooltip("The UI Label to inform the user that the connection is in progress")]
public GameObject progressLabel;
#endregion
public string RoomName = "ResultsGrid";
#region Private Variables
/// <summary>
/// This client's version number. Users are separated from each other by gameversion (which allows you to make breaking changes).
/// </summary>
string _gameVersion = "1";
/// <summary>
/// Keep track of the current process. Since connection is asynchronous and is based on several callbacks from Photon,
/// we need to keep track of this to properly adjust the behavior when we receive call back by Photon.
/// Typically this is used for the OnConnectedToMaster() callback.
/// </summary>
bool isConnecting;
#endregion
#region MonoBehaviour CallBacks
/// <summary>
/// MonoBehaviour method called on GameObject by Unity during early initialization phase.
/// </summary>
void Awake()
{
// #NotImportant
// Force LogLevel
PhotonNetwork.logLevel = Loglevel;
// #Critical
// we don't join the lobby. There is no need to join a lobby to get the list of rooms.
PhotonNetwork.autoJoinLobby = false;
// #Critical
// this makes sure we can use PhotonNetwork.LoadLevel() on the master client and all clients in the same room sync their level automatically
PhotonNetwork.automaticallySyncScene = true;
}
/// <summary>
/// MonoBehaviour method called on GameObject by Unity during initialization phase.
/// </summary>
void Start()
{
//Connect(); //Enable for auto connect
progressLabel.SetActive(false);
controlPanel.SetActive(true);
}
#endregion
#region Public Methods
/// <summary>
/// Start the connection process.
/// - If already connected, we attempt joining a random room
/// - if not yet connected, Connect this application instance to Photon Cloud Network
/// </summary>
public void Connect()
{
progressLabel.SetActive(true);
controlPanel.SetActive(false);
// keep track of the will to join a room, because when we come back from the game we will get a callback that we are connected, so we need to know what to do then
isConnecting = true;
// we check if we are connected or not, we join if we are , else we initiate the connection to the server.
if (PhotonNetwork.connected)
{
// #Critical we need at this point to attempt joining a Random Room. If it fails, we'll get notified in OnPhotonRandomJoinFailed() and we'll create one.
PhotonNetwork.JoinRandomRoom();
}
else
{
// #Critical, we must first and foremost connect to Photon Online Server.
PhotonNetwork.ConnectUsingSettings(_gameVersion);
}
}
#endregion
#region Photon.PunBehaviour CallBacks
public override void OnConnectedToMaster()
{
// we don't want to do anything if we are not attempting to join a room.
// this case where isConnecting is false is typically when you lost or quit the game, when this level is loaded, OnConnectedToMaster will be called, in that case
// we don't want to do anything.
if (isConnecting)
{
// #Critical: The first we try to do is to join a potential existing room. If there is, good, else, we'll be called back with OnPhotonRandomJoinFailed()
//PhotonNetwork.JoinRandomRoom();
PhotonNetwork.JoinRoom(RoomName);
}
}
public override void OnDisconnectedFromPhoton()
{
Debug.LogWarning("DemoAnimator/Launcher: OnDisconnectedFromPhoton() was called by PUN");
progressLabel.SetActive(false);
controlPanel.SetActive(true);
}
public override void OnPhotonRandomJoinFailed(object[] codeAndMsg)
{
Debug.Log("DemoAnimator/Launcher:OnPhotonRandomJoinFailed() was called by PUN. No random room available, so we create one.\nCalling: PhotonNetwork.CreateRoom(null, new RoomOptions() {maxPlayers = 4}, null);");
// #Critical: we failed to join a random room, maybe none exists or they are all full. No worries, we create a new room.
PhotonNetwork.CreateRoom(RoomName, new RoomOptions() { MaxPlayers = MaxPlayersPerRoom }, null);
}
public override void OnJoinedRoom()
{
Debug.Log("DemoAnimator/Launcher: OnJoinedRoom() called by PUN. Now this client is in a room.");
// #Critical: We only load if we are the first player, else we rely on PhotonNetwork.automaticallySyncScene to sync our instance scene.
if (PhotonNetwork.room.PlayerCount == 1)
{
Debug.Log("We load the 'Room for 1' ");
// #Critical
// Load the Room Level.
//PhotonNetwork.LoadLevel("Room for 1");
PhotonNetwork.LoadLevel("NioFactoryNetworked");
}
}
#endregion
}
}
```
@nukadelic
Copy link

Thanks for creating this, for the RPC's my version is somehow different and instead of PhotonTargets i used RpcTarget ( also it's possible to target a single player by passing Photon.Realtime.Player as the 2nd argument )

@oludev97
Copy link

thanks so much for this, was stuck with a bug for a few days until i came across this

@dondosh
Copy link

dondosh commented Nov 12, 2020

Thanks! Your second example just resolve my problem. Really useful!

@ssshake
Copy link
Author

ssshake commented Nov 23, 2020

Glad people are getting use out of this!

@Advxnced
Copy link

This didn't help fix my script at all. :(

@ArgosVU
Copy link

ArgosVU commented Mar 25, 2021

Its helping me thanks. :)

@cliffordeph
Copy link

I am a bit confused about the isWriting and isReading, is there any chances they overwrite each other?

@GuruLahima
Copy link

Thanks for the compilation of useful scripts. However I am confused about one thing. In the Lobby Management section it says you dont have to join lobby to get the room list, but from what I learned so far is that the only way to get room list is via the OnRoomListUpdate callback which is only called after we have joined a lobby. Am I missing something?

@GuruLahima
Copy link

I am a bit confused about the isWriting and isReading, is there any chances they overwrite each other?

no. the owner of the PhotonView will always be writing, and everyone else will always be reading the data.

@illesnagy
Copy link

Thanks, it's very very useful! One thing I've noticed: isWriting should be Capitalized (IsWriting), otherwise I got error message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment