Photon Unity Networking Game Tutorial Part 6 – Health, Score and Nickname

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.


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.

Tutorial uses Unity version V2017.2.0f3



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

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.

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.

Project source files

Project executable

Tutorial uses Unity version V2017.2.0f3

Photon Unity Networking Game Tutorial Part 4 – OnConnectedToMaster and Spawn Points

Welcome to part 4 of my Photon Unity Networked Game tutorial. In this part we will create a system for easily adding spawn points to any level, and also makes some improvements to the main menu.

Part 4a – Menu Changes
If you’ve been following the tutorial up until now, you may have noticed a minor bug when clicking the join game button. If you click it very quickly after starting the game you can get a run time error ‘JoinOrCreateRoom failed. Client is not on Master Server or not yet ready to call operations. Wait for callback: OnJoinedLobby or OnConnectedToMaster.’

This is because it takes a few seconds for the Photon networking system to connect and be ready, and until this is the case you can’t start or join a game. So to avoid the possibility of this happening we’ll disable the join game button until Photon tells us it is ready. While we are at it we’ll also make some changes to the way we have organised the menu in the game scene, in preparation for features that we’ll be adding later in the tutorial.

First of all rename the MainMenu game object to HUD and then change the Canvas Scaler UI Scale Mode to Scale With Screen Size. Next, add an empty game object as a child of HUD and call it MainMenu, then add an empty game object as a child of MainMenu and name it UI. Then make the JoinGameButton a child of UI by dragging it onto the UI game object in the hierarchy.

This is how your Hierarchy should look once all that is done:-

Now we need to write a small script for the MainMenu game object to handle the state of the join game button and the visibility of the main menu, but before we do that we need to add an extension class that we’ll use to find game objects by name, even if they are disabled. So create a new folder in Assets and call it Extensions, and in the extensions folder create a new c# script and call it TransformExtensions.
Then open the TransformExtensions script in your editor and replace the default code with this:-

This will add a new method to instances of transform (transform.FindAnyChild()) that will make it easy to get references to our HUD items without having to drag them into exposed properties in the inspector. Before you move on save the TransformExtensions script.

Now we can get to work on the HUD and MainMenu scripts, so create a new folder in Assets and call it HUD, and in the newly created  HUD folder create a c# script and call it HUD.
We will implement HUD as a singleton and use the DontDestroyOnLoad method to make it persist between scene changes in the same way we did with the GameManager script.

So open up the HUD script and replace the default code with the following and then save it :-

Now add the HUD script to the HUD game object by dragging the script onto the HUD game object in the hierarchy window.

For the time being that’s all we’ll be doing with the HUD, its main purpose is to act as a holder for all the game UI elements, however I may add some extra functionality to it further into the tutorial series.

Next, create a new c# script in the Assets\HUD folder and call it MainMenu, then add it to the MainMenu game object in the hierarchy by dragging the script onto it. Once again this is going to be a singleton, so open the MainMenu script for editing and replace the default code with this :-

You may have noticed that as well as making this a singleton class, it has also been made to inherit from Photon.Punbehaviour, this is so that we can override one of the Photon methods later in the script.

So next we need to start adding some code to allow the MainMenu script to enable the join game button when it’s okay to do so.

So firstly add these two lines to the top of the MainMenu class:-

And then add the following lines to the bottom of the Awake() function:-

What this does is find the UI game object and JoinGameButton in the menu hierarchy and stores the references in the two variables we declared, for easy access later. It then enables the UI game object (in case it has been disabled in the editor), which in turn means the join game button is visible, and then it makes the join game button non-interactable, so we can’t join a game until Photon is ready for us.

Having the menu (and later on, other UI elements) hierarchy organised in this way mean we can easily hide UI elements in the inspector (which can help to keep the scene view less cluttered with UI stuff), without disabling their associated script, which means that we don’t have to worry about their initial state when the game runs, and they will activate/deactivate themselves as required.

Save the MainMenu script and run the game, you should see that the join game button isn’t clickable, and you can’t proceed past the main menu.

All we need to do to fix this is add the following method :-

This method is called on all Photon.Punbehaviours once the game is connected to the Photon master server and is ready to host or join a game. By overriding it we can take whatever action we want at that point, and in our case we just make the join game button interactable.

We just need the MainMenu script to do one more thing for now, and that is disable or enable itself depending on whether we are in a game or not, we can do this by using the value of PhotonNetwork.inRoom in the Unity OnLevelwasLoaded() method. So add the following method below the OnConnectedToMaster() method:-

When a level is loaded this will hide the Main Menu if PhotonNetwork.inRoom is true, and show the Main Menu if it’s false;

This is the complete MainMenu script as it currently stands :-

If you save the script and run the game, you should notice the join game button starts disabled, but becomes enabled after a few seconds, at which point you can use it to join a game.

Before moving onto the next part make sure to save the scene now if you haven’t already done so.

Part 4b – Spawn Points
As it currently stands all players spawn at the same fixed part of the map, which obviously is far from ideal, so now we’ll put in place a flexible method of adding spawn points to the game level.

The first thing we need to do is create a SpawnPoint class, so create a new folder in Assets\Player and call it SpawnPoints, and in that folder create a new c# script and name it SpawnPoint. Open the SpawnPoint script for editing and replace the default code with the following, and save the script :-

As you can see it’s very simple, and the only thing it actually does is disable the game object it’s attached to as soon as it runs. We don’t actually need it to do anything else, except provide us with position and rotation information, which we can get from its transform component. To make any object in the scene a spawn point you can just add this script to it.

Now we need to add some spawn points to our game level, so open the Game Scene (save the current scene if prompted), select the CompleteLevelArt game object and add an empty child game object (GameObject->Create Empty Child). Rename the empty game object SpawnPoint and then add the SpawnPoint script to it by dragging it onto the SpawnPoint game object in the hierarchy window.

To make it easier to locate in the Scene view, give the SpawnPoint game object an icon, by clicking the coloured cube in the top left hand corner of the inspector window and selecting the green bar icon.

This is how it should look at this point :-
SpawnPoint

Now change the transform settings for the SpawnPoint as so:-
spTransform1

Then make a duplicate of the SpawnPoint (CTRL + d) and change its transform settings as so:-
spTransform2

Then make a further duplicate of the SpawnPoint and change its transform settings as so:-
spTransform3

Obviously these placings are just examples and you are free to create as many spawn points as you like and place them however you want. But assuming you followed my example then the game scene should look like this now :-
GameScene

Go ahead and save the Game Scene, and we’ll move on to making it so that the player can randomly pick one of the spawn points when the level is loaded.

The next thing we need to do in order for that to happen is to make some changes to the GameManager script (Assets\GameManager\GameManager.cs). So open it for editing and add the following using statement to the top of the script:-

Next add this static helper method to the bottom of the GameManager script:-

This performs a similar function to the Unity function GameObject.FindObjectsOfType(), but unlike that function it will also find inactive objects. The reason we need it be able to do that, is that we can’t be certain that the SpawnPoints won’t have deactivated themselves by the time we come to search for them.

Next, add this function above the GetAllObjectsOfTypeInScene function :-

This will return the transform component of one of the spawnpoints that it finds in the scene or the default spawn point (we’ll get around to that next) if it doesn’t find any.

To implement the default spawn point we need to add this member variable to the top of the script:-

And we initialise it during the awake function with this code, which just creates a new game object and gives its transform some default values.

The final part of the this particular jigsaw is to give the spawn point position and rotation to the player when it is instantiated. And to do this we need to make a couple of changes to the OnLevelWasLoaded() method.

We add the following line after the PhotonNetwork.inRoom check:-

and then change the statement that instantiates the player, so that it can use the spawn point information, as so:-

This is the full GameManager script with all the above changes in place:-

Save the GameManager script, open the Main Scene (save the current scene if prompted) and run the game, and you should see that the player now spawns at one of the points you placed in the game scene earlier.

So that’s it for this part, we’ve now got a less buggy main menu (the main menu will be completely reworked later in the series) and we have a simple and flexible method for adding spawn points for our players. In the next part we’ll be adding some weaponry.

As always any questions, comments and suggestions are very welcome, see you soon.

Download complete project for Part 4

Photon Unity Networking Game Tutorial Part 3 – Adding the Player and Game Scene

Welcome to part 3 of my Photon Unity Networked Game tutorial. In this part we will add the player object and the game scene.

Disclaimer: There are many solutions to any one given network game design, which will differ greatly due to a number of factors, including but not limited to, target audience numbers, security concerns and whether you are programming for fun or commercial gain.
Therefore, I do not suggest that is this the only approach you might take, or that this is even the best one, rather it is my attempt to demonstrate some core concepts in as clear and concise a fashion as possible, which can be used as a stepping stone into the often murky waters of networked game programming.

Part 3a – Setting up the game scene
Before we can set up the game scene in the editor we need some assets for the environment. This zip file contains a Unity package which has in it the game scene and player prefabs which are modified versions of some of the excellent assets from the Tanks Tutorial from the Unity Asset Store. Go ahead and download it then extract and install the Unity package.
After you’ve installed it your project folders should look like this (I have highlighted the new folders contained in the package).

ProjectWindow

Now that we’ve got the assets installed, make a new scene (File->New Scene).

Delete the default directional light (the level art prefab has its own lights) and then find the level art prefab (UnityTanksAssets\Complete\Prefabs\CompleteLevelArt.prefab) and drag it into the Hierarchy Window, and make sure the transform position is 0,0,0.

DragLevelArt2

You should now have a nice gaming arena in the scene, however the lighting is a bit ‘flat’ by default, so the next thing we need to do is adjust that. Open the lighting window (Window->Lighting) and set the following values:-

Skybox = Default-Skybox
Sun = None (light)
Ambient Source = Color (Set the Ambient Color RGB values to 133, 102, 0 to get a mid brown colour)
Precomputed Realtime GI = Ticked
Baked GI = Un-ticked
Auto Build = Un-ticked

This is how the lighting window should look

LightingSettings

This is how your editor should look if you have correctly imported the assets and added the level art to the scene.

Save the scene in the Assets folder, and call it Game Scene. Next we’ll add some code to handle loading of the game scene when we join a game.

Part 3b – Loading the game scene
Once you have saved the game scene you need to add both the Main Scene and the Game Scene to the scenes in build, so open the build settings window (File->Build Settings) and drag them both into it as so, making sure that the Main Scene is first in the list.

BuildScenes2

Next, open the GameManager script (Assets\GameManager\GameManager.cs) for editing.

All we need to do is add this snippet of code to the OnJoinedRoom() function

and this line of code to the end of the Awake() function

What this does is, if you are the host (first player in the game) it instructs the Photon Networking system to load the game scene level. And then the line we added to Awake() ensures that any new players joining will automatically load whichever scene the host has currently loaded.

Strictly speaking as we are only using the one game scene at the moment, we could make the host and clients load the game scene manually using Unity’s SceneManagement class, however if we come to add extra game arenas later, doing it this way will make it much easier to keep everyone synchronised. If the host changes its current scene, then all connected clients will automatically change to the same scene, and new clients will join with the same scene active.

Note: Photon uses the term MasterClient to indicate which client is currently the host and ‘in control’. The Master Client can be used as “authoritative” client/player to make decisions, run AI or carry out other tasks that you don’t want all clients to be able to do themselves. If the current Master Client leaves the game, Photon will very quickly assign someone else, which means that the game doesn’t stop if the host leaves.

This is the full GameManager script with the additions we just made, so go ahead and save this now, and then open the Main Scene again in the editor.

If you build and run two instances of the game, you will see that once you click the join button, the scene switches to the game scene. This happens on the client as well, even though we don’t explicitly load the scene on the client, because we set PhotonNetwork.automaticallySyncScene = true.

If you run one of the instances in the editor, and you don’t see the main menu when it runs, that is because you didn’t switch back to the Main Menu scene after you had finished making the Game Scene.

So that’s the game scene working, next we’ll add the player.

Part 3c – The Player
Before we can add the player object, we need to make a couple more changes to the GameManager script as follows:-

First we need to declare a variable to store a reference to the local player object when it is instantiated as so:-

public static GameObject localPlayer;

We can use this as a handy way to reference the player object any time we need to.

Then we need to add the following function:-

The above function will run whenever a scene loads, including the main menu. So to avoid spawning the player when we aren’t in the game scene we check to see if we are in a room or not. The game scene won’t load until we have joined a room, so if we are in a room we know that we must be in the game scene, and therefore we need to instantiate the player object and we also store a reference to it in the variable we just added. Otherwise we just return.

This is the full GameManager script now, which you can now save before proceeding:-

We imported a player object along with the level art, but we need to get it ready for networking before we can use it in our game, so navigate to the Assets\UnityTanksAssets\Complete\Prefabs folder and select the Player prefab located in there.

With the player prefab selected, click the Add Component button in the inspector, type photon in the search panel and add a Photon Transform View from the list of components.

TransformView

This will add a PhotonTransformView and a PhotonView component to the player prefab, and it is these components that will do the work of synchronizing our player object across the network.

We need to tell the PhotonTransformView what we would like to synchronize, and in our case that will be position and rotation, which we indicate by ticking the two relevant boxes. When we tick each box further options will appear relating to ‘smoothing’ of the networked player movement on remote clients. We’ll leave these values on their defaults for now.

Lastly we drag the PhotonTransformView component into the Observed field of the PhotonView component. The PhotonView will then be able to send the position and rotation of our player object across the network.

TransformView2

The Photon instantiate command requires that the prefab is contained within a Resources folder, so we’ll sort that now. Create a new folder in Assets and call it Player, then in the Player folder create another folder and call it Resources. Next, drag the Player prefab from the Assets\UnityTanksAssets\Complete\Prefabs folder and into the Assets\Player\Resources folder.

Next create a new script in the Assets\Player folder and call it Player and then open it for editing.

Replace the default code with the following:-

This does three things, firstly it makes sure that the player object is set to DontDestroyOnLoad, so that if we change levels during the game our player won’t get destroyed.
Secondly, it grabs a reference to the Camera gameobject attached to the Player prefab.
Thirdly it checks to see if photonView.isMine is false and if it is, it disables the player camera.

photonView.isMine is how we determine if this script is running on the local player object (the one we are controlling) or on a player object belonging to a remote player. True means that it is our Player object, false means it belongs to someone else. We only want the camera to be enabled for our own player object.

Note that this script derives from PUNBehaviour, which gives us easy access to lots of Photon stuff. In this case it gives us convenient access to the attached PhotonView component via the photonView property.

Save the script and then add it to the Player prefab (select the player prefab, click the Add Component button in the inspector and select Scripts->PunTutorial->Player).

You can now build and run two instances of the game, you’ll see that when the 2nd player joins it spawns in the same place as the first player, and the physics moves them apart. You’ll also notice that the positions of the tanks is synchronised in both clients, which means the PhotonTransformView component is doing its job.

Now we need to add a script to allow us to drive the tanks.

Create another new script in the Assets\Player folder and call it PlayerMovement and then open it for editing.

Replace the default code with the following:-

Notice that it derives from Photon.PunBehaviour, so we can easily check photonView.isMine. If it’s false, we disable the script as we only want this to run on our local player object.

I won’t go into much detail about how the rest of this script works, as it isn’t particularly relevant to networking. But basically it reads the values of the horizontal and vertical input axis, and uses those values to move and rotate the rigidbody of the player object.

Save the script and then add it to the Player prefab (select the player prefab, click the Add Component button in the inspector and select Scripts->PunTutorial->PlayerMovement).

There’s just one last thing we need to do, and that’s to remove the camera from the game scene, as our player prefab has its own camera attached and we’ll use that instead. So open the game scene, delete the Main Camera game object, then save the scene.

Next, open the Main Scene again, and if you run the game you’ll see that you can control the tank with the WASD keys. If you build and run two instances of the game, you’ll see that each tank can be controlled independently, and that their movement is synchronised across both clients.

That’s it for this part of the tutorial, in the next part we’ll add a system to handle different spawn positions.

See you next time.

Download UnityTanksAssets package
Download Complete Project for Part 3

Photon Unity Networking Game Tutorial Part 2 – Connecting to Photon and joining a game.

Welcome to part two of my Photon Unity Networked Game tutorial. In this part we are going to put in the place the basics to allow us to create or join a networked game.

Disclaimer: There are many solutions to any one given network game design, which will differ greatly due to a number of factors, including but not limited to, target audience numbers, security concerns and whether you are programming for fun or commercial gain.
Therefore, I do not suggest that is this the only approach you might take, or that this is even the best one, rather it is my attempt to demonstrate some core concepts in as clear and concise a fashion as possible, which can be used as a stepping stone into the often murky waters of networked game programming.

Part 2a – Connection to the Photon Cloud service and creating a game session

Having configured our project to work with Photon (in part 1 of the tutorial) our next step is to set up the initial scene which we will use as our entry  point into the game. So we’ll save the current scene and call it Main Scene.

Next let’s change the camera background, because we don’t need a skybox for the menu. So change the Main Camera clear flags to ‘solid color’ and set a suitable background colour, something like so:-
CameraBackground

Next we’ll add an empty GameObject to the scene and call it GameManager.
Now make a new folder Assets\GameManager and create a new c# script in the folder and also name it GameManager, then drag this script onto the GameManager object in the hierarchy.

The GameManager is going to be a Singleton object that doesn’t get destroyed when the scenes change, so we’ll define it initially as follows:-

Open the GameManager script and copy the above code into it, replacing what is already there, then save the script.
One important thing to note is that it is defined as a Photon.PunBehaviour rather than a Monobehaviour. The reason for this is that it will give us access to Photon Unity Networking functions and overrides, which we will be using later in the tutorial.

Having created our GameManager, it’s first job will be to connect us to the Photon Cloud. The Photon Cloud is essentially a collection of PCs running Photon Server, all of which are maintained and controlled by Exit Games. Once connected to the Photon Cloud, you can join a session of the same game being hosted by someone else, or you can host your own game session which other people can join. One of the benefits of using the Cloud is that you don’t need to worry about firewalls and port forwarding to make your game visible to other players. Bear in mind however that you do need an active internet connection to play multiplayer with Photon Cloud, even if all players are on the same LAN.

So we can connect to the Photon Cloud by adding this simple Start() function to the GameManager script. This is necessary because until we are connected to the Photon Cloud we can’t create or join any networked games:-

This simple bit of script uses the information from the server settings asset you configured in the previous part of the tutorial to connect to the Photon Cloud with our AppID and other pre-set settings. The only bit of additional information it needs is the game version string, in our example we use “PUNTutorial_Pt1”. Users are separated from each other by the game version, which means you can make changes to your game which aren’t compatible with previous versions, and as long as you update the game version string your users won’t be able to connect to an incompatible game.

Now we need a way to create or join a game, and for this we will create a Join Game button. So add a canvas to the Hierarchy and then add a button. Call the canvas MainMenu and the button JoinGameButton, and change the button’s text to ‘Join Game’.
Having done all the above, this is how your editor should look:-
Part2aEditor

Now we’ve got our Join Game button, we need to hook it up so that it actually does something. So we’ll add the following function to the GameManager script:-

In this function we use the Photon JoinOrCreate function, which when executed, searches for a room with the specified name (in our case ‘Default Room’) and joins it if found, otherwise it creates a new room and then joins it. We also use the RoomOptions argument to set the maximum number of players that can be in the room at any one time.

Now we want to hook this function up to the OnClick event on our JoinGameButton, so click the + button to add a new event handler, drag the GameManager onto it and select the JoinGame function as so:-
JoinGameButton

The final part of this step, is to create a function that executes when we have successfully joined the room, so add this function to the GameManager script:-

For now this will just print a debug string to notify us that we have joined the room, but in the next part of the tutorial we will use it to load the game scene and player character.

This is the full GameManager script so far:-

Save this and run the game and when you click the Join Game button  you should see a debug output that says ‘Joined Room’, not very exiting yet I grant you, but at this point you are effectively running a networked game. If you build and run two instances of the game they will both be connected with each other, although currently there will be no obvious evidence of this. (NOTE: You may need to wait 4-5 seconds after you run the game before you click the Join Game button, otherwise you may get an error. Part 4 of the tutorial makes a change which prevents this from happening.)
In a later part of the tutorial we will set up a game browser so you will be able to see a list of currently available games which you can join, or if you create your own game, other people will be able to see it listed and join it.

In the next part we will cover loading the game scene and spawning and controlling the player character, so hope to see you next time.

Download Complete Project for Part 2 (Photon Demo folder deleted to save space).

Photon Unity Networking Game Tutorial Part 1

Part 1

Welcome to my basic tutorial for Photon Unity Networking (PUN). In this tutorial we’ll look at how to download and install the PUN package into your project, and over the course of a number of parts we’ll develop a small multiplayer game that demonstrates some of the core concepts that are required to make a networked game.

The tutorial assumes you are on a Windows PC, have a reasonable level of familiarity with both C# and the Unity interface and little or no experience with programming networked games.

For this tutorial we will use the free version of PUN, however if you are using PUN+ there should be no difference in how it works, but please feel free to let me know if there are any inconsistencies and I will make appropriate notes in the tutorial accordingly.

We will also be making use of some of the assets from the Unity Tanks Tutorial, I will provide download links for the specific assets when it is appropriate.

Part 1a – Creating the project and downloading and installing the PUN SDK

The first thing we need to do is create a new project, I’m going to call it PUN Tutorial, however you can use whichever name you like.

Once you’ve created the new project and have it open in Unity, open the Asset Store Window (Ctrl + 9) and search for Photon Unity Networking. The first match should be the free version of Photon Unity, so click on the Download button, and when it’s finished downloading, click on the import button in the asset store window. When the import dialog opens up in Unity, make sure everything is selected and then click the import button. Strictly speaking you could un-tick the demos folder as we won’t be using anything in it, however it has some excellent example code which is well worth a look if you are just starting out with networking, so we’ll leave it in.

Import PUN

If you are unable to find PUN free in the store by searching, here is the current link to the Asset Store Page Photon Unity Networking Free.

Depending on the speed of your PC, it will take a minute or two to import everything, and once it’s done it will pop up the PUN Wizard dialog as so:-

Pun Wizard

If you already have an AppID or registered email, you can enter them now, but I’m going to assume you haven’t used PUN before and I’ll go through the process of creating a new account and setting up your first project.

Enter the email you would like to use in the box labelled AppID or Email and click setup project. It should respond with a message that the email isn’t registered and give you the option to open the cloud dashboard.
Pun Wizard 3

Click on the Cloud Dashboard Login button, and it will open up the Photon sign in page.

Photon Sign In 2

Click on the Register button, then enter the email address you want to register and click the orange Register button. You will be sent an email which contains an activation link, which you need to click on to set your account password and start using Photon.

Having created your password and activated your Photon account, switch back to Unity and click on the Cloud dashboard Login button on the Pun Wizard dialog. This will open a browser window where you can login to Photon with the email and password you specified in the previous steps.

Once logged in you’ll be taken to your dashboard, where you’ll find Photon has already created an app for Photon Realtime for you, with your registered email address as the app name. This will be where you can add and manage all your network apps using PUN.

Photon apps 2a

Obviously we would prefer a better name for our app, so click where it says [name your app] and enter a new name, I chose Doofah PUN Tutorial, but you can call it whatever you like. It doesn’t have to be the same name as your Unity project, but keeping it similar will make things easier later on if you have a lot of network projects, you can also give it an optional description at this point if you like.
Once you’ve renamed the app and clicked save you will be taken to this screen:-

Photon apps 3

We’ve nearly finished on the Photon dashboard for now, all we need to do is copy to the clipboard the long app ID beneath the app name, so we can paste it into Unity.
photon apps 4

So having selected and copied the app ID, switch back to Unity and paste it into the box labelled AppID or Email, then click the Setup Project button.
Setup 1

Assuming you’ve done everything as above, your project should now be setup for networking with Photon, and your photon server settings should be automatically selected ready for you to edit them. For now the only setting you might want to change is the region, you can click the drop down and select the region closest to where you are located.

Setup 2

So that’s it for the setup of the project and getting it ready for Photon networking, in the next part we will begin the creation of the actual game.

See you next time.

Scene Transition/Screen Fader Tutorial Part 2

Welcome to part two of my screen fader tutorial. In this part I will provide a short demonstration of how you might use the screen fader to smoothly transition between scenes.

I’ll add two scenes to the project, with different title text and background colour, so it’s obvious when the scene has changed, and a single button in the middle of the screen which will be used to make the scene change.

I’ve added a small script to the button, which exposes an Object property to the editor, into which I drag the scene that I would like the button to load when clicked. Note: any scenes that are to be loaded at runtime must be added to the build settings in the editor.
Build Settings

This button script is how an inexperienced Unity programmer might approach it, but it won’t work properly and if you try it you’ll see that the scene change happens immediately with no fade in or out.

The reason for that is that as soon as the fade out function is called Unity moves immediately onto the next line of code and executes it, this stops the fade out and loads the next scene. It then goes on to start the fade in function, which has no effect as the screen isn’t faded out.

So how do we prevent execution of the line after the fade out call until we are ready for it? One way would be with coroutines, but this can easily get messy and means that we can’t keep all our code in one function. So this is where the callback argument of the fadein/fadeout functions comes into play.

With the above in mind we could write our button code like this instead:-

So now we have separated the fadeout function call from the load scene and fadein function calls, and put them in a separate function. We have then provided the new function as the callback argument for the fadeout routine.

Now if you run the program and click the button you’ll see that it works exactly as it should, the original screen fades out, and then the new scene smoothly fades into view.
This is because now the fadeout routine is executing and only when it’s finished does it invoke the callback function which loads the new scene and fades it back in.

This is much better, in as much as it actually works. However earlier I mentioned that a disadvantage of using coroutines to achieve the same was that we couldn’t contain everything in one function, and you’ll probably have noticed by now that this solution suffers from the same problem.

We can fix that however by writing the callback function inline in the fadout function call, this isn’t strictly speaking necessary, but in a lot of instances it can keep the code simpler and easier to follow in my opinion.

So this is the final version of the button script:-

As you can see the whole process is now contained within a single 7 line script, with execution of the scene change and fadein delayed until the fadeout function has finished. I’ve also added a 0.5f second delay to the fadein function call as I think it looks better with a slight extra delay.

That’s it for this tutorial, as always any and all comments or suggestions are welcome. See you next time.

Unity project file

Scene Transition/Screen Fader Tutorial Part1

A common feature of games is that the screen briefly fades to black during scene changes. This is done to ‘soften’ the transition as a sudden switch from one scene to another can be a bit jarring if it’s visible when it happens.

So I’ve found it handy to have a screen fader that I can easily drop in to a new project any time I want to have fade to black (or some other colour) scene transitions and I decided that it would be a nice subject for a mini tutorial. It will also serve to demonstrate a couple of seemingly often overlooked but very useful features, i.e. callbacks and CanvasGroups.

The aim of this tutorial will be to provide a screen fader that is fairly simple to implement and is also flexible and easy to use.

The way this screen fader will work is by overlaying a UI image on the screen and changing it’s alpha value to fade it in and out.

To make it as easy as possible to add to a project, we’ll have the fader create its own UI elements in code, which means all we have to do is drop the screen fader script onto an empty game objet to make it available for us to use. To make this work we’ll need the screen fader to create its own Canvas, CanvasGroup component and an Image.

Step 1)
This is the code to create the Canvas at runtime:-

What this does is create a new GameObject with a Canvas component attached. It then parents it to the GameObject that script is on, then it sets the RenderMode to ScreenSpace overlay and sets the sort order to the value specified in the inspector (default 99). I have made the sort order easy to change so that you can make sure the fader image is always on top of everything else. However depending on what you wanted you could also use the sort order to have it appear behind some elements.)

Step 2)
This is the code to create the CanvasGroup at runtime:-

We use AddComponent() to add the CanvasGroup component to the canvas we just created, and then store, in a member variable, the reference to the component that it returns for later use. Then we set interactable and blocksraycasts to false, which means that any child objects of the canvas group won’t block mouse clicks. This isn’t strictly necessary for our purposes here, and indeed in some instances you may want your screen fader to block input, in which case you would set interactable and blocksraycasts to true.
Lastly we disable the GameObject as we don’t need to have it enabled until we are ready to use it.

Step 3)
This is the code to create the Image at runtime:-

Firstly we create a new GameObject with an Image Component attached, then we set it as a child of the canvas we created in step 1. Next we set its colour to the value specified in the inspector (default black), and lastly scale it up a bit, this last bit is done to prevent a slight border appearing around the edges in some screen resolutions.

Step 4)
Set the image position and size and make it scale with the screen size:-

The easiest way to change position and size of ScreenSpace UI elements is to use the RectTransform component, so firstly we get a reference to the Image RectTransform using GetComponent().

Next we set the anchors to the bottom left and top right to make the image stretch with the screen size. The anchors are Vector2s with values between 0,0 and 1,1, where 0,0 is the bottom left of the screen and 1,1 is the top right of the screen.
Next we centre the image by setting its anchoredPosition to 0,0;
Lastly we set the width and height to match the current screen size (due to the scaling we did in step 3, it will be slightly larger than the screen).

Now we need to put all of that above into the Awake function of the screen saver. This is the complete Awake() function.

The only part of the Awake() function I haven’t discussed yet is the very first block of code. Basically what this does is make the screen fader not get destroyed during scene changes, and also ensures that the only instance of it that can exist is the first one that is created. It also grabs a static reference to the first instance so that it is possible to access non static methods and properties.

That’s the basic setup finished, so next we’ll define a couple of static methods that will enable us to perform fade outs and fade ins. The methods will invoke callbacks when finished so that we can delay execution of other code whilst the fader is in operation. We will also be able to specify a delay before and/or after the screen fade.

This is the public static method to do a fade out:-

As you can see it is quite simple, all it does is check to see if the callback argument is null, and if so it points it to the default empty callback function and then it sets the canvas active.
Next it stops any current fade in/out operations (I’ll discuss that function further on) and runs the fade out coroutine, which is where most of the work is done.
It uses the variant of StartCoroutine that returns a CoRoutine object, which we store for use by the StopActiveFades() function.

This is the code for the fade out coroutine:-

There again, this is quite straightforward, first of all we yield wait for the delayBefore period.
Then we loop while the group.alpha property is less then 1. The alpha property controls the transparency of all children of the group (in this instance our image we created in step 3), a value of zero means fully transparent and a value of one means fully opaque.

In the while loop we use MathF.MoveTowards to linearly fade from transparent to opaque, we can use the speed property in the inspector to control how quickly this happens.
After the while loop exits we yield wait for the delayAfter period if any, and then we invoke the callback to signal that the fade is done.

The fade in functions are almost identical, except in the while loop we change the group.alpha from one to zero and when it’s finished it disables the canvas.

Now let’s take a look at the StopActiveFades() function:-

This function just ensures that there can’t be two fade operations running at the same time, as that could result in unwanted behaviour.
What it does is check the fadeInCR and fadeOutCR variables and if they aren’t null it stops the coroutine they point to and sets the variable to null.

Last of all is the default empty callback function, which doesn’t need any explanation.

In the next part of this tutorial I will show how we can use the screen fader in our applications, but in the meantime here is the full screen fader script:-

As always any and all comments are welcome, see you next time.

Unity 5 Network Tutorial Part 7 – Improved respawning and health pickups

Welcome to part 7 of my Unity 5 basic network game tutorial, in this, the final part, we will improve the respawn process and implement health pickups.

Part 7a – Improved respawn

In the last part of the tutorial, we implemented respawn behaviour when a player’s health reaches zero, however as it stands there is no delay between the player dying and being respawned, which dosn’t feel quite right. Also there is a subtle bug, whereby if the player dies close to his respawn point, you will see him ‘lerp’ to the respawn point on remote clients.
So we’ll go ahead and fix both of those issues now.

First of all, open the NetworkPlayer script in your editor and make the playerCam variable public.

public Camera playerCam

The reason for making the playerCam variable public is because we want to temporarily detach the camera from a player object when he dies. This enables us to reposition the player without the camera immediately following him. (This is a bit of a workaround to avoid the lerping issue mentioned earlier.

This is the only change we need to make to the NetworkPlayer script, so go ahead and save it, and then open the HealthAndDamage script in your editor, as we also need to make some changes there.

We’ll handle detaching and re-attaching the camera in the HealthAndDamage script, so we’ll store a reference to it in a variable in the HealthAndDamage script.
Add this line below the respawnPos variable:-

Camera playerCam;

And add this line to the OnStartLocalPlayer() function:-

playerCam = GetComponent<NetworkPlayer>().playerCam;

That’s the reference to the player’s camera sorted, so next we’ll add three new functions that will perform the improved respawn.

Add the following code just below the GetRandomSpawnPoint() function:-

So looking at these new fuctions, we have setVisibleState(bool state), what this does is turn on or off the player object renderer and name label, effectively making it invisible or visible depending on the state argument.

Then we have a coroutine, HandlePlayerDeath() which calls setVisibleState with a value of false, which makes all instances of the player object invisible.
Next, only if it’s running on the local player, it detaches the camera, preserving the camera’s current position, and then it repositions the player in the respawn position. As the camera is now not parented to the player, the game view stays in the position it was when the player died, and then it waits for two seconds. This is good for two reasons, first it gives a nice pause between dying and respawning and secondly it allows the clients to lerp to the new spawn position whilst they are invisible, so you don’t see it happen.
Finally it calls the Respawn() function.

The Respawn() function checks to see if it’s running on the local player, and if it is, it re-parents the camera to the player object and sets it’s local position to the correct value.
Then it tells the server to reset the player’s health to 100. Finally it makes the player visible again on all PCs on the network (which is why the last command needs to be outside of the isLocalPlayer check).

The final change we need to make is to the RpcHandlePlayerDeath() function, to make it call the HandlePlayerDeath() coroutine instead of handling the respawning itself. So replace the existing RpcHandlePlayerDeath() function with this new one:-

So that’s the new and improved respawn procedure finished, this is the new HealthAndDamage script with all the above changes implemented:-

Save this and then build and run the game, you will see that after a player dies he disappears for two seconds before reappearing at one of the spawn points with full health.


Part 7B – Health Pickup

Now let’s add a health pack that you can walk over to replenish your health. We’ll implement these as scene objects, so we can just place them in the editor and not have to worry about runtime instantiation and health spawn points etc. etc.

So the first thing we need to do is create a health pack game object. Open the online scene and add a 3d Cube game object (GameObject->3D Object->Cube) and rename it HealthPack.
Now make the following changes to the HealthPack game object:-

  • Set its transform.position to  -8,0.25,-5
  • Set its scale to 0.5,0.5,0.5.
  • Add a NetworkIdentity component to the HealthPack (with the HealthPack game object selected use the menu option (Component->Network->NetworkIdentity)).
  • Set its Box Collider Is Trigger property to ticked

Next we’ll give it some colour; Open the Assets/Materials folder, create a new material in there and rename it HealthPack, then set the albedo Colour to a nice bright green.
Then drag this new material onto the HealthPack game object in the hierarchy and you should see the cube take on the colour of your new material.

Your scene setup should look like this after you have done all the above.

Now let’s add a script to the HealthPack game object to implement the functionality. Create a new folder in Assets and rename it HealthPack, and in that folder create a new c# script and rename it HealthPack. Then open the Healthpack script in your editor and replace the default code with the following:-

This is a fairly straightforward script with only five functions, also notice that it derives from NetworkBehaviour instead of MonoBehaviour. Now let’s take a look at each part in turn and I’ll explain what each one does.

Firstly we declare a variable, bool visible, and make it a SycVar with a hook function. The hook function OnVisibleChanged just activates or deactivates the attached Renderer and Collider based on the newValue argument. This has the effect of hiding or showing the health pack in the scene. Anytime the value of visible is changed this hook function will run and update the state of the HealthPack in the scene.

Next we have OnStartServer(), this will only ever run on a dedicated server when it starts, or the host when it starts. Therefore we can use this function to set the initial state of the HealthPack, and as we want all health packs to start enabled we set visible to true.

Next comes OnStartClient(), this runs on all clients when they join the game, and at this point all SyncVars are guaranteed to have the correct synchronized value, so we manually call the visible hook function, passing the current value of visible, so we can update the state of the health pack in our scene. This ensures that if a health pack is hidden on the server at the time we join the game, our local copy also gets hidden.

Then we have the HandlePickup() coroutine.

All this does is set visible to false, which makes the hook function run, which hides the health pack.
Then it waits 15 seconds and then sets visible to true, which makes the health pack visible and available for use again.
We can make direct changes to the visible variable as this script is running on the server.

Lastly comes OnTriggerEnter(Collider other)

By default, this function runs on all clients whenever a trigger collision is detected, however we only want to handle collisions with the HealthPack on the server. We could ensure this in a couple of ways, either by checking if isServer is true, or (and this is how we do it) with the use of attribute tags.
The use of the [Server] tag, would mean that the function will only run on the server, however as we can’t control when this function is invoked, we can’t prevent clients from calling this function, and although the function won’t run on the client, we would get a console littered with debug warnings. Therefore instead of using the [Server] attribute, we’ll use the [ServerCallback] attribute, which still makes it only run on the server, but also supresses the warning messages if the function is called from the client.

So having ensured this will only run on server, what does it do. Firstly it starts the HandlePickup coroutine as described above, to hide the health pack.
The ‘other’ argument, will contain a reference to the player game object that collided with the HealthPack, so we use GetComponent to access its HealthAndDamage script and add 10 points to its publicly exposed health variable.
There again we can do this directly as this script is running on the server.
This is where the power of SyncVars shows, because this updated health value is now automatically sent to all clients and because we had previously set up a hook function for the health variable, it in turn updates the health display on the HUD. So just by changing the value of the health variable everything automatically synchronizes and updates, very neat!

Save the HealthPack script and add it to the HealthPack game object by dragging it onto the HealthPack in the hierarchy window.

Then save the Online scene, and the open the Offline scene. You can now build and run the game and test the HealthPack.

So now we have a working health pack, which when a player walks over it will add some health and disappear, before respawning again 15 seconds later. You can duplicate the health pack game object as many times as you like and position them around your game scene.

There are a couple of obvious things about the health pack that could be improved, such as preventing it from increasing health above 100, and also stopping the player from flashing yellow when he picks up a health pack when he is below 100 health. But I’ll leave those as an exercise for you to figure out.

So that’s it for this tutorial series, I hope it’s proved understandable and useful and I’ll do my best to answer any questions that might arise.

You can download the complete Unity project files here:-
Unity Networking Tutorial

Unity 5 Network Tutorial Part 6 – Score and Respawning

Welcome to part 6 of my Unity 5 basic networking tutorial, in this part we will add scores and respawning.

Part6a – Death and respawning

In the last part of the tutorial, we left it where a player’s health reduced when hit by a laser shot, but it didn’t detect when it reached zero. So we need to address that now.

Firstly open the HealthAndDamage script in your editor and change the replace the TakeDamage() function with the following new TakeDamage() function.

What this does is prevent the health value from going below zero.
Then if it is zero it calls the DoDeath routine to take the appropriate action, which in this case will be to move the player to one of the spawn points and reset their health to 100.

We need to add the DoDeath() function next, so add this code just below the TakeDamage() function.

We also need a new SyncVar to store the respawn point in, so add this code just below the health SyncVar.

All the above bits of extra code do is call another two functions, which we will add next, one to obtain the new spawn point, which is then stored in the respawnPos syncvar and the other is an RPC to move the player to the respawn point.
We mark the DoDeath() function with the [Server] attribute as we only want respawning to be handled on the server.

Add this code just below the DoDeath() function, this is what we will use to get a new spawn point, it makes use of the spawn points we set up in the NetworkManager previously.

Lastly we need to add the HandlePlayerDeath() function as so

This is the function that does most of the work of respawning our player. Firstly we check isLocalPlayer, as we only want this to run on the local player object (it will automatically synchronize the changes to remote clients).
Then we reset our position to the respawnPos value obtained earlier, and finally we tell the server to set our health back to 100.

This is the entire HealthAndDamage script as it now stands

Save this and build and run the game, you will see that when a player’s health reaches zero, it is instantly respawned in one of the spawn points and it’s health is restored to 100.

Part 6b – Scoring

The only thing left for this part of the tutorial is to handle scoring when a player gets a kill. So we’ll sort that out now.

Firsly we need to update the HUD to give it a way of displaying our score. So open up the Online Scene, and with the HUD game object selected add a Text object (GameObject->UI->Text) and rename it ScoreText, then make the following changes to its properties:-

  • Anchor = Bottom Left
  • Position = 223, 35, 0
  • Width = 410
  • Height = 80
  • Text = Score: 0
  • Font Size = 64
  • Colour = white

Once you’ve done that your Online scene should now look like this
ScoreText
Now we’ve got somewhere to display the score, we need to add some code to actually do that, but before we go on, save the scene with the above changes.

With the scene saved, the next thing we need to do is add a reference to the ScoreText game object and a new function to the HUD script. So open the HUD script in your editor.
Then add this line just below the healthText variable

Text scoreText;

Now add this line to the Awake() function

Now add the function that we can use to display the score for the player as it changes.

The entire HUD script should look like this now:-

Next, open up the HealthAndDamage script in your editor and add the following line to TakeDamage() function:

fromPlayer.GetComponent<PlayerShoot>().AddScore();

so the TakeDamage() function should now look like this:-

This calls the AddScore function on the player that fired the laser, if the targeted player’s health is zero, i.e. he is dead.
We now need to make the AddScore function as it doesn’t actually exist yet. So open the PlayerShoot script in your editor and add the following code beneath the CanFire() function:-

We also need to declare a variable to store the player score in, so add this line just below the nextFireTime variable:-

int score;

We are only interested in keeping track of the player’s score on the local player, so the score variable doesn’t need to be a SyncVar.
The AddScore function has to run on the server, as it invokes the RpcAddScore() RPC function we just added.

In the RpcAddScore() function we check it its running on the local player, as this is the only place we want to update the score, and if it is, we add 5 to the current score and then call the HUD.DisplayeScore() function we made earlier, to display the new score.

This is the entire PlayerShoot script with the above changes implemented:-

Save the PlayerShoot script and the HUD script, load the offline scene and then build and run the game. Now when you shoot and kill an opponent, you will see your score increase by 5 each time.

That’s it for part 6, in the next part we’ll improve the respawn function to add some delay and add health pick up items.

Unity 5 Network Tutorial Part 5 – Health and Damage

Welcome to part 5 of my Unity 5 basic networking tutorial, in this part we will add damage and health, so at least your laser will have some purpose now!

Before we get to the laser, I want to make a small change to the NetworkPlayer script, with the addition of two lines. So open the NetworkPlayer script in your editor and add these two lines to the OnPlayerIDChanged() function:-

name = playerID;
if(isLocalPlayer) name += “ Local“;

This makes it much easier to identify whether you are dealing with your local player object, or the player object belonging to a remote player, when it comes to debug.logging etc. It’s a little trick I use when programming a multiplayer game, and can be a real time saver when trying to pin down why something isn’t working as it should.

The OnPlayerIDChanged function should look like this now

Save the changes and then we can move on to the laser, making it do some damage.

Part 5a – Laser hit detection and health

So the first thing we need to do is enable the laser to detect if it has hit another player. We’ll use a ray cast for this and therefore we need to add these lines to the beginning of the Fire() function in the PlayerShoot script:-

What this does is fire a ray in the same direction and length as our laser LineRenderer and, if it detects a hit, it calls the server function CmdDoShotSomeone() and passes references to both the player that was hit, and the player that did the shooting, so that the server can take the appropriate action. Which in this case will be to reduce the health of the player that was hit.

Again, to keep things simple there is no checking done to determine if the object that was hit is actually a player. For this tutorial that’s all it can ever be, but in practice you’d probably want to make certain with some tag checking etc.
Also in terms of reducing the chance that players can cheat, the hit detection really ought to be carried out on the server. However, without extra client side code being implemented, this could result in it looking like shots miss, when the client sees a hit, but due to the latency, the server doesn’t register a hit and vice versa. So for this tutorial I’ve decided to ignore the possibility of cheating and implemented it the way that will give the best player experience, whilst keeping the code as simple as possible.

However, we do need to make the CmdDoShotSomeone() function, so also in the PlayerShoot script, add the following function just below the Fire() function

This gets a reference to the HealthAndDamage script on the player that was hit, and calls the TakeDamage() function that will reduce the players health. It also passes a reference to the player that did the shooting, as this will be needed later. We can’t call this function directly from our Fire() function because we don’t own the hitPlayer gameobject; it belongs to another player in the game, therefore the only way we can interact with it is via the server.

The full PlayerShoot script should look like this now, save this (ignore any errors regarding the HealthAndDamage script being missing) and we’ll move on:-

Now we need to make the HealthAndDamage script, as at the moment it doesn’t exist.
So in the Assets/Player folder create a new script and rename it HealthAndDamage. Open it in your editor and replace the default code with the following:-


So going through this one bit at a time, first of all we declare a variable to store our health in, and make a SyncVar with a hook, OnHealthChanged().

Lets take a look at the hook; What this does first is update the health variable to reflect the new value, then it checks to see if health is less than 100. If it is then that means we have taken some damage, so we should show some kind of feedback to indicate this, and to do that we start a coroutine ShowHitEffect().

ShowHitEffect is quite simple, it gets a reference to our player object’s material, saves the current value of its colour, and then sets its colour to yellow, waits for a short time and then resets the colour using the previously saved value.
So basically, when you get shot, your player flashes yellow briefly.

In OnStartLocalPlayer() we call the server command CmdSetHealth, passing in a value of 100, this sets our starting health when joining the game. Because of the ‘If(health<100)‘ check in the hook function, changing our health like this won’t trigger the hit feedback effect when the health value changes. If we didn’t have that check in place, we would flash yellow when our health was initialized to 100 when we joined the game.

The remaining function is the TakeDamage function, which reduces our health by 10 every time it is called. It is this function that is called by the PlayerShoot script, when a hit is detected.

With all the changes above made, save your script and then add it to your player prefab, by dragging the script onto the prefab in the project window.

Now if you build and run the game and start a host and client, you’ll see that every time you score a hit on your opponent he flashes yellow. If you run one game in the editor you can also check the value of health in the inspector for each player, as it was declared as a public variable, and you’ll see it reduce as you score hits.

Part 5b – HUD

Now we need some way to display our health on screen, and the way we’ll do this is with a scene object HUD.

So load the Online scene and add a Canvas (GameObject->UI->Canvas), then rename it HUD and set the UI Scale Mode to ‘Scale with screen size’. Your HUD should look like this:-

Hud

Next we’ll add a camera to the HUD, so right click the HUD in the hierarchy window and select Camera from the drop down menu that appears. We are using the camera purely so we can see the UI elements in the game window in the editor, as it makes it easier to check positioning while we are designing things. This camera will get disabled in play mode.

Right click the HUD again and this time select UI->Text from the popup menu to add a child text object to it, then rename it HealthText. Then set the anchor to bottom left.
Anchor

Now change the following other properties on the HealthText object you just added

  • Pos X = 580
  • Pos Y = 35
  • Width 410
  • Height 80
  • Text = Health: 100
  • Font Size = 64
  • Alignment = Right
  • Colour = White

When you’re done your HealthText should be set up like this

HealthText

and your game display should look something like this

GameDisplay

And your hierarchy should look like this:-

Hierarchy

Now create a new folder in Assets and rename it HUD. Then create a new script in the HUD folder and rename it HUD too. Open this new script in your editor and replace the default code with the following:-

In this script, the first thing that happens is that during Awake it sets up a static singleton reference, and ensures that only one instance of the HUD can ever exist in any scene.
Then it gets and saves a reference to the HealthText object.

It has one other function, DisplayHealth() which as the name suggest displays our current health in the HealthText Text component of our HUD.

So now we have our HUD script we need to save it and drag it onto the HUD game object in the hierarchy.

Now all that remains is to make a small change to our HealthAndDamage script to make the HUD update as our health changes. So open up the HealthAndDamage script in the editor and add the following lines of code to the end of the OnHealthChanged() function

What this does is, first of all check if isLocalPlayer is true, as we only want to display the health as it changes for our local player. If it is, we call the DisplayHealth function on the HUD and pass it our current health. This will in turn update the text display as our health changes.

This is the full HealthAndDamage script with the above changes:-

Save the HealthAndDamage script, then save the Online scene and load the Offline scene. Now if you build and run the game, you’ll see the health displayed in the lower right hand corner of the screen and as you take hits from the laser from the opposing player your health will decrease in value and be displayed on screen.

There is no check for when it reaches zero yet, but we will fix that in the next tutorial, along with handling re-spawning and scoring.

See you next time.