9 - Player UI Prefab

This section will guide you to create the Player UI system. We'll need to show the name of the player and its current health. We'll also need to manage the UI position to follow the players around.

This section has nothing to do with networking per say, however, it raises some very important design patterns, to provide some advanced features revolving around networking and the constraints is introduce in development.

So, the UI is not going to be networked, simply because we don't need to, plenty of other way to go about this and avoid taking up traffic. It's always something to strive for, if you can get away with a feature not to be networked, it's good.

The legitimate question now would be: how can we have a UI for each networked player?

We'll have a UI Prefab with dedicated PlayerUI Script. Our PlayerManager Script will hold a reference of this UI Prefab, and will simply instantiate this UI Prefab when the PlayerManager Starts, and tell the prefab to follow that very player.

Contents

Creating the UI Prefab

  1. Open any Scene, where you have a UI Canvas
  2. Add a Slider UI GameObject to the canvas, name it Player UI
  3. Set the Rect Transform vertical anchor to Middle, and the Horizontal anchor to center
  4. Set the RectTransform width to 80, and the height to 15
  5. Select the background child, set it's Image component color to Red
  6. Select the Child "Fill Area/Fill", set it's Image color to green
  7. Add a Text UI GameObject as a child of Player UI, name it Player Name Text
  8. Drag Player UI from the hierarchy into your Prefab Folder in your Assets, you know have a prefab
  9. Delete the instance in the scene, we don't need it anymore.

Back to Content

The PlayerUI Scripts Basics

  1. Create a new C# script, and call it PlayerUI
  2. Here's the basic script structure, edit and save PlayerUI script accordingly:

  3. Save the PlayerUI Script

Now let's create the Prefab itself.

  1. Add PlayerUI Script to the Prefab PlayerUI
  2. Drag and drop the child GameObject "Player Name Text" into the public field PlayerNameText
  3. Drag and drop the Slider Component into the public field PlayerHealthSlider

Back to Content

Instantiation and binding with the player

Binding PlayerUI with Player

The PlayerUI script will need to know which player it represents for one reason amongst others: being able to show its health and name, let's create a public method for this binding to be possible.

  1. Open the script PlayerUI
  2. Add a private property in the "Private Properties" Region

    We need to think ahead here, we'll be looking up for the health regularly, so it make sense to cache a reference of the PlayerManager for efficiency.

  3. Add this public method in the "Public Methods" region

  4. Add this method in the MonoBehaviour Messages Region

  5. Save the PlayerUI Script

With this, so far we'll know have the UI to show the targeted Player's name and health.

Instantiation

OK, so we know already how we want to instantiate this Prefab, everytime we instantiate a player Prefab. The best way to do this is inside the PlayerManager during its initialization.

  1. Open the script PlayerManager
  2. Add a public field to hold reference to the Player UI reference as follows:

  3. Add this code inside the Start() Method

  4. Save the PlayerManager Script

All of this is standard Unity coding. However notice that we are sending a message to the instance we've just created. We require a receiver, which means we will be alerted if the SetTarget did not find a component to respond to it. Another way would have been to get the PlayerUI Component from the instance, and then call SetTarget directly. It's generally recommanded to use Components directly, but it's also good to know you can achieve the same thing in various ways.

However this is far from being sufficient, we need to deal with the deletion of the player, we certainly don't want to have orphan UI instances all over the Scene, so we need to destroy the UI instance when it finds out that the target it's been assigned is gone.

  1. Open PlayerUI Script
  2. Add this to the Update() function

  3. Save PlayerUI Script

    This code, while easy, is actually quite handy. Because of the way Photon deletes Instances that are networked, it's easier for the UI Instance to simply destroy itself if the target reference is found null. This avoids a lot of potential problems, and is very secure, no matter the reason why a target is missing, the related UI will automatically destroy itself too, very handy and quick.

    but wait... when a new level is loaded, the UI is being destroyed yet our player remains... so we need to instantiate it as well when we know a level was loaded, let's do this:

  4. Open the script PlayerManager

  5. Add this code inside the CalledOnLevelWasLoaded() Method

  6. Save the PlayerManager Script

Note that there are more complex/powerful ways to deal with this and the UI could be made out with a singleton, but it would quickly become complex, because other players joining and leaving the room would need to deal with their UI as well. In our implementation, this is straight forward, at the cost of a duplication of where we instantiate our UI prefab. As a simple exercise, you can create a private method that would instantiate and send the "SetTarget" message, and from the various places, call that method instead of duplicating the code.

Parenting to UI Canvas

One very important constraint with the Unity UI system is that any UI element must be placed within a Canvas GameObject, and so we need to handle this when this PlayerUI Prefab will be instantiated, we'll do this during the initialization of the PlayerUI.

  1. Open the script PlayerUI
  2. Add this method inside the "MonoBehaviour Messages" region

  3. Save the PlayerUI Script

    Why going brute force and find the Canvas this way? because when scenes are going to be loaded and unloaded, so is our Prefab, and the Canvas will be everytime different. To avoid more complex code structure, we'll go for the quickest way. It's not really recommended to use "Find" however, because this is a slow operation. This is out of scope for this tutorial to implement more complex handling of such case, but a good exercise when you'll feel comfortable with Unity and scripting to find ways into coding a better management of the reference of the Canvas Element that takes into account loading and unloading.

Following the targeted Player

That's an interesting part, we need to have the Player UI following on screen the Player target. This means several small issues to solve:

  • The UI is a 2d element, and the player is a 3d element. How can we match positions in this case?
  • We don't want the UI to be slight above the player, how can we offset on screen from the Player position?

    1. Open PlayerUI Script
    2. Add this public property inside the "Public Properties" region

    3. Add these two private properties inside the "Private Properties Messages" region

    4. Append the following code to the SetTarget() method after _target was set.

      We know our player to be based off a CharacterController, which features a Height property, we'll need this to do a proper offset of the UI element above the Player.

    5. Add this public method in the "Public Methods" region

    6. Save the PlayerUI Script

So, the trick to match a 2d position with a 3d position is to use the WorldToScreenPoint function of a Camera, and since in our game we only have one, we can rely on accessing the main camera which is the default setup for a Unity Scene.

Notice how we setup the offset in several steps: first we get the actual position of the target, then we add the _characterControllerHeight, and finally, after we've deduced the screen position of the top of the Player, we add the screen offset.

Back to Content

Previous Part.

 To Document Top