Unity Tutorials Beta

Learn how to create games in Unity, a powerful and popular game engine.

Introduction to Unity Scripting – Part 2

In the second and last part of this tutorial, you will finish the game Sheep Rescue, in which you have to save the panicking sheep from their demise!

5/5 5 Ratings

Version

  • C# 7.3, Unity 2019.2, Unity

In the first part of this tutorial, you learned how to move GameObjects around, handle player input and how to use the physics system using scripting. Of course, there’s a lot more you can do with C# and Unity’s API! In the second and last part of this tutorial, you’ll learn how to:

  • Create lists and iterate them.
  • Update UI elements.
  • Use the Event System.
  • Make use of the Singleton pattern.
  • Pass data between scenes to customize a game.

This will finish up the game you’re creating along the way, Sheep Rescue, in which you have to save the panicking sheep from their demise. At the end of this tutorial, you’ll have a small, finished game that’s easy to expand upon.

Before starting off, make sure you have the latest stable version of Unity installed on your machine.

Getting Started

Download the materials for the project using the Download Materials button at the top or bottom of this tutorial, unzip it somewhere and open the starter project inside Unity. You can also use the finished project from the previous part of this tutorial as your starting point.

Open the Game scene under RW/Scenes if it isn’t opened automatically.

In the first part, you made a sheep run towards the ledge that could be shot by bales of hay launched by the hay machine. The Sheep is safely stored in the RW/Prefabs folder ready to be used.

The next step in creating the game is spawning the sheep at regular intervals in a way that they run over the little wooden bridges and towards their doom. There are several ways this can be tackled, but a simple way of doing it is using a bunch of spawn points and a small script that will create and keep track of the sheep.

Lists and Keeping Track of GameObjects

Start by creating a new empty GameObject at the root of the Hierarchy. Name it Sheep Spawn Points and reset its Transform.

Next, create three more empty GameObjects as children of Sheep Spawn Points and name each of these Spawn Point.

Now set the position of each of the spawn points to the following:

  • (X:-16, Y:0, Z:60)
  • (X:0, Y:0, Z:60)
  • (X:16, Y:0, Z:60)

This places the points evenly spaced on the map, right out of the camera’s view. Here are some sphere meshes added for illustration purposes to make it more clear:

With the spawn points defined, you’re ready to create the script for managing the sheep. Create a new C# script in RW/Scripts, name it SheepSpawner and open it in a code editor. Add the following variable declarations above Start:

public bool canSpawn = true; // 1

public GameObject sheepPrefab; // 2
public List<Transform> sheepSpawnPositions = new List<Transform>(); // 3
public float timeBetweenSpawns; // 4

private List<GameObject> sheepList = new List<GameObject>(); // 5

Here’s what they’ll be used for:

  1. As long as this stays true, the script will keep spawning sheep.
  2. A reference to a the Sheep prefab.
  3. The positions from where the sheep will be spawned. You need to create the list with the new keyword as Unity won’t initialize it correctly otherwise, causing errors if you try to add anything to the list.
  4. The time in seconds between the spawning of sheep.
  5. A list of all the sheep alive in the scene.

Now, add the most important method of this script, SpawnSheep:

private void SpawnSheep()
{
    Vector3 randomPosition = sheepSpawnPositions[Random.Range(0, sheepSpawnPositions.Count)].position; // 1
    GameObject sheep = Instantiate(sheepPrefab, randomPosition, sheepPrefab.transform.rotation); // 2
    sheepList.Add(sheep); // 3
    sheep.GetComponent<Sheep>().SetSpawner(this); // 4
}

This method spawns a single sheep at a random position:

  1. Use Unity’s Random class to get the position of one of the spawn point transforms. Random.Range(min, max) returns a random integer between 0 and the amount of spawn points available (three in this case), so the returned value will be either 0, 1 or 2.
  2. Create a new sheep and add it to the scene at the random position chosen in the previous line. Save this sheep in a temporary sheep variable.
  3. Add a reference to the sheep that was just created to the list of sheep.
  4. This line won’t compile yet as SetSpawner hasn’t been implemented yet so ignore the error. It will add a reference to the sheep spawner for the sheep to report to.

Save the SheepSpawner script and open the Sheep script. You’ll need to prepare it to make use of the spawner.

Add the following variable declaration below the others:

private SheepSpawner sheepSpawner;

This saves a reference to the sheep spawner. Now, add this method to fill in this reference:

public void SetSpawner(SheepSpawner spawner)
{
    sheepSpawner = spawner;
}

This method gets a reference to a SheepSpawner and caches it for later use.

Save the Sheep script and switch back to the SheepSpawner script.

Add this coroutine below the SpawnSheep method:

private IEnumerator SpawnRoutine() // 1
{
    while (canSpawn) // 2
    {
        SpawnSheep(); // 3
        yield return new WaitForSeconds(timeBetweenSpawns); // 4
    }
}

Coroutines are powerful methods that can run over time. Up until now, every method you’ve created ran to completion in a single frame. Coroutines, on the other hand, can be run over multiple frames or seconds as they can pause and resume their execution.

In the case of the SpawnRoutine, it will spawn a sheep, pause for a bit and then spawn another sheep for as long as the spawner is allowed to create new sheep. Here’s a rundown:

  1. All coroutines must use the IEnumerator return type. This allows you to yield (pause and resume execution) at any point.
  2. While canSpawn is true
  3. Spawn a new sheep.
  4. Pause the execution of this coroutine for the amount of seconds specified in timeBetweenSpawns using a yield instruction, WaitUntilSeconds in this case.
Note: There are a lot more yield instructions that can be used! There’s WaitForEndOfFrame, WaitForFixedUpdate and WaitUntil for example that are each useful. Check out this page for an example on how to use WaitUntil and links to all yield instructions.

Now, starting a coroutine is a bit different from simply calling a method. Add the following line inside of Start:

StartCoroutine(SpawnRoutine());

This line starts the coroutine. Instead of calling the coroutine directly you need to use StartCoroutine and pass the coroutine you want to start as a parameter.

Next, you need a way to remove sheep from the spawner’s list of sheep references when needed. Those references are being cached in order to easily destroy all sheep when the game ends. Add this method below SpawnRoutine:

public void RemoveSheepFromList(GameObject sheep)
{
    sheepList.Remove(sheep);
}

This method accepts a sheep as a parameter and removes its entry from the sheep list.

Now, add the following method:

public void DestroyAllSheep()
{
    foreach (GameObject sheep in sheepList) // 1
    {
        Destroy(sheep); // 2
    }

    sheepList.Clear();
}

This method destroys all of the sheep in the scene. Here’s how it does that:

  1. Iterate over every sheep in the list and destroy the GameObject each entry is referencing to.
  2. Clear the list of sheep references.

Save this script, open up the Sheep script and add this line to the top of both Drop and HitByHay:

sheepSpawner.RemoveSheepFromList(gameObject);

This removes the sheep from the spawner’s list when it drops off the edge of the world or gets hit by hay.

Now, save the Sheep script and return to the editor.

Add a new empty GameObject to the Hierarchy, name it Sheep Spawner and add a Sheep Spawner component.

To configure the spawner, start by dragging a Sheep from the RW/Prefabs folder onto the Sheep Prefab slot on the Sheep Spawner.

Next, expand Sheep Spawn Points and drag the spawn points one-by-one to the Sheep Spawn Positions list.

With the spawn points assigned, set Time Between Spawns to 2 to finish the setup.

Save the scene, press the play button and you should see the sheep appearing and running towards the hay machine. Try calming them with hay before they fall to their doom!

Looking good! The core gameplay is now ready, but you can go a step further by adding some cool effects.

Polishing the Game

While not necessary for the game to function, some polish adds to the overall experience. In this section, you’ll add some visual enhancements and sound effects.

Visuals

To start, you’ll add a small heart model that flies up from any saved sheep to show their appreciation.

This heart should do the following:

  • Appear when a sheep gets hit by hay.
  • Move up.
  • Rotate around.
  • Scale down.
  • Disappear after a short time.

Before you can implement this, you need to create a heart prefab to appear. Create a new empty GameObject in the Hierarchy, name it Heart and set its position to (X:0, Y:5, Z:0).

Now, add a model to it by dragging Heart from RW/Models onto Heart in the Hierarchy.

Name this child Heart Model, reset its Transform and set its rotation to (X:-90, Y:-45, Z:0).

You should now have a big red heart floating above the ground:

The movement and rotation are pretty easy to add since you’ve already written utility scripts for those in the first part of this tutorial. Add a Move component to Heart and set its Movement Speed to (X:0, Y:10, Z:0) to make it move up.

Now add a Rotate component and set its Rotation Speed to (X:0, Y:180, Z:0).

Press the play button to see the heart floating up into nothingness.

The heart needs to scale down while it’s moving up. So, create a new C# script in RW/Scripts, name it TweenScale and open it in a code editor.

The name of this script comes from “tweening“, which is short-hand for inbetweening, a process in animation for generating frames in between key frames that has existed since the early 1900’s.

In the case of this script, it simply means that you can supply the script an end value, an amount of time in which it needs to reach that value and the script makes the values in between happen.

To start creating the script, add these variable declarations right above Start:

public float targetScale; // 1
public float timeToReachTarget; // 2
private float startScale;  // 3
private float percentScaled; // 4

Here’s what these will be used for:

  1. The final scale. This value will be applied on all axes.
  2. The time in seconds it will take to reach the target scale.
  3. This is the scale of the GameObject at the moment this script is activated.
  4. A percent that’s between 0.0 and 1.0, this value will be incremented and used in calculations to change the scale from the starting value to the target value.

Next, add this to Start:

startScale = transform.localScale.x;

Here, you get the value of the scale in the X-axis and store it in startScale.

Now for actually doing the scaling, add this code to Update:

if (percentScaled < 1f) // 1
{
    percentScaled += Time.deltaTime / timeToReachTarget; // 2
    float scale = Mathf.Lerp(startScale, targetScale, percentScaled); // 3
    transform.localScale = new Vector3(scale, scale, scale); // 4
}

This is what this does:

  1. If the percent scaled isn't 1 (100%)...
  2. Add the time between this frame and the previous one, divided by the amount of seconds needed to reach the final value. If you'd simply add Time.deltaTime, the scaling would take exactly one second. Dividing this value by the actual time that needs to be spent, stretches this single second out to the desired time.
  3. Create a temporary variable named scale and store the lerped value of the scale inside. Mathf.Lerp performs a lerp, a linear interpolation between two values. It takes 3 parameters: Starting value, end value and the percentage at which the output value needs to be returned. For example, if you called Mathf.Lerp(0f, 50f, 0.5f);, the value returned would be 25 as that's 50% of 50.
  4. Get the scale value from the lerp and apply it to the transform on all axes.

As you can see, lerping is a valuable tool and you should keep it in mind for when you need to interpolate between two values.

Besides Mathf.Lerp, there's also Vector3.Lerp to lerp two vectors and Color.Lerp to lerp between two colors. The two last ones mostly exist for your convenience though, as you can achieve the same results with just Mathf.Lerp.

Now, save the script and return to the editor. Select Heart and add a Tween Scale component. Set its Target Scale to 0.5 and change Time To Reach target to 1.5. Play the scene and look at the heart, it now shrinks while moving up.

All that's left is making the heart disappear once it has been flying up for a while. Luckily, implementing this is trivial!

Create a new C# script in RW/Scripts, name it DestroyTimer and open it in your favorite IDE.

Add this line right above Start:

public float timeToDestroy;

This variable is the time in seconds before the GameObject this script is attached to is destroyed.

Now, add this to Start:

Destroy(gameObject, timeToDestroy);

This familiar piece of code destroys the GameObject after the delay set in timeToDestroy.

That should do it! Save this script and return to the editor. Next, add a Destroy Timer component to Heart and set its Time To Destroy to 1.5.

Now drag Heart to the RW/Prefabs folder to turn it into a prefab. Finally, delete Heart from the Hierarchy and open up the Sheep script again.

Add the following variable declarations below the others:

public float heartOffset; // 1
public GameObject heartPrefab; // 2

Here's what these are for:

  1. The offset in the Y axis where the heart will spawn.
  2. This holds a reference to the Heart prefab you just made.

Next, add this line to HitByHay:

Instantiate(heartPrefab, transform.position + new Vector3(0, heartOffset, 0), Quaternion.identity);

This line creates a new heart and positions it above the sheep, with heartOffset added to the Y position.

Now, to "animate" the sheep, you can dynamically add a TweenScale component to the sheep after it was hit by hay. Add the following lines below the last one you just added:

TweenScale tweenScale = gameObject.AddComponent<TweenScale>();; // 1
tweenScale.targetScale = 0; // 2
tweenScale.timeToReachTarget = gotHayDestroyDelay; // 3

These lines show off how easy it is to add a component and set it up at runtime:

  1. Add a TweenScale component to the GameObject this script is attached to and put a reference to it in a tweenScale variable.
  2. Set the target scale of TweenScale to 0 so the sheep shrinks down all the way.
  3. Set the time that TweenScale will take to the same time it takes to destroy the sheep.

Save this script and return to the editor. Select Sheep in RW/Prefabs, set Heart Offset to 4 and drag Heart from the same folder to the Heart Prefab slot.

Now, press the play button and shoot some sheep. They'll now show some love and shrink down once hit.

With the visual effects added, it's time to add some sound effects!

Sound Effects

To get started, you'll create a script that uses the Singleton pattern so it can be called from other scripts without needing a reference.

Create a new folder in RW/Scripts and name it Managers. Create a new C# script in that folder, name it SoundManager and open it in a code editor.

Add the following variable declarations right above Start:

public static SoundManager Instance; // 1

public AudioClip shootClip; // 2
public AudioClip sheepHitClip; // 3
public AudioClip sheepDroppedClip; // 4

private Vector3 cameraPosition; // 5

Here's what these are used for:

  1. This is a public static variable, so it can be accessed from any other script. It stores a reference to a SoundManager component.
  2. A reference to an AudioClip containing the hay shooting sound effect.
  3. Reference to the sound effect for when a sheep gets hit by hay.
  4. A reference to the sound a sheep makes when it drops off the edge.
  5. The cached position of the camera.

Now, replace Start with Awake and add this code to the method:

Instance = this; // 1
cameraPosition = Camera.main.transform.position; // 2

This is pretty simple:

  1. Cache this script inside the Instance variable.
  2. Cache the position of the main camera (the camera with a MainCamera tag).
Note: Awake is a good choice for script initialization when you want to set references. In terms of execution time, it runs before the Start method does in a script. If you wanted to reference your Singleton instance from another script in it's Start method, you could potentially run into a problem where the Singleton instance itself is not yet initialized. Using Awake ensures that this doesn't happen because of order of execution ensuring that Awake gets called first.

Next, add this method:

private void PlaySound(AudioClip clip) // 1
{
    AudioSource.PlayClipAtPoint(clip, cameraPosition); // 2
}

In an nutshell:

  1. This method accepts an AudioClip to play.
  2. Create a temporary AudioSource that plays the audio clip that was passed as a parameter at the location of the camera.

Finally, add these 3 methods that trigger the actual playing of the sound effects:

public void PlayShootClip()
{
    PlaySound(shootClip);
}

public void PlaySheepHitClip()
{
    PlaySound(sheepHitClip);
}

public void PlaySheepDroppedClip()
{
    PlaySound(sheepDroppedClip);
}

Each of the methods above calls PlaySound and passes its corresponding audio clip.

Now, that the audio manager is ready, you'll need to add some code to other scripts to make use of its audio playing capabilities. Save this script, open the HayMachine script and add this line to ShootHay:

SoundManager.Instance.PlayShootClip();

This calls PlayShootClip on the sound manager. Now, save the HayMachine script and open up the Sheep script. Add this line to HitByHay:

SoundManager.Instance.PlaySheepHitClip();

This plays another sound clip.

Next, add this line to Drop:

SoundManager.Instance.PlaySheepDroppedClip();

This plays the sound of the sheep falling.

Save this script and return to the editor. The sound manager needs to be added to a GameObject in order to work correctly, so create a new empty GameObject in the root of the Hierarchy and name it Managers. Now create another empty GameObject, name it Sound Manager and make it a child of Managers.

Select Sound Manager and add a Sound Manager component to it. Now drag the audio clips from RW/Sounds to their corresponding slots on Sound Manager.

Now, play the scene and shoot some sheep again, you'll hear the sound effects playing!

Keeping Score

The game has no failure state at the moment, no matter how many sheep drop to their demise, the player can keep going. Of course, there needs some kind of visual feedback so the player knows how he/she is doing.

Before you can add the visuals, you need to store the amount of sheep saved and dropped somewhere. Another manager is ideal for this!

Add a new C# script in RW/Scripts/Managers, name it GameStateManager and open it in a code editor.

To start with, add this below the other using statements:

using UnityEngine.SceneManagement;

This allows you to use scene related methods.

Now, add these variable declarations right above Start:

public static GameStateManager Instance; // 1

[HideInInspector]
public int sheepSaved; // 2

[HideInInspector]
public int sheepDropped; // 3

public int sheepDroppedBeforeGameOver; // 4
public SheepSpawner sheepSpawner; // 5

Here's what these are used for:

  1. Saves a reference of the script itself, which can be called from any other script.
  2. Amount of sheep that were saved by giving them hay. The [HideInInspector] attribute makes it so Unity won't show the variable it's assigned to in the editor, but it can still be accessed from other scripts. Doing this for public variables that are fully managed by scripts is good practice!
  3. The number of sheep that fell down.
  4. Amount of sheep that can be dropped before the game is over.
  5. Reference to a Sheep Spawner component.

Next, replace Start with Awake and add this line to it:

Instance = this;

This caches the current script so it can be accessed from other scripts.

Now add this small method:

public void SavedSheep()
{
    sheepSaved++;
}

This increments sheepSaved to keep score every time a sheep gets saved.

Next, add this method:

private void GameOver()
{
    sheepSpawner.canSpawn = false; // 1
    sheepSpawner.DestroyAllSheep(); // 2
}

This method will get called when too many sheep bite the dust. Here's what it does:

  1. Stop the sheep spawner spawning.
  2. Destroy all sheep that are still running around.

Finally, add this method that gets called every time a sheep falls down.

public void DroppedSheep()
{
    sheepDropped++; // 1

    if (sheepDropped == sheepDroppedBeforeGameOver) // 2
    {
        GameOver();
    }
}

Here's what this does:

  1. Increment sheepDropped.
  2. If the amount of dropped sheep equals the amount that are allowed to be dropped before it's game over, call GameOver.

Finally, add this code to Update:

if (Input.GetKeyDown(KeyCode.Escape))
{
    SceneManager.LoadScene("Title");
}

When the Escape key is pressed, SceneManager gets called and loads the title screen scene. SceneManager provides several options for loading and unloading scenes, both synchronously and asynchronously.

This script is ready for now! Save it and open the Sheep script.

Next, you'll need to call the Game State Manager's methods so it'll be aware of both the happy and the, uh, less happy sheep.

Add this line to HitByHay:

GameStateManager.Instance.SavedSheep();

This tells the manager that a sheep was saved.

Next, add this code to Drop, right above sheepSpawner.RemoveSheepFromList(gameObject);:

GameStateManager.Instance.DroppedSheep();

With this line, the manager gets notified that a sheep was dropped.

Now, save the script and return to the editor. Create a new empty GameObject as a child of Managers, name it Game State Manager and add a Game State Manager component.

Set Sheep Dropped Before Game Over to 3 and drag Sheep Spawner from the Hierarchy to the slot with same name.

Now, play the scene and let a few sheep run off the edge. After three sheep have hit the invisible trigger behind the hay machine, the game will end.

Manipulating User Interfaces

Of course, it's a bit silly to keep track of the score and mistakes if there's no way to view them. In order to fix that, drag Game UI from RW/Prefabs into the root of the Hierarchy. This is a pre-made Canvas with some text, a few images and a very simple game over window that's disabled.

Note: If you want to learn more about creating user interfaces in Unity, check out our Unity UI series of tutorials!

When you fully fold out Game UI, you'll see that all of the elements have unique names to show what they're used for.

To interact with the UI, you'll need another manager that will reference all of the elements and change their values based on the state of the game.

Create a new C# script in RW/Scripts/Managers, name it UIManager and open it in a code editor.

Most of the classes used for manipulating UI elements live in the UnityEngine.UI namespace. So, add this using statement below the others:

using UnityEngine.UI;

With this added, you can start adding references to UI components. Add the following variable declarations above Start:

public static UIManager Instance; // 1

public Text sheepSavedText; // 2
public Text sheepDroppedText; // 3
public GameObject gameOverWindow; // 4

Here's what these are:

  1. A reference to this UI Manager.
  2. Cached reference to the Text component of DroppedSheepText.
  3. This references the Text component of SavedSheepText.
  4. Reference to the Game Over Window.

Next, change Start to Awake and add this line to it:

Instance = this;

This stores this script inside the Instance variable to allow easy access to other scripts.

Now, delete Update and add these methods in its place:

public void UpdateSheepSaved() // 1
{
    sheepSavedText.text = GameStateManager.Instance.sheepSaved.ToString();
}

public void UpdateSheepDropped() // 2
{
    sheepDroppedText.text = GameStateManager.Instance.sheepDropped.ToString();
}

These methods update the text at the top of the screen:

  1. Get the amount of sheep saved from the Game State Manager, convert it to a string and use it to set the text of sheepSavedText.
  2. Same as the other method, but use the amount of sheep dropped instead.

Finally, add this method:

public void ShowGameOverWindow()
{
    gameOverWindow.SetActive(true);
}

This activates the Game Over Window, so it becomes visible.

That's it for this script, but you still need a way of calling its methods as the UI Manager isn't aware when a sheep was saved or dropped.

Save this script and open the GameStateManager script.

Add the following line to SavedSheep:

UIManager.Instance.UpdateSheepSaved();

This updates the text that shows the amount of sheep saved.

Next, add this line to DroppedSheep, right below sheepDropped++;:

UIManager.Instance.UpdateSheepDropped();

This calls the UI Manager to update the dropped sheep text.

Finally, add this line to GameOver:

UIManager.Instance.ShowGameOverWindow();

The game over window gets shown once the game is over with this method call. That should do it! Save this script and return to the editor.

Add a new empty GameObject as a child of Managers and name it UI Manager. Add a UI Manager component and drag the children of Game Canvas to their corresponding slots.

Now, play the game for a bit and notice the UI updates as sheep get saved and fall down. As you can see, manipulating UI elements using scripting is pretty easy!

If you let three sheep drop down, a simple game over window pops up. If you press the Escape key, the Title scene loads, but you won't be able to click any of the buttons just yet. Time to fix that!

Title Screen Buttons

Save the currently opened scene and open the Title scene from RW/Scenes. Take a look around the scene, it's the same environment as the game itself, but with some props added together and a different camera view.

Now, direct your attention to the Hierarchy and expand Main Camera, which is hiding the 3D menu elements Title, Start Button and Quit Button.

To get these buttons working, you'll be using a combination of the physics system and the event system. The physics are essential for detecting if the mouse cursor is hovering over a model, while the event system is needed to detect mouse clicks. Don't worry, it'll be crystal clear what this entails soon enough!

Start by selecting Start Button and Quit Button in the Hierarchy. Add a Box Collider to both of them and set their Size to (X:2.5, Y:1, Z:0.4). That covers the physics part.

Now, add an Event System to the Hierarchy by selecting GameObject ► UI ► Event System from the top menu. This will add a GameObject named EventSystem to the scene with an Event System and a Standalone Input Module component attached.

The event system is used to handle all sorts of mouse, keyboard, gamepad and touch input and passes the data on to the game. It's an essential part of handling player input when it comes to user interfaces. There's one more component that's needed before interacting with these buttons will work.

Select Main Camera and add a Physics Raycaster component to it. This component, as its name implies, casts rays into the scene and passes events to the event system when a 3D collider is detected (like when hovering over a button).

That should do it, the scene is now ready to be interacted with! To make use of it, you'll need to create another script. Create a new folder in RW/Scripts and name it Title.

Now create a new C# script in there named StartButtton and open it in a code editor.

For starters, add these using statements below the others:

using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;

These are used so you can easily access the classes from both the event system and the scene manager.

Now, replace this line:

public class StartButton : MonoBehaviour

With this:

public class StartButton : MonoBehaviour, IPointerClickHandler

Inheriting from the IPointerClickHandler interface allows this class to receive OnPointerClick callbacks from the event system. In fact, it's required to use the method when inheriting from that interface, so you'll probably see some red squiggly lines showing up. Remove Start and Update. Add the following method to fix that:

public void OnPointerClick(PointerEventData eventData) // 1
{
    SceneManager.LoadScene("Game"); // 2
}
  1. This method needs to have a specific signature in order to work with the event system, you need a single PointerEventData parameter that holds all information you could wish for about the click event.
  2. Use the scene manager to load the Game scene.

Now, save the script and return to the editor. Select Start Button, add a Start Button component to it and take a look at the bottom of the Inspector. It now shows an extra section named Intercepted Events, which lists all of the events that will be passed from the event system onto the selected GameObject.

In this case, when you click this GameObject, OnPointerClick will be called on StartButton, just as expected!

Next, play the scene and click on the Start button, the Game scene will be loaded.

Looking good so far! To get the quit button working, duplicate the StartButton script, name the copy QuitButton and open it in a code editor.

Replace this line:

public class StartButton : MonoBehaviour, IPointerClickHandler

With this one:

public class QuitButton : MonoBehaviour, IPointerClickHandler

This is to rename the class to match the file name. The script won't compile if the names don't match.

Now, remove this line inside of OnPointerClick:

SceneManager.LoadScene("Game");

Finally, add this in its place:

Application.Quit();

As you might've guessed, this quits the game. Save the script and return to the editor.

Select Quit Button, add a Quit Button component to it and run the scene. Try clicking the Quit button and you'll notice nothing happens. What gives?

Application.Quit does actually get called, but it will only work in an actual build. You can give it a try by building the game to an executable by pressing CTRL (or CMD) + B and selecting a folder to deploy to. After the game has launched, try clicking the Quit button again, it will close the game as expected now.

There's one final adjustment to the buttons that will make it more clear to the player that they can be clicked, and that's by changing their color when the mouse cursor hovers over them. Create a new C# script in RW/Scripts/Title, name it ChangeColorOnMouseOver and open it in a code editor.

Add this familiar using statement below the others to allow use of the event system:

using UnityEngine.EventSystems;

Now, replace this line:

public class ChangeColorOnMouseOver : MonoBehaviour

With this one:

public class ChangeColorOnMouseOver : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler

Just like IPointerClick, these pointer event interfaces are used to react to events triggered by the event system. In this case, they supply methods for when the pointer enters and exits the GameObject. You will get some errors again about not implementing the interface members, but ignore those for now, they'll disappear once you add the methods.

Now, add these variable declarations right above Start:

public MeshRenderer model; // 1
public Color normalColor; // 2
public Color hoverColor; // 3

Here's what these are used for:

  1. A reference to the mesh renderer that needs its color changed.
  2. The default color of the model.
  3. The color that should be applied on the model when the pointer is hovering over it.

Now, add this to Start:

model.material.color = normalColor;

This changes the model's color to the normal one.

Now, remove Update and add these methods in its place:

public void OnPointerEnter(PointerEventData eventData) // 1
{
    model.material.color = hoverColor;
}

public void OnPointerExit(PointerEventData eventData) // 2
{
    model.material.color = normalColor;
}

These methods will be called when a pointer enters and exits the attached GameObject:

  1. This is another method that needs to have a single PointerEventData variable as its parameter. It gets called when the pointer enters the GameObject and changes the color of the model's material.
  2. Called when the pointer exits the GameObject. This resets the color of the material to its normal value.

That's it! Save this script and return to the editor. Select both buttons and add a Change Color On Mouse Over component to each.

Set the alpha value of Normal Color to 100 and change its hexadecimal value to CCCCCC (a light grey) on both buttons.

Now, do the same for Hover Color, but set its hexadecimal color value to pure white, FFFFFF. Next, select just the Start Button and drag its child onto the Model slot.

Now, do the same for Quit Button and play the scene. Hover over the buttons with your cursor and you'll notice they change color.

The full game is done now! However, there's one more technique to share with you: How to pass data between scenes.

Passing Data Between Scenes

The ability to pass data between scenes can be useful for passing things like game settings from an options menu to the game. For this game, you'll implement the option to change the color of the hay machine from the title screen.

To start, you need something to click on to make the color change happen. Create an empty GameObject in the root of the Hierarchy, name it Hay Machines and set its position to (X:16.5, Y:-0.5, Z:-20). This will act as a container for the differently colored hay machines.

Now, select all of the hay machines in RW/Prefabs/Hay Machine Models and drag them to Hay Machines.

Next, with the three hay machines elected in the Hierarchy, set their Rotation to (X:-90, Y:0, Z:0). The hay machines are now upright:

Right now, all of the machines are overlapping so a random gets visibly rendered. The default machine color is blue, so disable the other two by clicking the little checkbox next to their names, or by pressing Alt + Shift + A.

To make the hay machines clickable, they need a collider. Add a Box Collider to Hay Machines, set its Center to (X:0, Y:3.5, Z:0) and its Size to (X:5, Y:6, Z:8).

You're now ready to do some scripting! Create a new folder in RW/Scripts, name it Shared and create a new C# script inside of it named HayMachineColor.

Open it in a code editor and strip out all of the using statements and both methods.

Now, replace this line:

public class HayMachineColor : MonoBehaviour

With this:

public enum HayMachineColor

As you might've guessed, this script isn't meant to be used as a component, it's actually just a simple enum that will be used by other scripts. Enums are actually just constant integers under the hood (like 0, 1, 2, etc.) but using them makes for a much more approachable and readable way of accessing variables. Add this inside of the body of the enum to add some values:

Blue, Yellow, Red

That's it for this file, save it, return to the editor and create another C# script inside RW/Scripts/Shared. Name it GameSettings and open it in your code editor. Strip the using statements and the methods again, this will be a a regular class.

Replace this line:

public class GameSettings : MonoBehaviour

With this one:

public static class GameSettings

This turns this script into a static class that can hold data for other scripts to use, even across scenes. Any static variables added to this class can be set from the title screen and then read by the game screen for example, which can be quite powerful!

Now, add this variable declaration to the body of the class:

public static HayMachineColor hayMachineColor = HayMachineColor.Blue;

This is where the magic happens! This variable will be used to set and get the color of the hay machine. It uses the enum you just created for readability.

Save this script, return to the editor and create yet another script in RW/Scripts/Shared named HayMachineSwitcher.

Open the script in a code editor and add these using statements below the others:

using UnityEngine.EventSystems;
using System;

This is needed to be able to use the pointer click event.

Replace this line:

public class HayMachineSwitcher : MonoBehaviour

With this one:

public class HayMachineSwitcher : MonoBehaviour, IPointerClickHandler

Like before, you'll get an error about missing an interface member, but don't worry about that!

Now, add these variable declarations:

// 1
public GameObject blueHayMachine;
public GameObject yellowHayMachine;
public GameObject redHayMachine;

private int selectedIndex; // 2
  1. These variables reference each of the colored hay machine models you added as children of Hay Machines earlier.
  2. This is an index that will be incremented whenever the hay machine selector is clicked, triggering the next machine color to be chosen. It will become clear what this entails exactly once you add the method next.

Now, remove the Start and Update method and add the most important method of this script:

public void OnPointerClick(PointerEventData eventData) // 1
{
    selectedIndex++; // 2
    selectedIndex %= Enum.GetValues(typeof(HayMachineColor)).Length; // 3

    GameSettings.hayMachineColor = (HayMachineColor)selectedIndex; // 4

    // 5
    switch (GameSettings.hayMachineColor)
    {
        case HayMachineColor.Blue:
            blueHayMachine.SetActive(true);
            yellowHayMachine.SetActive(false);
            redHayMachine.SetActive(false);
        break;

        case HayMachineColor.Yellow:
            blueHayMachine.SetActive(false);
            yellowHayMachine.SetActive(true);
            redHayMachine.SetActive(false);
        break;

        case HayMachineColor.Red:
            blueHayMachine.SetActive(false);
            yellowHayMachine.SetActive(false);
            redHayMachine.SetActive(true);
        break;
    }
}

This is what the method does:

  1. This is the same method as you used with the title buttons; it gets called when this GameObject gets clicked and has a single parameter that contains a bunch of information about the pointer input.
  2. Increment selectedIndex so the next color gets selected.
  3. This line uses algebra to prevent writing unnecessary if-statements. Using the modulo (%) operator to get the remainder of the division, the index can be "looped around". The right part of this equation looks intimidating, but it simply gets a list of all values contained in the HayMachineColor enum and counts them. Its value, in this case, would be 3. Here's an example of how these work together: If the index is 3, the remainder after dividing it by the amount of colors (3) is 0. So, the final index value would be 0. On the other hand, if the index is 2 and it gets divided by the amount of colors (3), the remainder is 2. So, the value of the index stays 2. Here's a website where you can try playing with these values. The dividend is the current index value, the divisor is the amount of colors and the remainder is the final index value.
  4. Set the chosen color in GameSettings depending on the selected index, which is cast to an enum by adding the cast type in parenthesis before the variable.
  5. Enable and disable the hay machine models based on the chosen machine color using a switch-statement.

This was the most complicated block of code in this project by far, but, the end result will be pretty cool!

Save this script and return to the editor. Select Hay Machines, add a Hay Machine Switcher component and drag the hay machine children onto their matching slots.

Now, for the moment of truth! Play the scene and click a few times on the hay machine on the right. You'll see it switching colors!

If you start the game now, though, the hay machine will still be blue, no matter what other color you've chosen. The final piece of the puzzle is editing the hay machine script, so it'll take the chosen color value and apply it on startup.

Open the HayMachine script and add the following variable declarations below the others:

public Transform modelParent; // 1

// 2
public GameObject blueModelPrefab;
public GameObject yellowModelPrefab;
public GameObject redModelPrefab;

These will be used for swapping the machine model:

  1. The parent Transform of the model.
  2. References to the hay machine model prefabs.

Now, add the following method below Start:

private void LoadModel()
{
    Destroy(modelParent.GetChild(0).gameObject); // 1

    switch (GameSettings.hayMachineColor) // 2
    {
        case HayMachineColor.Blue:
            Instantiate(blueModelPrefab, modelParent);
        break;

        case HayMachineColor.Yellow:
            Instantiate(yellowModelPrefab, modelParent);
        break;

        case HayMachineColor.Red:
            Instantiate(redModelPrefab, modelParent);
        break;
    }
}

This replaces the default model with a new one depending on the machine color saved in the game settings:

  1. Destroy the current model.
  2. Instantiate a hay machine model prefab based on the chosen color and parent it to modelParent.

Finally, add this line to Start:

LoadModel();

This calls the LoadModel method you just created.

Now, save the script and return to the editor. Make sure the Title scene is saved. Open the Game scene, select Hay Machine and expand it in the Hierarchy.

The Hay Machine component now has some extra fields that need to be filled in for the model switching to work. Drag Model Parent to the Model Parent slot and drag the prefabs found in RW/Prefabs/Hay Machine Models to their corresponding slots.

With the hay machine fully set up, save the Game scene. Open the Title scene again and press the Play button. Switch the color of the hay machine to something different than blue, and press the Start button.

You'll now see that the hay machine has the color you've chosen!

Congratulations! You have made a complete game from just a few models and implemented some cool mechanics while learning about Unity's API along the way.

Where to Go From Here?

You can download the finished project by clicking the Download Materials buttons at the top or bottom of this tutorial.

If you want to know more about Unity's API, check out these handy resources:

Don't stop there though! You can make this game your own by adding more features, changing around assets and by introducing levels. Here are some ideas to get you started:

  • Make the sheep spawn faster over time to increase the pressure.
  • Create different environments, like an ice level, a western setting, maybe even sci-fi!
  • Implement power-ups like multi-shot, a net that traps the sheep behind the machine, extra lives, etc.
  • Polish the user interface with some sweet sprites and animations.
  • Add some screen shake, that's always cool!

We hope you enjoyed this tutorial, if you have any questions or comments, join the forum discussion below!

Average Rating

5/5

Add a rating for this content

5 ratings

Contributors

Comments