As part of the Unity Junior Programmer Pathway, the second tutorial has you creating your first feature rich game. specifically a simple game where animals rush towards you and you need to feed them by firing a food item at them. The animal and the food item will despawn when they catch the food (it hits them).

This tutorial is the first introduction to Prefabs which are preconfigured GameObjects that can be instantiated with settings and components as originally designed.

You can try this game out just below (scroll down). To reset the game, you’ll need to refresh your browser (I’ve not bothered to implement restart functionality as it’s not part of the scope of the tutorial).

Controls

The control scheme is pretty simple, but only supports PC.

  • A (or left arrow)
    • Move Left
  • D (or right arrow)
    • Move Right
  • Spacebar
    • Fire a cookie at the animals

Feed the animals

How it was built

While this was part of a tutorial, I find it useful to treat these like a blog post of sorts, this ensures that I can articulate how everything works and means that it is sinking in.

Prefabs

The asset pack provided for the tutorial gave a few options for assets to use in the game.

Each of these prefabs included the following components:

  • Box Collider surrounding the asset’s geometry, IsTrigger set to true
  • Script; MoveForward.
  • Script; DestroyOutOfBounds.

I chose the following and turned them into Prefabs:

Animal_Doe

Doe Prefab

Animal_Fox

Fox Prefab

Animal_Stag

Stag Prefab

Food

This prefab is a simple cookie and serves as the projectile you’re shooting at the animal to feed them.

In addition to the components previously mentioned, this Prefab includes a Rigidbody so that the game can detect when one object touches another.

Food Prefab

Prefab Scripts

There are two scripts that are shared by all Prefab objects in this game - a good example of standard Software Engineering practices such as DRY (Don’t Repeat Yourself), Encapsulation and Code Resuability.

MoveForward

The MoveForward script is very simple, it simply allows for a GameObjects’ Transform to be able to move a specified distance, the specified direction in the specified amount of time (in this case, every second). This uses the Translate method.

// ./MoveForward.cs
public class MoveForward : MonoBehaviour
{
    public float speed = 40f;

    // Start is called before the first frame update
    void Start() {}

    // Update is called once per frame
    void Update()
    {
        transform.Translate(speed * Time.deltaTime * Vector3.forward);
    }
}

DestroyOutOfBounds

The DestroyOutOfBounds script is used to despawn items that have exited the field of play.

// ./DestroyOutOfBounds.cs
public class DestroyOutOfBounds : MonoBehaviour
{
    private float topBound = 35f;
    private float lowerBound = -13f;

    // Start is called before the first frame update
    void Start() {}

    // Update is called once per frame
    void Update()
    {
        if (transform.position.z > topBound)
        {
            Destroy(gameObject);
        }
        
        if (transform.position.z < lowerBound)
        {
            Debug.Log("Game over!");
            Destroy(gameObject);
        }
    }
}

The scene itself

Unity Feed the animals scene

There’s a bit going on in this scene, so I’ll be brief.

Scene objects

We have the following:

  • A Camera.
  • Directional Light.
  • A grassy plain with left and right borders.
  • The Player; he’s a farmer.
  • A SpawnManager object.

Some parts are ootb, so I won’t detail them.

The Player

The Player object is quite simple, he’s a 3D model with a script attached. The script itself has a few pieces responsibilities:

  • Define a maximum horizontal movement speed.
  • Define the boundaries for the Player within the scene.
  • Actually move the player horizontally in the scene.
  • Launch projectiles (Prefabs > Food).
// ./PlayerController.cs
public class PlayerController : MonoBehaviour
{
    public float horizontalSpeed = 10f;
    public float horizontalInput;
    public float bounds = 10f;

    public GameObject projectilePrefab;

    // Start is called before the first frame update
    void Start() {}

    // Update is called once per frame
    void LateUpdate()
    {
        #region Keep player in bounds
        if (transform.position.x < -bounds)
        {
            transform.position = new Vector3(-bounds, transform.position.y, transform.position.z);
        }
        
        if (transform.position.x > bounds)
        {
            transform.position = new Vector3(bounds, transform.position.y, transform.position.z);
        }
        #endregion

        #region Player movement
        horizontalInput = Input.GetAxis("Horizontal");
        transform.Translate(horizontalInput * horizontalSpeed * Time.deltaTime * Vector3.right);
        #endregion

        #region Projectile management
        if (Input.GetKeyDown(KeyCode.Space)) // launch projectile
        {
            Instantiate(projectilePrefab, transform.position, projectilePrefab.transform.rotation);
        }
        #endregion
    }
}

Player Object

The SpawnManager

The SpawnManager object is an empty GameObject with a script attached by the same name. This includes the following functionality:

  • An array of GameObjects used to store the Prefabs of animals.
  • ValidateSpawnNextAnimal; this is invoked within the Start method, and waits a random number of seconds before setting a boolean signifying that the next frame should spawn an animal; spawnAnimalNextFrame.
  • The Update method simply checks whether a new animal instance should be spawned, and invokes SpawnRandomAnimal.
  • SpawnRandomAnimal surrounds instantiation functionality with exception handling, and finally resets the spawnAnimalNextFrame boolean to false, (potentially) ready for next frame.
// ./SpawnManager.cs
public class SpawnManager : MonoBehaviour
{
    public GameObject[] animalPrefabs;
    public float bounds = 20f;
    public float animalPositionZ = 20f;

    private bool spawnAnimalNextFrame = false;
    public int spawnTimeMin = 500;
    public int spawnTimeMax = 3000;

    // Start is called before the first frame update
    void Start()
    {
        InvokeRepeating(nameof(ValidateSpawnNextAnimal), 2f, 1f);
    }

    void ValidateSpawnNextAnimal()
    {
        int msToWaitForSpawn = UnityEngine.Random.Range(spawnTimeMin, spawnTimeMax);
        _ = TimeSpan.FromMilliseconds(msToWaitForSpawn);

        spawnAnimalNextFrame = true;
    }

    // Update is called once per frame
    void Update()
    {
        if (spawnAnimalNextFrame)
        {
            SpawnRandomAnimal();
        }
    }

    private void SpawnRandomAnimal()
    {
        int animalIndex = UnityEngine.Random.Range(0, animalPrefabs.Length);
        float animalPositionX = UnityEngine.Random.Range(-bounds, bounds);
        Vector3 spawnPosition = new Vector3(animalPositionX, 0, animalPositionZ);

        try
        {
            Instantiate(animalPrefabs[animalIndex], spawnPosition, animalPrefabs[animalIndex].transform.rotation);
        }
        catch (System.Exception ex)
        {
            Debug.Log(ex.Message);
        }
        finally
        {
            spawnAnimalNextFrame = false;
        }
    }
}