Unity: a guide to moving platforms in 2D

Hi everyone! Today we would like to share with you our experience with moving platforms and physic based character controller in Unity.

This is an basic / intermediate tutorial. We will assume you know a bit of C#.

The problem

I will take as an example the elevator that our team has designed to allow our Dragon Bros to reach the lower levels of the mining facility.

The first approach was to create a Gameobject with a BoxCollider2D, a Rigidbody2D and an animator. We also added a custom script to it, to keep track of the elevator status: waiting or moving.

The lift starts moving when one of the character triggers a designated collider (typically when entering the elevator’s cage).

Finally, an animation controls the movement of the lift, by simply changing the position of the elevator.

First problem

The elevator’s movement is shaky. It doesn’t matter if the character is on it or not.

First solution

Do NOT animate a rigidbody position. It is best to use RigidBody.MoveTo, so the rigidbody can compute the exact force needed to move to a position in one frame, by countering gravity, friction and inertia.

Once this is fixed, we encountered…

…A second problem

The resulting lift correctly moves, but the character is falling for a couple of frames, reaches the platform, collides with it, and then again fall again in an endless loop. It looks terrible and flickers.

ElevatorIssue

And a second solution!

To solve this issue we decided to implement a SliderJoint2D to constrain the position of character on the y axis(vertical one).

void ConnectTo(Rigidbody2D character)
{  
  SliderJoint2D joint = GetComponent<SliderJoint2D>();
  joint.connectedBody = character;
}

void OnCollisionEnter2D(Collision collision)
{
    if (collision.collider.gameObject.layer == LayerMask.NameToLayer("Character")
    {
        ConnectTo(collision.collider.GetComponent<RigidBody2D>());
    }
}

The slider is connected to the character RigidBody2D as soon as the character collide with the elevator. Now the character is not falling / flickering anymore, but the character has lost the capabilities to jump!

The solution to this is to create an event to be invoked when the player press the jump button. When the event is called, we release the slider. We will reconnect it when the character lands again on the platform.

void Connect(RigidBody2D  character)
{
    ...

    // listen to the character jump event
    character.GetComponent<CharacterController>().onJump += OnCharacterJump;
}

void OnCharacterJump(CharacterController character)
{
    Disconnect(CharacterController character);
}

void Disconnect(CharacterController character)
    // do not listen to this anymore
    character.onJump -= OnCharacterJump;

    // disable the joint by unplugging the character
    joint.connectedBody = null;
}

Similarly the slider is removed when the player walks away from the platform.

void FixedUpdate()
{
    if(joint.connectedBody != null
    &&  Mathf.Abs(transform.position.x - joint.connectedBody.transform.position.x) 
        >= kBreakingDistance)
    {
        Disconnect(joint.connectedBody.GetComponent<CharacterController>());
    }
}

Now the character is constrained on top of the elevator. But it also is when colliding with its bottom or side! To solve this issue we will add a check on the direction of the collision.

void OnCollisionEnter2D(Collision collision)
{
    if (collision.collider.gameObject.layer == LayerMask.NameToLayer("Character"))
    {
        Vector2 direction = collision.contacts[0].normal;
        if (Mathf.Approximately(direction.y, -1f))
        {
            ...

And now the position of the character can be adjusted by properly setting the anchor point of the slider before connecting the two RigidBody2D.

// upon collision, record our distance and use it as an offset.
joint.anchor = new Vector2(0f, Mathf.Abs(transform.position.y - character.transform.position.y));

It looks better, but we still face a subtle flickering in the character and the platform.

We tried to decrease the Physic TimeStep to 0.01f instead of 0.02f and it actually made this issue less noticeable, but still visible and also lowering the overall performance. It was not a real solution, just hiding the problem!

We also tried to the change the physic material to increase the friction and zeroed the bounciness, made sure to move the lift RigidBody2D with the method RigidBody.Moveposition() and also increased its mass but the flickering remained.

A bit desperate, we tried parenting the character to the elevator, which kind of worked but raised other issues. Disabling the collision between the elevator and the character kind of worked as well, but proved unnecessary later.

Final solution to the flickering

The flickering is due to the opposite forces being applied by the slider and the collider of the platform counterbalancing the weight of the character, plus the approximation made by the physic engine.

To solve the problem, we nullified the gravity scale on the character as soon as it collides with the elevator and store it. When it disconnect, its gravity scale is set back to its stored original value.

And there you go…. we now have the perfect elevator!

THE FUTURE

Local multiplayer

If you have two players or more sharing the elevator, you will probably encounter problems with the previous code!

The solution is pretty simple: have one slider joints per player. Store them in a table and store the players in another table. Surround the code with a few loops, you’re done!

Complex cart movement

Maybe you want your cart to stop midway, as if temporary out of order? Or you want it to go very fast suddenly, as if the brakes are giving up? There are many ways to do this. The one we chose was to have a list of waypoints, each assigned with a speed and an optional wait. We created dummy game objects in our scene, named “Elevator Waypoint 1”, etc, and assigned them to the elevator’s list of waypoints.

Super mining cart

This lift is the first step towards… a mining cart of course! Now that you have locked the vertical movement, you will simply need to lock the horizontal one as well, with a slider joint at 90 degrees. This system combined with the cart movement will allow you to easily create crazy mines levels!

Social media frenzy

8 thoughts on “Unity: a guide to moving platforms in 2D”

  1. Hi, this is some useful information and thanks for sharing.
    Is your game made for mobile devices. If so,could you give few insights and tips on how you are implementing multiplayer.

  2. Hi Vicky, glad you found it useful!

    I am not sure whether you want to know more about local multi or online multi. If you ask about local multiplayer on mobile, then your main concerns will be: controls and readability. You will need to make sure that one side of the screen is for player 1 input, and the other half for player 2 for instance. If you have more than a couple of button, you may prevent the players from seeing the screen correctly because of their fingers! About the readability, if you plan to have the game on a small screen, you will have to be double sure that it’s easy for each player to know who they are on screen. That can be some tough design!

    If you are asking about online multiplayer, I’d say it’s much more complex.
    Our game is targeted at consoles and PC, and we decided to have local multiplayer only. Online multiplayer is much more complex for a few reasons:
    – replication: I think Unity is pretty good at replicating data across multiple clients, but you will still need to adapt it a bit,
    – matchmaking: you will need a way to connect clients to each other. That may be the hardest part. In the old days, asking your player to enter the IP address of its friends might have been ok, but not anymore! You will probably need to have a matchmaking server. Every client interested in playing an online match will contact the server. The server will then try to create groups of players as fast as possible. It will then distribute clients IPs to this group. They are then on their own until the end of the match. Try to keep it as simple as possible, for it gets complex rapidly! You may want to create groups by geographical proximity (so they communicate faster), experience (so new players are not paired with veterans ones) and of course per game mode. Now if you do not have enough players at the same time, what should happen? Should you force a game mode after match making? Or do you accept that players with low pings due to distance to each other play together, knowing that the experience will be poor? Then another problem arises: you need high availability server. That means your server needs to be up all the time. If you stop it, many players could get annoyed because they can’t play for an hour and you may loose players for good! So you will have to do it at night when no one is around. And you will need a server provider offering 99.99% availability, unless you want to deal with disgruntled players again. And what if your game is succesful? You will need to upgrade your server load capacity and optimize your server code so your server doesn’t crash. Then you have restriction from your platform: apple and android may ask you to encrypt any data leaving the phone with a certain algorithm (just guessing) and ask you that your EULA covers this. And on consoles I am pretty certain it is mandatory to encrypt your data, have a minimal server uptime, a maximum matchmaking wait, proper error message for every kind of error.
    – online age restriction: it gets more tricky to make sure that your age restriction is enforced online. Maybe you plan to have a chat? Then you need to have an option to mute it and to do it automatically when the user is less than 13 (for instance). If you have user generated content, then you need to monitor it and allow user to point at inappropriate content. Then if a user is a troll (not playing, slowing the team intentionally), you need a banning or reputation system.

    All in all, it can get complex quickly to make an online game! It’s hard to do it without a dedicated server with some of your code (Java being the most common I think) running on it. Not hard to learn once you know C# (though you could use C# and pay a bit more for a MS server). The only other reasonable options I can think about are Bluetooth (local only), direct connect (enter IP adress of your opponent). But I’m no network expert!

    Anyway, I’d recommend to plan your budget and planning well to make a serious online game (if it’s just for fun use bluetooth).

  3. And I forget to mention another online difficulty: dealing with players dropping out and deciding quickly who will be responsible for the AI and events next!

  4. I’ve tryed this solution but my character is still flickering even if I change it’s gravity scale to 0. Could you post a video with your character being lifted? It would help me to check if there are any shakes in your character Y axis.

  5. I am so sorry to hear that, we must have overlooked something! Ok, let’s check the following:
    – the platform’s rigidbody needs to be set to “IsKinematic”, with a 1000 mass, no drag, no gravity, set to interpolate, with a continuous collision detection, constraint on x and z.
    – what kind of collider is your platform? Ours is a box collider with a sticky physics material.
    – does your character controller use the “MoveTo” function? Ours doesn’t: it only adds horizontal forces to its rigid body.
    – slider joint 2D: Auto configure angle is false, angle is 180.

    Could you check those are true for you? I’d like to understand what went wrong :S

  6. I’ve got all the same including horizontal forces on Player’s RGB.
    In this case it looks like this:
    https://gyazo.com/83bce7072774dd8bb41fa230be44ffd5
    https://gyazo.com/aa31ada7b139ab7a2e0acceb46a7e886
    https://gyazo.com/780ed55ea3e0cddb4eaafe2d260153da
    It doesn’t matter if the colliders are touching each other.
    My platform components:
    https://gyazo.com/323b3877a9955b6ee088f2d68d31a4ac
    My platform is moving by transform.position not by velocity maybe this causes this issue.

  7. The platform being a rigid body, it should be moved with “RigidBody2D.Move();” instead of transform.position yes. In our case, the platform was heavily flickering when we used the transform.position.

    The only case I can think of where it is ok to move a rigidBody with transform.position directly would be when you reset your level to its initial state.

    The reason why physics engines usually mix badly with direct position movement is due to their inability to correctly compute collision upon teleportation. In some games, it means you give the rigidBody an infinite force, which may send other objects flying away or result in unhanded cases.

Leave a Reply

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