Now that we have prepared our project to start coding, let's create our first behavior. We will see the basics of how to move objects through scripting using the Transform component, which will be applied for the movement of our Player with the Keys, the constant movement of bullets, and other objects' movement. Also, we will see how to create and destroy objects during the game, such as bullets our Player and Enemy shoot and the Enemy Wave Spawners. These actions can be used in several other scenarios, so we will explore a few to reinforce the idea.
In this chapter, we will examine the following scripting concepts:
We will start scripting components to do the previously mentioned movement behavior, and then we will continue with object creation and destruction.
Almost every object in the game moves one way or another, the Player character with the keyboard, the Enemies through AI, the bullets simply move forward, and so on. There are several ways of moving objects in Unity, so we will start with the simplest one, that is, through the Transform component.
In this section, we will examine the following movement concepts:
First, we will explore how to access the Transform component in our script to drive the Player movement, to later apply movement based on the Player's keyboard Input. Finally, we are going to explore the concept of Delta Time to make sure the movement speeds are consistent in every computer. We are going to start learning about the Transform API to master simple movement.
Transform is the component that holds the Translation, Rotation, and Scale of an object, so every movement system such as Physics or Pathfinding will affect this component. Anyway, sometimes we want to move an object in a specific way according to our game by creating our own script, which will handle the movement calculations we need and modify Transform to apply them.
One concept implied here is that components alter other components. The main way of coding in Unity is to create components that interact with other components. Here, the idea is to create one that accesses another and tells it to do something, in this case, to move. To create a script that tells Transform to move, do the following:
Important Note
Every component inherits a transform field (to be specific, a getter) that is a reference to the Transform of the GameObject the component is placed, it represents the sibling Transform of our component. Through this field, we can access the Translate function of the Transform, which will receive the offset to apply in X, Y, Z local coordinates:
Important Note
I recommend you temporarily disable the Playable Director object and increase the priority of CM vcam1, which will disable the introduction cutScene and make the Character Following Camera activated by default, reducing the time needed to test the game. Another option is to create a secondary Scene just to test the Player Movement, something that is actually done in real projects, but for now, let's keep things simple.
You will notice that the Player is moving too fast and that's because we are using a fixed speed of 1 meter, and because update is executing all frames, we are moving 1 meter per frame. In a standard 30 FPS game, the Player will move 30 meters per second, which is too much. We can control the Player speed by adding a speed field and using the value set in the editor instead of the fixed value of 1. You can see one way to do this in the next screenshot, but remember the other options we discussed in the previous chapter (using the Serialize Field attribute):
Now if you save the script to apply the changes and set the Speed of the Player in the Editor, you can play the game and see the results. In my case, I used 0.1, but you might need another value (more on this later):
You will notice that the Player will move automatically. Now let's see how to execute the movement based on Player Input such as keyboard and mouse input.
Unlike NPCs, we want the Player movement to be driven by the Player's Input, based on which keys they press, the mouse movement, and so on. We can recall the original key mapping we designed in Chapter 1, Designing a Game from Scratch, from the next two tables:
Check out the mouse mapping in the following table:
Important Note
The latest Unity version has a new Input system, but requires some settings before using it. For now we will use the default Input system to simplify our scripts
To know whether a certain key is pressed, such as the Up arrow, we can use the Input.GetKey(KeyCode.W) line, which will return a Boolean, indicating whether the key specified in the KeyCode enum is pressed. We can change the key to check the changing of the KeyCode enum value and combine the GetKey function with an If statement to make the translation execute only when that condition is met (the key is currently.
Important Note
The latest Unity version has a new Input system, but requires some settings before using it. For now we will use the default Input system to simplify our scripts.
Let's start implementing the keyboard movement by doing the following:
Important Note
Remember that using If without brackets means that only the line inside the if statement is going to be the one right next to the if statement, in this case, the transform.Translate calls. Anyway, in the final code, I recommend keeping the brackets.
Something to take into account is that, first, we have another way to map several keys to a single action by configuring the Input Manager, a place where action mappings can be created, and second, at the time of writing this, Unity released an experimental new Input system that will replace this. For now, we will use this one because it is simple enough to start a basic game and because experimental Unity packages can have bugs or changes in the way they work. In games with complex input, controls are recommended to look for more advanced tools.
Now, let's implement the mouse controls. In this section, we will only cover rotation with mouse movement; we will shoot bullets in the next section. In the case of mouse movement, we can get a value by saying how much the mouse has moved both horizontally or vertically. This value isn't a Boolean but a number, a type of input usually known as Axis, a number that will indicate the intensity of the movement with bigger values, and the direction with the sign of the number. For example, if Unity's "Mouse X" axis says 0.5, it means that the mouse moved to the right with a moderate speed, but if it says -1, it moved quickly to the left, and if there is no movement, it will say 0. The same goes for sticks in gamepads; the Horizontal axis represents the horizontal movement of the left stick in common joysticks, so if the Player pulls the stick fully to the left, it will say -1.
We can create our own axes to map other common joysticks' pressure-based controls, but for our game, the default ones are enough. To detect mouse movement, do the following:
Now that we have completed our movement script, we need to refine it to work in every machine by exploring the concept of Delta Time.
Unity's update loop executes as fast as the computer can. You can specify in Unity the desired frame rate but achieving that depends exclusively on whether your computer can reach that, which depends on lots of factors, not only hardware, so you cannot expect to always have consistent FPS. You must code your scripts to handle every possible scenario. Our current script is moving at a certain speed per frame, and the per frame part is important here.
We have set the movement speed to 0.1, so if my computer runs the game at 120 FPS, the Player will move 12 meters per second. Now, what happens in a computer where the game runs at 60 FPS? As you may guess, it will move only 6 meters per second, making our game to have inconsistent behavior across different computers. And here is where Delta Time saves the day.
Delta Time is a value that tells us how much time has passed since the previous frame. This time depends a lot on our game's graphics, amount of entities, physics bodies, audio, and countless aspects that will dictate how fast your computer can process a frame. As an example, if your game runs at 10 FPS, it means that, in a second, your computer can process the update loop 10 times, meaning that each loop takes approximately 0.1 seconds; in that frame, Delta Time will provide that value. In the next diagram, you can see an example of 4 frames taking different times to process, which can happen in real-life cases:
Here, we need to code in such a way to change the per frame part of the movement to per second; we need to have consistent movement per second across different computers. A way to do that is to move proportionally to Delta Time: the higher the Delta Time value, the longer that frame is, and the greater the movement should be to match the real time that has passed since the last update. We can think about our speed field's current value in terms of 0.1 meters per second; our Delta Time saying 0.5 means that half a second has passed, so we should move half the speed, 0.05. After two frames, a second has passed, and the sum of the movements of the frames (2 x 0.05) matches the target speed, 0.1. Delta Time can be interpreted as the percentage of a second that has passed.
To make Delta Time affect our movement, we should simply multiply our speed by Delta Time every frame because Delta Time can be different every frame, so let's do that:
We just learned how to mix the Input system of Unity, which tells us about the state of the keyboard, mouse, and other input devices, with the basic Transform movement functions. This way, we can start making our game feel more dynamic.
Now that we have finished the Player's movement, let's discuss how to make the Player shoot bullets using Instantiate functions.
We have created lots of objects in the Editor that define our level, but once the game begins, and according to the Player actions, new objects must be created to better fit the scenarios generated by Player interaction. Enemies might need to appear after a while, or bullets must be created according to the Player input; even when enemies die there's a chance of spawning some power-up. This means that we cannot create all the necessary objects beforehand but should create them dynamically, and that's done through scripting.
In this section, we will examine the following spawning concepts:
We will start seeing the Unity Instantiate function, which allows us to create instances of Prefabs on runtime, such as when pressing a key, or in a time-based fashion, such as making our enemy spawn bullets every certain amount of time. Also, we will learn how to destroy these objects to prevent our Scene from starting to perform badly due to too many objects being processed.
Let's start with how to shoot bullets according to the Player's Input.
To spawn an Object in runtime or Play Mode, we need a description of the Object, which components it has, and its settings and possible sub-objects. You might be thinking about Prefabs here, and you are right, we will use an instruction that will tell Unity to create an instance of a Prefab via scripting. Remember that an instance of a Prefab is an Object created based on the Prefab, basically a clone of the original one.
We will start shooting the Player's bullets, so first let's create the bullet Prefab by doing the following:
I suggest you try to solve this by yourself first and look at the screenshot in the next step with the solution later as a little challenge to recap the movement concepts we saw previously. If you don't recall how to create a script, please read Chapter 13, Introduction to Unity Scripting with C#, and check the previous section to see how to move objects.
Now that we have our bullet Prefab, it is time to instantiate it (clone it) when the Player presses a key. To do that, do the following:
We need a way for the script to have access to the Prefab to know which Prefab to use from the probably dozens that we will have in our project. All of the data our script needs that depends on the desired game experience is in the form of a field, such as the speed field used so far, so in this case, we need a field of the GameObject type, a field that can reference or point to a specific Prefab, which can be set using the Editor.
Important Note
As you might guess, we can use the GameObject type to not only reference Prefabs but also other objects. Imagine an Enemy AI needing a reference to the Player object to get its position, using a GameObject to link the two objects. The trick here is considering that Prefabs are just regular Gameobjects that live outside the Scene; you cannot see them but they are in memory, ready to be copied or instantiated. You will only see them through copies or instances that are placed in the Scene with scripting or via the Editor as we have done so far.
This way, we tell our script that the bullet to shoot will be that one. Remember to drag the Prefab and not the bullet in the Scene (that should be deleted by now).
We will shoot the bullet when the Player presses the left mouse button as specified in the design document, so let's place the proper if statement to handle that in the update event function, like the one shown in the next screenshot:
You will notice that this time, we used GetKeyDown instead of GetKey, the former being a way to detect the exact frame the pressing of the key started; this if statement will execute its code only in that frame, and until the key is released and re-pressed, it won't enter again. This is one way to prevent bullets from spawning in every frame, but just for fun, you can try using GetKey instead to see how it would behave. Also, zero is the mouse button number that belongs to left-click, one being right-click and two middle-click.
We can use the Instantiate function to clone the Prefab, passing the reference to it as the first parameter. This will create a clone of the mentioned Prefab that will be placed in the Scene:
If you save the script and play the game, you will notice that when you press the mouse, a bullet spawn, but probably not in the place you are expecting, and if you don't see it, try to check the Hierarchy for new objects; it will be there. The problem here is that we didn't specify the desired spawn position, and we have two ways of settings that, which we will see in the next steps.
The first way is to use the transform.position and transform.rotation inherited fields from MonoBehaviour, which will tell us our current position and rotation. We can pass them as the second and third parameters of the Instantiate function, which will understand that this is the place we want our bullet to appear. Remember that it is important to set the rotation to make the bullet face the same direction as the Player, so it will move that way:
The second way, which will be longer but will give us more flexibility to change other aspects of the object, is by using the previous version of Instantiate, but saving the reference returned by the function, which will be pointing to the clone of the Prefab. Having a reference to the instantiated bullet allows us to change whatever we want from it, not only position but also rotation, but for now, let's limit ourselves to position and rotation. In this case, we will need the following three lines; the first will instantiate and capture the clone reference, the second will set the position of the clone, and the third will set the rotation. You will notice we will also use the transform.position field of the clone, but this time to change its value by using the = (assignment) operator:
Use the version you like the most—both do the same. Remember that you can check the project repository to see the full script finished. Now you can save the file with one of the versions and try to shoot.
If you try the script so far, you should see the bullet spawn in the Player's position, but in our case, it will probably be the floor. The problem here is that the robot pivot is there, and usually, every humanoid character has the pivot there. We have several ways to fix that, the most flexible one being to create a shoot point, an empty Player's child Object placed in the position we want the bullet to spawn. We can use the position of that Object instead of the Player's position by doing the following:
You will notice that now shooting and rotating with the mouse has a problem; when moving the mouse to rotate, the pointer will fall outside the Game View, and when clicking, you will accidentally click the Editor, losing the focus on the Game View, so you will need to click the Game View again to regain focus and use Input again. A way to prevent this is to disable the cursor while playing. To do this, follow these steps:
Now that we've covered the basics of object spawning, let's see an advanced example by combining it with timers.
Not entirely related to spawning, but usually used together, timing actions is a common task in videogames. The idea is to schedule something to happen later; maybe we want the bullet to be destroyed after a while to prevent memory overflow, or we want to control the spawn rate of enemies or when they should spawn, and that's exactly what we are going to do in this section, starting with the second, the Enemy waves.
The idea is that we want to spawn enemies at a certain rate at different moments of the game; maybe we want to spawn enemies from seconds 1 to 5 at a rate of 2 per second, getting 10 enemies, before giving the Player up to 20 seconds to finish them and programming another wave to start at second 25. Of course, this depends a lot on the exact game you want, and you can start with an idea like this one and modify it after some testing to find the exact way you want the wave system to work. In our case, we will exemplify timing with the previously mentioned logic.
First of all, we need an Enemy, and for now, we will simply use the same robot character as the Player, but adding a Forward Movement script to simply make it move forward; later in this book, we will add AI behavior to our enemies. I suggest you try to create this Prefab by yourself and look at the next steps once you have tried, to see the correct answer:
Now, to schedule actions, we will use the Invoke functions suite, a set of functions to create timers that are basic but enough for our requirements. Let's use it by doing the following:
We will use the InvokeRepeating function to schedule a custom function to repeat periodically. You will need to schedule the repetition just once; Unity will remember that, so don't do it every frame. This is a good case to use the Start event function instead. The first argument of the function is a string (text between quotation marks) with the name of the other function to execute periodically, and unlike Start or update, you can name the function whatever you want. The second argument is the time to start repeating, our startTime field, in this case. Finally, the third argument is the repetition rate of the function, how much time needs to happen between each repetition, this being the spawnRate field. You can find how to call that function in the next screenshot, along with the custom Spawn function:
If you test this script setting the Prefab startTime and spawnRate fields to some test values, you will notice that the enemies will start spawning but never stop, and you can see that we haven't used the endTime field so far. The idea is to call the CancelInvoke function, one function that will cancel all InvokeRepeating calls we made, but after a while to then use the Invoke function, which works similarly to InvokeRepeating, but this one executes just once. In the next screenshot, you can see how we added an Invoke call to the CancelInvoke function in Start, using the endTime field as the time to execute CancelInvoke. This will execute CancelInvoke after a while, canceling the first InvokeRepeating call that spawns the prefab:
Important Note
This time, we used Invoke to delay the call to CancelInvoke. We didn't use a custom function because CancelInvoke doesn't receive arguments. If you need to schedule a function with arguments, you will need to create a parameterless wrapper function that calls the one desired and schedule that one, as we did with Spawn, where the only intention is to call Instantiate with specific arguments.
You should see the enemies being spawned one next to the other, and because they move forward, they will form a row of enemies. This behavior will change later with AI:
If you want, you can create several Wave Spawner objects, scheduling waves for the later stages of the game. Remember the difficulty balance we discussed in Chapter 1, Designing a Game from Scratch; you will need to try this with the final AI for the enemies, but the number of waves, times, and spawn rates will determine the difficulty of the game, and that's why it is important to set those values properly. Also, there are plenty of methods to create waves of enemies; this is just the simplest one I could find. You may need to change it according to your game.
Now that we have discussed timing and spawning, let's discuss timing and destroying objects to prevent our bullets from living forever in memory.
This is going to be super short but is a widely used function, so it deserves its own section. We can use the Destroy function to destroy Object instances. The idea is to make the bullets have a script that schedules their own auto-destruction after a while to prevent them from living forever. We will create the script by doing the following:
The Destroy function expects the object to destroy as the first argument, and here, we are using the gameObject reference, a way to point to our GameObject to destroy it. If you use the this pointer instead, we will be destroying only the Autodestroy component; remember that in Unity, you never create Gameobjects but components to add to them:
Of course, we don't want the bullet to be destroyed as soon as it is spawned, so we need to delay the destruction. You may be thinking about using Invoke, but unlike most functions in Unity, Destroy can receive a second argument, which is the time to wait until destruction.
Now, we can create and destroy objects at will, which is something very common in Unity scripting.
Important Note
Look into the concept of object pooling; you will learn that sometimes creating and destroying objects is not that performant.
We have created our first real scripts, which provide useful behavior. We discussed how to instantiate Prefabs via scripting, to create objects at will according to the game situation. Also, we saw how to schedule actions, in this case, spawning, but this can be used to schedule anything. Finally, we saw how to destroy created objects, to prevent increasing the number of objects to an unmanageable level. We will be using these actions to create other kinds of objects, such as sounds and effects, later in this book.
Now you are able to create any type of movement or spawning logic your objects will need and make sure those objects are destroyed when needed. You might think that all games move and create shooting systems the same way, and while they are similar, being able to create your own movement and shooting scripts allows you to customize those aspects of the game to behave as intended and create the exact experience you are looking for.
In the next chapter, we will be discussing how to detect collisions to prevent the Player and bullets from passing through walls and much more.