avatarMina Pêcheux

Summary

This article provides a tutorial on how to create a simple 3D moving platform with customizable speed and waypoints in Godot 4 using C#.

Abstract

The tutorial begins by explaining the importance of dynamic objects in video games, such as moving platforms, and introduces the concept of using a Path and PathFollow node to move a platform along a specific path. The article then goes on to explain how to create a platform object with a CollisionShape3D child node and an AnimatableBody3D root node, which allows the object to move and still collide with other physics objects. The tutorial then covers how to draw a path for the platform to follow, create a PathFollow3D child node, and instantiate the platform inside. The article also explains how to adjust the platform's speed and ensure the collider follows the visuals properly. The tutorial concludes by providing a demo scene and all the assets for the example on the author's Github page.

Opinions

  • The author emphasizes the importance of dynamic objects in video games, such as moving platforms, to create a more engaging and interactive experience for players.
  • The author suggests using a Path and PathFollow node to move a platform along a specific path, which can be customized to fit the needs of the game.
  • The author recommends using an AnimatableBody3D root node for the platform object, which allows it to move and still collide with other physics objects.
  • The author provides a detailed tutorial on how to create a moving platform in Godot 4 using C#, including how to draw a path, create a PathFollow3D child node, and instantiate the platform inside.
  • The author encourages readers to check out their other Godot tutorials and assets on their Github page.
  • The author provides a demo scene and all the assets for the example, making it easy for readers to follow along and implement the tutorial in their own projects.
  • The author concludes by emphasizing the importance of dynamic objects in video games and encourages readers to experiment with different types of dynamic objects to create a more engaging and interactive experience for players.

Implementing a basic moving platform (Godot 4/C#)

Let’s see how to create a simple 3D moving platform with customisable speed and waypoints!

Video games are about characters, avatars and NPCs… but they’re also about environment, scenery and cool dynamic objects in the decor! Typically, when you’re making an adventure/platformer game, you’ll almost always need to make some animated platforms at some point.

So, today, let’s see how to make a simple moving platform in Godot 4/C#!

By the end of this article, you’ll know how to use a Path and a PathFollow node to have a platform object move along a specific path, how to have this platform loop or walk back the other way, and finally how to ensure the collider of the platform follows the visuals properly.

By the way, in this tutorial, we’ll see an example in 3D — but everything works the same with the 2D version of the nodes that we’ll study :)

Of course, don’t forget that you can get the demo scene and all the assets for this example on my Github 🚀 with all my other Godot tutorials :)

🔶 Also, the assets are from Kenney’s amazing free library!

The tutorial is also available as a video — text version is below:

With all that said, let’s dive in and discover how to setup a moving platform in Godot 4 and C#!

Oh and by the way: if you’re still a bit new to Godot 4 and C# game dev, go ahead and check out my brand new “short-read” ebook: L’Almanach: Getting Started!

This quick, practical guide will have you explore the fundamentals, and it will teach you how to setup scenes, program in C#, design UIs, and even build your own tic-tac-toe game step-by-step :)

So if you wanna embark on your Godot 4/C# journey in less than 100 pages and for a low-price, don’t hesitate to have a look at the Gumroad page

Setting up our path

Alright, first of all, let’s see how we can move our platform easily along a path.

In Godot, there’s actually a couple of node types that are extremely useful when dealing with this kind of use case: the Path and PathFollow nodes. We talked about those in this previous tutorial of the series, on a 2D example, when we saw how to spawn waves of enemies regularly and move them along a pre- determined path:

Today, we’re going to do something similar but in 3D, and with a bit more customisation of the object’s movement. Oh, and also, I’ll be using assets from Kenney’s amazing free library, more precisely his brand new Mini Dungeon kit:

In this tutorial, I’ll use Kenney’s Mini Dungeon free asset kit (https://kenney.nl/assets/mini-dungeon)

For this tutorial, I’ve already prepared a demo scene and the platform prefab that we’ll be using. It looks like this:

As you can see, it’s a simple physics body node with a CollisionShape3D child node, and some meshes for the visuals.

However, the root node is not a StaticBody3D, as you might expect for an environment object in Godot. Instead, because we want our object to move in the level and still collide with the rest of the physics objects, we can use a great node type: an AnimatableBody3D.

Basically, this node can be moved in various ways (e.g. via code, with a path like here, using an animation, or even a tween object…) but, as long as you enable its Sync To Physics option, you tell Godot to synchronise this movement with the rest of the physics objects in the scene, and so you still get collisions or triggers :)

So ok — since we already have our platform and our demo scene, the first step here is to draw the path we want our platform to follow inside a Path3D node. Of course since, this time, we’re in 3D, we can place points in all three directions and create a non-horizontal path:

Now that we have our path, the next step is just to give it a PathFollow3D child node, and instantiate our platform inside:

From that point on, if we play around with the Progress or the ProgressRatio parameters of this PathFollow3D node, we see our platform moves along our curve:

That being said, we should make sure to change the Rotation Mode of our PathFollow3D node to None because we don’t want our platform to rotate as it moves along, and we’re also going to toggle off the Loop option to better control the movement of our platform (we’ll want to handle this in our script later on):

But of course, we also know that we can do this progress update via code — so let’s simply create a new C# script on our PathFollow3D node, called MovingPlatform.cs:

And inside, for now, we’ll only keep the _Process() function. This is where we’ll modify our progress value to move the object.

using Godot;

public partial class MovingPlatform : PathFollow3D
{
    public override void _Process(double delta)
    {}
}

Typically, if we define and export some _speed value for our platform, then we can use it in the _Process() method to increase our ProgressRatio value. We can thus translate our platform object until we’ve reached the end of the path, which we can check for easily by comparing the normalised ProgressRatio value to 1:

public partial class MovingPlatform : PathFollow3D
{
    [Export] private float _speed;

    public override void _Process(double delta)
    {
        if (ProgressRatio < 1)
            ProgressRatio += _speed;
    }
}

Also, don’t forget to multiply the _speed variable by the time delta parameter we’re given in the _Process() function to make the movement more stable:

public partial class MovingPlatform : PathFollow3D
{
    [Export] private float _speed;

    public override void _Process(double delta)
    {
        if (ProgressRatio < 1)
            ProgressRatio += _speed * (float)delta;
    }
}

After rebuilding the project, we’re now able to adjust the Speed value in the inspector to our liking:

Important note: You’ll need to adjust this speed to better match your own scene, and possibly modify it as we go through the tutorial and we modify the way our platform moves… be sure to test it and see how it looks ;)

Ok — at this point, we have a nice moving platform that looks cool and moves along our path as expected:

However, there are two big problems:

  • One, in this current implementation, the platform does the journey only once, and then stops at the end — which is clearly not ideal for an adventure/platformer game like our little demo.
  • Two, there aren’t actually any collisions on our platform! So if I were to add a player object to my scene and move to my platform when it’s close to the ledge, I’d just fall through it…

So let’s see how to solve both problems.

Preparing a nicer platform movement

An overview of the features

First of all, let’s handle the “one-shot” movement issue. What I’d like to setup here is a system to offers a few options to my game designer users:

  • First, the platform may stop for a while at each point on our path, and use those as intermediate waypoints. This is actually quite a common feature in video games, because it makes it easier to hop on and off the platform during its overall course.
  • Second, when the platform reaches the end of the path, it could either teleport back to the beginning instantly (as if we had the Loop option enabled), or follow the path the same away, but in the other direction. I want to let my users easily switch between those two modes.

To do all of this, we’re going to need to improve our C# script so that it understands the notion of “waypoint”, and that it tracks its current moving direction.

To begin with, however, we can simply define two new exported variables in our class: _pauseTimeAtWaypoints and _jumpToStart:

public partial class MovingPlatform : PathFollow3D
{
    [Export] private float _speed;
    [Export] private float _pauseTimeAtWaypoints = 0;
    [Export] private bool _jumpToStart = false;

    public override void _Process(double delta) { ... }
}

The first variable (_pauseTimeAtWaypoints) is a float: it will be the duration in seconds we want to wait at each waypoint (if any). If it’s zero, then we’ll get the same behaviour as we currently have, meaning the platform ignores the intermediate waypoints; else it will slow down for each, stop for a while, and then accelerate again to reach the next one.

The second variable (_jumpToStart) is a boolean: if it’s on, the platform will teleport back to the start point when it reaches the end. Else, it will backtrack to the beginning by moving along the path in the other direction.

With that in mind, let’s now see how to add the waypoint-by-waypoint mechanic.

The theory behind our waypoint system

The idea here will be to store the positions of the different points in our curve and the current waypoint index to know what chunk of the path our platform should move along.

The trick will be to use the normalised positions of those intermediate spots. This way, we’ll have values between 0 (at the beginning of the path) and 1 (at the end of the path):

Then, depending on the current waypoint index for our platform, we’ll know what segment to consider; and we’ll be able to interpolate a float between the first “from” point and the second “to” point of this sub-path:

This interpolation could be either linear, or with little speed ups and slow downs if we’re supposed to stop at each waypoint and we want a more natural feel:

All of this will make our platform movement highly customisable, and yet easy to define for the designers, since they’ll just need to create additional points on the path to create those intermediate stops.

The truly delicate part is to properly determine the speed of our platform so that it is constant on the path. Indeed, when using the ProgressRatio of a Godot PathFollow node, if you’re not careful, you’ll actually get relative speeds depending on the length of the path to walk.

To avoid this issue, we just need to use some basic physics formula and actually base our interpolation on the expected travel time.

As you probably know, the speed of an object S can be computed by dividing the travelled distance D by the time T it took the object to travel:

But of course, this also means that travelling the distance D at this speed S takes a T amount of time:

In our case, that’s pretty neat, because:

  • We’ve already exposed our platform speed in the inspector (with the _speed variable), so it’s user-defined data.
  • And we can easily identify the distance to travel based on our waypoint ratios, and our total curve length L:

This means that we could easily set up a counter that counts down this time amount T, and then interpolate our platform’s position along our curve sub-chunk to have it be at the first waypoint at the beginning of the countdown, and at the second waypoint when the time has elapsed:

Alright — that’s a lot of theory! So time to clear things out by putting all of this in practice :)

Implementing our waypoint mechanic

Back in our script, we’re going to add a few extra variables for this waypoint mechanic.

We’ll need to keep track of the total length of the curve, the number of waypoints, the index of the current waypoint, and the list of waypoint ratios, as a list of floats:

using Godot;
using System.Collections.Generic;

public partial class MovingPlatform : PathFollow3D
{
    // ...

    private float _totalCurveLength;
    private int _nWaypoints;
    private int _curWaypointIdx;
    private List<float> _waypointRatios;

    public override void _Process(double delta) { ... }
}

Then, in our _Ready() function, we’ll get our path’s curve data:

public partial class MovingPlatform : PathFollow3D
{
    // ...

    public override void _Ready()
    {
        Curve3D c = GetParent<Path3D>().Curve;
    }
}

This allows us to get the number of points in the curve, go through each of these points to get their ratios, and finally store the total length of the curve:

public override void _Ready()
{
    Curve3D c = GetParent<Path3D>().Curve;
    _nWaypoints = c.PointCount;
    _waypointRatios = new();
    _totalCurveLength = c.GetBakedLength();
    float curLength = 0;
    for (int i = 0; i < _nWaypoints; i++) {
        if (i > 0) {
            float r1 = c.GetClosestOffset(c.GetPointPosition(i - 1));
            float r2 = c.GetClosestOffset(c.GetPointPosition(i));
            curLength += r2 - r1;
        }
        _waypointRatios.Add(curLength / _totalCurveLength);
    }
}

Note: Nesting the GetClosestOffset() and GetPointPosition() methods like this allows us to get the proper waypoint ratios even if the path isn’t just made of straight lines ;)

At the end of this initialisation step, let’s also make sure we start our platform movement at the beginning of the curve by resetting the ProgressRatio and our waypoint index to 0:

public override void _Ready()
{
    Curve3D c = GetParent<Path3D>().Curve;
    _nWaypoints = c.PointCount;
    _waypointRatios = new();
    _totalCurveLength = c.GetBakedLength();
    float curLength = 0;
    for (int i = 0; i < _nWaypoints; i++) {
        if (i > 0) {
            float r1 = c.GetClosestOffset(c.GetPointPosition(i - 1));
            float r2 = c.GetClosestOffset(c.GetPointPosition(i));
            curLength += r2 - r1;
        }
        _waypointRatios.Add(curLength / _totalCurveLength);
    }

    ProgressRatio = 0;
    _curWaypointIdx = 0;
}

Then, let’s create a new private method in our script: _StartMove(), and call it from our _Ready() function. This will be the function that makes our platform move from one waypoint to the next along our path.

public partial class MovingPlatform : PathFollow3D
{
    // ...

    public override void _Ready()
    {
        // ...
        _StartMove();
    }

    private void _StartMove() {}
}

We’re going to implement this method bit by bit so, for now, let’s stay simple and just increment our waypoint index by one:

private void _StartMove()
{
    _curWaypointIdx += 1;
}

Now, we’re going to setup everything we need to handle the travel time counter and position interpolation we talked about. We need to tell our script what chunk of path we’re working on, what the travelling time should be, and where we’re at in our countdown…

So, all of this requires the following variables:

  • First, the _moveFrom value that is the ratio associated with the point at the last index (i.e. the first point in the path sub-chunk we’re interested in).
  • Second, the _moveTo value that is the ratio associated with the point at the current index (i.e. the last point in the path sub-chunk we’re interested in).
  • Third, the _moveTime which, as we said, can be computed by taking the difference between our two ratios, multiplying it by the total curve length, and dividing the result by our speed.
  • Fourth, the _moveDelay which we will initialise to 0, and then increase in our _Process() method until it reaches our _moveTime value — so that will be our actual time counter.
  • Finally, let’s also setup a _moving boolean to make it easier to know if we need to update this counter or not.
public partial class MovingPlatform : PathFollow3D
{
    // ...

    private bool _moving;
    private float _moveFrom, _moveTo;
    private float _moveTime, _moveDelay;

    private void _StartMove()
    {
        _curWaypointIdx += 1;

        _moveFrom = _waypointRatios[_curWaypointIdx - 1];
        _moveTo = _waypointRatios[_curWaypointIdx];
        _moveTime = Mathf.Abs(_moveTo - _moveFrom) * _totalCurveLength / _speed;
        _moveDelay = 0;
        _moving = true;
    }
}

With all of this ready, it’s time to go back to our _Process() method and replace our current logic with the interpolation system.

So, first of all, if we are not moving, we can just return early right there:

public override void _Process(double delta)
{
    if (!_moving) return;
}

Else, if we are moving, there are two possibilities:

  1. either our delay is lower than the travel time — in which case we need to increase it by the frame time delta to update our counter, and move our platform
  2. or we’ve reached our travel time threshold, and so we need to stop our movement, optionally stop at our waypoint, and continue our progression onto the next chunk of the curve
public override void _Process(double delta)
{
    if (!_moving) return;

    if (_moveDelay < _moveTime) {
        _moveDelay += (float) delta;
        // move our platform!
    } else {
        // stop our movement, wait at waypoint, continue progress...
    }
}

Now, to move our platform, we said that we want to interpolate its normalised position along our subpath. This is easy to do thanks to the Mathf.Lerp() method.

This built-in function takes in the “from” and “to” values to interpolate between, and finally what we call the t-value. This third parameter is the 0-to-1 value that determines how far along we are on our subpath.

To start simple and make a linear movement, we set this t-value to be our _moveDelay divided by our _moveTime. (Basically, we are “normalising” our time counter.)

public override void _Process(double delta)
{
    if (!_moving) return;

    if (_moveDelay < _moveTime) {
        _moveDelay += (float) delta;
        float t = _moveDelay / _moveTime;
        ProgressRatio = Mathf.Lerp(_moveFrom, _moveTo, t);
    } else {
        // stop our movement, wait at waypoint, continue progress...
    }
}

Last but not least, when our counter is done, we’ll make sure our platform is exactly on the waypoint, turn our _moving boolean back to false and call a little function that directly restarts the movement to the next waypoint:

public partial class MovingPlatform : PathFollow3D
{
    // ...

    public override void _Process(double delta)
    {
        if (!_moving) return;
    
        if (_moveDelay < _moveTime) {
            _moveDelay += (float) delta;
            float t = _moveDelay / _moveTime;
            ProgressRatio = Mathf.Lerp(_moveFrom, _moveTo, t);
        } else {
            ProgressRatio = _moveTo;
            _moving = false;
            _ReachWaypoint();
        }
    }
    
    private void _ReachWaypoint()
    {
        _StartMove();
    }
}

Before we restart the move however, if our _pauseTimeAtWaypoints is not null, we’d like to tell Godot to wait for this amount of time before running the rest of our logic.

So let’s create a mini one-shot timer with this value as timeout, make our function asynchronous (with the async keyword) and add the await keyword in front of our timer line:

private async void _ReachWaypoint()
{
    if (_pauseTimeAtWaypoints > 0) {
        await ToSignal(
            GetTree().CreateTimer(_pauseTimeAtWaypoints),
            Timer.SignalName.Timeout);
    }
    _StartMove();
}

While we’re at it, we can copy these lines to our _Ready() function, too, to have our system wait at the very beginning, before the platform even starts to move. Don’t forget to make the _Ready() function async too ;)

public async override void _Ready()
{
    // ...

    ProgressRatio = 0;
    _curWaypointIdx = 0;

    if (_pauseTimeAtWaypoints > 0) {
        await ToSignal(
            GetTree().CreateTimer(_pauseTimeAtWaypoints),
            Timer.SignalName.Timeout);
    }

    _StartMove();
}

At this point, if we run our game, we’ll get something that is quite similar to our previous trial… except that it’s actually taking into account all of our waypoints! So if we set our pause time to a higher value than zero, we’ll see our platform indeed stops for a while at each point :)

But of course, this brutal stop is not very natural. Ideally, we’d like for the platform to slow down as it reaches the waypoint…

Smoothing out our stops

To make our movement smoother, we are going to boost our interpolation system with a custom easing function.

For now, because we are lerping use our t-value as-is, we have a linear movement:

To make our movement more natural, we just need to put this t-value inside a special computing function that transforms it into a more interesting curve:

And luckily, there’s this great website called easings.net that gives the formulas of a wide variety of nice interpolation curves!

easings.net is a super useful website to get formulas of a wide variety of easing functions!

Typically, here, I’m going to pick the quart ease-in-out function — we can get its code by clicking on the thumbnail, scrolling to the bottom and copying the logic. We can re-implement it in our C# script like this:

public partial class MovingPlatform : PathFollow3D
{
    // ...

    private float _EaseInOutQuart(float x) {
        return x < 0.5f ? 8 * x * x * x * x : 1 - Mathf.Pow(-2 * x + 2, 4) / 2;
    }
}

Then, we just need to wrap our t-value into a call of this easing function:

public override void _Process(double delta)
{
    if (!_moving) return;

    if (_moveDelay < _moveTime) {
        _moveDelay += (float) delta;
        float t = _EaseInOutQuart(_moveDelay / _moveTime);
        ProgressRatio = Mathf.Lerp(_moveFrom, _moveTo, t);
    } else { ... }
}

And that’s it! If we run our game again, our platform now nicely slows down to the waypoint, stops for a while, and then accelerates again on the next chunk of the path :)

Though, we need to be careful because, if we’re not stopping at the waypoint, we shouldn’t have these slow downs and speed ups… So let’s ensure that if the pause time is null, we use our previous basic t-value, or else we compute the eased-out version:

public override void _Process(double delta)
{
    if (!_moving) return;

    if (_moveDelay < _moveTime) {
        _moveDelay += (float) delta;
        float t = _pauseTimeAtWaypoints > 0
            ? _EaseInOutQuart(_moveDelay / _moveTime)
            : _moveDelay / _moveTime;
        ProgressRatio = Mathf.Lerp(_moveFrom, _moveTo, t);
    } else { ... }
}

And here we are :)

Our platform movement is now pretty cool, with optional stops and controllable speed… Except that when it reaches the end of the path, our code starts to go on the fritz because it tries to find a point index outside of our ratios list!

So we need to handle the last phase of our movement: going back to the start point…

Handling the return journey

As we said before, we want the designers to be able to choose between two different systems:

  1. either a direct teleportation back to the start
  2. or a round-trip where the platform actually walks back the path the other way

The first case is quite easy to implement.

We’ll simply go back to our _StartMove() function, check for the current index and, if it’s equal to our number of points (meaning we’ve reached the last spot), force our waypoint index back to 1:

private void _StartMove()
{
    _curWaypointIdx += 1;
    if (_curWaypointIdx == _nWaypoints) {
        _curWaypointIdx = 1;
    }

    _moveFrom = _waypointRatios[_curWaypointIdx - 1];
    _moveTo = _waypointRatios[_curWaypointIdx];
    _moveTime = Mathf.Abs(_moveTo - _moveFrom) * _totalCurveLength / _speed;
    _moveDelay = 0;
    _moving = true;
}

This way, we’ll instantly be back to our first chunk of curve:

The second case requires a bit more work.

First of all, of course, we’re going to need a new variable to know which direction we’re going. By setting it to +1 for the forward direction and -1 for the backward direction (+1 being the default), like this:

public partial class MovingPlatform : PathFollow3D
{
    // ...
    private int _platformDirection = 1; // 1: forward, -1: backwards
}

We’ll be able to use it in our _StartMove() logic as the index increment value, and in the _moveFrom computation:

private void _StartMove()
{
    _curWaypointIdx += _platformDirection;
    if (_curWaypointIdx == _nWaypoints) {
        _curWaypointIdx = 1;
    }

    _moveFrom = _waypointRatios[_curWaypointIdx - _platformDirection];
    _moveTo = _waypointRatios[_curWaypointIdx];
    _moveTime = Mathf.Abs(_moveTo - _moveFrom) * _totalCurveLength / _speed;
    _moveDelay = 0;
    _moving = true;
}

This way, depending on its +1 or -1 value, we’ll either get the point before or after in our list, and thus our platform will move in one direction or the other.

However, this also slightly complicates things for extrema checks. Indeed, now, we need to check both if our current index equals the number of points in the curve, and if it’s equal to -1.

private void _StartMove()
{
    _curWaypointIdx += _platformDirection;
    if (_curWaypointIdx == _nWaypoints) {
        _curWaypointIdx = 1;
    }  else if (_curWaypointIdx == -1) {
    }

    _moveFrom = _waypointRatios[_curWaypointIdx - _platformDirection];
    _moveTo = _waypointRatios[_curWaypointIdx];
    _moveTime = Mathf.Abs(_moveTo - _moveFrom) * _totalCurveLength / _speed;
    _moveDelay = 0;
    _moving = true;
}

That’s because we could be reaching either end of the path ;)

In our previous check, we’ll start by using our _jumpToStart flag to isolate our previous code; and if this flag is false (meaning we want our platform to actually move back along the path to the start) we’ll change our direction variable to -1 and reduce our current index:

private void _StartMove()
{
    _curWaypointIdx += _platformDirection;
    if (_curWaypointIdx == _nWaypoints) {
        if (_jumpToStart) _curWaypointIdx = 1;
          else {
              _platformDirection = -1;
              _curWaypointIdx -= 2;
          }
    }  else if (_curWaypointIdx == -1) {
    }

    _moveFrom = _waypointRatios[_curWaypointIdx - _platformDirection];
    _moveTo = _waypointRatios[_curWaypointIdx];
    _moveTime = Mathf.Abs(_moveTo - _moveFrom) * _totalCurveLength / _speed;
    _moveDelay = 0;
    _moving = true;
}

Note that we need to reduce it by 2, and not just 1, because we already incremented it before our code “realised” it should go the other way, so we have to take this extra offset into account.

Similarly, if our index reaches -1, we need to set the direction back to +1, and add 2 to our index:

private void _StartMove()
{
    _curWaypointIdx += _platformDirection;
    if (_curWaypointIdx == _nWaypoints) {
        if (_jumpToStart) _curWaypointIdx = 1;
          else {
              _platformDirection = -1;
              _curWaypointIdx -= 2;
          }
    }  else if (_curWaypointIdx == -1) {
        _platformDirection = 1;
        _curWaypointIdx += 2;
    }

    _moveFrom = _waypointRatios[_curWaypointIdx - _platformDirection];
    _moveTo = _waypointRatios[_curWaypointIdx];
    _moveTime = Mathf.Abs(_moveTo - _moveFrom) * _totalCurveLength / _speed;
    _moveDelay = 0;
    _moving = true;
}

Ok, that’s pretty cool! We now have a complete moving scheme that handles various cycling options, and is highly customisable from the inspector :)

Now, to wrap up this tutorial, let’s see how to fix our last issue: our intangible platform floor!

Fixing the collision

Alright — to really get a functioning platform system, one last problem we have to take care of is our player avatar just falling through the platform:

As we said before, our platform is an AnimatableBody3D node — and our player avatar is a CharacterBody3D, just like we discussed in this previous episode of the series

So it should work, right? What’s the issue?

Well, in fact, what’s truly happening is that, although our platform is indeed dragged along the path thanks to the PathFollow node, its collider isn’t!

We can actually visualise this by going to the Debug menu and enabling the colliders debug:

This is a known caveat of moving AnimatableBody nodes using a PathFollow node, like here — but luckily, there’s an easy solution!

We just need to use a RemoteTransform3D node.

Now, in essence, this node is very simple: it allows you to synchronise the transform properties of a node with another (so you can push the location, rotation and/or scale of the source onto the target node easily).

So to fix our collision, the trick is, rather than putting our platform as a child of the PathFollow node and moving it like this directly, which doesn’t properly update the position of its collider and invalidates our collision, to use a RemoteTransform3D node as a child of the PathFollow node, and then apply its location onto our platform that is somewhere else in the hierarchy:

So, apart from updating our scene tree, we of course need to make sure that, in the RemoteTransform3D node’s inspector, we assign our platform as the target, with the Remote Path property:

In the Update section, we can also toggle off the rotation and scale synchronisation since we’re only interested in the location:

And that’s it! Our platform now moves exactly the same as before, with all the same options, but if we try to walk on it, we see there is a collision and our player doesn’t fall through the floor anymore :)

Conclusion

So here you go: you know how to setup a basic moving platform system in Godot and C#! I hope you liked this tutorial and that it helped you understand concepts like interpolation, easing or even Godot’s paths and RemoteTransform3D node.

If you enjoyed the tutorial, feel free to clap for the article and follow me to not miss the next ones — and of course, don’t hesitate to drop a comment with ideas of Godot tricks that you’d like to learn!

As always, thanks a lot for reading, and take care :)

To read more of my content, and articles from many other great writers from Medium, consider becoming a member & following me!

Godot
Csharp
Programming
Tutorial
Game Development
Recommended from ReadMedium