How to create a moving platform in Unity

How to code a moving platform for your 2D platformer in Unity

One of the most common questions I get from students looking to build a platformer is this: how do I build a moving platform in Unity? Well, if you are looking for code that you can just copy-paste into your project, look no further.

What are moving platforms made of?

If you want a moving platform in your game, you will need a GameObject that is able to do 2 things:

  1. Collide with other objects.
  2. Cannot be affected by forces, from collision or otherwise.

That means that you will need a GameObject that has a collider attached, as well as a Rigidbody2D component marked as Kinematic.

Once that is done, you will need to either write a script to move the platform, or create an animation for the platform.

The moving platform scripts

Here, we have 2 scripts that you can import into your project:

KinematicPlatform2D.cs

using UnityEngine;

[RequireComponent(typeof(Rigidbody2D),typeof(Collider2D))]
public abstract class KinematicPlatform2D:MonoBehaviour {

    public float cycleRunTime = 3f; // Time it takes to complete 1 cycle of movement.
    public float cycleWaitTime = 2f; // How long to wait before starting another cycle.
    protected float currentCycleTime = 0; // For subclasses to check what the current cycle time is.
    protected float currentWaitTime = 0; // For subclasses to check what the current wait time is.

    // After a cycle completes, does the time start from 0 again? Or does it go in the opposite direction?
    public enum CycleType {
        repeat, pingPong
    }
    public CycleType cycleType;

    protected sbyte cycleDirection = 1; // Only for ping pong mode.

    protected Rigidbody2D rb; // Rigidbody for child classes, so they don't have to call it again.

    // We use Awake() here so we don't have to override start in child classes.
    protected virtual void Awake() {
        // We require a Rigidbody as this makes collider movement more efficient.
        // See https://forum.unity.com/threads/non-static-colliders-without-rigidbody-still-cost-in-unity-5.452177/#post-4343569
        rb = GetComponent<Rigidbody2D>();
    }

    protected virtual void Reset() {
        // Force the Rigidbody to kinematic mode when this component is added.
        GetComponent<Rigidbody2D>().bodyType = RigidbodyType2D.Kinematic;
    }

    // This is protected because it needs to be overriden.
    protected virtual void Update() {
        if(currentWaitTime <= 0) {
            currentCycleTime += Time.deltaTime * cycleDirection;
            HandleCycleEnd();
        } else {
            currentWaitTime -= Time.deltaTime;
            HandleCycleRestart();
        }
    }

    void HandleCycleRestart() {
        if(currentWaitTime < 0) {
            currentCycleTime -= currentWaitTime;
            currentWaitTime = 0;
        }
    }

    // Checks if the cycle has ended. If it has, set the object
    // to wait mode. Algorithm works if cycleWaitTime is 0.
    void HandleCycleEnd() {
        // If the cycle is finished, set the Wait time and the currentCycleTime.
        // Works if cycleWaitTime is 0.
        switch(cycleType) {

            case CycleType.repeat:

                if(currentCycleTime > cycleRunTime) {
                    // We also subtract the wait time by the time we already exceeded.
                    currentWaitTime = cycleWaitTime - (currentCycleTime - cycleRunTime);
                    currentCycleTime = 0; // The next cycle time to start from.
                }

                break;

            case CycleType.pingPong:

                // Invert the cycle direction if we exceed the run time.
                if(currentCycleTime > cycleRunTime) {
                    // We account for if the currentCycleTime exceeds the cycleRunTime.
                    currentWaitTime = cycleWaitTime - (currentCycleTime - cycleRunTime);
                    cycleDirection = -1;

                    // Set the next cycle time to start from.
                    currentCycleTime = cycleRunTime;
                } else if(currentCycleTime < 0) {
                    currentWaitTime = cycleWaitTime - currentCycleTime;
                    cycleDirection = 1;

                    // Set the next cycle time to start from.
                    currentCycleTime = 0;
                }

                break;
        }
    }

    // Parents any Rigidbody when they step on the platform,
    // so it moves along with the platform.
    protected virtual void OnCollisionEnter2D(Collision2D collision) {
        collision.rigidbody.transform.SetParent(transform);
    }

    // Unparents any Rigidbody when they leave the platform,
    // so it stops moving along with the platform.
    protected virtual void OnCollisionExit2D(Collision2D collision) {
        collision.rigidbody.transform.SetParent(null);
    }
}

Article continues after the advertisement:


StraightMovingPlatform2D.cs

using UnityEngine;
using UnityEditor;

public class StraightMovingPlatform2D:KinematicPlatform2D {

    public Vector2 endPoint = new Vector2(5,0);
    Vector2 origin;

    void Start() {
        origin = transform.position;
    }

    // Update is called once per frame
    protected override void Update() {
        // Needed since it manages the cycle time.
        base.Update();

        // Since the cycle time is there, we can just use Lerp.
        transform.position = Vector2.Lerp(origin,origin + endPoint,currentCycleTime / cycleRunTime);
    }

    protected override void Reset() {
        base.Reset();

        // Set the endpoint to always be a straight line forward by default. For convenience.
        endPoint = transform.position + transform.right * 8;

        // Doesn't make sense for straight moving platforms to repeat.
        cycleType = CycleType.pingPong;
    }

    private void OnDrawGizmosSelected() {

        Vector2 start = EditorApplication.isPlaying ? origin : (Vector2)transform.position,
                end = start + endPoint;
        Gizmos.color = Color.cyan;
        Gizmos.DrawLine(start,end);
        Gizmos.DrawWireSphere(start,0.25f);
        Gizmos.DrawWireSphere(end,0.25f);
    }
}

You can attach the StraightMovingPlatform2D component onto your moving platform after importing your script.

The KinematicPlatform2D is an abstract class, and is not meant to be used. It contains code to control how the platform moves, and you can extend it to create other platform components that move in different ways (e.g. circular moving platform).

Configuring your platform

The StraightMovingPlatform2D script has editor gizmos coded into it to make them easy to use:

Kinematic moving platform
There are gizmos to show you the line where the platform is moving along. You can change the End Point to change where the platform moves to.

You can also change the Cycle Type property to control whether the platform cycles between 2 points:

Kinematic platform - ping pong
Cycle Type: Ping Pong

Or only moves in a single direction:

Kinematic platform - repeat
Cycle Type: Repeat

Using the platform

Whenever any Rigidbody2D object falls onto the platform, it will automatically move along with the platform.

How the moving platform works.
How the moving platform works.

Conclusion

If there are any bugs or issues with our code, please share them in the comments below!


Article continues after the advertisement:


Leave a Reply

Your email address will not be published. Required fields are marked *

Note: You can use Markdown to format your comments.

For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

I agree to these terms.

This site uses Akismet to reduce spam. Learn how your comment data is processed.