Problem with custom climb function

I have a persistent bug and I can’t pinpoint the cause of it. Below I will try explain the situation.

To determine whether the player can climb or not I check the distance from the player model (white dot to be precise) to the bottom of the ladder. While climbing I move the player model up. The rigidbody remains on the ground. When the model is at the top of the ladder, I also teleport my rigidbody up and reset the player model to match the rigidbody’s position again.

            // get distance from player model to ladder bottom
            var distanceBottomPoint = playerFront.getPosition().distance(targetBottomPoint.getPosition());

            // if distance from player model to ladder bottom is close enough to start climb up
            if (distanceBottomPoint < 1) {
                playerIsClimbingUp = true;
            }

            // if player is climbing up
            if (playerIsClimbingUp) {
                // if player model is lower then ladder top (keep rigidody on ground and move player model up)
                if (playerModel.getPosition().y < targetTopPoint.getPosition().y) {
                    playerIsClimbing = true;
                    player.rigidbody.teleport(targetBottom.getPosition().x, targetBottom.getPosition().y, targetBottom.getPosition().z, targetBottom.getEulerAngles());
                    playerModel.setPosition(targetBottomPoint.getPosition().x, playerModel.getPosition().y, targetBottomPoint.getPosition().z);
                    playerModel.setEulerAngles(0, 90,0);
                    playerModel.translateLocal(0,0.5,0);
                    this.entity.anim.setInteger("Direction", 1);
                }
                
                // if player model is on top of ladder (teleport rigidody and reset player model)
                else {
                    playerIsClimbing = false;
                    playerIsClimbingUp = false;
                    player.rigidbody.teleport(targetTop.getPosition());
                    playerModel.setLocalPosition(0,0,0);
                    playerModel.setLocalEulerAngles(0,180,0);
                    this.entity.anim.setInteger("Direction", 0);
                }
            }

However, for unknown reason, sometimes the climb process starts again and everything is moved back down the ladder. I suspect that this is because the rigidbody was not updated quickly enough, so that the player model is set to the old position of the rigidbody, which is then still at the bottom of the ladder. Or is there something wrong with my logic in the script?

Hi @Albertos, I’m a novice but I’ll try and make some comments.

  • I’m not sure teleport( px, py, pz, vec3 for Euler) is a valid signature. If the rotation is ignored does the existing player rotation make the model lower than you think?
  • Does you logic get called repeatedly eg in update method, so that the climbing code called more than once? If so, then do you need to teleport the rigidbody to the same location each time?
  • Is the rigid body falling under gravity and sometimes causing the effect you’re seeing?
  • could you introduce a “climbed” boolean so that the code won’t execute once at the top of the ladder?
  • playerFront is used to determine whether to start climbing. Is that position correct?

Hi @Kulodo133! Thank you so much for taking the time to read my code! I will respond to all your points below.

It’s based on the manual, so I’m assuming it’s correct, or do I misunderstand something?

No, I don’t think so.

Yes, because it’s needed to move the playerModel up over time. Probably I need to use a tween for this and maybe that will also solve the problem.

No, but I tried to do this once before climbing and it doesn’t solve the issue.

Yes, good one. The end point is above the second floor platform, but on the same height. I could try to set it a little higher, to prevent the rigidbody is be able to fall inside the platform.

No, it will maybe avoid the issue but not solve the issue. I also need to reset the boolean somewhere, so that makes it difficult to use an extra boolean.

Yes, I checked this, and it is on the correct position all the time.

I’m reading that as

  1. teleport(vec3, vec3) or
  2. teleport(vec3, quat) or
  3. teleport(num, num, num, num, num, num)

But I think you have teleport( num, num, num, vec3 ) which isn’t one of the three.

I think they only use a couple of use cases as example, but I think you can combine them. Maybe someone else can clear this out.

I will try to create a sample project for my issue (because I’m curious why it happens) and I will change my own code with using a tween. I think thats much better than do all inside the update function.

I did a quick test.

    var p = new pc.Vec3(0, 0, 0);
    var v = new pc.Vec3(0, 30, 0);
    //this.entity.rigidbody.teleport(p, v);  // This works.
    //this.entity.rigidbody.teleport(0, 0, 0, 0, 30, 0 );  // This works.
    this.entity.rigidbody.teleport(0, 0, 0, v);  // Position set OK but the v is ignored.

Hmm, I’m surprised. Good to know for the future! Thanks for figuring out!

Yes, that last method override isn’t implemented. You can check the method implementation here:

Talking about this, is it done in the update or postUpdate?

The physics simulation simulates a step on each update cycle:

Does that mean that in the example below, the teleporting of the first line has finished before the second line is executed?

player.rigidbody.teleport(targetTop.getPosition());
playerModel.setLocalPosition(0,0,0);

It depends how you interpret finish. Both methods will execute when called and update the internal state of their respective object.

For the rigidbody that would be the Ammo body, as seen here:

For the playerModel that would be a pc.Entity instance. If you pause the application execution just after both methods run, visually you won’t see any difference.

Because for both methods to actually produce a visual result the application needs to render first.

The physics simulation will step (update) before the application renders.

For the pc.Entity update method, it depends on where it’s executed, but likely it will come before render as well e.g. if you call it in update() or postUpdate() or as inside an input listener.

I mean, could it be that the local position of the playerModel is reset to the old position of the rigidbody, because in that case the position is still at the bottom of the ladder and that could causing the problem.

I can confirm this, because I was be able to reproduce the problem in a sample project.

In the sample project there are two rounds. When round 1 is finished, round 2 should be started and after round 2, round 1 should be started again. If the same round is started again, it means the problem is occurring. (Please do not discuss that teleporting inside the update function is not the right way, because I’m aware of that and I will change the whole function in the original project).

https://playcanvas.com/editor/scene/1298974

Could it be that also this problem is 120hz screen related?

This is what I see on a 60Hz screen

Yes, that’s how it should be.

This is what I see on my laptop with a 120hz screen:

You aren’t using dt in the update function so the balls movement is twice as fast which could be causing the differences between devices. Try changing those lines to:

this.child.translateLocal(0, ((0.05) * (1/60)) * dt, 0);

I’m not at home right now, but if that will solve the problem I guess the problem is also visible on a 60hz screen when we speed up the movement? Or is using delta time always be able to prevent this?

Let’s see if that gives the same behaviour first and work from that.