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.
- The Prefab Basics
- Animator Setup
- User Inputs
- Camera Setup
- Beams Setup
- Health Setup
The Prefab Basics
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.
Kyle.fbx is located in /Assets/Photon Unity Networking/Demos/Shared Assets/
Here is one way to start using the
Kyle Robot.fbx for your player:
- 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/'
- Create a new empty scene, and save it as
Kyle Testin /PunBasics_tutorial/Scenes/
- Drag and drop
Robot Kyleonto the "Scene Hierarchy".
- Rename the GameObject you've just created in the hierarchy to
My Robot Kyle
- Drag and drop
My Robot Kyleinto /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
Now we can start working with it.
- 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.
Double click on
My Kyle Robotto 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.
In the CharacterController Component change the
Center.yproperty to 1 ( half of its
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".
Assigning an Animator Controller
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
To Assign this
Kyle Robot Controller to our Prefab, simply set the property
Controller of the Animator component to point to
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
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.
- Create a new c# script called
- Attach this script to the Prefab
My Robot Kyle
- Surround the Class with your NameSpace
- Save the Script
Animator Manager: Speed Control
The first thing we need to code is getting the Animator Component so we can control it.
- Make sure you are editing the Script
- Create a private Variable
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.
Save the Script
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.
- 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.
- Position it at
0,-0.5,0because the height of the cube is 1. we want the top face of the cube to be the floor.
- Scale the cube to
30,1,30so that we have room to experiment
- 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.
- Final step, move
My Robot Kyle0.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.
- 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
- Make sure you are editing the Script
Create a public float variable
DirectionDampTimewithin a new region "PUBLIC PROPERTIES" of the script just above the "PRIVATE PROPERTIES" region.
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
Speedis 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.
- Save the Script
- Play your Scene, and use all arrows to see how well your character is walking and turning around
- 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
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.
- Make sure you are editing the Script
Insert this before we catch the user inputs in the Update() method.
- Save the Script
- 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
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
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.
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.
- Add the component
My Kyle Robotprefab
- 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.
- Set the property
0,4,0which 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.
- Play the scene
Kyle Test, and move the character around to verify the camera is properly following the character.
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.
- Add a Cube to the Scene, name it
- Modify it to look like a long beam, and be positioned properly against the left eye
My Kyle Robotinstance in the hierarchy
- locate the Head child
- Add an empty gameobject as a child of the
HeadGameObject, name it
- Drag and drop
Beams Left, name it
- Position it so that it's aligned with the right eye
- Remove the Box Collider component from
Beams Left's "Box Collider" center and size of to encapsulate both beams
Beams Left's "Box Collider"
True, we only want to be informed of beams touching players, not collisions.
- Create a new Material, name it
Red Beam, save it in 'DemoAnimator_tutorial/Scenes/'
Red Beammaterial to both beams
- Apply changes back to the prefab
You should have something like that now:
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
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.
My Kyle Robotin the scene Hierarchy
- Add the
My Kyle Robot
- drag and drop
My Kyle Robot/Root/Ribs/Neck/Head/Beamsinto the
Beamsproperty in the inspector
- 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.
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.
PlayerManagerinto a Photon.PunBehaviour to expose the PhotonView component
Add a public
Healthproperty within the
Add the following two methods to the
MonoBehaviour CallBacksRegion. Then save
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
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.
Add in the Public Properties Region this variable
Add in the Start() method this new piece of code
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
- In the Update() method, after we ProcessInputs, let's add this and save
- 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.
OK, now we are diving into networking!