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:
- Implementing movement
- Implementing spawning
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:
- Moving objects through Transform
- Using Input
- Understanding Delta Time
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:
- Create and add a script called Player Movement to our character. In this case, it would be the animated robot object we created previously. Remember to move the script to the Scripts folder after creation:
- Double-click the created script asset to open an IDE to edit the code.
- We are moving, and the movement is applied every frame, so this script will use only the update function or method, and we can remove Start (it is a good practice to remove unused functions):
- To move our object along its forward axis (Z-axis), add the transform.Translate(0,0,1); line to the update function as shown in the following image.
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:
- Save the file and play the game to see the movement.
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:
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.
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:
- Make the forward movement execute only when the W key is pressed, as shown in the next screenshot:
- We can add other movement directions with more If statements. We can use S to move backward and A and D to move left and right, as shown in the following screenshot. Notice how we used the minus sign to invert the speed when we needed to move in the opposite axis direction:
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.
- If you also want to consider the arrow keys, you can use an OR inside if, as shown in the following screenshot:
- Save the changes and test the movement in Play Mode.
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:
- Use the Input.GetAxis function inside update, next to the movement if statements, as shown in the following screenshot, to store the value of this frame's mouse movement into a variable:
- Use the transform.Rotate function to rotate the character. This function receives the degrees to rotate in the X-, Y-, Z-axis order. In this case, we need to rotate horizontally, so we will use the mouse movement value as the Y-axis rotation, as shown in the next screenshot:
- If you save and test this, you will notice that the Player will rotate but very quickly or slowly, depending on your computer. Remember, this kind of value needs to be configurable, so let's create a rotationSpeed field to configure the speed of the Player in the Editor:
- Now we need to multiply the mouse movement value by the speed, so, depending on rotationSpeed, we can increase or reduce the rotation amount. As an example, if we set a value of 0.5 in the rotation speed, multiplying that value by the mouse movement will make the object rotate at half the previous speed, as shown in the following screenshot:
- Save the code and go back to the Editor to set the rotation speed value. If you don't do this, the object won't rotate because the default value of the float type fields is 0:
- You might also notice that the camera controlled by Cinemachine might have a delay to adapt to the new Player position. You can adjust the interpolation speed as I did in the next screenshot to have more responsive behavior:
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 access Delta Time using Time.deltaTime. We can start affecting the movement by multiplying Delta Time in every Translate:
- We can do the same with the rotation speed, chaining the mouse and speed multiplications:
- If you save and play the game, you will notice that the movement will be slower than before and that's because now 0.1 is the movement per second, meaning 10 centimeters per second, which is pretty slow; try raising those values. In my case, 10 for speed and 180 for rotation speed was enough, but the rotation speed depends on the Player's preferred sensitivity, which can be configurable, but let's keep that for another time.
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.
- Spawning objects
- Timing actions
- Destroying objects
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:
- Create a sphere in GameObject | 3D Object | Sphere. You can replace the sphere mesh with another bullet model if you want, but we will keep the sphere in this example for now.
- Rename the sphere Bullet.
- Create a material by clicking on the + button of the Project window and choosing the option Material, and call it Bullet. Remember to place it inside the Materials folder.
- Check the Emission checkbox in the material and set the emission Map and Base Map colors to red. Remember, the Emission color will make the bullet shine, especially with the bloom effect in our post-processing volume:
- Apply the Material to the Sphere by dragging the material to it.
- Set the Scale to a smaller value—(0.3, 0.3, 0.3) worked in my case:
- Create a script called ForwardMovement to make the bullet constantly move forward at a fixed speed.
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.
- The next screenshot shows you what the script should look like:
- Add the script (if not already there) to the bullet, and set the speed to a value you see fit. Usually, bullets are faster than the Player, but that depends on the Player experience you want to get (remember the questions in Chapter 1, Designing a Game from Scratch). In my case, 20 worked fine. Test it by placing the bullet near the Player and playing the game:
- Drag the bullet GameObject instance to the Prefabs folder to create a Bullet Prefab. Remember that the Prefab is an asset that has a description of the created bullet, like a blueprint of how to create a bullet:
- Remove the original bullet from the Scene; we will use the Prefab to create bullets when the Player presses a key (if ever).
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:
- Create and add a script to the Player's GameObject (the Robot) called PlayerShooting and open it.
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.
- Adding the field code would look like this:
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.
- In the Editor, click on the circle to the right of the property and select the Bullet Prefab. Another option is to just drag the Bullet Prefab to the property:
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:
- Create an empty GameObject in GameObject | Create Empty. Rename it ShootPoint.
- Make it a child of the Player's Robot Character Object, and place it where you want the bullet to appear, probably a little higher and further forward than the original spawn position:
- As usual, to access the data of another Object, we need a reference to it, such as the Prefab reference, but this time that one needs to point to our ShootPoint. We can create another GameObject type field, but this time drag ShootPoint instead of the Prefab. The script and the Object set would look as shown in the following screenshot:
- We can access the position of shootPoint by using the transform.position field of it again, as shown in the following screenshot:
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:
- Add a Start event function to our Player Movement Script.
- Add the two lines you can see in the following screenshot to your script. The first one will make the cursor visible, and the second one will lock it in the middle of the screen, so it will never abandon the Game View. Consider that latter; you will need to reenable the cursor when you switch back to the main menu or the pause menu, to allow the mouse to click the UI buttons:
- Save and test this. If you want to stop the game, you could either press Ctrl + Shift + P (command + Shift + P on Mac) or press the Esc key to reenable the mouse. Both only work in the Editor; in the real game, you will need to reenable manually.
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:
- Drag the Robot FBX model to the Scene to create another Robot character, but rename it Enemy this time.
- Add the ForwardMovement script created for the bullets but this time to Enemy and set it at a speed of 10 for now.
- Drag the Enemy GameObject to the Project to create a Prefab based on that one; we will need to spawn it later. Remember to choose Prefab Variant, which will keep the Prefab linked with the original model to make the changes applied to the model automatically apply to the Prefab. Remember also to destroy the original Enemy from the Scene.
- Create an Empty GameObject at one end of the Base and call it Wave1a.
- Create and add a script called WaveSpawner to it.
- Our spawner will need four fields: the Enemy Prefab to spawn, the game time to start the wave, the endTime to end the wave spawning, and the spawn rate of the enemies—basically, how much time there should be between each spawn during the given spawning period. The script and the settings will look like the following screenshot:
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:
- Inside the Spawn function, we can put the spawning code as we know, using the Instantiate function. The idea is to call this function at a certain rate to spawn one Enemy per call. This time, the spawn position will be in the same position as the spawner, so place it carefully:
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:
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.
- Now you can save and set some real values to our spawner. In my case, I used the ones shown in the following screenshot:
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.
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:
- Select the Prefab of Bullet and add a script called Autodestroy to it as you did with other objects using the Add Component | New Script option. This time, the script will be added to the Prefab, and each instance of the Prefab you spawn will have it.
- You can use the Destroy function as shown in the next screenshot to destroy the Object just once in Start.
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.
- Create a delay field to use as the second argument of Destroy, as shown in the next screenshot:
- Set the delay field to a proper value; in my case, 5 was enough. Now check how the bullets despawn after a while by looking at them being removed from the Hierarchy.
Now, we can create and destroy objects at will, which is something very common in Unity scripting.
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.