5 - Building the Player

This section will guide you to create from scratch the player Prefab that will be used in this tutorial, so that we cover every step of the creation process.

It's always a good approach to try and create a player Prefab that can work without PUN being connected, so that it's easy to quickly test, debug and make sure everything at least works without any network features. Then, you can build up and modify slowly and surely each features into a network compliant character: Typically, user input should only be activated on the instance owned by the player, not on other players computers. We'll cover this in details below.

Contents

The Prefab Basics

The first and important convention to know about PUN is that for a Prefab to be instantiated over the network, it needs to be inside a Resources folder, it can not be otherwise.

The second important side effect of having Prefabs inside Resources is that you need to watch for their names. You should not have two Prefab under your Assets' Resources paths named the same, as Unity will pick the first one it finds, so always make sure that within your Project Assets, there is no two Prefabs within a Resources folder path that is named the same. We'll get to that soon.

We are going to use the Kyle Robot that Unity provides as a free asset. It comes as an Fbx file, which is generated by 3d softwares like 3ds Max, Maya, cinema4d and the likes. This is out of scope for this tutorial to cover the creation of the mesh and animation on these softwares, however essential for creating your own characters and animations. This Robot Kyle.fbx is located in /Assets/Photon Unity Networking/Demos/Shared Assets/

Kyle Robot Fbx Asset
Kyle Robot Fbx Asset

Here is one way to start using the Kyle Robot.fbx for your player:

  1. In your "Project Browser", create a folder named exactly "Resources" somewhere, typically it's suggested that you organized your content, so could have something like 'DemoAnimator_tutorial/Resources/'
  2. Create a new empty scene, and save it as Kyle Test in /PunBasics_tutorial/Scenes/
  3. Drag and drop Robot Kyle onto the "Scene Hierarchy".
  4. Rename the GameObject you've just created in the hierarchy to My Robot Kyle
  5. Drag and drop My Robot Kyle into /PunBasics_tutorial/Resources/

We have now created a Prefab that is based of Kyle Robot Fbx asset, and we have an instance of it in the hierarchy of your scene Kyle Test. Now we can start working with it.

CharacterController

  1. Let's add a CharacterController Component to My Kyle Robot instance in the hierarchy. You could do this directly on the Prefab itself but we need to tweak it, so this is quicker this way.

    This Component is a very convenient Standard Asset Provided by Unity for us to produce faster typical characters using Animator, so let's make use of these great Unity features.
  2. Double click on My Kyle Robot to have the Scene View zooming in. Notice the "Capsule Collider" centered at the feet; We actually need the "Capsule Collider" to match the character properly.

  3. In the CharacterController Component change the Center.y property to 1 ( half of its Height property).

    Kyle Robot Capsule Collider
    Kyle Robot Capsule Collider

  4. Hit "Apply" to affect the change we made. It's a very important step, because we've edited an instance of our prefab My Kyle Robot, but we want these changes to be for every instance, not just this one, so we hit "Apply".

    Apply Prefab Changes
    Apply Prefab Changes

Back to Content

Animator Setup

Assigning an Animator Controller

The Kyle Robot Fbx asset needs to be controlled by an Animator Graph. We won't cover the creation of this graph in this tutorial, and so we provide a controller for this, located in your project assets under Photon Unity Networking/Demos/PunBasics/Animator/ called Kyle Robot

AnimatorController
AnimatorController

To Assign this Kyle Robot Controller to our Prefab, simply set the property Controller of the Animator component to point to Kyle Robot

Assigning AnimatorController
Assigning AnimatorController

Don't forget that if you do that on the instance of My Kyle Robot, you need to hit "Apply" for the Prefab itself to incorporate these changes.

Working with the Controller Parameters

The critical feature to understand with the Animator Controller is Animation Parameters which is how we are going to control our animations via scripts. In our case, we have parameters like Speed, Direction, Jump, Hi.

One of the great feature of the Animator Component is the ability to actually move the character around based on its animation, this feature is called Root Motion, and there is a property Apply Root Motion on the Animator Component that is true by default, so we are good to go.

So, in effect, to have the character walking, we just need to set the Speed Animation Parameter to a positive value and it will start walking and moving forward. Let's do this!

Animator Manager Script

Let's create a new script where we are going to control the Character based on User's input.

  1. Create a new c# script called PlayerAnimatorManager in DemoAnimator/tutorial/Scripts/
  2. Attach this script to the Prefab My Robot Kyle
  3. Surround the Class with your NameSpace Com.MyCompany.MyGame like below
  4. Surround the Start() and Update() with a region MONOBEHAVIOUR MESSAGES just for clarity

  5. Save the Script PlayerAnimatorManager

Animator Manager: Speed Control

The first thing we need to code is getting the Animator Component so we can control it.

  1. Make sure you are editing the Script PlayerAnimatorManager
  2. Create a private Variable animator of type Animator
  3. Store the Animator Component in this variable within the Start() method.

    Notice that since we require an Animator Component, if we don't get one, we log an error so that it doesn't go unnoticed and gets addressed straight away by the developer. You should always write code as if it's going to be used by someone else basically :) It's tedious but worth it in the long run.

  4. Let's now listen to the User Inputs and control the Speed Animation Parameter. Then save the script PlayerAnimatorManager.

  5. Save the Script PlayerAnimatorManager

Let's study what this script is doing:

Since our game do not allow going backward, we make sure that v is less than 0. The user is pressing the down key or 's' key, we do not allow for this and force the value to 0.

You'll notice also that we've squared both inputs, why? So that it's always a positive absolute value as well as adding some easing. Nice subtle trick right here. You could use Mathf.Abs() too, that would work fine.

We also add both inputs to control Speed, so that when just pressing left of right input, we still gain some speed as we turn.

All of this is very specific to our character design of course, depending on your game logic, you may want the character to turn in place, or have the ability to go backward, so the control of Animation Parameters is always very specific to the game.

Test, test, 1 2 3...

Let's validate what we've done so far. Make sure you have the Kyle Test Scene opened. Currently, in this scene, we only have a camera and the Kyle Robot instance, the scene is missing ground for the Robot to stand on, if you'd ran now the scene the Kyle robot would fall. Also, we won't care in the scene for lighting or any fanciness, we want to test and verify our character and scripts are working properly.

  1. Add a "Cube" to the scene. Because a cube has by default a "Box Collider", we are good to use it as is for the floor.
  2. Position it at 0,-0.5,0 because the height of the cube is 1. we want the top face of the cube to be the floor.
  3. Scale the cube to 30,1,30 so that we have room to experiment
  4. Select the Camera and move it away to get a good overview. One nice trick is to get the view you like in the "Scene View", Select the camera and Go to the menu "GameObject/Align With View", the camera will match the scene view.
  5. Final step, move My Robot Kyle 0.1 up in y, else collision is missed on start and the character goes through the floor, so always leave some physical space between colliders for the simulation to create the contacts.
  6. Play the scene, and press the up arrow key or 'a', the character is walking! You can test with all the keys to verify.

This is good, but still plenty of work ahead of us, camera needs to follow, and we can't turn yet...

If you want to work on the Camera right now, go to the dedicated section, the rest of this page will finish the Animator controls and implement the rotation.

Animator Manager Script: Direction Control

Controlling the rotation is going to be slightly more complex; we dont' want our character to rotate abruptly as we press the left and right keys, we want gentle and smoothed out rotation. Luckily, an Animation Parameter can be set using some damping

  1. Make sure you are editing the Script PlayerAnimatorManager
  2. Create a public float variable DirectionDampTime within a new region "PUBLIC PROPERTIES" of the script just above the "PRIVATE PROPERTIES" region.

  3. At the end of Update() function, add:

    So we notice straight away that animator.SetFloat() has different signature. The one we use for controlling the Speed is straightforward, but for this one takes two more parameters, one if the damping time, and one the deltaTime. Damping time makes sense: it's how long it will take to reach the desired value, but deltaTime?. It essentially lets you write code that is frame rate independent since Update() is dependant on the frame rate, we need to counter this by using the deltaTime. Read as much as you can on the topic and what you'll find when searching the web for this. It's only after you understand the concept that you'll be able to make the most out of many Unity features when it comes to animation and consistent control of values over time.

  4. Save the Script PlayerAnimatorManager
  5. Play your Scene, and use all arrows to see how well your character is walking and turning around
  6. Test the effect of DirectionDampTime: put it 1 for example, then 5 and see long it takes to reach maximum turning capability. You'll see that the turning radius increases with the DirectionDampTime.

Animator Manager Script: Jumping

with Jumping, we'll need a bit more work, because of two factors. One, we don't want the player to jump if not running, and two, we don't want the jump to be looping.

  1. Make sure you are editing the Script PlayerAnimatorManager
  2. Insert this before we catch the user inputs in the Update() method.

  3. Save the Script PlayerAnimatorManager
  4. Test. Start running and press the "alt" key, and Kyle should jump.

OK, so the first thing is to understand how we know that the animator is running or not, we do this using stateInfo.IsName("Base Layer.Run"). We simply ask if the current active state of the Animator is Run. We must append Base Layer because the Run state is in the Base Layer.

If we are in the Run state, then we listen to the Fire2 Input and raise the Jump trigger is necessary.

So, this is the full PlayerAnimatorManager Script so far:

Not too bad for few lines of code when you consider what it achieves in the scene. Now let's deal with the camera work since we are able to evolve around our world we need a proper camera behaviour to follow along.

Back to Content

Camera Setup

In the section, we are going to use the CameraWork script as is to stay focus on the "Player" prefab overall creation process. If you want to write CameraWork from scratch, please go to the next part and come back here when done.

  1. Add the component CameraWork to My Kyle Robot prefab
  2. Turn on the property Follow on Start, which effectually makes the camera instantly follow the character. We'll turn it off when we'll start network implementation.
  3. Set the property Center Offset to 0,4,0 which makes the camera look higher, and thus gives a better perspective of the environment than if the camera was looking straight at the player, we would see too much ground for nothing.
  4. Play the scene Kyle Test, and move the character around to verify the camera is properly following the character.

Back to Content

Beams Setup

Our Robot Character is still missing its weapon, let's create some laser Beams coming out of its eyes.

Adding the Beams models

For the sake of simplicity, we are going to use simple cubes and scale them to be very thin and long. There are some trick to get this done quickly: don't add a cube directly as child of the head, but instead create it move it and scale it up on its own and then attach it to the head, it will prevent guessing the proper rotation values to have your beam aligned with eyes.

The other important trick is to use only one collider for both beams. This is for the physics engine to work better, thin colliders are never a good idea, it's not reliable, so we'll make a big box collider so that we are sure to hit targets reliably.

  1. Open Kyle test Scene
  2. Add a Cube to the Scene, name it Beam Left
  3. Modify it to look like a long beam, and be positioned properly against the left eye
  4. Select My Kyle Robot instance in the hierarchy
  5. locate the Head child
    Kyle Robot Head Hierarchy
    Kyle Robot Head Hierarchy
  6. Add an empty gameobject as a child of the Head GameObject, name it Beams
  7. Drag and drop Beam Left inside Beams
  8. Duplicate Beams Left, name it Beams Right
  9. Position it so that it's aligned with the right eye
  10. Remove the Box Collider component from Beams Right
  11. Adjust Beams Left's "Box Collider" center and size of to encapsulate both beams
  12. turn Beams Left's "Box Collider" IsTrigger property to True, we only want to be informed of beams touching players, not collisions.
  13. Create a new Material, name it Red Beam, save it in 'DemoAnimator_tutorial/Scenes/'
  14. Assign Red Beam material to both beams
  15. Apply changes back to the prefab

You should have something like that now:

Kyle Robot Beams
Kyle Robot Head Beams
Kyle Robot Updated Head Hierarchy
Kyle Robot Updated Head Hierarchy

Controlling the Beams with User Input

Ok, now that we have our beams, let's plug the Fire input to trigger them.

Let's create a new C# script, called PlayerManager. Here's below the full code of the first version to get the beams to work.

The main point of this script at this stage is to activate or deactivate the Beams. When activated, Beams will effectivly trigger when collision occurs with other models, and so we'll be later on catch these trigger to affect the health of each characters.

We've also exposed a public property Beams that will let us reference the exact GameObject inside the hierarchy of My Kyle Robot Prefab. Let's look at how we need to work in order to connect Beams, because this is tricky within prefabs, since in the Assets browser, Prefabs only expose the first childs, not sub childs, and our Beams is indeed buried inside the Prefab Hierarchy, so we need to do that from an instance in a scene, and then Apply it back to the Prefab itself.

  1. Open Kyle test Scene
  2. Select My Kyle Robot in the scene Hierarchy
  3. Add the PlayerManager Component to My Kyle Robot
  4. drag and drop My Kyle Robot/Root/Ribs/Neck/Head/Beams into the PlayerManager Beams property in the inspector
  5. Apply the changes from the instance back to the Prefab

If you hit play, and press the Fire1 Input (that is left mouse or left ctrl key by default), the Beams will show up, and hide right away when you release.

Back to Content

Health Setup

Let's implement a very simple Health system that will decrease when Beams hits the player. Since it's not a Bullet, but a constant stream of energy, we'll need to account for health damage in two ways, when we get hit by a beam, and during the whole time the beam is hitting us.

  1. Open PlayerManager Script
  2. Turn PlayerManager into a Photon.PunBehaviour to expose the PhotonView component

  3. Add a public Health property within the Public Variables region

  4. Add the following two methods to the MonoBehaviour CallBacks Region. Then save Player Manager Script.

  5. Save PlayerManager Script

First of all, the two methods are almost identical, the only difference is that we decrement the health using DeltaTime during TriggerStay for the speed of decrement not to be dependant on frame rate. This is an important concept that usually applies to animation, but here, we need this too, we want the health to decrease in a predictable way across all devices, it would not be fair that on a faster computer, your health decreases faster :) Deltatime is here to guarantee consistency. Get back to us if you have questions and learn about DeltaTime by searching the Unity Community until you completely assimilated the concept, it's essential.

The second important aspect, that by now should be understood is that we only affect health on the local player, that's why we exit the method early is PhotonView is not Mine.

And last, we only want to affect health if the Object that hits us is a beam, so we check this using the tag "beam", which is how we tagged our beams GameObjects.

For easy debugging we made the Health float a public float so that it's easy to check its value while we are waiting for the UI to be built.

OK, this looks all done right? well... the health system is not complete without taking into account the game over state of the player, that occurs when health hits 0, let's do that now.

Health checking for game over

To keep things simple, when the player's health reaches 0, we simply leave the room, and if you remember, we've already created a method in the GameManager script to leave the room. It would be great if we could reuse this method instead of coding twice the same feature. Duplicated code for the same result is something that you should avoid at all costs. This will also be a good time to introduce a very handy programming concept, "Singleton". While this topic itself could fill up several tutorials, we'll only do the very minimal implementation of a "Singleton". Understanding Singleton, their variants within Unity context and how they can help creating powerful features is very important and will save you a lot of trouble. So, don't hesitate to take the time aside this tutorial to learn more about it.

  1. Open GameManager Script
  2. Add in the Public Properties Region this variable

  3. Add in the Start() method this new piece of code

  4. Save GameManager Script

Notice we've decorated the Instance variable with the [static] keyword, meaning that this variable is available without having to hold a pointer to an instance of GameManager, so you can simply do GameManager.instance.xxx() from anywhere in your code. It's very practical indeed! Let's see how that fits for our game over logic management

  1. Open PlayerManager Script
  2. In the Update() method, after we ProcessInputs, let's add this and save PlayerManager Script
  3. Save PlayerManager Script

    • notice we take into account that health could be negative, since the damages caused by the laser beams varies in strength.
    • notice we reach the LeaveRoom() public method of the GameManager instance without actually having to get the Component or anything, we just rely on the fact that we assume a GameManager component is on a GameObject in the current Scene.

Back to Content

OK, now we are diving into networking!

Next Part.
Previous Part.

 To Document Top