Smooth snake movement, such as in "Snake Vs Block"

I’m trying to recreate the motion of the snake in the game “Snake Vs Block” where the head follows the players touch and the tail moves along the same path as the head.

One tricky thing about the movement is that if the head is moved rapidly, the length of the snake and the distance between each body part remains the same… it compensates the fast movement by speeding up the rest of the body. Here’s an example of what I mean:

I’ve got the movement for the head nailed using a forward vector and rotating the head for horizontal movement. I’m just not sure how to create the effect of the body as shown in the video above.

I’ve tried using coroutines to have a delayed movement for the rest of the body but that didn’t turn out as nice as I thought.

I’ve also tried to store the heads position in time intervals and lerp the body to follow the trail. The downside to this is that when the head is moving quick, a big gap is created between the body parts as well as the head.

Any help would be great, cheers!

Head Movement:

    void MoveSnake()
    {
        snakeParts[0].transform.Translate(snakeParts[0].transform.up * speed * Time.deltaTime, Space.World);

        if (Input.GetAxis("Horizontal") > 0)
        {
            snakeParts[0].transform.Rotate(-Vector3.forward * rotationSpeed * Time.deltaTime);
        }

        if (Input.GetAxis("Horizontal") < 0)
        {
            snakeParts[0].transform.Rotate(Vector3.forward * rotationSpeed * Time.deltaTime);
        }
     }

Body Instantiation:

    void Start () {
        for (int i = 0; i < snakeLength; i++)
        {
             snakeParts[i] = Instantiate(Resources.Load("Prefabs/Part"), playerTransform.position + new Vector3(0.0f, -1.5f * i, 0.0f), Quaternion.identity) as GameObject;
        }
    }

I was messing around and came up with this, it’s not quite there but maybe it’ll help:

public class test_script_2 : MonoBehaviour
{
    [SerializeField]
    private GameObject part;
    private GameObject newPart;
    public List<GameObject> snakeParts;
    [SerializeField]
    private float speed = .1f;
    [SerializeField]
    private float movementSpeed = 1f;
    [SerializeField]
    private int snakeLength = 5;
    private Vector2 lastLocation;

    // Use this for initialization
    void Start()
    {
        for (int i = 0; i < snakeLength; i++)
        {
            if (i == 0)
            {
                newPart = Instantiate(part, transform.position, Quaternion.identity);
            }
            else
            {
                newPart = Instantiate(part, new Vector3(snakeParts[i - 1].transform.position.x, snakeParts[i - 1].transform.position.y - 1f, 0f), Quaternion.identity);
            }
            snakeParts.Add(newPart);
            Debug.Log(i);
        }
    }

    // Update is called once per frame
    void Update()
    {
        snakeParts[0].transform.Translate(Vector2.up * movementSpeed * Time.deltaTime);
        lastLocation = snakeParts[0].transform.position;
        Camera.main.transform.position = new Vector3(Camera.main.transform.position.x, lastLocation.y, Camera.main.transform.position.z);
        foreach (GameObject part in snakeParts)
        {
            if (part != snakeParts[0])
            {
                var newPosition = new Vector2(lastLocation.x, lastLocation.y - 1);
                lastLocation = part.transform.position;
                part.transform.position = newPosition;

            }

        }

        if (Input.GetAxis("Horizontal") > 0)
        {
            snakeParts[0].transform.Translate(Vector2.right * movementSpeed);
        }

        if (Input.GetAxis("Horizontal") < 0)
        {
            snakeParts[0].transform.Translate(Vector2.left * movementSpeed);
        }
    }
}

PS it’ll look more like that if you add some kind of acceleration on input.

Hello Friends, I’m trying a moment like "SNAKE VS BlOCK " game a snake movement . so Hear is the script to see how snake move smooth.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SnakeMovement : MonoBehaviour {

    [Header("Managers")]
    public GameController GC;

    [Header("Some Snake Variables & Storing")]
    public List<Transform> BodyParts = new List<Transform>();
    public float minDistance = 0.25f;
    public int initialAmount;
    public float speed = 1;
    public float rotationSpeed = 50;
    public float LerpTimeX;
    public float LerpTimeY;

    [Header("Snake Head Prefab")]
    public GameObject BodyPrefab;

    [Header("Parts Text Amount Management")]
    public TextMesh PartsAmountTextMesh;

    [Header("Private Fields")]
    private float distance;
    private Vector3 refVelocity;

    private Transform curBodyPart;
    private Transform prevBodyPart;

    private bool firstPart;

    [Header("Mouse Control Variables")]
    Vector2 mousePreviousPos;
    Vector2 mouseCurrentPos;

    [Header("Particle System Management")]
    public ParticleSystem SnakeParticle;

    // Use this for initialization
    void Start () {

       
        //At the beginning, if it's the first part, spawn it at (0, -3, 0)
        firstPart = true;

        //Add the initial BodyParts
        for (int i = 0; i < initialAmount; i++)
        {
            //Use invoke to avoid a weird bug where the snake goes down at the beginning.
            Invoke("AddBodyPart", 0.1f);
        }
       
    }

    public void SpawnBodyParts()
    {
        firstPart = true;

        //Add the initial BodyParts
        for (int i = 0; i < initialAmount; i++)
        {
            //Use invoke to avoid a weird bug where the snake goes down at the beginning.
            Invoke("AddBodyPart", 0.1f);
        }
    }
       
    // Update is called once per frame
    void Update () {

        //We constantly move if the game is on
        if (GameController.gameState == GameController.GameState.GAME)
        {
            Move();

            //Check if no more snake parts
            if (BodyParts.Count == 0)
                GC.SetGameover();
        }

        //Update the Parts Amount text
        if(PartsAmountTextMesh != null)
            PartsAmountTextMesh.text = transform.childCount + "";

     
    }

    public void Move()
    {
        float curSpeed = speed;

        //Always move the body Up
        if(BodyParts.Count > 0)
            BodyParts[0].Translate(Vector2.up * curSpeed * Time.smoothDeltaTime);


        //check if we are still on screen
        float maxX = Camera.main.orthographicSize * Screen.width / Screen.height;

        if (BodyParts.Count > 0)
        {
            if (BodyParts[0].position.x > maxX) //Right pos
            {
                BodyParts[0].position = new Vector3(maxX - 0.01f, BodyParts[0].position.y, BodyParts[0].position.z);
            }
            else if (BodyParts[0].position.x < -maxX) //Left pos
            {
                BodyParts[0].position = new Vector3(-maxX + 0.01f, BodyParts[0].position.y, BodyParts[0].position.z);
            }
        }

        //Move the snake on the Horizontal Axis with mouse control
        if (Input.GetMouseButtonDown(0))
        {
            mousePreviousPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        }
        else if (Input.GetMouseButton(0))
        {
            //check if he is still in screen

            if ( BodyParts.Count > 0 && Mathf.Abs(BodyParts[0].position.x) < maxX)
            {
                mouseCurrentPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);

                float deltaMousePos = Mathf.Abs(mousePreviousPos.x - mouseCurrentPos.x);
                float sign = Mathf.Sign(mousePreviousPos.x - mouseCurrentPos.x);


                //BodyParts[0].Translate(Vector3.left * rotationSpeed * Time.deltaTime * deltaMousePos * sign);
                BodyParts[0].GetComponent<Rigidbody2D>().AddForce
                    (Vector2.right * rotationSpeed * deltaMousePos * -sign);

                mousePreviousPos = mouseCurrentPos;
            }
            else if (BodyParts.Count > 0 && BodyParts[0].position.x > maxX) //Right pos
            {
                BodyParts[0].position = new Vector3(maxX - 0.01f, BodyParts[0].position.y, BodyParts[0].position.z);
            } else if (BodyParts.Count > 0 && BodyParts[0].position.x < maxX) //Left pos
            {
                BodyParts[0].position = new Vector3(-maxX + 0.01f, BodyParts[0].position.y, BodyParts[0].position.z);
            }
        }


        //Move the other body parts depending on the Head, that's why we start the loop at 1
        for (int i = 1; i < BodyParts.Count; i++)
        {
            curBodyPart = BodyParts[i];
            prevBodyPart = BodyParts[i - 1];

            distance = Vector3.Distance(prevBodyPart.position, curBodyPart.position);

            Vector3 newPos = prevBodyPart.position;

            newPos.z = BodyParts[0].position.z;

            //Try 2 Lerps, one on the x pos and one on the Y
            Vector3 pos = curBodyPart.position;

            pos.x = Mathf.Lerp(pos.x, newPos.x, LerpTimeX);
            pos.y = Mathf.Lerp(pos.y, newPos.y, LerpTimeY);

            curBodyPart.position = pos;

    }
    }

    public void AddBodyPart()
    {
        Transform newPart;

        if (firstPart)
        {
            newPart = (Instantiate(BodyPrefab, new Vector3(0, 0, 0),
                       Quaternion.identity) as GameObject).transform;

            //Disable the collision with snake

            // Set this part as the parent of the Text Mesh
            PartsAmountTextMesh.transform.parent = newPart;

            //Place it correctly
            PartsAmountTextMesh.transform.position = newPart.position +
                new Vector3(0, 0.5f, 0);

            firstPart = false;
        }
        else
            newPart = (Instantiate(BodyPrefab, BodyParts[BodyParts.Count - 1].position,
           BodyParts[BodyParts.Count - 1].rotation) as GameObject).transform;


        newPart.SetParent(transform);

        BodyParts.Add(newPart);

    }
}
1 Like