Photonnetwork



Hello again and welcome to part 6 of my PUN networking tutorial. In this part, we will add score and health display, name labels and destructible tanks.

  1. Photonnetwork
  2. Photon Networking
  3. Photonnetwork.loadlevel
  4. Photonnetwork Playerlist

Overview This page primarily introduces Photon Messages, which is a bunch of data structures used in communication among our off-chain payment channels. Users or Clients can send and receive these messages to claim what they plan to do and present their intention to their transaction partners. Requests the rooms and online status for a list of friends and saves the result in PhotonNetwork.Friends. Works only on Master Server to find the rooms played by a selected list of users. The result will be stored in PhotonNetwork.Friends when available. That list is initialized on first use of OpFindFriends (before that, it.

Photon is a real-time multiplayer game development framework which has server and cloud services. Even though the networking implementation of the Kickstarter demo for LFG: The Fork of Truth was build with Unity networking, it was build with PUN in the back of our minds. In PUN, nearly all of the main functions are contained in the PhotonNetwork class (meant to be analogous to the Network class in Unity). There is no MasterServer equivalent in PUN: all the required functions are moved to the PhotonNetwork class. Rather than initializing servers, in PUN you create 'rooms' via PhotonNetwork.CreateRoom.

I’ve made a number of changes to the scripts from the previous part, so before you continue you should download the complete project files for this part which can be found here.

You can download the project .exe here.


Part 6a – Nickname and name label

It would be nice if we could enter a nickname that will be shown above the tank so that we can identify our intended target, so I’ve added a text input field to the start menu where you can type your chosen nickname and a public static property in the MainMenu script which affords us easy access to the name entered.

In the Awake() function, as a convenience, we prefill the nickname input field with the previously saved nickname if we’ve logged in before (the nickname is saved to playerprefs in the JoinGame() function of the GameManager).

To enable other players to see your nickname it will need to be synchronised with all players over the network. Handily PUN comes with a built-in way to do this in the form of The PhotonNetwork.playerName property. We just need to pass the value from the NickName input field to this before we join a room and this will set the NickName property of your PhotonPlayer once created to the same value for retrieval during the game when required. This is automatically synchronized between all clients.

A convenient place to set the PhotonNetwork.playerName property is in the JoinGame() function of the GameManager script as so:-

We want to display the name of other players in the game above their tanks, so to do this I’ve added a canvas and UIText object to the player prefab to act as a name label.
A handy time to set the name label text would be in OnPhotonInstantiate, but as the name label is a child of the player prefab it won’t receive that callback. To solve this, I use BroadcastMessage in the OnPhotonInstatiate method of the Player script to call a method on all ll scripts on the player, including children. In this case, I named the method OnInstantiate. This can be quite a handy way to initialise child GameObjects as soon as the player object is created. BroadcastMessage should be used with a certain amount of care, as it isn’t a particularly fast function, and should be avoided in things like update loops. As it’s only being called once here when the player is first created it won’t affect performance.

I also took the opportunity to rename the Player GameObject to reflect the chosen nickname, as this makes it easier identifying specific clients player objects in the hierarchy.

So the Player script OnPhotonInstantiate method now looks like this:-

I also added a simple script to the name label GameObject:-

As you can see, this script implements a single method, OnInstantiate, wherein it checks to see if it is running on the local player (PhotonView.isMine) and if it is, it disables the name label GameObject because we don’t want to display the name above our own tank (we will display our own nickname at the top of the screen, as described a bit later on), otherwise, it sets the name label text to the PhotonNetwork.playerName value.

This method is invoked by the Player script when the player object is instantiated as described above.

I’ve also added a script to the name label that keeps it orientated towards the screen regardless of the rotation of the tank:-

That’s all that’s required to display the player’s nickname above other player’s tanks.

To display the nickname for the localplayer I have created a gameUI game object as a child of the HUD gameobject, which also displays the health and remaining hit points. This is the script that is attached to the gameUI gameobject:-

Photonnetwork

the gameUI script uses the SceneLoaded event to enable or disable itself depending on whether we are in a room or not. It also uses the OnJoinedRoom callback to set the NickName text to the value of the PhotonNetwork.playerName, which as described above is the nick name chosen on the main menu.

It has a couple of other public static methods which handle setting the HP text and score text.

Part 6b – Handling player damage
In the previous part of the tutorial, we didn’t synchronize the player health value over the network, as we only showed its value to the local player. However, I want to show a health bar above all players tanks and for this to be possible the player’s health value needs to be synchronised on all clients.

We’ll achieve this by making the HP value a PhotonPlayer.CustomProperty. Custom Properties are a key-value set (Hashtable) which is available to all players in a room. They can relate to the room or individual players and are useful when only the current value of something is of interest. (More info regarding CustomProperties is available from the Photon Unity Networking class reference here.)

Custom properties values are automatically updated on all clients when their value changes and an event is also raised; You can listen for this event and use it to update visual elements to reflect the new value; in this case, we’ll update the health bar on all network instances of the player’s tank and the HUD display for the local player.

To facilitate this, we’ll change the PlayerHealth.hitPoints field into a property and implement the Getter and Setter to manipulate the HP Custom Property as so:-

The getter of the property checks to see if the key exists in the hashtable of custom properties associated with the client, and if it does it returns the associated value. If it doesn’t exist, that means this property value hasn’t been set yet, so it just returns the default value.
The setter of the property updates the key/pair value in the hashtable. For the local player, this happens instantly, for other clients the new value will be sent via the Photon server so there will be a small delay.

As I mentioned earlier whenever a custom property value changes it raises an event which we can handle by overriding the OnPhotonPlayerPropertiesChanged() method which in this case we do like so:-

The OnPhotonPlayerPropertiesChanged() method is passed an array of data which contains the PhotonPlayer of the client that updated its property (in element 0 of the array) and a hash table of the updated properties and their values (in element 1 of the array), so we can use this information to take the appropriate action.

Networking

The first thing we do is retrieve the PhotonPlayer.id of the client that updated its property and compare that with the id of the PhotonPlayer of the photonView that owns this script, and only continue if they match, this is to make sure we only continue on scripts that belong to the player that has had an updated property. Then we check that this is a message relating to a change in the hitPoints by checking if the hash table contains the key “HP”. Assuming the two previous checks are true we call a method (DisplayHealth) to handle the display of the updated hitPoints, which is implemented as follows:-

The first thing displayHealth does is update the size of the health bar above the tank by calling the healthBar.SetHealthBarValue() method ; this will happen on all clients.
Secondly we want to display the HP figure in the HUD for the localplayer only, so we wrap the call to GameUI.SetHealth() in a photonView.isMine check.

I’ve also updated the playerHealth script to check if our hitPoints have reduced to zero, so we can take the appropriate action, which in this case is to blow the tank up and add a point to the attacking player’s score. To do this we send an RPC to the missile owner that will add 1 point to their score and we start a coroutine that will handle the visual display of the tank exploding and respawning.

This is the updated PlayerHealth.DoDamage method :-

and this is the tank explosion coroutine :-

The explosion coroutine invokes the following RPC on all clients, which means that everyone sees the tank explode. It also temporarily disables movement, shooting and collisions…

it then waits 7 seconds and invokes the RPC_Respawn method, passing the respawn position and rotation as arguments. The position vector is converted to an array of 2 Shorts to reduce bandwidth.

The respawn method re-enables movement and shooting for the local player, and then for all clients it re-enables collisions and the tank model, and places it in the respawn position and orientation. It also resets the hitPoints to maximum.

The last line is a bit of a workaround to handle the fact that positioning of remote client tanks doesn’t happen instantly, so to avoid the chance of seeing them reappear in the previous position for a few frames before being moved to the new spawnpoint we wait for a short while before reenabling the tank model for remote clients.

Part 6c – Displaying the score
The final thing we need to do is make a small change to the PlayerScore script to make it display the score on the HUD of the local player which we do by calling the GameUI.SetScore() method as so:-

That’s everything for this part. As always comments and suggestions are very welcome, or if any parts require further clarification post a comment.

In the next part we’ll be adding ammo pickups, hope to see you then.

Photon Unity Networking (PUN) is a Unity package for multiplayer games. It provides authentication options, matchmaking and fast, reliable in-game communication through Exit Games Photon backend.

In this tutorial we will use PUN for simple two player network game. Photon Unity Networking is available on Unity Assets Store. Download and import the package in Unity

After installation, Setup Wizard asks for Photon account email or already register application ID. Cloud with the 'Free Plan' is free and without obligation. There is only one step needed to setup Photon plugin, so just enter email address and the Wizard will complete project setup

Let’s setup our scene. There are two boxes with attached Rigidbody and BoxCollider2D components for physics and collision calculations. This will be our simple scene to test multiplayer experience.

Next step we will create NetworkManager script to handle connection between different devices

As we already saved our AppId in the PhotonServerSettings file while Photon Setup, we can now connect by calling PhotonNetwork.ConnectUsingSettings. The gameVersion parameter should be any short string to identify this version.

After connection established we can connect to room JoinOrCreateRoom or create new one if no free rooms are available. There are room options we can specify, in our case we will make public room with players count limited by 2

Photonnetwork

Photon networking

At this point we need some place to manage game state. Let’s create empty object with new component attached - GameManager. It will be singleton pattern, there is simplest implementation below:

We need IsPaused property to control game execution. Add code below to GameManager class

Game will be paused from start till two players connected. At some point NetworkManager will set IsPaused to false and game boxes will start to fall

Now we need a script to control boxes falling inside a scene. Create NetworkBox component and attach to existing boxes.

PhotonnetworkPhotonnetwork

First of all, to make this class network-enabled change base class from MonoBehaviour to Photon.MonoBehaviour. Also we will need sync data between all networked instances of PhotonView, so we need to implement IPunObservable interface. There is empty NetworkBox class that we created

Next step is to add and setup PhotonView component of our boxes. NetworkBox component should be added to Observed Components list of PhotonView. It handles all network communications for our objects.

Currently, our boxes are falling on each device independently, but we need a way to sync their positions. Usually only master client calculates movements and sends new positions to other devices, which interpolate them and move smoothly.

OnPhotonSerializeView responds to data synchronization and called several times per second for both sender and receiver. To differentiate what is going now, there is stream.isWriting boolean property exists.

There is a comparison of two instances running side by side. Master game is on the right side. You can see that client game (left side) has small lag and boxes are falling not so smooth. We can interpolate position changes, so boxes will fall more smoothly, but master-client lag will still exists due to network latency.

When new position is received in OnPhotonSerializeView, we save it and next frames we will use it to smoothly interpolate between old and new positions

Photon Networking

The main difference is transform.position and transform.rotation assignment. It’s applied for client devices only and calculated with Lerp (linear interpolation) methods.

There is estimatedSpeed const, which controls speed of movement during interpolation. If objects are moving with constant and known speed, we can interpolate more precisely. But in current example, we just empirically found this value launching game few times.

Photonnetwork.loadlevel

Side by side demonstration of final result. As you can see, client screen (left side) is running smoothly now.

Photon has built-in transform synchronization component. It’s called Photon Transform View and can be customized for particular usage scenario.

Replace out custom position synchronization with it and drag Photon Transform View to PhotonView Observed Components list and you are done with automatic position syncrhonization.

Conclusion

Photonnetwork Playerlist

We built a simple prototype of multiplayer game in Unity using Photon Unity Networking. Our game founds random opponent, sync positions and rotations of objects and smooth movement with interpolation methods. You can build much more complicated game on top of it, adding additional game logic and network interactions.