Creating obstacles
It’s great that we have some basic tiles, but it’s a good idea to give the player something to do, or in our case, something to avoid. This will provide the player with some kind of challenge and a basic gameplay goal, which is avoiding obstacles here. In this section, you’ll learn how to customize your tiles to add obstacles for your player to avoid. So, let’s look at the steps:
- Just like we created a prefab for our basic tile, we will create a single obstacle through code. I want to make it easy to see what the obstacle will look like in the world and make sure that it’s not too large, so I’ll drag and drop a Basic Tile prefab back into the world.
- Next, we will create a cube by going to GameObject | 3D Object | Cube. We will name this object
Obstacle
. Change the Scale value of Y to2
and position it above the platform at (0
,1
,0.25
):
Figure 1.27 – Adding obstacles
- We can then play the game to see how that’ll work:
Figure 1.28 – Obstacles stop the player
- As you can see in the preceding screenshot, the player gets stopped, but nothing really happens. In this instance, we want the player to lose when they hit this obstacle and then restart the game; to do that, we’ll need to write a script. From the Project window, go to the Scripts folder and create a new script called
ObstacleBehaviour
. We’ll use the following code:using UnityEngine; using UnityEngine.SceneManagement; // LoadScene public class ObstacleBehaviour : MonoBehaviour { [Tooltip("How long to wait before restarting the game")] public float waitTime = 2.0f; private void OnCollisionEnter(Collision collision) { // First check if we collided with the player if (collision.gameObject.GetComponent <PlayerBehaviour>()) { // Destroy the player Destroy(collision.gameObject); // Call the function ResetGame after // waitTime has passed Invoke("ResetGame", waitTime); } } /// <summary> /// Will restart the currently loaded level /// </summary> private void ResetGame() { // Get the current level's name string sceneName = SceneManager.GetActiveScene().name; // Restarts the current level SceneManager.LoadScene(sceneName); } }
- Save the script and return to the editor, attaching the script to the
Obstacle
GameObject we just created. - Save your scene and try the game:
Figure 1.29 – Obstacles destroy the player
As you can see in the preceding screenshot, once we hit the obstacle, the player gets destroyed, and then after a few seconds, the game starts up again. You’ll learn how to use particle systems and other things to polish this up, but at this point, it’s functional, which is what we want.
- Now that we know it works correctly, we can make it a prefab. Just as we did with the original tile, go ahead and drag and drop the Obstacle object from Hierarchy into the Project tab and into the Prefabs folder:
Figure 1.30 – Creating the Obstacle prefab
- Next, we will remove the Obstacle object, as we’ll spawn it upon creating the tile. To do so, select the Obstacle object in the Hierarchy window and then press the Delete key.
- We will make markers to indicate where we would possibly like to spawn our obstacles. Expand the Basic Tile object to show its children and then duplicate the
Next Spawn Point
object and move the new one’s Position to (0
,1
,4
). We will then rename the objectCenter
. - Afterward, to help see the objects within the Scene window, go to the Inspector window and click on the gray cube icon, and then on the Select Icon menu, select whichever of the color options you’d like (I went with blue). Upon doing this, you’ll see that we can see the text inside the editor if we are close to the object (but it won’t show up in the Game tab by default):
Figure 1.31 – Creating a Center marker
- We want a way to get all of the potential spawn points we will want in case we decide to extend the project in the future, so we will assign a tag as a reference to make those objects easier to find. To do that, at the top of the Inspector window, click on the Tags dropdown and select Add Tag.... From the menu that pops up, press the + button and then name it
ObstacleSpawn
:
Figure 1.32 – Creating the ObstacleSpawn tag
- Go back and select the Center object and assign the Tag property to ObstacleSpawn:
Figure 1.33 – Assigning the tag to the Center object
Note
For more information on tags and why we’d want to use them, check out https://docs.unity3d.com/Manual/Tags.html.
- Go ahead and duplicate this twice and name the others
Left
andRight
, moving them two units to the left and right of the center to become other possible obstacle points:
Figure 1.34 – Creating the Left and Right markers
- Note that these changes don’t affect the original prefab, by default; that’s why the objects are currently black text. To make this happen, select Basic Tile, and then in the Inspector window under the Prefab section, click on Overrides and select Apply All:
Figure 1.35 – Applying changes to the prefab
- Now that the prefab is set up correctly, we can go ahead and remove it by selecting it in the Hierarchy window and pressing Delete.
- We then need to go into the
GameManager
script and make some modifications. To start with, we will need to introduce some new variables:/// <summary> /// Manages the main gameplay of the game /// </summary> public class GameManager : MonoBehaviour { [Tooltip("A reference to the tile we want to spawn")] public Transform tile; [Tooltip("A reference to the obstacle we want to spawn")] public Transform obstacle; [Tooltip("Where the first tile should be placed at")] public Vector3 startPoint = new Vector3(0, 0, -5); [Tooltip("How many tiles should we create in advance")] [Range(1, 15)] public int initSpawnNum = 10; [Tooltip("How many tiles to spawn with no obstacles")] public int initNoObstacles = 4;
The first of these variables is a reference to the obstacle that we will be creating copies of. The second is a parameter of how many tiles should be spawned before spawning obstacles. This is to ensure that the player can see the obstacles before they need to avoid them.
- Then, we need to modify the
SpawnNextTile
function in order to spawn obstacles as well:/// <summary> /// Will spawn a tile at a certain location and setup /// the next position /// </summary> /// <param name="spawnObstacles">If we should spawn an /// obstacle</param> public void SpawnNextTile(bool spawnObstacles = true) { var newTile = Instantiate(tile, nextTileLocation, nextTileRotation); // Figure out where and at what rotation we should // spawn the next item var nextTile = newTile.Find("Next Spawn Point"); nextTileLocation = nextTile.position; nextTileRotation = nextTile.rotation; if (spawnObstacles) { SpawnObstacle(newTile); } }
Note that we modified the SpawnNextTile
function to now have a default parameter set to true
, which will tell us whether we want to spawn obstacles or not. At the beginning of the game, we may not want the player to have to start dodging immediately, but we can tweak the value to increase or decrease the number we are using. Because it has a default value of true
, the original version of calling this in the Start
function will still work without an error, but we will be modifying it later on.
- Here, we ask whether the value is
true
to call a function calledSpawnObstacle
, but that isn’t written yet. We will add that next, but first, we will be making use of theList
class and we want to make sure that the compiler knows whichList
class we are referring to, so we need to add ausing
statement at the top of the file:using UnityEngine; using System.Collections.Generic; // List
- Now we can write the
SpawnObstacle
function. Add the following function to the script:private void SpawnObstacle(Transform newTile) { // Now we need to get all of the possible places // to spawn the obstacle var obstacleSpawnPoints = new List<GameObject>(); // Go through each of the child game objects in // our tile foreach (Transform child in newTile) { // If it has the ObstacleSpawn tag if (child.CompareTag("ObstacleSpawn")) { // We add it as a possibility obstacleSpawnPoints.Add(child.gameObject); } } // Make sure there is at least one if (obstacleSpawnPoints.Count > 0) { // Get a random spawn point from the ones we // have int index = Random.Range(0, obstacleSpawnPoints.Count); var spawnPoint = obstacleSpawnPoints[index]; // Store its position for us to use var spawnPos = spawnPoint.transform.position; // Create our obstacle var newObstacle = Instantiate(obstacle, spawnPos, Quaternion.identity); // Have it parented to the tile newObstacle.SetParent(spawnPoint.transform); } }
- Lastly, let’s update the
Start
function:/// <summary> /// Start is called before the first frame update /// </summary> private void Start() { // Set our starting point nextTileLocation = startPoint; nextTileRotation = Quaternion.identity; for (int i = 0; i < initSpawnNum; ++i) { SpawnNextTile(i >= initNoObstacles); } }
Now, as long as i
is less than the value of initNoObstacles
, it will not spawn a variable, effectively giving us a buffer of four tiles that can be adjusted by changing the initNoObstacles
variable.
- Save the script and go back to the Unity Editor. Then, assign the
Obstacle
variable of the Game Manager (Script) component in the Inspector window with the Obstacle prefab we created previously:
Figure 1.36 – Assigning the Obstacle property
- It’s a bit hard to see things currently due to the default light settings, so let’s go to the Hierarchy window and select the Directional Light object. A directional light acts similar to how the Sun works on Earth, shining everywhere from a certain position.
- With the default settings, the light is a bit too bright and the shadows are too dark by default, so in the Inspector window, I changed Intensity to
0.5
and then the Realtime Shadows | Strength property to0.5
:
Figure 1.37 – Adjusting the Directional Light
- Save your scene and play the game:
Figure 1.38 – The current state of the game
As you can see in the preceding screenshot, we now have a number of obstacles for our player to avoid!
Note
For more information on directional lights and the other lighting types that Unity has, check out https://unity3d.com/learn/tutorials/topics/graphics/light-types?playlist=17102.