Sei sulla pagina 1di 42

Classic 2D Snake Tutorial

Hello, and welcome to this 2D Classic Snake game tutorial. I am raiden from the
Unity forums, and I am a programmer.
Recently, I was googling classic games online, and I ran across the classic Snake
game, originally published in 1976 by Gremlin, I thought to myself, why not make
it in Unity?
So I set forth, as an objective to not only program the game, but do it in a way that
required absolutely 0 knowledge of art design or modeling experience.
Then I decided, since I was programming in a very oop way, why not make it a
programming tutorial and that is what I hope to accomplish in this tutorial.
Things you need:
Unity Game Engine - Unity

CFXR For Mac Users


SFXR For Windows Users

Things you should know:


Basic understanding of the Unity Editor

Basic understanding of Monodevelop


Some general programming

Tutorial Contents:

What you will learn

Creating the project

The start of the GameManager.cs script

Testing, playing and wrapping it up

The ScreenBorder.cs script


The first Helper Script, TextureHelper.cs
Let's see something!
The ScreenField.cs script
Our game playing field is complete!
The Scores.cs script
The Lives.cs script
Time for some Food!
Nothing fancy, but hey it works!
The SnakeGame.cs script
Our player, time for the Snake.cs script
Our last helper script, InputHelper.cs
Our final script, ScreenDeath.cs

What you will learn:


The idea behind this tutorial is to give you some better understanding of some
intermediate C# knowledge so that you can use the concepts in your own games.
Our main focus will be what is called a Singleton in C#. I will show you some
practical examples in our games, how they can be useful for keeping code
structured and organized in a way that makes it self sufficient and more of an object
oriented way.
I will show you the power of dynamic instantiation and how it can not only show
you how to create things at run time, but give you more control of what and when
you want things to happen in your game.
We will also cover what is commonly called a Helper script, and show you how
these scripts can be useful to quickly perform certain actions and can be used in any
of your game projects.
Alright, enough of that let's get going! Next up, we start with our game project, and
the basic folder/file structure we will need for our game. Let's go!

Creating the Project:

Start with creating a new project in Unity, let's call the project Uni2DSnake,
and don't import any assets.
Save the scene as Uni2DSnake
Add the following folders to the project view: Resources, Scripts, and inside
the Scripts folder add the following folders: BuildScripts & Utils
Add the following C# script to the Scripts folder: GameManager.cs
Add the following C# scripts to the BuildScripts folder: Food.cs, Lives.cs,
Score.cs, ScreenBorder.cs, ScreenDeath.cs, ScreenField.cs, Snake.cs,
SnakeGame.cs

Add the following C# scripts to the Utils folder: InputHelper.cs &


TextureHelper.cs
Add an empty GameObject to the scene, name it GameManager and drag the
GameManager.cs script on to it
Download the audio files from here: Uni2DSnake-Sounds and add the
contents including the Sounds folder they are in, to the Resources folder.

That's it, your project should be setup like this:


Uni2DSnake Tutorial Project Setup View

The Start of GameManager.cs Script


Let's start by opening GameManager.cs script in Monodevelop, by default Unity
will insert the code like this:
using UnityEngine;
using System.Collections;
public class GameManager : MonoBehaviour {

// Use this for initialization


void Start () {
}

// Update is called once per frame


void Update () {
}
}

What we need to do is begin scripting the start of our GameManager script for our
game like this:
using UnityEngine;
using System.Collections;

/// <summary>
/// Stores game state and game information
/// </summary>
public class GameManager : MonoBehaviour
{
//--------------------------------------------------------------------------------------------// Start()

//--------------------------------------------------------------------------------------------// Unity method, called at game start automatically


//--------------------------------------------------------------------------------------------void Start ()
{
// build our game screen border
ScreenBorder.Instance.Initialize();
}
}

So basically all we have for now, is 1 line of code in the Start method, referencing
ScreenBorder.Instance.Initialize(); What we are doing here is using what is called a
Singleton.
Singletons are useful in the sense that with that 1 line of code, I can perform some
action in-game that references a completely different object, in our case the object
for now is a script called ScreenBorder. No need to use Unity's GameObject.Find or
GameObject.FindWithTag, Singleton's give you access to script instances, allowing
you to use any public fields or methods (in our case above Initialize()).
I'll explain more about what's happening above in our next script ScreenBorder.cs,
also if you try and run your game in Unity you will get errors, because we haven't
scripted ScreenBorder yet, let's get to that now!

The ScreenBorder.cs Script


using UnityEngine;
using System.Collections;
public class ScreenBorder : MonoBehaviour
{
// private fields
private static ScreenBorder instance = null;

//--------------------------------------------------------------------------------------------// constructor field: Instance


//--------------------------------------------------------------------------------------------// Creates an instance of ScreenBorder if one does not exists
//-------------------------------------------------------------public static ScreenBorder Instance
{
get
{
if (instance == null)
{
instance = new GameObject("ScreenBorder").AddComponent<ScreenBorder>();
}
return instance;
}

//--------------------------------------------------------------------------------------------// Unity method: OnApplicationQuit()


//--------------------------------------------------------------------------------------------// Called when you quit the application or stop the editor player
//--------------------------------------------------------------------------------------------public void OnApplicationQuit()
{
DestroyInstance();
}
//--------------------------------------------------------------------------------------------// DestroyInstance()
//--------------------------------------------------------------------------------------------// Destroys the ScreenBorder instance
//--------------------------------------------------------------------------------------------public void DestroyInstance()
{
print("Screen Border Instance destroyed");
instance = null;
}

//--------------------------------------------------------------------------------------------// Initialize()
//--------------------------------------------------------------------------------------------// Initializes ScreenBorder
//--------------------------------------------------------------------------------------------public void Initialize()
{
print("ScreenBorder initialized");
// make sure our localScale is correct for a GUItexture
transform.position = new Vector3(0,0,-1);
transform.rotation = Quaternion.identity;
transform.localScale = new Vector3(0.01f, 0.01f, 1.0f);
// add our GUITexture component
GUITexture gameBorderBackground = gameObject.AddComponent<GUITexture>();
// we create a texture for our GUITexture mainTexture
Texture2D gameBorderTexture = TextureHelper.Create1x1Texture(Color.gray);
// we intialize some GUITexture properties
gameBorderBackground.texture = gameBorderTexture;
gameBorderBackground.pixelInset = new Rect(0,0,1024,768);
}
}

Here we have our ScreenBorder script, and you can see everything is neatly
structured and commented, two very good rules for programming.
So the first part of the script, we declare a private static instance of our class
ScreenBorder. When we do this we are declaring a Singleton. This allows us to
link in a way to our ScreenBorder script from other scripts, by calling the instance

of the class. Below that is another declaration of type ScreenBorder but it is a


different field in two ways, first it's name is different because it has a capital I in
the field name Instance rather than the previous version called instance and
second, we use the keyword get inside the field name in order to return the class
instance to the field Instance.
I call this a constructor field because we are creating a gameobject inside the field
using get if our instance is null. That's really cool, we now have dynamic
Instantiation of a gameobject, and a singleton reference to our ScreenBorder class's
public fields and methods.
If you look at GameManager, you see exactly how this works, notice in
GameManager we have the line: ScreenBorder.Instance.Initialize(); and if you look
at the ScreenBorder code, you will see a public method called Initialize(), so this
tells you we are calling the method Initialize() in ScreenBorder from
GameManager.
Next we have OnApplicationQuit(), this is a Unity method, and happens when we
quit a game or quit playing in the editor. This simply calls another public method
DestroyInstance(), which prints some info to the console, and sets the instance to
null. Just a tidy way of cleaning things up when you quit the game.
Finally we have the core code of ScreenBorder and that is Initialize(). This handles
setting up the gameobject when it is created, it starts with setting some transform
properties, adding a GUITexture component, and using one of our helper scripts
TextureHelper to create a texture dynamically for our GUITexture texture property.
If you try and run this now, you will get and error because we are referencing
TextureHelper and we haven't scripted it yet.
Next that's exactly what we will do, script our TextureHelper script.

Scripting our TextureHelper.cs script


using UnityEngine;
using System.Collections;
public class TextureHelper
{
// Create and return a black texture

public static Texture2D CreateBlackTexture(int width, int height)


{
Texture2D texture = new Texture2D(width, height);
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
texture.SetPixel(i, j, Color.black);
}
}
texture.Apply();
return texture;
}

// Create and return a texture with Color


public static Texture2D CreateTexture(int width, int height, Color color)
{
Texture2D texture = new Texture2D(width, height);
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
texture.SetPixel(i, j, color);
}
}
texture.Apply();
return texture;
}

// Create and return a 1x1 black texture


public static Texture2D Create1x1BlackTexture()
{
Texture2D texture = new Texture2D(1, 1);
texture.SetPixel(0, 0, Color.black);
texture.Apply();
return texture;
}

// Create and return a 1x1 texture with Color


public static Texture2D Create1x1Texture(Color color)
{
Texture2D texture = new Texture2D(1, 1);
texture.SetPixel(0, 0, color);
texture.Apply();
return texture;
}
}

Our TextureHelper script provides us useful methods to extend Texture2D in

Unity, giving us quick ways to do certain things in code.


Most everything is pretty straightforward, we are creating public static methods
so that we have direct access to them without having to create in instance of them in
the game. This allows you to do exactly what we have done in ScreenBorder.cs in
the Initialize() method, by calling TextureHelper.Create1x1Texture(Color.gray); you
can call a scripts method directly without it being a created gameobject in the
world.
So you can see we are performing some basic routines in the methods to return a
texture. In each method, we declare a new Texture2D, and initialize it to either a 1 x
1 pixel wide and tall, or with the size that we pass through the arguments width
and height Create1x1Texture(Color color) allows us to pass through a color we
want (see Unity's documentation on Color to learn more about it).
Now that we have TextureHelper scripted, that should clear up all of our errors in
Unity so let's see the results!
Displaying ScreenBorder
Well, that's not that exciting, just a blank gray screen, but notice the added
gameobject to the Hiearchy view ScreenBorder, and look at it's components. As a
programmer, that's pretty cool to write a script, that creates a gameobject, adding
the components needed to perform various actions.
Next up, time to script ScreenField

Scripting our ScreenField.cs script


using UnityEngine;
using System.Collections;
public class ScreenField : MonoBehaviour
{
// private fields
private static ScreenField instance = null;

//--------------------------------------------------------------------------------------------// constructor field: Instance

//--------------------------------------------------------------------------------------------// Creates an instance of ScreenField if one does not exists


//--------------------------------------------------------------------------------------------public static ScreenField Instance
{
get
{
if (instance == null)
{
instance = new GameObject("ScreenField").AddComponent<ScreenField>();
}
return instance;
}
}

//--------------------------------------------------------------------------------------------// Unity method: OnApplicationQuit()


//--------------------------------------------------------------------------------------------// Called when you quit the application or stop the editor player
//--------------------------------------------------------------------------------------------public void OnApplicationQuit()
{
DestroyInstance();
}
//--------------------------------------------------------------------------------------------// DestroyInstance()
//--------------------------------------------------------------------------------------------// Destroys the ScreenField instance
//--------------------------------------------------------------------------------------------public void DestroyInstance()
{
print("Screen Field Instance destroyed");
instance = null;
}

//--------------------------------------------------------------------------------------------// Initialize()
//--------------------------------------------------------------------------------------------// Initializes ScreenField
//--------------------------------------------------------------------------------------------public void Initialize()
{
print("ScreenField initialized");
// make sure our localScale is correct for a GUItexture
transform.position = Vector3.zero;
transform.rotation = Quaternion.identity;
transform.localScale = new Vector3(0.01f, 0.01f, 1.0f);
// add our GUITexture component
GUITexture gameScreenBackground = gameObject.AddComponent<GUITexture>();
// we create a texture for our GUITexture mainTexture
Texture2D gameTexture = TextureHelper.Create1x1BlackTexture();
// we initialize some GUITexture properties
gameScreenBackground.texture = gameTexture;
gameScreenBackground.pixelInset = new Rect(22,84,980,600);
}

Hey, this sure looks familiar :), nope its not ScreenBorder, but it has a lot of
similarities, in fact we are doing pretty much the same thing in this script, as we did
the last, the only real major differences are that we are setting the z position in the
Initialize() method of the transform.position to 0 instead of -1, because this puts our
playing field (ScreenField) in front of our field border (ScreenBorder). Also instead
of creating a gray texture for display, we are creating a black texture.
No need in covering everything in this script, I've explained the concepts pretty
well, and they are no different here in this script.
Let's see the results! Woah, wait, I've pressed play and nothing changed!
You are correct, we need to make a small change to GameManager to see what
ScreenField does. Let's update GameManager.cs to look like this now:
using UnityEngine;
using System.Collections;

/// <summary>
/// Stores game state and game information
/// </summary>
public class GameManager : MonoBehaviour
{
//--------------------------------------------------------------------------------------------// Start()
//--------------------------------------------------------------------------------------------// Unity method, called at game start automatically
//--------------------------------------------------------------------------------------------void Start ()
{
// build our game screen border
ScreenBorder.Instance.Initialize();

// build our game screen field


ScreenField.Instance.Initialize();
}
}

Now when you press Play in Unity, you should start to see some better results, here
is a screenshot of what you should now have:
Displaying ScreenField

Looking much better, but we still don't have a game, let's keep going, next up is
adding some Score.

Scripting the Score.cs script


using UnityEngine;
using System.Collections;
public class Score : MonoBehaviour
{
// private fields
private static Score instance = null;
private GUIText gameScoreText;

//--------------------------------------------------------------------------------------------// constructor field: Instance


//--------------------------------------------------------------------------------------------// Creates an instance of Score if one does not exists
//--------------------------------------------------------------------------------------------public static Score Instance
{
get
{
if (instance == null)
{
instance = new GameObject("GameScore").AddComponent<Score>();
}
return instance;
}
}

//--------------------------------------------------------------------------------------------// Unity method: OnApplicationQuit()


//--------------------------------------------------------------------------------------------// Called when you quit the application or stop the editor player
//--------------------------------------------------------------------------------------------public void OnApplicationQuit()
{
DestroyInstance();
}
//--------------------------------------------------------------------------------------------// DestroyInstance()
//--------------------------------------------------------------------------------------------// Destroys the Score instance
//--------------------------------------------------------------------------------------------public void DestroyInstance()
{
print("Score Instance destroyed");
instance = null;
}

//--------------------------------------------------------------------------------------------// UpdateScoreText()
//--------------------------------------------------------------------------------------------// Updates the Score GUIText.text with a new string

//--------------------------------------------------------------------------------------------public void UpdateScoreText(string appendString)


{
gameScoreText.text = "";
gameScoreText.text = "Score: " + appendString;
}
//--------------------------------------------------------------------------------------------// Initialize()
//--------------------------------------------------------------------------------------------// Initializes Score
//--------------------------------------------------------------------------------------------public void Initialize()
{
print("Score initialized");
// make sure our localScale is correct for a GUItexture
transform.position = Vector3.zero;
transform.rotation = Quaternion.identity;
transform.localScale = Vector3.one;
// add our GUITexture component
gameScoreText = gameObject.AddComponent<GUIText>();
// we define the initial text of the GUIText.text
gameScoreText.text = "Score: ";
// we intialize the GUIText properties
gameScoreText.pixelOffset = new Vector2(10,758);
}
}

So here we have once again done something very similar as in the ScreenBorder
and ScreenField scripts, to recap we have defined two fields to reference our Score
class, dynamically created the gameobject when we play the game, and returned the
instance reference to the field Instance so we can access it from other scripts.
Then we have the same OnApplicationQuit() and DestroyInstance() methods to
handle quitting the game, and next we have the method UpdateScoreText(string
appendString). This handles updating our GUIText text display in game during run
time, so when we make a score, we get points for that, and it shows our updated
score.
Finally we have the re-occurring method named Initialize(), in here we set some
transform properties, add a GUIText component, define the guiText.text property,
and set the position to pixel placement 10, 758.
Pretty straightforward, let's continue on with our next script, Lives.cs.

Scripting the Lives.cs script


using UnityEngine;
using System.Collections;
public class Lives : MonoBehaviour
{
// private fields
private static Lives instance = null;
private GUIText gameLivesText;

//--------------------------------------------------------------------------------------------// constructor field: Instance


//--------------------------------------------------------------------------------------------// Creates an instance of Lives if one does not exists
//--------------------------------------------------------------------------------------------public static Lives Instance
{
get
{
if (instance == null)
{
instance = new GameObject("GameLives").AddComponent<Lives>();
}
return instance;
}
}

//--------------------------------------------------------------------------------------------// Unity method: OnApplicationQuit()


//--------------------------------------------------------------------------------------------// Called when you quit the application or stop the editor player
//--------------------------------------------------------------------------------------------public void OnApplicationQuit()
{
DestroyInstance();
}
//--------------------------------------------------------------------------------------------// DestroyInstance()
//--------------------------------------------------------------------------------------------// Destroys the Lives instance
//--------------------------------------------------------------------------------------------public void DestroyInstance()
{
print("Lives Instance destroyed");
instance = null;
}

//--------------------------------------------------------------------------------------------// UpdateLivesText()
//--------------------------------------------------------------------------------------------// Updates the Score GUIText.text with a new string
//--------------------------------------------------------------------------------------------public void UpdateLivesText(string appendString)
{
gameLivesText.text = "";
gameLivesText.text = "Lives: " + appendString;

//--------------------------------------------------------------------------------------------// Initialize()
//--------------------------------------------------------------------------------------------// Initializes Lives
//--------------------------------------------------------------------------------------------public void Initialize()
{
print("Lives initialized");
// make sure our localScale is correct for a GUItexture
transform.position = Vector3.zero;
transform.rotation = Quaternion.identity;
transform.localScale = Vector3.one;
// add our GUITexture component
gameLivesText = gameObject.AddComponent<GUIText>();
// we define the initial text of the GUIText.text
gameLivesText.text = "Lives: ";
// we intialize the GUIText properties
gameLivesText.pixelOffset = new Vector2(944,758);
}
}

Wow, is this the same as Score.cs??? Lol, nope there are a couple of differences, but
one things for sure, copy/paste are a programmer's best friend :).
Two subtle differences, the text in UpdateLivesText is initialized to display Lives
instead of Score and in Initialize() we are changing the position on screen to be to
the right side of the screen, instead of where Score is set to display on the left side
of the screen.
Once you have Lives scripted, let's save and take a look in Unity.
What! Nothing changed, what's up with that!
Right again, we have to fix GameManger.cs once again, let's modify it to look like
this:
using UnityEngine;
using System.Collections;

/// <summary>
/// Stores game state and game information
/// </summary>
public class GameManager : MonoBehaviour
{
//---------------------------------------------------------------------------------------------

// Start()
//--------------------------------------------------------------------------------------------// Unity method, called at game start automatically
//--------------------------------------------------------------------------------------------void Start ()
{
// build our game screen border
ScreenBorder.Instance.Initialize();
// build our game field
ScreenField.Instance.Initialize();
// build our Score object
Score.Instance.Initialize();
// build our Lives object
Lives.Instance.Initialize();
}
}

You should now see this:


Displaying Score & Lives
Now things are starting to come together! Your doing great, time to start getting in
to the nuts and bolts of the game, next up is the GameSnake.cs script, here we
go....

Scripting the SnakeGame.cs script


using UnityEngine;
using System.Collections;
public class SnakeGame : MonoBehaviour
{
// prvate fields
private static SnakeGame instance = null;

// fields
public int gameScore = 0;
public int gameLives = 3;
public int scoreMultipler = 100;
//--------------------------------------------------------------------------------------------// constructor field: Instance
//--------------------------------------------------------------------------------------------// Creates an instance of SnakeGame if one does not exists
//--------------------------------------------------------------------------------------------public static SnakeGame Instance
{
get
{
if (instance == null)
{

instance = new GameObject("SnakeGame").AddComponent<SnakeGame>();


}
return instance;
}
}

//--------------------------------------------------------------------------------------------// Unity method: OnApplicationQuit()


//--------------------------------------------------------------------------------------------// Called when you quit the application or stop the editor player
//--------------------------------------------------------------------------------------------public void OnApplicationQuit()
{
DestroyInstance();
}
//--------------------------------------------------------------------------------------------// DestroyInstance()
//--------------------------------------------------------------------------------------------// Destroys the SnakeGame instance
//--------------------------------------------------------------------------------------------public void DestroyInstance()
{
print("Snake Game Instance destroyed");
instance = null;
}

//--------------------------------------------------------------------------------------------// UpdateScore()
//--------------------------------------------------------------------------------------------// Updates the game score, and the Score Instance text display
//--------------------------------------------------------------------------------------------public void UpdateScore(int additive)
{
// add to our current game score
gameScore += additive * scoreMultipler;
// update our display
Score.Instance.UpdateScoreText(gameScore.ToString());
}

//--------------------------------------------------------------------------------------------// UpdateLives()
//--------------------------------------------------------------------------------------------// Updates the snakes lives, and the Lives Instance text display, switches to menu on dead
//--------------------------------------------------------------------------------------------public void UpdateLives(int additive)
{
// add to our current game score
gameLives += additive;
// clamp to o if lower
gameLives = Mathf.Clamp(gameLives, 0, gameLives);
// update our display
Lives.Instance.UpdateLivesText(gameLives.ToString());
}

//--------------------------------------------------------------------------------------------// Initialize()

//--------------------------------------------------------------------------------------------// Initializes SnakeGame


//--------------------------------------------------------------------------------------------public void Initialize()
{
print("SnakeGame initialized");
// initialize transform information
transform.position = Vector3.zero;
transform.rotation = Quaternion.identity;
transform.localScale = Vector3.one;
// initialize
gameScore = 0;
gameLives = 3;
scoreMultipler

SnakeGame variables
// no score initially
// 3 lives to start with
= 100; // adjusts score display

UpdateScore(0);
UpdateLives(0);
}
}

Alrighty then, we have a few things to go over, first again we do the same thing at
the start of the script as we done for all of our build scripts so far, so you should be
familiar with that. We do however have some additional public fields, gameScore,
gameLives and scoreMultiplier, these are an important part of the game, and by
making them public, we can modify them when we get a score in the game or lose a
life. (btw, scoreMultiplier is nothing but a multiplier for gameScore, for example,
by updating score by 1, according to what we have set above, you will get 100
points).
You will notice too in our Initialize() method, we are settings these public fields
(remember, Initialize() is called at the start of the game), so that we show the
correct score and lives when we start the game.
Well, what are you waiting for! :) Let's see what it does, remember we need to
update GameManager once again, update it to look like this:
Displaying SnakeGame
Pretty cool! We have updated our Score and Lives display to show a score of 0 and
lives to 3.
Your doing great!
I know it seems like we have gone a long way just to show some colors and text on

screen, but hang in there, we are getting to the nitty gritty now. It's time to create
some food, lets get going with Food.cs
using UnityEngine;
using System.Collections;
public class Food : MonoBehaviour {
// public fields
public Rect foodPos = new Rect(0,0,20,20);

// private fields
private static Food instance = null;
private static int[] initXPos = new int[]
{22,42,62,82,102,122,142,162,182,202,222,242,262,282,302,322,342,362,382,402,422,442,462,482,502,522,542,562,582,
602,622,642,662,682,702,722,742,762,782,802,822,842,862,882,902,922,942,962,982};
private static int[] initYPos = new int[]
{94,114,134,154,174,194,214,234,254,274,294,314,334,354,374,394,414,434,454,474,494,514,534,554,574,594,614,634,6
54} ;
private Texture2D foodTexture;
private AudioClip foodPickup;
// --------------------------------------------------------------------------------------------------// constructor field: Instance
// --------------------------------------------------------------------------------------------------// Creates an instance of Food if one does not exists
// --------------------------------------------------------------------------------------------------public static Food Instance
{
get
{
if (instance == null)
{
instance = new GameObject("Food").AddComponent<Food>();
}
return instance;
}
}

// --------------------------------------------------------------------------------------------------// Unity method: OnApplicationQuit()


// --------------------------------------------------------------------------------------------------// Called when you quit the application or stop the editor player
// --------------------------------------------------------------------------------------------------public void OnApplicationQuit()
{
DestroyInstance();
}
//
//
//
//
//

--------------------------------------------------------------------------------------------------DestroyInstance()
--------------------------------------------------------------------------------------------------Destroys the Food instance
---------------------------------------------------------------------------------------------------

public void DestroyInstance()


{
print("Food Instance destroyed");
instance = null;
}

/ --------------------------------------------------------------------------------------------------// UpdateFood()
// --------------------------------------------------------------------------------------------------// Updates the Food position
// ---------------------------------------------------------------------------------------------------

public void UpdateFood()


{
print("Food updated");

// play our food pickup sound


audio.Play();
// initialize pixelInset random positions
int ranX = Random.Range(0, initXPos.Length);
int ranY = Random.Range(0, initYPos.Length);
// assign a random position to the pixelInset
foodPos = new Rect(initXPos[ranX],initYPos[ranY],20,20);
}

// --------------------------------------------------------------------------------------------------// OnGUI()
// --------------------------------------------------------------------------------------------------// Unity method for handling GUI rendering, used for rendering the Food texture
// --------------------------------------------------------------------------------------------------void OnGUI()
{
if (Food.Instance != null)
{
GUI.DrawTexture(foodPos, foodTexture);
}
}
// --------------------------------------------------------------------------------------------------// Initialize()
// --------------------------------------------------------------------------------------------------// Initializes Food
// --------------------------------------------------------------------------------------------------public void Initialize()
{
print("ScreenField initialized");
// add our AudioSource component
if (!gameObject.GetComponent<AudioSource>())
{
// load in our clips
foodPickup = Resources.Load("Sounds/Food Pickup") as AudioClip;
gameObject.AddComponent<AudioSource>();

// initialize some audio properties


audio.playOnAwake = false;
audio.loop = false;
audio.clip = foodPickup;
}

// make sure our localScale is correct for a GUItexture


transform.position = Vector3.zero;
transform.rotation = Quaternion.identity;
transform.localScale = Vector3.one;
// we create a texture for our GUITexture mainTexture
foodTexture = TextureHelper.CreateTexture(20,20,Color.red);
// set a random position
// initialize pixelInset random positions
int ranX = Random.Range(0, initXPos.Length);
int ranY = Random.Range(0, initYPos.Length);
// assign a random position to the pixelInset
foodPos = new Rect(initXPos[ranX],initYPos[ranY],20,20);
}
}

Ok, so here we have our Food script, first you will notice we are creating a public
Rect, which stores and updates the position of our food on screen. Remember we
have to make this public so that our SnakeGame can access it for evaluation.
Next we continue like we have done in previous scripts by creating a private static
Instance of our class, and we have two private arrays, initXPos and initYPos that
are initialized. If all the numbers set to the arrays are confusing, don't worry, these
are just every possible position on the x and y coordinates a food can be placed. It
will become a little more clearer when we get to the UpdateFood method.
Finally we declare the texture and audio for our food.
We can skip the constructor field, OnApplicationQuit and DestroyInstance methods,
you should already know what these do.
Our OnGUI() method handles drawing the food texture on screen. Now for the
Initialize method, we add an audio component if we don't have one, set some
properties, then we set some transform properties, create our food texture and
initialize a couple of integers with a random number. Notice the number range is
from 0 to the length of the initXPos and initYPos length, this ensures we do not
get invalid index errors in unity.
Finally we set the Rect of the food texture. I'll explain how this works. Ok, so
above we set a random integer with a number from 0 to the length of the arrays,
InitXPos and initYPos, so let's say on ranX we got back 10, and ranY we got back 8,
when the foodPos is set next, with the Rect(initXpos[ranyX],initYPos[ranY],20,20),
If we look up in the array, we are setting the Rect x with 222, and the Rect y with
254. These are the numbers that are the 10th and 8th positions in the arrays.
This is perfect, cause it ensures that we can use any possible position in the array
And we get a new random position each time we start the game.
Great! Well I hope I explained that ok, now it's time to program what you could
consider our player for the game. This script will be much larger than what we've
done, but don't worry, I'll explain everything as best possible, and as always
comment just about everything. Are you ready? Let's script our SnakeGame.cs

Scripting SnakeGame.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Snake : MonoBehaviour
{
// private static instance of class
private static Snake instance = null;

// private fields
private List<Rect> snakePos = new List<Rect>();
private List<Texture2D> snakeIcon = new List<Texture2D>();
private int snakeLength = 2;
private float moveDelay = 0.5f;
private AudioClip move1;
private AudioClip move2;
private AudioClip death;
// direction enum for clarification
public enum Direction
{
UP,
DOWN,
LEFT,
RIGHT
}
//--------------------------------------------------------------------------------------------// constructor field: Instance
//--------------------------------------------------------------------------------------------// Creates an instance of Snake if one does not exists
//--------------------------------------------------------------------------------------------public static Snake Instance
{
get
{
if (instance == null)
{
instance = new GameObject("Snake").AddComponent<Snake>();
}
return instance;
}
}

//--------------------------------------------------------------------------------------------// Unity method: OnApplicationQuit()


//--------------------------------------------------------------------------------------------// Called when you quit the application or stop the editor player
//--------------------------------------------------------------------------------------------public void OnApplicationQuit()
{
DestroyInstance();
}
//---------------------------------------------------------------------------------------------

// DestroyInstance()
//--------------------------------------------------------------------------------------------// Destroys the Snake instance
//--------------------------------------------------------------------------------------------public void DestroyInstance()
{
print("Snake Instance destroyed");
instance = null;
}

//--------------------------------------------------------------------------------------------// Start()
//--------------------------------------------------------------------------------------------// Unity MonoBehavior call, runs auto when object is created, used for initialization
//--------------------------------------------------------------------------------------------void Start ()
{
// start our SnakeUpdate loop
StartCoroutine(UpdateSnake());
}
//--------------------------------------------------------------------------------------------// UpdateSnake()
//--------------------------------------------------------------------------------------------// Our main player loop, this is what runs the logic for the snake movement, food, and adding segments
//--------------------------------------------------------------------------------------------IEnumerator UpdateSnake ()
{
while(true)
{
// handle multi key presses
if (InputHelper.GetStandardMoveMultiInputKeys())
{
Debug.Log ("We are pressing multiple keys for direction");
yield return null;
continue;
}
// are we moving up
if (InputHelper.GetStandardMoveUpDirection())
{
yield return StartCoroutine(MoveSnake(Direction.UP));
//Debug.Log ("We are moving UP");
}
// are we moving left
if (InputHelper.GetStandardMoveLeftDirection())
{
yield return StartCoroutine(MoveSnake(Direction.LEFT));
//Debug.Log ("We are moving LEFT");
}
// are we moving down
if (InputHelper.GetStandardMoveDownDirection())
{
yield return StartCoroutine(MoveSnake(Direction.DOWN));
//Debug.Log ("We are moving DOWN");
}
if (InputHelper.GetStandardMoveRightDirection())
{

yield return StartCoroutine(MoveSnake(Direction.RIGHT));


//Debug.Log ("We are moving RIGHT");
}

// here we check for snake collision (it can only collide with itself)
if (SnakeCollidedWithSelf() == true)
{
break;
}
yield return new WaitForSeconds(moveDelay);
}

// play our death sound


audio.clip = death;
audio.Play();
// we are hit
yield return StartCoroutine(ScreenDeath.Instance.FlashDeathScreen());
// we reduce number of lives
SnakeGame.Instance.UpdateLives(-1);
// check for playable lives left
if (SnakeGame.Instance.gameLives == 0)
{
// reload the level, resetting the game
Application.LoadLevel("UniSnake");
}
else
{
// here we handle resetting the snake after a collision
Initialize();
// we have to call Start manually because this object is already Instantiated
Start();
}
}

//--------------------------------------------------------------------------------------------// MoveSnake()
//--------------------------------------------------------------------------------------------// Moves the snake texture (pixel movement / update snake texture Rect)
//--------------------------------------------------------------------------------------------public IEnumerator MoveSnake(Direction moveDirection)
{
// define a temp List of Rects to our current snakes List of Rects
List<Rect> tempRects = new List<Rect>();
Rect segmentRect = new Rect(0,0,0,0);
// initialize
for (int i = 0; i < snakePos.Count; i++)
{
tempRects.Add(snakePos[i]);
}
switch(moveDirection)
{
case Direction.UP:
if (snakePos[0].y > 94)
{
// we can move up
snakePos[0] = new Rect(snakePos[0].x, snakePos[0].y - 20,

snakePos[0].width, snakePos[0].height);

// now update the rest of our body


UpdateMovePosition(tempRects);
// check for food
if (CheckForFood() == true)
{
// check for valid build segment position and add a segment
// create a temporary check position (this one is below the last
segment in snakePos[])
segmentRect = CheckForValidDownPosition();
if (segmentRect.x != 0)
{
// we build another segment passing the Rect as an argument
BuildSnakeSegment(segmentRect);

// increment our snake length


snakeLength++;
// decrement our moveDelay
moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);
// give control back to our calling method
yield break;
}

// create a temporary check position (this one is to the left the


last segment in snakePos[])
segmentRect = CheckForValidLeftPosition();
if (segmentRect.x != 0)
{
// we build another segment passing the Rect as an argument
BuildSnakeSegment(segmentRect);

// increment our snake length


snakeLength++;
// decrement our moveDelay
moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);
// give control back to our calling method
yield break;
}

// create a temporary check position (this one is to the right the


last segment in snakePos[])
segmentRect = CheckForValidRightPosition();
if (segmentRect.x != 0)
{
// we build another segment passing the Rect as an argument
BuildSnakeSegment(segmentRect);

// increment our snake length


snakeLength++;
// decrement our moveDelay
moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);
}

// no need to check Up, because we are pressing the Up key, we do


not want a segment above us

// toggle the audio clip and play


audio.clip = (audio.clip == move1) ? move2 : move1;
audio.Play();
}
break;
case Direction.LEFT:
if (snakePos[0].x > 22)
{
// we can move left
snakePos[0] = new Rect(snakePos[0].x - 20, snakePos[0].y,
snakePos[0].width, snakePos[0].height);

// now update the rest of our body


UpdateMovePosition(tempRects);
// check for food
if (CheckForFood() == true)
{
// check for valid build segment position and add a segment
// create a temporary check position (this one is to the right the
last segment in snakePos[])
segmentRect = CheckForValidRightPosition();
if (segmentRect.x != 0)
{
// we build another segment passing the Rect as an argument
BuildSnakeSegment(segmentRect);

// increment our snake length


snakeLength++;
// decrement our moveDelay
moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);
// give control back to our calling method
yield break;
}

// create a temporary check position (this one is above the last


segment in snakePos[])
segmentRect = CheckForValidUpPosition();
if (segmentRect.x != 0)
{
// we build another segment passing the Rect as an argument
BuildSnakeSegment(segmentRect);

// increment our snake length


snakeLength++;
// decrement our moveDelay
moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);
// give control back to our calling method
yield break;
}

// create a temporary check position (this one is below the last


segment in snakePos[])
segmentRect = CheckForValidDownPosition();
if (segmentRect.x != 0)
{

// we build another segment passing the Rect as an argument


BuildSnakeSegment(segmentRect);
// increment our snake length
snakeLength++;
// decrement our moveDelay
moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);
// give control back to our calling method
yield break;
}

// no need to check Left, because we are pressing the Left key, we


do not want a segment ahead of us
}

// toggle the audio clip and play


audio.clip = (audio.clip == move1) ? move2 : move1;
audio.Play();
}
break;
case Direction.DOWN:
if (snakePos[0].y < 654)
{
// we can move down
snakePos[0] = new Rect(snakePos[0].x, snakePos[0].y + 20,
snakePos[0].width, snakePos[0].height);

// now update the rest of our body


UpdateMovePosition(tempRects);
// check for food
if (CheckForFood() == true)
{
// check for valid build segment position and add a segment
// create a temporary check position (this one is above the last
segment in snakePos[])
segmentRect = CheckForValidUpPosition();
if (segmentRect.x != 0)
{
// we build another segment passing the Rect as an argument
BuildSnakeSegment(segmentRect);

// increment our snake length


snakeLength++;
// decrement our moveDelay
moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);
// give control back to our calling method
yield break;
}

// create a temporary check position (this one is to the left the


last segment in snakePos[])
segmentRect = CheckForValidLeftPosition();
if (segmentRect.x != 0)
{
// we build another segment passing the Rect as an argument
BuildSnakeSegment(segmentRect);

// increment our snake length


snakeLength++;
// decrement our moveDelay
moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);
// give control back to our calling method
yield break;
}

// create a temporary check position (this one is to the right the


last segment in snakePos[])
segmentRect = CheckForValidRightPosition();
if (segmentRect.x != 0)
{
// we build another segment passing the Rect as an argument
BuildSnakeSegment(segmentRect);

// increment our snake length


snakeLength++;
// decrement our moveDelay
moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);
}

// no need to check Down, because we are pressing the Down key, we


do not want a segment below us
}

// toggle the audio clip and play


audio.clip = (audio.clip == move1) ? move2 : move1;
audio.Play();
}
break;
case Direction.RIGHT:
if (snakePos[0].x < 982)
{
// we can move right
snakePos[0] = new Rect(snakePos[0].x + 20, snakePos[0].y,
snakePos[0].width, snakePos[0].height);

// now update the rest of our body


UpdateMovePosition(tempRects);
// check for food
if (CheckForFood() == true)
{
// check for valid build segment position and add a segment
// create a temporary check position (this one is left of the last
segment in snakePos[])
segmentRect = CheckForValidLeftPosition();
if (segmentRect.x != 0)
{
// we build another segment passing the Rect as an argument
BuildSnakeSegment(segmentRect);

// increment our snake length


snakeLength++;
// decrement our moveDelay
moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);

// give control back to our calling method


yield break;
}

// create a temporary check position (this one is to the left the


last segment in snakePos[])
segmentRect = CheckForValidUpPosition();
if (segmentRect.x != 0)
{
// we build another segment passing the Rect as an argument
BuildSnakeSegment(segmentRect);

// increment our snake length


snakeLength++;
// decrement our moveDelay
moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);
// give control back to our calling method
yield break;
}

// create a temporary check position (this one is below the last


segment in snakePos[])
segmentRect = CheckForValidDownPosition();
if (segmentRect.x != 0)
{
// we build another segment passing the Rect as an argument
BuildSnakeSegment(segmentRect);

// increment our snake length


snakeLength++;
// decrement our moveDelay
moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);
}

// no need to check Right, because we are pressing the Right key,


we do not want a segment ahead of us
}
// toggle the audio clip and play
audio.clip = (audio.clip == move1) ? move2 : move1;
audio.Play();
}
break;
}
yield return null;
}

//--------------------------------------------------------------------------------------------// UpdateMovePosition()
//--------------------------------------------------------------------------------------------// Updates the snakePos list of Rect's to the new Rect positions after a move
//--------------------------------------------------------------------------------------------private void UpdateMovePosition(List<Rect> tmpRects)
{
// update our snakePos Rect with the tmpRect positions
for (int i = 0; i < tmpRects.Count - 1; i++)
{
// exe. size of 3, assign 1,2,3 to 0,1,2 - snakePos[0] is already assigned
snakePos[i+1] = tmpRects[i];

}
}

//--------------------------------------------------------------------------------------------// CheckForFood()
//--------------------------------------------------------------------------------------------// Checks if the first snake segment is same position as the food on the game field, returns bool
//--------------------------------------------------------------------------------------------private bool CheckForFood()
{
if(Food.Instance != null)
{
Rect foodRect = Food.Instance.foodPos;
if (snakePos[0].Contains(new Vector2(foodRect.x,foodRect.y)))
{
Debug.Log ("We hit the food");

// we re-position the food


Food.Instance.UpdateFood();
// we add to our score
SnakeGame.Instance.UpdateScore(1);
return true;
}
}
return false;
}

//--------------------------------------------------------------------------------------------// CheckForValidDownPosition()
//--------------------------------------------------------------------------------------------// Checks if the last snake segment is not in the lowest position on the game field, returns Rect
//--------------------------------------------------------------------------------------------private Rect CheckForValidDownPosition()
{
if (snakePos[snakePos.Count-1].y != 654)
{
return new Rect(snakePos[snakePos.Count-1].x, snakePos[snakePos.Count-1].y - 20, 20, 20);
}
return new Rect(0,0,0,0);
}

//--------------------------------------------------------------------------------------------// CheckForValidUpPosition()
//--------------------------------------------------------------------------------------------// Checks if the last snake segment is not in the highest position on the game field, returns Rect
//--------------------------------------------------------------------------------------------private Rect CheckForValidUpPosition()
{
if (snakePos[snakePos.Count-1].y != 94)
{
return new Rect(snakePos[snakePos.Count-1].x, snakePos[snakePos.Count-1].y + 20, 20, 20);
}
return new Rect(0,0,0,0);
}

//--------------------------------------------------------------------------------------------// CheckForValidLeftPosition()

//--------------------------------------------------------------------------------------------// Checks if the last snake segment is not in the far left position on the game field, returns Rect
//--------------------------------------------------------------------------------------------private Rect CheckForValidLeftPosition()
{
if (snakePos[snakePos.Count-1].x != 22)
{
return new Rect(snakePos[snakePos.Count-1].x - 20, snakePos[snakePos.Count-1].y, 20, 20);
}
return new Rect(0,0,0,0);
}

//--------------------------------------------------------------------------------------------// CheckForValidRightPosition()
//--------------------------------------------------------------------------------------------// Checks if the last snake segment is not in the far right position on the game field, returns Rect
//--------------------------------------------------------------------------------------------private Rect CheckForValidRightPosition()
{
if (snakePos[snakePos.Count-1].x != 982)
{
return new Rect(snakePos[snakePos.Count-1].x + 20, snakePos[snakePos.Count-1].y, 20, 20);
}
return new Rect(0,0,0,0);
}

//--------------------------------------------------------------------------------------------// BuildSnakeSegment()
//--------------------------------------------------------------------------------------------// Adds a snake segment, pass the Rect to apply the position to
//--------------------------------------------------------------------------------------------private void BuildSnakeSegment(Rect rctPos)
{
// define our snake head and tail texture
snakeIcon.Add(TextureHelper.CreateTexture(20, 20, Color.green));
// define our snake head and tail GUI Rect
snakePos.Add(rctPos);
}

//--------------------------------------------------------------------------------------------// SnakeCollidedWithSelf()
//--------------------------------------------------------------------------------------------// Checks if the snake has hit any part of its body, returns true/false
//--------------------------------------------------------------------------------------------private bool SnakeCollidedWithSelf()
{
bool didCollide = false;
if (snakePos.Count <= 4)
{
return false;
}
for (int i = 0; i < snakePos.Count; i++)
{
if (i > 0)
{
if (snakePos[0].x == snakePos[snakePos.Count - i].x && snakePos[0].y ==
snakePos[snakePos.Count - i].y)
{

// we have collided
didCollide = true;
break;
}
}
}
return didCollide;
}

//--------------------------------------------------------------------------------------------// OnGUI()
//--------------------------------------------------------------------------------------------// Unity method, handles displaying the snake on screen
//--------------------------------------------------------------------------------------------void OnGUI()
{
for (int i = 0; i < snakeLength; i++)
{
GUI.DrawTexture(snakePos[i], snakeIcon[i]);
}
}
//--------------------------------------------------------------------------------------------// Initialize()
//--------------------------------------------------------------------------------------------// Initializes Snake
//--------------------------------------------------------------------------------------------public void Initialize()
{
print("Snake initialized");
// clear our Lists
snakePos.Clear();
snakeIcon.Clear();
// initialize our length to start length
snakeLength = 2;
// intialize our moveDelay
moveDelay = 0.5f;
// add our AudioSource component
if (!gameObject.GetComponent<AudioSource>())
{
// load in our clips
move1 = Resources.Load("Sounds/Move1 Blip") as AudioClip;
move2 = Resources.Load("Sounds/Move2 Blip") as AudioClip;
death = Resources.Load("Sounds/Death") as AudioClip;
gameObject.AddComponent<AudioSource>();

// initialize some audio properties


audio.playOnAwake = false;
audio.loop = false;
audio.clip = move1;
}

// make sure our localScale is correct for a GUItexture


transform.position = Vector3.zero;
transform.rotation = Quaternion.identity;
transform.localScale = Vector3.one;

// define our snake head and tail texture


snakeIcon.Add(TextureHelper.CreateTexture(20, 20, Color.green));
snakeIcon.Add(TextureHelper.CreateTexture(20, 20, Color.green));
// define our snake head and tail GUI Rect
snakePos.Add(new Rect(Screen.width * 0.5f - 10, Screen.height * 0.5f - 10, snakeIcon[0].width,
snakeIcon[0].height));
snakePos.Add(new Rect(Screen.width * 0.5f - 10 + 20, Screen.height * 0.5f - 10,
snakeIcon[1].width, snakeIcon[1].height));
}
}

Phew! Thats a ton of code! You got it, but we are handling our player character (the
snake), and for that we need to check for a lot of different things to make sure we
look and act correctly in the game. Also, we have to make sure that the game acts as
it should based on certain conditions, such as eating food, or hitting ourself, which
causes us to die and lose a life.
It's important to know how this game plays, what happens is you move the snake
around the ScreenField area, based on keyboard input. Now when you release the
movement key, the snake will stop, also, you can run in to borders without dieing,
you will only die if you run in to yourself. So this game may vary a little from the
original, but it's still fun to see how far you can go, and we will implement a speed
modifier in the game to make sure it challenges you as your snake gets longer.
Ok, let's break this script down, first we start out as usual, you'll notice the
instance's of Snake so we create the connection to the script and it's public fields
and methods. These are exactly like the other scripts, so no big surprise there.
We declare some private fields, notice we are using a type of array called a List<>
in order to use this we have to add at the top of the script under System.Collections,
System.Collections.Generic, this gives us new types in our code to use. So we have
2 List's one is a container for our Rect positions, and the other is a container for our
snake texture. Our snake will be displayed in OnGUI(). We also have the field
snakeLength, this is initialized to 2, and is the starting length of our snake (2-20x20
square textures in length), then we have moveDelay set at 0.5, remember earlier
when I said the game would have a speed modifier, this is it. What we do is in our
UpdateSnake() method, we delay the length of moveDelay, and as we grow longer,
we reduce moveDelay so our snake moves faster! :) Next we have 3 audioclips, for
our move and our death. Finally we have an enum field, to define the direction our
snake is moving based on user Input.

Now unlike our other build scripts, Snake.cs has a Start method, this is a Unity
method and is called when you press play in the editor or play the game. What we
do in Start() is call a coroutine UpdateSnake, we have to declare UpdateSnake as
IEnumerator type method because it contains a yield statement. Also, we are
running a while loop in UpdateSnake, so this is where the yield statement comes in.
So let's jump right in to UpdateSnake(), first we start with the while(true) loop, and
inside the while loop brackets, we are testing for multiple key presses at the same
time with InputHelper.GetStandardMoveMultiInputKeys() What's this! Another
helper script? You got it, we will get in to InputHelper soon, but for now just know
that we are checking the standard keyboard movement keys (w,a,s,d, uparrow,
leftarrow, downarrow, rightarrow) for two or more of these keys pressed at once, in
this case we return back to the top of the script. For example, we don't want the
snake to react if we are left+right arrow keys at the same time, that will only cause
confusion in the script below and possibly process some inaccurate direction.
So in our loop, we test if we can move in all 4 directions, up, down, left and right,
by calling InputHelper to check and return true or false if we are pressing those
keys. So for example, if we are pressing uparrow or the w key, we call
yield return StartCoroutine(MoveSnake(Direction.UP)); What we are doing
here is yielding until a full position movement in the up direction has been made,
then we allow the code below that statement to process. Below that statement, we
do the same thing, only for the other directions.
Next we have another check if (SnakeCollidedWithSelf() == true)
This tests to see if we have ran in to ourselves, if so it will break out of the while
loop. Notice in the if statement, we are calling a method to test, this is a handy way
to check for multiple circumstances, and return true or false based on a number of
different things. If we look at the SnakeCollidedWithSelf() method, you will see we
are doing just that. In SnakeCollidedWithSelf() we start with setting a bool
didCollide to false, this is important because if we don't have a collision, then we
automatically return false to the if check above. We also check to see if the length
of the snake is greater than 4, it's impossible to hit ourself unless we are 5 or more
squares big, so we return false if we are not. Then we run a for loop, and inside this
loop, we check to see if our snake's head is inside any other of the snakes positions,
if it is, we set didCollide to true, break the loop and return that to the if statement.

This gives us a way to be killed in game, and will move us to the next step in our
UpdateSnake() method, if we have hit ourself, we will break out of the main while
loop, and set our audio clip to our death clip, play that sound, then we display our
fancy death screen :) (flashy red, oooh!!! lol), notice though how we yield that
statement, this is one way to have control of execution, the following lines after
yield return StartCoroutine(ScreenDeath.Instance.FlashDeathScreen());

will not process until FlashDeathScreen() has finished running, pretty cool. Next we
reduce the number of lives (remember this is handled in our SnakeGame script),
then we check to see if our lives are less than or equal to 0, if so we reload the level,
starting the whole game over again. If we still have lives left, we simply call our
Snake.cs Initialize() method again, and then the Start() method, to reset our snake
back to it's start position and length.
Now one last line I want to mention in UpdateSnake() is

yield return new

WaitForSeconds(moveDelay);

This is where we delay the whole movement loop, creating the speed of the snake
itself. MoveDelay starts out at 0.5, but as I mentioned before, when we grow larger,
that variable gets adjusted down 0.01, so as it gets smaller, we move faster.
On to MoveSnake(MoveDirection direction), well we have a ton of stuff going on
here, we start with creating some temp fields, another List of Rect's, this will hold
the intial positions of the snake Rects, this gives us a way to update the snake's
position on screen when we move. The idea is pretty simple, say we move up, we
change the rect of the snakes head position to the heads width and height position
on screen above itself, well since we have updated snakePos Rect, we store the
intial positions so we can tell the other pieces of the snake where to go after we
move the head. So for example, say we have 4 segments of our snake, when we
move up, we update the position of snakePos[0] (the head) to the up position, as
long as we are not at the border (pixel position 94), and then we update the rest of
the snakePos segments in UpdateMovePosition(List<Rect> tmpRects), notice in
this method, we are looping through the intial snakePos Rect positions minus 1, and
then setting the snakePos rect positions index after 1 (thus leaving the [0] index
head position where it moved to.
I'm not sure if I explained that very well, but in short, we are storing the initial
positions, and repositioning the snakes position (with the excepetion of the head) to
1 index ahead of where the snake currently was.

The next line if (CheckForFood() == true), here we are seeing if every move we
make, if our position is in the same position as a food block, if so the next test we
have to do is to see if we have a valid position to build the piece on to, we should
only add a piece if the tail of the snake is border to a wall to the correct side of the
tail to make sure it isn't added inside the border of the wall.
So we return the Rect of the open position next to the tail and test for the .x not
equal to 0, and then build the segment with BuildSnakeSegment(tempRect), passing
in the open position Rect. This will add a piece to the snake. The next line
increments the length of the loop to check to, this is very important, because we
ensure that we are counting up to the last piece added in the array of segments.
The last line moveDelay = Mathf.Max(0.05f, moveDelay - 0.01f);
decreases the moveDelay float so that th egame speeds up a small bit, this adds
progression to the game, and creates a faster pace for gameplay.
The rest of the codes basically repeats what we have already discussed, the only
difference is we use an enum to check each block of code for the direction we
moved.
So our next method below is UpdateMoveDirection() this is called from
MoveSnake(), and updates the position of the snake when it is moved on screen.
Our next method CheckForFood this is also called from MoveSnake() and returns if
our snake head position is in the same position as our food object on screen, of so it
returns true, if not it returns false.
Then we have CheckForValidDownPosition(), ..Up.., ..Left.., ..Right.. these return
back to MoveSnake that we have a valid position to add another piece to the snake.
I could have combined all 4 of these in to 1 method, passing arguments, but for
clarity I broke it up in to 4 methods.
The mext method is BuildSnakeSegment() this is where we add another piece to the
snake when we collect food.
Then we have SnakeCollidedWithSelf(), this is where we check to see if the head of

the snake has positioned itself within any part of its bodies position, if so, we have
hit ourself and return true, this will break the UpdateSnake loop, and start the death
sequence calls of the snake.
Our OnGUI() method is a unity method, and this is responsible for displaying the
snake. Notice how we use a 'for' loop to ensure we have drawn every texture of the
snake before processing another frame of OnGUI(), this is very important.
And finally we have our Initialize() method, like the other build scripts, this is were
we start out building the start head of our snake and setting up the intial positions,
resetting variables, our arrays, and also note we look for an audiosource and
initialize our audio clips through Resources.Load, this is a nice way of using assets
in code, by putting them in the Resources folder, and loading them in to the game
using Unity's calll Resources.Load().

Scripting InputHelper.cs
using UnityEngine;
using System.Collections;
public class InputHelper
{
// handle multi Input
public static bool GetStandardMoveMultiInputKeys()
{
// check W
if (Input.GetKey (KeyCode.W) && Input.GetKey (KeyCode.A)) { return true; }
if (Input.GetKey (KeyCode.W) && Input.GetKey (KeyCode.S)) { return true; }
if (Input.GetKey (KeyCode.W) && Input.GetKey (KeyCode.D)) { return true; }

// check A
if (Input.GetKey (KeyCode.A) && Input.GetKey (KeyCode.S)) { return true; }
if (Input.GetKey (KeyCode.A) && Input.GetKey (KeyCode.D)) { return true; }
// check S
if (Input.GetKey (KeyCode.S) && Input.GetKey (KeyCode.D)) { return true; }
// D is resulted in the above checks
//
if
if
if

check UpArrow
(Input.GetKey (KeyCode.UpArrow) && Input.GetKey (KeyCode.LeftArrow)) { return true; }
(Input.GetKey (KeyCode.UpArrow) && Input.GetKey (KeyCode.DownArrow)) { return true; }
(Input.GetKey (KeyCode.UpArrow) && Input.GetKey (KeyCode.RightArrow)) { return true; }

// check LeftArrow
if (Input.GetKey (KeyCode.LeftArrow) && Input.GetKey (KeyCode.DownArrow)) { return true; }
if (Input.GetKey (KeyCode.LeftArrow) && Input.GetKey (KeyCode.RightArrow)) { return true; }
// check DownArrow
if (Input.GetKey (KeyCode.DownArrow) && Input.GetKey (KeyCode.RightArrow)) { return true; }
// RightArrow is resulted in the checks above

return false;
}

// handle up direction
public static bool GetStandardMoveUpDirection()
{
if (Input.GetKey (KeyCode.W) || Input.GetKey(KeyCode.UpArrow)) { return true; }
return false;
}

// handle left direction


public static bool GetStandardMoveLeftDirection()
{
if (Input.GetKey (KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) { return true; }
return false;
}

// handle down direction


public static bool GetStandardMoveDownDirection()
{
if (Input.GetKey (KeyCode.S) || Input.GetKey(KeyCode.DownArrow)) { return true; }
return false;
}

// handle left direction


public static bool GetStandardMoveRightDirection()
{
if (Input.GetKey (KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) { return true; }
return false;
}
}

Now for our final script, InputHelper.cs is another helper script like
TextureHelper.cs and remember, helper scripts are useful to use in multiple game
projects.
From the top, notice again we removed : MonoBehavior after the class name
because the purpose of this script is to never be added to a game object in the
hiearchy view, we simply reference it in code, and get the results we need.
So from the top, GetStandardMoveMultiInputKeys(), we test for multiple standard
move keyboard presses and return true if we are pressing more than one of them
down at the same time, or false if we are not.
The methods below, do the same except they are only testing for a particular key
press, returning true or false if that key is pressed down.

Once you have completed typing in InputHelper, save your code, if you have no
errors, your game should be complete and ready to run. Keep in mind, we are not
drawing on screen nothing but squares for our snake and food, as well as our border
and playing field, so don't expect any fancy art :)
Here is the game view in playing progress:

We have made to the last segment of this tutorial, and our lats script. ScreenDeath.cs
Don't worry this, this one will be a breeze. Here's the code.

using UnityEngine;
using System.Collections;
public class ScreenDeath : MonoBehaviour
{
// private fields
private static ScreenDeath instance = null;
private GUITexture deathScreenBackground;
private Color deathColor;
private Color invisColor;

// --------------------------------------------------------------------------------------------------// constructor field: Instance


// --------------------------------------------------------------------------------------------------// Creates an instance of ScreenField if one does not exists
// --------------------------------------------------------------------------------------------------public static ScreenDeath Instance
{
get
{
if (instance == null)
{
instance = new GameObject("ScreenDeath").AddComponent<ScreenDeath>();
}
return instance;
}
}

// --------------------------------------------------------------------------------------------------// Unity method: OnApplicationQuit()


// --------------------------------------------------------------------------------------------------// Called when you quit the application or stop the editor player
// --------------------------------------------------------------------------------------------------public void OnApplicationQuit()
{
DestroyInstance();
}
//
//
//
//
//

--------------------------------------------------------------------------------------------------DestroyInstance()
--------------------------------------------------------------------------------------------------Destroys the ScreenDeath instance
---------------------------------------------------------------------------------------------------

public void DestroyInstance()


{
print("Screen Death Instance destroyed");
instance = null;
}

//
//
//
//
//

--------------------------------------------------------------------------------------------------FlashDeathScreen()
--------------------------------------------------------------------------------------------------Flashes the death screen for a number of times
---------------------------------------------------------------------------------------------------

public IEnumerator FlashDeathScreen()


{
deathScreenBackground.color = deathColor;
yield return new WaitForSeconds(0.1f);
deathScreenBackground.color = invisColor;
yield return new WaitForSeconds(0.1f);
deathScreenBackground.color = deathColor;
yield return new WaitForSeconds(0.1f);
deathScreenBackground.color = invisColor;
yield return new WaitForSeconds(0.1f);
deathScreenBackground.color = deathColor;
yield return new WaitForSeconds(0.1f);
deathScreenBackground.color = invisColor;
yield return new WaitForSeconds(0.1f);
deathScreenBackground.color = deathColor;
yield return new WaitForSeconds(0.1f);
deathScreenBackground.color = invisColor;
yield return new WaitForSeconds(0.1f);
}

// --------------------------------------------------------------------------------------------------// Initialize()
// --------------------------------------------------------------------------------------------------// Initializes ScreenField
// --------------------------------------------------------------------------------------------------public void Initialize()
{
print("ScreenDeath initialized");
// make sure our localScale is correct for a GUItexture
transform.position = new Vector3(0,0,2);
transform.rotation = Quaternion.identity;
transform.localScale = new Vector3(0.01f, 0.01f, 1.0f);
// add our GUITexture component
deathScreenBackground = gameObject.AddComponent<GUITexture>();
// we create a texture for our GUITexture mainTexture
Texture2D deathTexture = TextureHelper.Create1x1Texture(Color.red);
// we intialize some GUITexture properties
deathScreenBackground.texture = deathTexture;
// set our texture's invisible color
invisColor = new Color(deathScreenBackground.color.r, deathScreenBackground.color.g,
deathScreenBackground.color.b, 0.0f);
// set our texture's death color
deathColor = new Color(deathScreenBackground.color.r, deathScreenBackground.color.g,
deathScreenBackground.color.b, 0.3f);
deathScreenBackground.pixelInset = new Rect(0,0,1024,768);
deathScreenBackground.color = invisColor;
}
}

So for our final script DeathScreen.cs, this script handles flashing a red screen to
Simulate a player death, and also handles subtracting a life from the display.
We start with 4 private fields, the first one we have explained, this is our class
instance, the next one is our background texture, this will be the red screen flash,
and the last 2 will be set to the death (red) and invisible colors to flash on screen if
we run our snake into itself.
Then we have the public static field instance of ourself, you should be familiar
with this already, as well as the OnApplicationQuit and DestroyInstance methods.
Then we have FlashDeathScreen, notice first that the return type is IEnumerator.
This type of return allows us in C# to use yield which is exactly what we need
When we want to setup like a cut scene scenario or a delay in our game. So in the
FlashDeathScreen method, you can see we are flashing the red screen on and off
using yield to pause between the flashes. This gives us a better visual effect.
Finally, we have the good 'ol Initialize method, like our other scripts, we are
setting some similar properties for our deathScreenBackground, initializing the
on/off colors and the Rect for our deathScreenBackground pixelInset which is the
GUITextures size and finally setting the color of the background to invisible (we
don't want to see it until we have hit ourself).
If your wondering how this method gets called, take a look at line 138 in Snake. It
uses a yield StartCoroutine to fire off FlashDeathScreen, when the movement loop
Has been broken because you have hit yourself.

Congratulations!!!
Pat yourself on the back, you have completed this tutorial. I hope you gained some
programming knowledge, and have found some useful tips for helping you along
with your all your game projects.
If you have any questions, comments please email me at: pendlemac@cox.net
Thanks!

Potrebbero piacerti anche