Snake tail follow - need an offset

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.

In the Start, add:

snake.Add(new SnakePart(Vector3.zero, Vector3.up));

That way, you create at least the first part of the snake.

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.

you can post the whole code?

@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?

Generally speaking you should post your own thread with your question, and details.

Anyways, it sounds like you need to make sure you’re setting the parent to a game object in the scene, and not a prefab.

Feel free to start a thread and post your code, using code tags, if you are not sure how to do it. Someone can probably help you out. :slight_smile:

Consider taking the time to run through some more basic tutorials, if you are very new to Unity. :slight_smile:

Ok Thanks man for the suggestion will do