
Полная версия
Making PC game together
7. Character Controller
Let’s continue by writing this line of code:
CharacterController CharControl;
This is a variable of the «CharacterController’ type, which is called «CharControl’. The declaration of such a variable occurs in the same way as we did above. To better understand the analogy and remember the principle, imagine that we had a variable of the ’float’ type called ’speed’. And now we have a variable of the «CharacterController’ type called «CharControl’. Fix in your mind that all declarations in the future will occur in the same way, then the picture of what is happening will be immediately clear.
«CharacterController’ is a specialized Unity3d class created for universal and fast creation of movements and collisions of characters in the game, in our case the player himself.
Before continuing, it is important to understand that in C#, variable types can have a specific value or only references to other variables. For example, the ’float’ variables described above are value types, i.e. they can be used to specify that speed = 2.0f; Reference types, such as «CharacterController’, are only a pointer (reference) to the corresponding class. Logic suggests that if this is a just reference, then you need to get something that can be referenced, i.e. get a specific class or object. To do this, we use one of the main methods that I described earlier, it is called Start (). Let’s write a line of the Start () method in the «body» of our FPCharacter script:
using UnityEngine;
public class FPCharacter: MonoBehaviour
{
public float speed = 2.0f;
public float speedfast = 50.0f;
public float gravity = -9.8f;
CharacterController CharControl;
void Start ()
{
}
}
Let’s write a line of code to this method to obtain a specific «CharacterController’ class:
void Start ()
{
CharControl = GetComponent
}
You may notice that the class and method declarations look similar — between the curly braces is the main code, the so-called «body» of the structure.
Now the variable «CharControl’ we created before, using the function «GetComponent <> () ’ (get component), becomes a reference to the component «CharacterController’. But where is this component that we got? If the function of getting starts as we have it immediately with the words «GetComponent’ after the equal sign, then getting something happens right «here», in this game object on which the code script is located.
The general view of the program is now as follows:
using UnityEngine;
public class FPCharacter: MonoBehaviour
{
public float speed = 2.0f;
public float speedfast = 50.0f;
public float gravity = -9.8f;
CharacterController CharControl;
void Start ()
{
CharControl = GetComponent
}
}
A new word ’void’ appeared before the word «Start’, it means «empty», this is the most frequently used type when specifying a method. The fact is that a method in the C# programming language can produce some result of its calculations. In this case, instead of ’void’, the type that should be the result is put. For example, if the result of the calculations is a floating number (number with comma), then instead of ’void’, ’float’ is put.
As I said earlier, the Start () method is executed once. Now, when the program is launched, the reference to the «CharacterController’ component will be stored all the time while the program is running.
Switch back to the Unity3d editor, select our «Player’ object in the «Hierarchy’ window and click the «Add Component’ button in the «Inspector’ window. In the menu that opens, enter the first few letters in the search bar, for example: ’char’ and we can immediately see that the search results already show the required «CharacterController’ component — add it. Now we see 3 components on our «Player’ object: «Transform’, «FPCharacter’ and «CharacterController’. Set the object position (in the «Transform’ component) equal to 0,0,0, so the object will move to the zero position of the scene space, coordinate axes. Make it a rule when creating and setting up a new object to always set it to position 0,0,0 and rotation 0,0,0. This will save you from a lot of confusion associated with placing objects «where you need them». To get closer to our object in the «Scene’ window, you can always press the «F» key on the keyboard, it will focus the scene view on the selected object.
We see our object as a green wire frame, these are the boundaries of the «CharacterController’ component, i.e., the actual size and height of the player. We need to change several parameters, set the values MinMoveDistance = 0, Radius = 0.35, Height = 1.8. So we get a game character 1.8 meters tall. A unit of measurement equal to 1 in Unity3d is equal to 1 meter. Maintaining approximately the exact dimensions as in reality is very important when creating a game, since too high dimensions, for example, if the player is not 1.8, but 18 Unity3d units, can negatively affect many different aspects. The same applies to excessive reduction. Therefore, if, for example, you are going to create some 3D models in the 3D modeling program 3ds Max, then you need to set the System Unit Scale: 1 Unit = Meters, Display Unit Scale: Metric, Millimeters, rotate the rotation point (green marker) up by 90 degrees and export the 3D model in the. FBX format with Units = Meters forcedly set. With this method of sending models to the Unity3d development environment, your models will always correspond to real sizes and will always be rotated correctly. Of course, it will be necessary to maintain the correct sizes when modeling in the 3ds Max program according to its rules.
8. If () condition operator
Back to the code. It’s time for operators. An operator is a symbol or a text word that denotes some actions with data. Let’s clarify one of the most important operators of the entire C# programming language, it is called ’if’ (if) — condition. It can be characterized as one of the most fundamental along with the cyclic repetition operator ’for’ (for) — cycle. It can be conceptually said that all programs on planet Earth are variations of codes based on the ’if’ condition operator and cyclic repetition operators for, regardless of the specific programming language. Let’s write the following lines of code in the «body» of our FPCharacter script, right under the Start () method:
void Update ()
{
if (Input.GetKey (KeyCode. LeftShift))
{
_speedfast = speedfast;
}
}
First, now we have the previously described Update () method, which is launched every frame during the game program’s operation. Second, we can now see two nested «bodies» of structures: one of them is the contents of the Update () method, i.e. everything that is between its curly brackets, and the second «body» belongs to the if () operator — is, so to speak, within its scope of influence. There is an interesting feature here that must be mentioned: if you declare any variable «in the body» of the if () operator, it will be available only «in the body» of the if () operator. On the contrary, if you declare a variable «in the body» of the Update () method, it will be available both in Update () and in if ().
Now the if () operator. This is an operator that is needed to simply describe the condition: «if — then». In our case, now, this is a description of the action «if such and such a key is pressed, then do such and such an action». The Input.GetKey (KeyCode. LeftShift) code uses a class and a method to determine whether the user has pressed a certain key on the keyboard, in this case, «Left Shift’. This entire expression is the condition of the if () operator. If the key is pressed, then the code located «in the body» of the if () operator is executed.
What’s interesting is that the «key pressed» check will happen, for example, 60 times per second if the game runs at 60 FPS — this is exactly what was described at the beginning of the book.
Let’s continue. «In the body» of the if () operator, one new variable '_speedfast’ has appeared. First, we declare it, «in the body» of our «FPCharacter’ class, immediately under the previously declared speed variables, we write the following line of code:
float _speedfast;
Now we have declared a ’float’ type variable without specifying the ’public’ keyword. This is done so that it does not appear in the «Inspector’ window of the editor, since it does not require manual installation and is not used anywhere except this script (FPCharacter), i.e. it is internal or local. Also, we have not set its value, so in this case this variable is equal to 0.
The general form of our script code is now as follows:
using UnityEngine;
public class FPCharacter: MonoBehaviour
{
public float speed = 2.0f;
public float speedfast = 50.0f;
public float gravity = -9.8f;
float _speedfast;
CharacterController CharControl;
void Start ()
{
CharControl = GetComponent
}
void Update ()
{
if (Input.GetKey (KeyCode. LeftShift))
{
_speedfast = speedfast;
}
}
}
Now, when you press the Left Shift key on the keyboard, the '_speedfast’ variable will take the value of the ’speedfast’ variable. But now such an assignment of values will make little sense since it has no reverse effect, so let’s continue examining the if () operator. The ’speedfast’ variable is needed to speed up the movement of our character while holding the Left Shift key, i.e. when the Left Shift key is not pressed, the character’s speed should be normal. To implement this, we write the following lines of code, right after the curly bracket of the «body» of the if () operator:
else
{
_speedfast = 1;
}
We have a new part of the if () operator, which is called ’else’ (else), this is branching, i.e. if the condition specified by the code in the if () operator is not met for some reason, then the execution of the condition can be branched — to execute a piece of code located «in the body» of the else operator. Finally, the if () operator in our case will look like this:
if (Input.GetKey (KeyCode. LeftShift))
{
_speedfast = speedfast;
}
else
{
_speedfast = 1;
}
Thus, each frame, many times in the Update () method, a check will be made for the Left Shift key being pressed: if it is pressed, then the internal (local) ’speed’ variable will be equal to the manually set ’speedfast’ variable, or if the Left Shift is not pressed, then the local variable will be equal to 1. It is necessary to remember that the ’else’ operator cannot be used without if (). There are also other condition branches when additional data for checking could be added to the ’else’ operator. This will be discussed later in the book. Let’s continue our «Player’ script. Let’s write the following lines of code «in the body» of the Update () method:
float offsetX = Input.GetAxis («Horizontal») * speed * _speedfast;
float offsetZ = Input.GetAxis («Vertical») * speed * _speedfast;
We declare two local variables offsetX and offsetZ, which read the pressing of the orientation keys in space. Again we see the Unity3d class called Input and getting the coordinate axes «Horizontal’ and «Vertical’. The two words «Horizontal’ and «Vertical’ are, so to speak, collective for all the buttons of the keyboard, joystick, etc., divided into 2 categories: horizontal and vertical. Vertical in this case is understood as the opposite of the horizontal axes, because the character’s movement is left-right and forward-backward, but not left-right, up-down. Receiving data through the Input class from the player’s keyboard, we also multiply the data by the speed and by the fast speed (for example, running), which we have just analyzed. The asterisk sign means ordinary multiplication.
9. Movement
Let’s add the following line of code, which forms a local variable of a vector in space, in three coordinates — X, Y, Z:
Vector3 movement = new Vector3 (offsetX, 0, offsetZ);
«Vector3» in Unity3d is actually just 3 float variables described earlier put together, but is used to describe position in space and direction of movement.
We see a new operator called ’new’, this operator is needed to create a new instance of the data type. In this case, we create a new vector with two variables offsetX and offsetZ. The Y variable, which describes the up-down movement, is missing and is not needed now.
The correct directions along the axes in Unity3d can be easily remembered using associative memorization. For example, to remember that the Z coordinate is forward movement, you can imagine that Z is like a zigzag movement of a boat on water going into the distance, i.e. we are sailing forward (forward-backward). Y is like an hourglass that only flows down (up-down), and for X there is remains only left-right.
Now we have made a movement vector, which will be responsible for the movement of our character forward-backward and left-right, depending on which keys on the keyboard the player presses. But now, with our current code, the movement forward-backward and left-right will be slower than the diagonal movements. This will happen because the diagonal movement will take into account the offset values of both offsetX and offsetZ together. To make the movement uniform in all directions, you need to add the following code as next line in your code:
movement = Vector3.ClampMagnitude (movement, speedfast);
«ClampMagnitude’ is an internal Unity3d method that will limit the magnitude of our motion vector so that the speed of movement is uniform in all directions.
Let’s add next line of code:
movement. y = gravity;
A little higher we did not use the Y variable, which is needed for moving up and down. Now we add it too. In our movement vector there are 3 variables and we can get or set each of them separately, now it looks like ’movement. y’, i.e. we set this variable equal to gravity so that our future player can freely fall down if he has no support under his feet.
Next we must make sure that our future player can move the same way speed on computers of different power, at different game speeds, at different game FPS, etc. To do this, we will write the following line of code:
movement = movement * Time.deltaTime;
In this line of code we link our movement vector via the «Time’ class (a multitasking class of the game time category) with the rendering time of the previous frame — ’deltaTime’. Thus, any movement in the game can be calculated regardless of the specific computer and its power. Always use multiplication by 'Time.deltaTime’ in any code based on some changes in values over time.
Also, the C# language provides the ability to write the last line of code, so to speak, in a shortened form, which is what we will do:
movement *= Time.deltaTime;
In this form, the line of code completely corresponds to the previous one, so we will leave it. Now we need to «shoe another leg», namely, our character will soon be able to move, but as soon as he turns, his movement will be disrupted, i.e. the character will look somewhere to the side, but will continue to move strictly forward-backward, left-right. To prevent this, we need to transform the calculations of our movement vector from local coordinates to global ones. Let’s write the following line:
movement = transform.TransformDirection (movement);
The word ’transform’ is a reference to the corresponding component, which is located on our game object Player. Here is the same technology as with «GetComponent <> ()» described earlier. If after the equal sign immediately write ’transform’ or «GetComponent <> ()», it means that the operations are performed with the current game object on which our code script is located. «TransformDirection ()» is a Unity3d method that transforms our vector into world coordinates (global). Let me briefly explain that world coordinates are the basis of the entire 3D scene of the Unity3d development environment, and they are immutable. And local coordinates belong to some object of the scene and constantly change their directions, depending on the rotation of this object.
Now our character’s movements forward-backward, left-right will occur in accordance with its rotation.
The character’s movement program is ready and needs to be executed, or rather applied to our Player game object. To do this, we will communicate our movement vector to the «CharacterController’ component using its internal «Move’ method, and add a line of code:
CharControl.Move (movement);
Thus, our lines of code, running infinitely every frame in the Update () method, will read the keys pressed by the player, form a vector of the direction and speed of the player’s movement and move him. Now the entire written code looks like this:
using UnityEngine;
public class FPCharacter: MonoBehaviour
{
public float speed = 2.0f;
public float speedfast = 50.0f;
public float gravity = -9.8f;
CharacterController CharControl;
float _speedfast;
void Start ()
{
CharControl = GetComponent
}
void Update ()
{
if (Input.GetKey (KeyCode. LeftShift))
{
_speedfast = speedfast;
}
else
{
_speedfast = 1;
}
float offsetX = Input.GetAxis («Horizontal») * speed * _speedfast;
float offsetZ = Input.GetAxis («Vertical») * speed * _speedfast;
Vector3 movement = new Vector3 (offsetX, 0, offsetZ);
movement = Vector3.ClampMagnitude (movement, speedfast);
movement. y = gravity;
movement *= Time.deltaTime;
movement = transform.TransformDirection (movement);
CharControl.Move (movement);
}
}
Let’s save our script and switch to the Unity3d editor.
10. Launching the Player
Let’s test our player, see how he moves. To do this, first of all, you need to switch all the editor windows to a convenient layout so that you can see both the «Game’ window and the «Scene’ window at once. In the upper right corner of the editor, click the «Select editor layout’ button and choose from the list — «2 by 3», this is what you need.
Now let’s make something like the ground so that our player does not fall down into the abyss. Click the GameObject tab> 3D Object> Plane at the top. A Plane game object with a collider will appear in the scene. Due to the fact that the calculation of collisions and physical interactions is a heavy-duty task even for today’s computers, the Unity3d development environment has «Colliders’ components (from the English collide — to collide), which perform the functions of virtual surfaces used to calculate collisions and simulate the physical behavior of objects. For example, a complex (i.e. multi-polygonal) sphere object can be enclosed in a collider in the form of a simple cube. Then such a sphere will become an obstacle to passing through it and can already be used in physical calculations. The main advantage in this case will be that the computer will need to calculate only 6 surfaces of the virtual cube for collisions, and not hundreds or more surfaces of the sphere itself.
So, the plane we just created already has a collider, i.e. it can be used immediately as ground or an obstacle for falling down. Place our Player in the «Scene’ window (green wire frame) slightly above this plane so that when the program starts, it does not «fall through the ground» (if suddenly the movement markers or better to say arrows disappear, you can press the «W» key to activate them again. It is also necessary that the «Player’ object is selected in the «Hierarchy’ window).
Press the Play button at the top in the middle (don’t forget to click once in the «Game’ window so that the playback focus is the same as in the finished game, otherwise the game controls will not work). Now you can press the WASD and arrow keys, and our game character will start moving and may even fall if he goes beyond the plane boundaries. If you press the Left Shift while moving, the player’s movement speed will increase. But as we understand, there is no rotation of the virtual head using a computer mouse, so we will add this feature. Press Stop (same place at the top in the middle) and return to our «FPCharacter’ code.
Write the following line of code «in the body» of the Update () method:
transform.Rotate (0, Input.GetAxis («Mouse X») * SensHoriz, 0);
And «in the body» of the «FPCharacter’ class we declare a variable:
public float SensHoriz = 2.0f;
The line of code we wrote will rotate our Player game object using the Rotate () method along the Y axis by reading the mouse movements. The «SensHoriz’ variable is needed to adjust the sensitivity or speed of the head rotation. But this rotation will only happen to the sides, so it’s time to add the «head itself» and the remaining up-down rotation.
11. Head
Let’s go back to the editor, select our Player object in the «Hierarchy’ window, right-click on it and «Create Empty’. This way, our Player object will have a child object, let’s call it «Head’ (To rename any object in Unity3d, you can press the «F2» key). This «Head’ object will be our head. We need to set the Y value to 0.7 for the «Position’ parameter of its «Transform’ component, since we understand that the head should be above the «stomach» level. Now let’s add eyes to our player’s head, to do this, on the newly created «Head’ object in the «Inspector’ window, click «Add Component’ and enter «Camera’ in the search, add it. If your scene contains (see the «Hierarchy’ window) a camera called «Main Camera’, then delete it, since it is no longer needed. Let’s go back to the code.

