Hi there I’m working on a clone of snake and I’m having an issue with the tail.
At the moment my snake is made up of a series of sprite prefabs (i use 2dtoolkit) which are instantiated when the food is eaten. The issue is with how it is currently moving: the tail segments will follow the head (the head being the first object in the array) correctly except they overlap. This is causing issues in my attempts to add lose conditions should the head collide with the tail.
With some digging I’ve noticed that the distance between each sprite will lengthen if I increase the move speed. This isn’t what I want. Instead I would like the segments to be a set distance away from one another (my offset value) at all times but maintain the movement behaviour they currently have, ideally with a movespeed which can still be altered as the game progresses. Unfortunately I’ve hit a wall with my knowledge and can’t seem to figure out how to do so.
I hope that makes sense, let me know if any clarification is needed. Below I’ve posted the script currently taking care of movement.
Any help would be greatly appreciated.
using UnityEngine;
using System.Collections;
public class GameManager : MonoBehaviour {
GameObject Snake;
Food FoodScript;
public GameObject SnakePrefab;
public GameObject FoodPrefab;
public GameObject[] SnakeTail;
float offset = 0.075f; //the ideal distance between each body part
public float moveSpeed = 0.1f;
bool up = false;
bool down = false;
bool right = false;
bool left = false;
void Start () {
//sets starting length of the snake
SnakeTail = new GameObject[3];
//will spanw a segment for each snake tail object
for(int i =0; i < SnakeTail.Length; i++)
{
Instantiate(SnakePrefab, new Vector3(0, 0,0), Quaternion.identity);
offset += 0.075f;
}
SnakeTail = GameObject.FindGameObjectsWithTag("Tail");
//if there is no food make one
if(GameObject.FindGameObjectWithTag("Food") == null)
Instantiate(FoodPrefab, new Vector3(Random.Range(-1.3f,1.3f), Random.Range(-0.92f,1f), 0), Quaternion.identity);
}
void Update ()
{
PlayerInput();
if(down)
MoveDown();
if(up)
MoveUp();
if(right)
MoveRight();
if(left)
MoveLeft();
}
//updates the postition of the tail
void DrawSnake()
{
for(int i = (SnakeTail.Length-1); i >0; i--)
{
SnakeTail[i].transform.position = SnakeTail[i-1].transform.position;
}
}
#region Movement Functions
//down
public void MoveDown()
{
SnakeTail = GameObject.FindGameObjectsWithTag("Tail");
DrawSnake();
SnakeTail[0].gameObject.transform.position = new Vector3(SnakeTail[0].gameObject.transform.position.x, SnakeTail[0].gameObject.transform.position.y - moveSpeed, SnakeTail[0].gameObject.transform.position.z);
}
//up
public void MoveUp()
{
SnakeTail = GameObject.FindGameObjectsWithTag("Tail");
DrawSnake();
SnakeTail[0].gameObject.transform.position = new Vector3(SnakeTail[0].gameObject.transform.position.x, SnakeTail[0].gameObject.transform.position.y + moveSpeed, SnakeTail[0].gameObject.transform.position.z);
}
//right
public void MoveRight()
{
SnakeTail = GameObject.FindGameObjectsWithTag("Tail");
DrawSnake();
SnakeTail[0].gameObject.transform.position = new Vector3(SnakeTail[0].gameObject.transform.position.x + moveSpeed, SnakeTail[0].gameObject.transform.position.y, SnakeTail[0].gameObject.transform.position.z);
}
//left
public void MoveLeft()
{
SnakeTail = GameObject.FindGameObjectsWithTag("Tail");
DrawSnake();
SnakeTail[0].gameObject.transform.position = new Vector3(SnakeTail[0].gameObject.transform.position.x - moveSpeed, SnakeTail[0].gameObject.transform.position.y, SnakeTail[0].gameObject.transform.position.z);
}
#endregion
public void PlayerInput()
{
//up
if(Input.GetKeyDown(KeyCode.UpArrow)==true down == false)
{
up = true;
down = false;
right = false;
left = false;
Debug.Log("up");
}
//down
if(Input.GetKeyDown(KeyCode.DownArrow)==true up == false)
{
up = false;
down = true;
right = false;
left = false;
Debug.Log("down");
}
//left
if(Input.GetKeyDown(KeyCode.LeftArrow)==true right == false)
{
up = false;
down = false;
right = false;
left = true;
Debug.Log("left");
}
//right
if(Input.GetKeyDown(KeyCode.RightArrow)==true left == false)
{
up = false;
down = false;
right = true;
left = false;
Debug.Log("right");
}
}
//instantiate another tail segment
public void SpawnNewTail()
{
Instantiate(SnakePrefab, new Vector3(0, 0,0), Quaternion.identity);
}
}
Well, I checked over some of your code. Though I am kind of miffed at the logic.
So I decided to just build a new one. It seemed to me that if you have a series of points. Why keep instantiating new objects. A good idea would be to keep a list of these objects and reuse them when needed, and just hide them when you dont.
There is probably some minor errors in there, but you should be able to work them out.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Snake : MonoBehaviour {
public GameObject head; // the actual head
public GameObject body; // to be instantiated for the body
public GameObject tail; // for the end
private List<SnakePart> snake; // manages points for the snake
private List<GameObject> bodyparts; // manages the snake body
Vector3 move = Vector3.zero; // holds the move
float nextTick = 0; // the time we wait til for the next update
public float updateEvery = 1; // duration of each step
void Start(){
snake = new List<SnakePart>();
bodyparts = new List<GameObject>();
}
void Update(){
move = new Vector3(Input.GetAxisRaw("Horizontal"),0,Input.GetAxisRaw("Vertical"));
if(Time.timeScale == 0)
if(updateEvery <= 0) return;
while(Time.time > nextTick){
UpdateSnake();
nextTick += updateEvery;
}
}
void UpdateSnake(){
Vector3 nextPoint = snake[0].position + move;
snake.Insert(0, new SnakePart(nextPoint, move));
if(!isEatingFood(nextPoint)){
snake.RemoveAt(snake.Count - 1);
}
DrawSnake();
}
bool isEatingFood(Vector3 point){
// check to see if food exists in the next spot
if(Physics.OverlapSphere(point, 0.5f).Length > 0){
return true;
}
return false;
}
void DrawSnake(){
for(int i=0; i<snake.Count; i++){
if(i == 0){
head.transform.position = snake[i].position;
head.transform.LookAt(head.transform.position + snake[i].direction);
} else if(i == snake.Count - 1){
tail.transform.position = snake[i].position;
tail.transform.LookAt(tail.transform.position + snake[i].direction);
} else {
if(i-1 == bodyparts.Count){
GameObject obj = (GameObject)Instantiate(body, Vector3.zero, Quaternion.identity);
bodyparts.Add(obj);
}
bodyparts[i-1].active = true;
bodyparts[i-1].transform.position = snake[i].position;
bodyparts[i-1].transform.LookAt(bodyparts[i-1].transform.position + snake[i].direction);
}
}
for(int i=snake.Count-2; i<bodyparts.Count; i++){
bodyparts[i].active = false;
}
}
}
using UnityEngine;
public class SnakePart{
public Vector3 position;
public Vector3 direction;
public SnakePart(Vector3 pos, Vector3 dir){
position = pos;
direction = dir;
}
}
Well i suppose it’s never a good sign when your logic is flawed le sigh.
Thanks for the code but it has me a little confused. I’ve very limited knowledge in terms of lists so I’m not sure what’s happening in the start function: at which point are they initially populated? (i.e. in the similar way that my array is populated by finding the gameobjects with the appropriate tag). It’s throwing out an ‘OutOfRange’ error straight away and I understand why but not how to amend it. Any ideas?
A list is an array like object. If you use Javascript in web pages, it is much like a javascript array. You can add objects and remove objects from the list. You can also use ListVariable[index] to get items from the list.
A list is dissimilar in that you can use some really specific functions to add and remove items.
Here is the general documentation for it:
OK, an OutOfRange means that you are trying to access an element on the list when it is not there. So if you have 5 elements and you try to access the 6th one (ListVar[5], since it starts at zero) you will get that error.
So, when writing it, I supposedly made it where it would add elements onto the list as needed. However, one of hte things I dont see, is something to start the whole mess.
Ok’s after tinkering around with it some. I made the whole thing work just fine.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Snake : MonoBehaviour {
public GameObject head; // the actual head
public GameObject body; // to be instantiated for the body
public GameObject tail; // for the end
private List<SnakePart> snake; // manages points for the snake
private List<GameObject> bodyparts; // manages the snake body
Vector3 move = Vector3.zero; // holds the move
float nextTick = 0; // the time we wait til for the next update
public float updateEvery = 1; // duration of each step
void Start(){
snake = new List<SnakePart>();
bodyparts = new List<GameObject>();
move = Vector3.up;
snake.Add(new SnakePart(Vector3.zero, Vector3.up));
head.transform.parent = transform;
tail.transform.parent = transform;
}
void Update(){
var newMove = new Vector3(Input.GetAxisRaw("Horizontal"),Input.GetAxisRaw("Vertical"),0);
if(newMove != Vector3.zero) move = newMove;
if(Time.timeScale == 0)
if(updateEvery <= 0) return;
while(Time.time > nextTick){
UpdateSnake();
nextTick += updateEvery;
}
}
void UpdateSnake(){
Vector3 nextPoint = snake[0].position + move;
snake.Insert(0, new SnakePart(nextPoint, move));
if(!isEatingFood(nextPoint)){
snake.RemoveAt(snake.Count - 1);
}
DrawSnake();
}
bool isEatingFood(Vector3 point){
// check to see if food exists in the next spot
Collider[] food = Physics.OverlapSphere(point, 0.4f);
if(food.Length > 0){
if(food[0].transform.parent == transform){
Destroy(gameObject);
return false;
}
foreach(Collider morsel in food){
Destroy(morsel.gameObject);
}
return true;
}
return false;
}
void DrawSnake(){
for(int i=0; i<snake.Count; i++){
if(i == 0){
head.transform.position = snake[i].position;
head.transform.LookAt(head.transform.position + snake[i].direction);
} else if(i == snake.Count - 1){
tail.transform.position = snake[i].position;
tail.transform.LookAt(tail.transform.position + snake[i].direction);
} else {
if(i-1 == bodyparts.Count){
GameObject obj = (GameObject)Instantiate(body, Vector3.zero, Quaternion.identity);
obj.transform.parent = transform;
bodyparts.Add(obj);
}
bodyparts[i-1].active = true;
bodyparts[i-1].transform.position = snake[i].position;
bodyparts[i-1].transform.LookAt(bodyparts[i-1].transform.position + snake[i].direction);
}
}
if(snake.Count > 2){
for(int i=snake.Count-2; i<bodyparts.Count; i++){
bodyparts[i].active = false;
}
}
}
}
I had to add a start point and a start direction. (move was Vector3.zero, so that is not good) I also refined the “food” section and added in where if you tried to eat yourself, you died.
This with the “SnakePart” from above works just fine.
Start, speed and directions are controlled in newMove, the Move in Start and the adding of the first point in the snake in Start.
@bigmisterb how to make the movement smooth the snake runs stuttering the whole time and thanks for the script on how to make snake movement but what to do the snake runs stuttering due to the updateevery and nexttick would u suggest an improvement what to do, it would be helpful.
@bigmisterb and also when i assign the script to head object and then put all of them in inspector then game runs but when i make prefab of them and drag and drop it gives me error of "Setting the parent of a transform which resides in a prefab is disabled to prevent data corruption. UnityEngine.Transform:set parent(Transform) " what to do?