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