I've been creating a simple snake game in Unity(C#) following this Tutorial I found:
https://www.youtube.com/watch?v=U8gUnpeaMbQ&t=1s&ab_channel=Zigurous
I found this to be a very nice Tutorial, and by the end I had a perfectly fine snake game, however, I wanted to go a bit further, making movement a bit more pleasant, adding a tail, Gameover, etc.
Right now my issue is that, if a player presses 2 acceptable directions in quick succession trying to grab some food, the snake's head jumps over the food, missing it entirely.
This happens due to the following bit of code:
private void Update() //Gets Key Inputs and execute Commands
{
if (Input.GetKeyDown(KeyCode.UpArrow) )
{
while(tempPosition == _segments[0].position)
{
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
);
}
if(_direction != Vector2.down)
{
_direction = Vector2.up;
tempPosition = _segments[0].position;
}
}
else if (Input.GetKeyDown(KeyCode.LeftArrow) )
{
while (tempPosition == _segments[0].position)
{
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
);
}
if (_direction != Vector2.right)
{
_direction = Vector2.left;
tempPosition = _segments[0].position;
}
}
else if (Input.GetKeyDown(KeyCode.RightArrow) )
{
while (tempPosition == _segments[0].position)
{
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
);
}
if (_direction != Vector2.left)
{
_direction = Vector2.right;
tempPosition = _segments[0].position;
}
}
else if (Input.GetKeyDown(KeyCode.DownArrow) )
{
while (tempPosition == _segments[0].position)
{
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
);
}
if (_direction != Vector2.up)
{
_direction = Vector2.down;
tempPosition = _segments[0].position;
}
}
As you can see, pressing a key moves the Snake's head instantly, causing the issue.
However if not coded like so, pressing 2 keys in rapid succession caused the snake to collide with itself (suppose the snake is moving right, if up and left are pressed in rapid succession the snake would start moving left before being able to move up, colliding with it's body).
Below is the full code:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class Snake : MonoBehaviour
{
private Vector2 _direction = Vector2.right;
public List<Transform> _segments = new List<Transform>();
public Transform segmentPrefab;
public Transform tail;
public int initialSize = 4;
public int score = 0;
private Vector3 tempPosition;
public GameObject food;
public Text gameOver;
private void Start()
{
ResetState();
}
private void Update() //Gets Key Inputs and execute Commands
{
if (Input.GetKeyDown(KeyCode.UpArrow) )
{
while(tempPosition == _segments[0].position)
{
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
);
}
if(_direction != Vector2.down)
{
_direction = Vector2.up;
tempPosition = _segments[0].position;
}
}
else if (Input.GetKeyDown(KeyCode.LeftArrow) )
{
while (tempPosition == _segments[0].position)
{
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
);
}
if (_direction != Vector2.right)
{
_direction = Vector2.left;
tempPosition = _segments[0].position;
}
}
else if (Input.GetKeyDown(KeyCode.RightArrow) )
{
while (tempPosition == _segments[0].position)
{
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
);
}
if (_direction != Vector2.left)
{
_direction = Vector2.right;
tempPosition = _segments[0].position;
}
}
else if (Input.GetKeyDown(KeyCode.DownArrow) )
{
while (tempPosition == _segments[0].position)
{
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
);
}
if (_direction != Vector2.up)
{
_direction = Vector2.down;
tempPosition = _segments[0].position;
}
}
if(Input.GetKeyDown(KeyCode.R))
{
ResetState();
}
}
private void FixedUpdate() //Handles moviment
{
if (gameOver.gameObject.activeSelf == false)
{
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
);
}
}
/*Instantiates a new segment, sets it's position to tail position,
destroys tail from list and adds new segment in it's place, adds new tail at end*/
private void Grow()
{
Transform segment = Instantiate(this.segmentPrefab);
segment.position = _segments[_segments.Count - 1].position;
Destroy(_segments[_segments.Count - 1].gameObject);
_segments.Remove(_segments[_segments.Count - 1]);
_segments.Add(segment);
Transform segmenttail = Instantiate(this.tail);
segmenttail.position = _segments[_segments.Count - 1].position;
_segments.Add(segmenttail);
}
private void ResetState()
{
gameOver.gameObject.SetActive(false);
tempPosition.x = 1000;
score = 0;
for (int i = 1; i < _segments.Count; i )
{
Destroy(_segments[i].gameObject);
}
_segments.Clear();
_segments.Add(this.transform);
for (int i = 1; i < initialSize; i )
{
_segments.Add(Instantiate(this.segmentPrefab));
}
_segments.Add(Instantiate(this.tail));
this.transform.position = Vector3.zero;
this.GetComponent<SpriteRenderer>().enabled = (true);
food.GetComponent<Food>().RandomizePosition();
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Food")
{
Grow();
score ;
}
else if(other.tag == "Obstacle")
{
for (int i = 1; i < _segments.Count; i )
{
Destroy(_segments[i].gameObject);
}
this.GetComponent<SpriteRenderer>().enabled=(false);
_segments.Clear();
food.gameObject.SetActive(false);
gameOver.gameObject.SetActive(true);
}
}
}
tl;dr: In a simple Snake game, when two directions are pressed in rapid succession, how can I ensure that the snake will move in the first direction before changing towards the second direction without bugs insuing.
THANKS!
CodePudding user response:
Make a red cube, which controls the direction of the snake's movement, as well as the function of encountering food and eating food. In Update(), WSAD and direction keys control the movement direction of the snake head. And when the snake head moves upward, it cannot move downward, and when the snake moves to the left, it cannot move to the right.
void Update () {
if (Input.GetKey(KeyCode.W)||Input.GetKey("up")&&direction!=
Vector2.down)
{
direction = Vector2.up;
}
if (Input.GetKey(KeyCode.S) || Input.GetKey("down") && direction != Vector2.up)
{
direction = Vector2.down;
}
if (Input.GetKey(KeyCode.A) || Input.GetKey("left") && direction != Vector2.right)
{
direction = Vector2.left;
}
if (Input.GetKey(KeyCode.D) || Input.GetKey("right") && direction != Vector2.left)
{
direction = Vector2.right;
}
}
After the snake collides with food, its body will grow a section. When encountering food, it will destroy the food first, and then increase the length of its own body. At this time, the set collision bit flag will become true, and the body length will increase. However, when it hits itself, and when it hits a wall, it will die, and at this time, it will be imported into the scene at the beginning.
void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag("Food"))
{
//Debug.Log("hit it!");
Destroy(other.gameObject);
flag = true;
}
else
{
//SceneManager.LoadScene(0)
Application.LoadLevel(1);
}
}
The algorithm that the body grows each time is the difficulty of eating snakes. Many algorithms on the Internet are implemented by using list linked lists. The nodes of the linked list indicate that the increase or decrease of the snake is very convenient. When moving, you only need to add a head node and remove it. The tail node is enough, and to eat food, you only need to add a head node. This algorithm is absolutely ingenious, but because there are too many on the Internet, the following is another snake-eating algorithm implemented by a linked list. The head of the snake remained motionless, the last of the body moved to the front, and then slowly moved backwards. The blue square below (a setting of the body part) moves step by step, and you can see this effect. The code for the body part of the snake is posted below. If the food is eaten, the flag is true. This is to insert a prefabricated Cube into the snake's body, and the snake's body will grow longer. When there is no food, it will look at the number of the body at this time. When the number is greater than 0, the last one will be placed in the front, and the cycle will continue until the end.
void Move()
{
Vector3 VPosition = transform.position;
transform.Translate(direction);
if (flag)
{
GameObject bodyPrefab = (GameObject)Instantiate(gameObjecgtBody, VPosition, Quaternion.identity);
Body.Insert(0, bodyPrefab.transform);
flag = false;
}
else if (Body.Count > 0)
{
Body.Last().position = VPosition;
Body.Insert(0, Body.Last());
Body.RemoveAt(Body.Count - 1);
}
}
The appearance of food is a random process. At this time, food appears in a random position. InvokeRepeating("ShowFood", 1, 4); means that the ShowFood() function will be called in four seconds, and it will appear randomly in ShowFood at this time. food. Below is the code for the ShowFood() function
void ShowFood()
{
int x = Random.Range(-30, 30);
int y = Random.Range(-22, 22);
Instantiate(SSFood, new Vector2(x,y), Quaternion.identity);
}
Special attention is that when making the snake head and body, if the volume of the collision body is set to unit 1, the side of the snake body will also hit the food, triggering the collider. So set the volume of the collider to 0.8, which is slightly less than 1.I also found information from the Internet, I hope it can help you, this link is the source code https://github.com/xiaogeformax/Snake/tree/master/Snake5.2