Moving platforms are a staple of platformer games- and for good reason! They take what would otherwise be a static environment and transform it into a dynamic playground. Vertical lifts, flipping tiles, spinning bridges- is a platformer really complete without them?
On a technical level, moving platforms are simply objects which change their transform (or world matrix) over time: whether that be changes in position, rotation, scale or some combination thereof.
To introduce moving platforms we can add a step to each game tick where we compute transform changes for each platform we’d like to update. This will result in repositioning and reorienting each platform’s collision triangles in world space. We can then run the character move step as usual. In the default case we require no special handling: if a platform update results in new intersections with the character collider, the subsequent character move step should handle pushing out the character as well as updating their grounded state.
There is a case though that does need special attention: if a character is standing on a moving platform. By default such a platform will simply slide out from under the character. Instead we would like to move the character with the platform as though it were attached to it. It’s worth noting that standing on a platform is just one of several cases where we’d like the character to stay attached to a platform: if a character is wall sliding or hanging on to a moving platform we similarly would like to move the character along with the platform’s motion. How you define the conditions for a character to be considered attached will be dependent on your particular game.
Once you decide that a character should move as though attached to a platform, how do you actually go about doing that?
Consider that a moving platform’s motion between ticks can be fully represented by two transformation matrices: the one at the start of the tick and the one after any changes. Let’s call these the previous and next transforms.
What do these previous and next transforms actually represent? Let’s look at the vertices that make up a platform. Each vertex is represented as a position vector that is relative to the platform (sometimes referred to as local to the platform’s space). Applying the previous transform to each vector (multiplying them by the world matrix) produces the world position for each vertex for the platform’s previous transformation. Similarly applying the next transform to each local vertex produces the world position of each vertex for the next transformation.
A character’s position is also represented by a position vector except that it is in world space. If a character’s position was instead represented by a vector that was relative to a platform, we could apply the platform’s next transform to get the new world position. More specifically, we would apply it to the character position if it were a vector relative to the platform before the transform change.
To convert a position vector from world space to an object’s local space is simply a matter of multiplying the vector by the inverse of the object’s world matrix (sometimes referred to as the worldToLocal matrix). Because we would like the character position relative to s platform before any changes, we specifically want to multiply the character position by the inverse of the platform’s previous transform matrix.
Now with the character position represented as a position relative to the platform (or a position local to the platform’s space), we can multiply it by the platform’s next transform matrix (sometimes referred to as the localToWorld matrix). The result is a vector that represents the character position in world space as though it had undergone the same changes (from previous to next transform) as the platform.
This sort of transformation to the character’s position is the logical equivalent of what would happen had we instead made the character a hierarchical child of the platform, updated the platform, and then unparented the character from the platform. The position would have been converted from world to local relative to the previous transform, the platform/parent transform would be updated and the local position would have been converted from local to world by the next transform. We simply achieved the same result without modifying any object hierarchies.
If we now take the difference between the newly computed character position and the original character position, we can add that vector onto the character’s move vector for the coming move step. Because we used the previous and next transforms, this next position delta takes into account all translation, rotation and scale changes the platform had gone through.
We can apply a similar strategy for rotational updates. We can take the original character rotation, represent it as a rotation relative to the platform’s previous transform, then compute the next rotation using the platform’s next transform.
If we represent both our character’s rotation and our platform’s previous and next rotations as quaternions, we can work with quaternions to convert to local space and back to world space. Otherwise we can use the platform’s previous and next transforms as matrices so long as we convert the character’s rotation to a matrix representation first, then do the multiplication, and finally extract just the rotation component from the final result.
If we would like to additionally keep the character rotation upright, we need to apply one additional rotation to the next character rotation. That rotation can be thought of as the rotation that rotates the character’s local up vector to the world up vector. This is usually referred to as a from-to vector rotation.
The from vector can be computed by applying the next character rotation to [0,1,0]. This represents the upward direction of the character in world space under the next rotation. The to vector is simply the world up vector, or the upward direction which we would like the character’s upward direction to align to. Once the from-to rotation is computed using these vectors, we can multiply the next character rotation by it. The result is the next character rotation after platform motion and then aligned to world up.