Unity 2020 Mobile Game Development
上QQ阅读APP看书,第一时间看更新

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 the 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:

  1. 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.
  2. Next, we will create a cube by going to GameObject | 3D Object | Cube. We will name this object Obstacle. Change the Y Scale value to 2 and position it above the platform at (0, 1, 0.25):
  1. We can then play the game to see how that'll work:
  1. 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; so, 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()
{
// Restarts the current level
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
  1. Save the script and return to the editor, attaching the script to the Obstacle 
    GameObject we just created.
  2. Save your scene and try the game:

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.

  1. 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 it from Hierarchy into the Project tab and into the Prefabs folder:
  1. 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.
  2. 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 Object object and move the new one's Position to (0, 1, 4). We will then rename the object Center.
  3. Afterward, to help see the objects within the Scene window, go to the Inspector window and click on the gray cube icon and then at 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):
  1. 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:
  1. Go back and select the Center object and assign the Tag property to ObstacleSpawn:
For more information on tags and why we'd want to use them, check out  https://docs.unity3d.com/Manual/Tags.html.
  1. Go ahead and duplicate this twice and name the others Left and Right, moving them two units to the left and right of the center to become other possible obstacle points:
  1. 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:
  1. 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.
  2. We then need to go into the GameController script and make some modifications. To start with, we will need to introduce some new variables:
/// <summary> 
/// Controls the main gameplay
/// </summary>
public class GameController : 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 initially 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.

  1. 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.

  1. Here we ask whether the value is true to call a function called SpawnObstacle, but that isn't written yet. We will add that next, but first we will be making use of the List class and we want to make sure that the compiler knows which List class we are referring to, so we need to add a using statement at the top of the file:
using UnityEngine;
using System.Collections.Generic; // List
  1. 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 object from the ones we have
var spawnPoint = obstacleSpawnPoints[Random.Range(0,
obstacleSpawnPoints.Count)];

// 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);
}
}
  1. 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.

  1. Save the script and go back to the Unity Editor. Then, assign the Obstacle variable of the Game Controller (Script) component in the Inspector window with the Obstacle prefab we created previously:
  1. 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 similarly to how the sun works on Earth, shining everywhere from a certain position.
  1. With the default settings, the shadows are too dark by default, so in the Inspector window, I changed the Realtime Shadows | Strength property to 0.5:
  1. Save your scene and play the game:
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
.

As you can see in the preceding screenshot, we now have a number of obstacles for our player to avoid!