Back in late 2021 I got to work on building out a full action platformer game for mobile platforms. My game, Shadow of the Orient, is loosely based on history and incorporates fictional enemies like dragons and Yeti’s. When I was working on the dragon character I wanted it to move around like a snake because that’s the way it should move but i really had no idea how to pull it off…at first. After giving it some thought I started putting the pieces together and the following video sample was the end result:

Structure Overview

The structure of the dragon is created with a master parent gameObject followed by each body part as a child gameObject as seen in the following reference image:

The reason for this structure is to allow the entire dragon to move around in any direction while having the body parts behave independently regardless of the direction the parent gameObject is moving. The “dragon_head” gameObject is responsible for controlling the vertical movement using a script which calculates a wave motion and each body part which follows the dragon’s head is controlled through an additional script. The following are the two scripts which I coded to get the dragon to move that I way wanted:

WaveMovement.cs

The following code is assigned to the “dragon_head” gameObject and is responsible for controlling the vertical motion of the dragon – it also allows for horizontal motion if desired.

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

public enum WaveMovementDirection
{
    horizontal,
    vertical
}

public class WaveMovement : MonoBehaviour
{
    [SerializeField] private bool playOnStart = false;
    [SerializeField] private bool disableEventCall = false;

    [Header("Wave Settings")]
    [SerializeField] private Transform _parentTransform;
    [SerializeField] private WaveMovementDirection waveDirection;
    [SerializeField] private float maxWave = 10f;

    [Header("Movement Settings")]
    [SerializeField] private bool moveObject = false;
    [SerializeField] private float moveSpeed = 0f;
    [SerializeField] private Vector2 moveDirection;

    //Internal vars
    private bool _canMove = false;
    private float _waveNum = 0f;
    private float _yMotion = 0f;
    private float _xMotion = 0f;
    private Vector3 _movementVector;
    private Transform _transform;
    private float _xPos;
    private float _yPos;

    private void Awake()
    {
        //Cache the transform
        _transform = transform;
    }

    // Start is called before the first frame update
    void Start()
    {
        //Check if the motion should play on start
        if (playOnStart)
            _canMove = true;
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        //Check if we can move
        if(_canMove)
            WaveMove();
    }

    private void WaveMove()
    {
        //Get the current x and y positions
        _xPos = _transform.position.x;
        _yPos = _transform.position.y;

        //Check which direction we need to calculate
        if (waveDirection == WaveMovementDirection.horizontal)
        {
            //Calculate horizontal motion
            _xMotion = _parentTransform != null ? _parentTransform.position.x : _transform.position.x;
            _xPos = _xMotion + Mathf.Cos(_waveNum) * maxWave;

            //Check if the object should move vertically
            if (moveObject)
                _yPos += moveSpeed * moveDirection.y;
        } 
        else
        {
            //Calculate vertical motion
            _yMotion = _parentTransform != null ? _parentTransform.position.y : _transform.position.y;
            _yPos = _yMotion + Mathf.Cos(_waveNum) * maxWave;

            //Check if the object should move horizontally
            if (moveObject)
                _xPos += moveSpeed * moveDirection.x;
        }

        //Assign x and y positions to transform
        _movementVector.Set(_xPos, _yPos, 0);
        _transform.position = _movementVector;

        //Increment _waveNum to get the motion going
        _waveNum += 0.1f;
    }

    public void PlayWaveMovement(bool value)
    {
        if(!disableEventCall)
            _canMove = value;
    }

    
}

SnakeMovement.cs

The following code is assigned to the “dragon_head” gameObject  and is responsible for the motion of each body part in relation to the “dragon_head” gameObject. Basically, a transform reference for each body part is stored in the _bodyParts List array and in the FixedUpdate we check each body part against its previous body part to determine where we need the current body part in the loop to move in the next frame – I know it sounds confusing but it works. The code also checks to see which way each body part sprite should be facing (or rather flipped) using InverseTransformPoint.

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

public class SnakeMovement : MonoBehaviour
{

    [Header("Child Objects")]
    [SerializeField] private List<Transform> _bodyParts = new List<Transform>();
    [SerializeField] private float _minDistance = .25f;
    [SerializeField] private float _speed = 2f;
    [SerializeField] private float _bodyPartSpacing = 5f;

    //Interal vars
    private float _distance;
    private Transform _currBodyPart;
    private Transform _prevBodyPart;
    private Rigidbody2D _firstBodyPart;
    private bool _canMove = true;
    private Vector3 _newPosition;
    private float _time;
    private SpriteRenderer _sprite;
    private Transform _targetTransform;
    private Vector3 _relativePoint;

    private void Start()
    {
        if(_bodyParts.Count > 0)
            _firstBodyPart = _bodyParts[0].GetComponent<Rigidbody2D>();
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        if(_canMove)
        {
            for (int i = 1; i < _bodyParts.Count; i++)
            {
                //Get a reference the current and previous body part within the loop
                _currBodyPart = _bodyParts[i];
                _prevBodyPart = _bodyParts[i - 1];

                //Check the distance between the current and previous body parts
                _distance = Vector3.Distance(_prevBodyPart.position, _currBodyPart.position);

                //Calculate a new position
                _newPosition = _prevBodyPart.position;
                _newPosition.x = _bodyParts[i - 1].position.x + (_firstBodyPart.transform.localScale.x > 0 ? -_bodyPartSpacing : _bodyPartSpacing);
                _newPosition.y = _bodyParts[i - 1].position.y;

                //Calculate the timing for the slerp functions
                _time = Time.deltaTime * _distance / _minDistance * _speed;

                //Cap the time
                if (_time > 0.5f)
                    _time = 0.5f;

                //Use slerp functions to apply a smooth motion
                _currBodyPart.position = Vector3.Slerp(_currBodyPart.position, _newPosition, _time);
                _currBodyPart.rotation = Quaternion.Slerp(_currBodyPart.rotation, _prevBodyPart.rotation, _time);

                //Lets flip the sprites based on prev body part position
                _sprite = _bodyParts[i].GetComponent<SpriteRenderer>();
                _targetTransform = _bodyParts[i - 1].transform;
                _relativePoint = _bodyParts[i].transform.InverseTransformPoint(_targetTransform.position);

                if (_relativePoint.x > 0)
                    _sprite.flipX = false;
                else
                    _sprite.flipX = true;
            }
        }
    }

    public void StopSnakeMovement()
    {
        _canMove = false;
    }
}

And that’s pretty much it – feel free to play around with the settings in each component to get the desired motion you want for your “snake” like motion – there are controls available in the SnakeMovement script for fine tuning the motion and spacing of each body part.

If you’d like to face off against my dragon feel free to try out Shadow of the Orient which is currently in early access.

Have a question? Feel free to post a comment a below.