Help creating a grid-like snake movement

Hey all,

I’ve been having an issue with trying to get my snake game to function like the classic game of snake would. What I have is a player and a series of GameObjects that are spawned in every few seconds. Once the Player collides with the GameObject, the object is the added to a list of GameObjects called ‘tail’, like:

public List<GameObject> tail = new List<GameObject>();

My issue that I have been trying to solve for weeks is trying to get the list of GameObjects to follow around the tail, in a grid-like manner. Things I have tried are

tail[0].transform.position = Vector3.Lerp(tail[0].transform.position, GameObject.Find("Player").transform.position, Time.deltaTime);
        for (int i = 1; i < tail.Count; i++)
        {

            tail[i].transform.position = Vector3.Lerp(tail[i].transform.position, tail[i - 1].transform.position,   Time.deltaTime);

        }

But when I do this, the snake’s body follows behind it in a non-grid manner, it just follows the shortest path.

I’ve also tried:

tail[0].transform.position = Vector3.Lerp(tail[0].transform.position, GameObject.Find("Player").transform.position, Time.timeScale);
        for (int i = 1; i < tail.Count; i++)
        {

            tail[i].transform.position = tail[i - 1].transform.position;

        }

But when I do it this way, the GameObjects just past on top each other, in a single location not moving.

Any insight into how I could get this working would be much appreciated. I’ve been stuck on this issue for weeks, and don’t think this is something I can solve on my own. I’ve consulted many tutorials on Unity for snake games, but none of them are helpful because they don’t seem to be creating a list of GameObjects for the tail, which is crucial for the functionality of my game.

In case this is needed, here is the code for my Player movement

public float unitsPerFrame = 1;
    public float snakeSpeed = 0.2f;
   if (Input.GetKey(KeyCode.UpArrow) && downDir == false)
        {
            transform.localRotation = Quaternion.Euler(0, 0, 0);
            mDir = Vector3.up * unitsPerFrame;

            upDir = true;
            downDir = false;
            rightDir = false;
            leftDir = false;
        }

… All the directions are similar.

First off, kill GameObject.Find with fire.

Secondly, your movement code is really cumbersome. Rather than having 4 different bools, why not just store a single Vector3 indicating the direction the snake head is going?

So, you basically are trying to create a Snake a la the old phone game, right?

To be blunt, there is not a way to get this sort of movement without storing positions. You will absolutely have to have an array of positions - the “history” of the snake’s head - and have the body parts crawl along this array. The good news is, you should be able to do this only storing the ‘corners’, including timing.

So, when you spawn each snake tail piece, assign it a float variable - this is basically going to be “how many seconds behind the snake head is this piece?” Each new piece will have this number X seconds higher than the last one, where X is probably 1f / snakeSpeed.

Now, to store the positions. My initial thought was a List, but I think we want to actually use Vector4, and use .w as our “time” dimension.

So every time your player presses a new direction, we’re going to add a new keyframe:

public List<Vector4> pointsHistory;

void Awake() {
pointsHistory = new List<Vector4>();
}

void Update() {
if (blah blah when a new direction is pressed) {
Vector4 newPoint = transform.position;
newPoint.w = Time.time;
pointsHistory.Add(newPoint);
}
}

You should be able to watch this list grow in the Inspector as you play the game.

Now, for following it, you need to do this on the snake pieces:

  1. loop through the snake head’s list
  2. when you find a time >= target time, lerp between that and the previous point

myTimeOffset below is the “X” above for the piece delays.

Vector3 GetPosition(List<Vector4> allPoints, Vector3 currentHeadPos, float myTimeOffset) {
if (allPoints == null || allPoints.Count == 0) return Vector3.zero;
if (allPoints.Count == 1) return (Vector3)allPoints[0];

float myTargetTime = Time.time - myTimeOffset;

for (int p=1; p < allPoints.Count; p++) {
if (allPoints[p].w >= myTargetTime) {
Vector3 lastPoint = (Vector3)allPoints[p-1];
Vector3 nextPoint = (Vector3)allPoints[p];
float timeLerpValue = (myTargetTime - allPoints[p-1].w) / (allPoints[p].w - allPoints[p-1].w);
return Vector3.Lerp(lastPoint, nextPoint, timeLerpValue);
}
}
//the head has been moving in a straight line and doesn't have the "next" point yet... but we do have the last point + the current position and time!
Vector3 lastPoint = (Vector3)allPoints[allPoints.Count - 1];
float lastTime = allPoints[allPoints.Count - 1].w;
float timeLerpValue = (myTargetTime - lastTime) / (Time.time - lastTime);
return Vector3.Lerp(lastPoint, currentHeadPos, timeLerpValue);
}

This should get you most of the way there.