To be able to:
Insert a screenshot of a new file and name it "Snake"
This will zoom out the camera overlooking your scene making object you add seem smaller without editing them.
Name the new Sprite "Snake"
It's time to build your main character!
Name the new Sprite "Snake"
Instead of the collider being on the edge of the square, it is inside the square.
Shrinking your box collider down will prevent your snake from hitting itself once you eat an apple and grow!
That is now your character complete and you are ready to build the food object!
Name the new Sprite "Food"
Now your food is ready for your character to eat!
Name the new Sprite "Wall"
Name your new wall "Wall Right"
As your Learning Objective said, you are going to be controlling the physics of your characters through code this time round.
But don't worry, it's not as scary as it sounds.
As you are typing out your code, make sure to copy all capital letters, brackets and semi colons EXACTLY as you see it on the slide!
If you don't your code isn't going to work and your characters won't move or eat the food pellets.
Step one is to create a new C# Script in your assets, make sure to name this script "Snake".
Then, double click your script to open the file.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Snake : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
Initially, your code will look like this on the left.
The first thing we need to do is on Line 7, we need to tell our snake which way to travel when we start our game.
Add the line of code below to line 7 to tell our character to start by travelling to the right of the screen.
private Vector2 _direction = Vector2.right;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Snake : MonoBehaviour
{
private Vector2 _direction = Vector2.right;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
Our Start() function will remain empty so we can delete lines 9-13.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Snake : MonoBehaviour
{
private Vector2 _direction = Vector2.right;
// Update is called once per frame
void Update()
{
}
}
The Update() function is a routine that will update once per frame of our game. This is dependant on the speed of your computer but is useful for deciding when to change the direction of our main character.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Snake : MonoBehaviour
{
private Vector2 _direction = Vector2.right;
// Update is called once per frame
void Update()
{
}
}
So inside of the Update() function (on line 12), can you add the following code to check for an arrow key being pressed and to update the direction of your character.
private void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow)) {
_direction = Vector2.up;
} else if (Input.GetKeyDown(KeyCode.LeftArrow)) {
_direction = Vector2.left;
} else if (Input.GetKeyDown(KeyCode.DownArrow)) {
_direction = Vector2.down;
} else if (Input.GetKeyDown(KeyCode.RightArrow)) {
_direction = Vector2.right;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Snake : MonoBehaviour
{
private Vector2 _direction = Vector2.right;
// Update is called once per frame
private void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow)) {
_direction = Vector2.up;
} else if (Input.GetKeyDown(KeyCode.LeftArrow)) {
_direction = Vector2.left;
} else if (Input.GetKeyDown(KeyCode.DownArrow)) {
_direction = Vector2.down;
} else if (Input.GetKeyDown(KeyCode.RightArrow)) {
_direction = Vector2.right;
}
}
}
Your code now will check if we have pressed a button and will update the direction of your character based on the key you have pressed.
Take a moment now to proof read your Update() function to make sure you have got your capital letters, your curly brackets "{}" and your semi colons ";" in the correct positions. Otherwise your character won't be able to move properly.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Snake : MonoBehaviour
{
private Vector2 _direction = Vector2.right;
// Update is called once per frame
private void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow)) {
_direction = Vector2.up;
} else if (Input.GetKeyDown(KeyCode.LeftArrow)) {
_direction = Vector2.left;
} else if (Input.GetKeyDown(KeyCode.DownArrow)) {
_direction = Vector2.down;
} else if (Input.GetKeyDown(KeyCode.RightArrow)) {
_direction = Vector2.right;
}
}
}
The final step in our code for the main character is to create a function that will tell the character itself how to move in this new direction.
Create a new line after line 21.
On this line write:
private void Fixed Update() {
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Snake : MonoBehaviour
{
private Vector2 _direction = Vector2.right;
// Update is called once per frame
private void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow)) {
_direction = Vector2.up;
} else if (Input.GetKeyDown(KeyCode.LeftArrow)) {
_direction = Vector2.left;
} else if (Input.GetKeyDown(KeyCode.DownArrow)) {
_direction = Vector2.down;
} else if (Input.GetKeyDown(KeyCode.RightArrow)) {
_direction = Vector2.right;
}
}
private void FixedUpdate()
{
this.transform.position = new Vector3(
Mathf.Round(this.transform.position.x) + _direction.x,
Mathf.Round(this.transform.position.y) + _direction.y,
0.0f
);
}
}
Now add in the lines of code from line 24-28.
What we are doing here is saying at fixed intervals, give our character a new position (x, y and z), which should be the position we had plus our new position. Our z position will always be 0.0f because our game is 2D.
We also added Mathf.Round at the beginning of each new position which ensures we always give it a whole number.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Snake : MonoBehaviour
{
private Vector2 _direction = Vector2.right;
// Update is called once per frame
private void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow)) {
_direction = Vector2.up;
} else if (Input.GetKeyDown(KeyCode.LeftArrow)) {
_direction = Vector2.left;
} else if (Input.GetKeyDown(KeyCode.DownArrow)) {
_direction = Vector2.down;
} else if (Input.GetKeyDown(KeyCode.RightArrow)) {
_direction = Vector2.right;
}
}
private void FixedUpdate()
{
this.transform.position = new Vector3(
Mathf.Round(this.transform.position.x) + _direction.x,
Mathf.Round(this.transform.position.y) + _direction.y,
0.0f
);
}
}
Once you are happy with it, press CTRL + S to save your work and go back to Unity.
Take a moment now to proof read your Update() function to make sure you have got your capital letters, your curly brackets "{}" and your semi colons ";" in the correct positions. Otherwise your character won't be able to move properly.
To slow down your character, we need to change the project settings so our FixedUpdate() has a longer wait time between refreshes.
To do this, can you go to Edit, then Project Settings.
Now go to Time and the first box, Fixed Timestep, needs a value of 0.07
You can test your code again now to check it works properly.
The next part of logic is to make your food appear in random places each time one gets eaten.
Create a new Script and call it "Food"
Do not open the file yet.
Currently if we are to make the food placement random, it could appear outside of our boundaries. Meaning we cannot eat it.
So we need to create a new invisible object that will set the perimeter of our food placement.
Name your new object "GridArea"
VERY IMPORTANT!
In the inspector of GridArea, right click the three dots of the Transform, then press reset.
This will place your object back into the middle of your screen so we can have an even field of view across your screen.
Add a box collider 2D to this, then tick 'Is Trigger'.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Food : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
Open up your Food script.
The first thing we are going to do is remove our Update() function, lines 13-17.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Food : MonoBehaviour
{
public BoxCollider2D gridArea;
// Start is called before the first frame update
void Start()
{
}
}
With Update() deleted, we need to get the information from our invisible area, so on line 7 add in "public BoxCollider2D gridArea;" and we will have access to the information in our script now.
Then we are going to create a random position for our food each time we eat it.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Food : MonoBehaviour
{
public BoxCollider2D gridArea;
// Start is called before the first frame update
void Start()
{
}
private void RandomisePosition()
{
// Generate the boundaries of the invisible grid
Bounds bounds = this.gridArea.bounds;
// Generate a random x and y position within the grid
float x = Random.Range(bounds.min.x, bounds.max.x);
float y = Random.Range(bounds.min.y, bounds.max.y);
// Give the random position above to our food pellet
this.transform.position = new Vector3(Mathf.Round(x), Mathf.Round(y), 0.0f);
}
}
With Update() deleted, we need to get the information from our invisible area, so on line 7 add in "public BoxCollider2D gridArea;" and we will have access to the information in our script now.
Then we are going to create a random position for our food each time we eat it.
Don't forget to press CTRL + S to save your code.
Your food is now capable of appearing in random positions within your playable area.
Next step: Detecting collisions between:
First things first, we need to add some tags to our objects.
First things first, we need to add some tags to our objects.
First things first, we need to add some tags to our objects.
First things first, we need to add some tags to our objects.
First things first, we need to add some tags to our objects.
First things first, we need to add some tags to our objects.
Re-Open your Food Script by double clicking Food in the Assets Area
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Food : MonoBehaviour
{
public BoxCollider2D gridArea;
// Start is called before the first frame update
void Start()
{
}
private void RandomisePosition()
{
// Generate the boundaries of the invisible grid
Bounds bounds = this.gridArea.bounds;
// Generate a random x and y position within the grid
float x = Random.Range(bounds.min.x, bounds.max.x);
float y = Random.Range(bounds.min.y, bounds.max.y);
// Give the random position above to our food pellet
this.transform.position = new Vector3(Mathf.Round(x), Mathf.Round(y), 0.0f);
}
}
We are going to add a new function below line 26 (above line 27)
I will still show RandomisePosition() to help you see where to place your new code, everything above will disappear for now.
DO NOT DELETE IT FROM YOUR CODE!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Food : MonoBehaviour
{
public BoxCollider2D gridArea;
// Start is called before the first frame update
void Start()
{
}
private void RandomisePosition()
{
// Generate the boundaries of the invisible grid
Bounds bounds = this.gridArea.bounds;
// Generate a random x and y position within the grid
float x = Random.Range(bounds.min.x, bounds.max.x);
float y = Random.Range(bounds.min.y, bounds.max.y);
// Give the random position above to our food pellet
this.transform.position = new Vector3(Mathf.Round(x), Mathf.Round(y), 0.0f);
}
}
private void RandomisePosition()
{
// Generate the boundaries of the invisible grid
Bounds bounds = this.gridArea.bounds;
// Generate a random x and y position within the grid
float x = Random.Range(bounds.min.x, bounds.max.x);
float y = Random.Range(bounds.min.y, bounds.max.y);
// Give the random position above to our food pellet
this.transform.position = new Vector3(Mathf.Round(x), Mathf.Round(y), 0.0f);
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player"){
RandomisePosition();
}
}
}
This new function, OnTriggerEnter2D() looks for a collision and checks to see what has hit our food pellet, if it was a Player then it will call RandomisePosition(); and therefore moves our pellet to a new location.
Make sure to press
CTRL + S
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Snake : MonoBehaviour
{
private Vector2 _direction = Vector2.right;
// Update is called once per frame
private void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow)) {
_direction = Vector2.up;
} else if (Input.GetKeyDown(KeyCode.LeftArrow)) {
_direction = Vector2.left;
} else if (Input.GetKeyDown(KeyCode.DownArrow)) {
_direction = Vector2.down;
} else if (Input.GetKeyDown(KeyCode.RightArrow)) {
_direction = Vector2.right;
}
}
private void FixedUpdate()
{
this.transform.position = new Vector3(
Mathf.Round(this.transform.position.x) + _direction.x,
Mathf.Round(this.transform.position.y) + _direction.y,
0.0f
);
}
}
Now we need to do the same inside our Snake script, so double click your Snake script to open it and we will add a similar function underneath FixedUpdate()
private void FixedUpdate()
{
this.transform.position = new Vector3(
Mathf.Round(this.transform.position.x) + _direction.x,
Mathf.Round(this.transform.position.y) + _direction.y,
0.0f
);
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle"){
this.transform.position = new Vector3(0.0f, 0.0f, 0.0f);
}
}
}
This time in our IF statement, we are checking to see if the character has hit a wall.
If it has hit a wall then we are going to give the snake a new position,
"this.transform.position = new Vector3(0.0f, 0.0f, 0.0f)"
Once you have added this, press CTRL + S to save your code.
Extension on the next slide(s)
Now that we have got the basis of the game working, we need to add a function or two to give the snake an extra block when it eats a food pellet.
Instructions are as follows:
When we drag our object into the assets, we create what's called a "prefab", a recreation of our object ready to be called upon to create a new object exactly the same as the original.
So step 1, duplicate your Snake object by pressing CTRL + D
Because the SnakeSegment isn't the head of the snake, it won't need a RigidBody2D or Snake Script attached to them.
Only delete these in the Inspector of SnakeSegment, Don't delete them from your assets.
Now we have the ability to add in new segments of our character, we need to write the code that will create a new segment when we eat an apple. This we are going to do in our Snake Script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Snake : MonoBehaviour
{
private Vector2 _direction = Vector2.right;
private List<Transform> _segments;
public Transform segmentPrefab;
private void Start()
{
_segments = new List<Transform>();
_segments.Add(this.transform);
}
// Update is called once per frame
private void Update()
{
At the top of your document above the Update() function we need to add lines 9-17.
This code will generate a list of items that will hold the length of our main character.
We have also created a reference to our prefab on line 11. This will then allow us to create new snake objects when we eat.
private void FixedUpdate()
{
this.transform.position = new Vector3(
Mathf.Round(this.transform.position.x) + _direction.x,
Mathf.Round(this.transform.position.y) + _direction.y,
0.0f
);
}
private void Grow()
{
Transform segment = Instantiate(this.segmentPrefab);
// Set the position of this new segment to the position of the previous
segment.position = _segments[_segments.Count - 1].position;
// Add the segment to the list of available segments
_segments.Add(segment);
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle"){
this.transform.position = new Vector3(0.0f, 0.0f, 0.0f);
}
}
}
In the script below FixedUpdate() we have added a new function called Grow()
This new function creates a new segment, gives it a position, then adds the segment to our list so we have a reference of its existence.
private void FixedUpdate()
{
this.transform.position = new Vector3(
Mathf.Round(this.transform.position.x) + _direction.x,
Mathf.Round(this.transform.position.y) + _direction.y,
0.0f
);
}
private void Grow()
{
Transform segment = Instantiate(this.segmentPrefab);
// Set the position of this new segment to the position of the previous
segment.position = _segments[_segments.Count - 1].position;
// Add the segment to the list of available segments
_segments.Add(segment);
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle"){
this.transform.position = new Vector3(0.0f, 0.0f, 0.0f);
} elif (other.tag == "Food") {
Grow();
}
}
}
In our OnTriggerEnter2D() function we need to add another IF statement to check for if we have eaten food, and when we do we need to call Grow()
private void FixedUpdate()
{
for (int i = _segments.Count - 1; i > 0; i--){
_segments[i].position = _segments[i - 1].position;
}
this.transform.position = new Vector3(
Mathf.Round(this.transform.position.x) + _direction.x,
Mathf.Round(this.transform.position.y) + _direction.y,
0.0f
);
}
private void Grow()
{
Transform segment = Instantiate(this.segmentPrefab);
// Set the position of this new segment to the position of the previous
segment.position = _segments[_segments.Count - 1].position;
// Add the segment to the list of available segments
_segments.Add(segment);
}
The last step of our character growth is to create a loop that will ensure the new segments follow the head of our character.
To do this we are going to add a loop at the beginning of the FixedUpdate() function.
You may notice that your snake won't reset if you hit a wall yet, nor will it break if you cross over yourself.
We will fix that on the next slide.
Continuing in our Snake Script, we are going to add a ResetState() function after Grow() and before OnTriggerEnter2D()
We also need to make a change on line 27 so that we call ResetState() instead of resetting the position.
Don't forget to press CTRL + S to save your work.
private void Grow()
{
Transform segment = Instantiate(this.segmentPrefab);
// Set the position of this new segment to the position of the previous
segment.position = _segments[_segments.Count - 1].position;
// Add the segment to the list of available segments
_segments.Add(segment);
}
private void ResetState()
{
for (int i = 1; i < _segments.Count; i++){
Destroy(_segments[i].gameObject);
}
_segments.Clear();
_segments.Add(this.transform);
this.transform.position = Vector3.zero;
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Obstacle"){
ResetState();
} else if (other.tag == "Food"){
Grow();
}
}
}