Creating realistic physical jumps

I was discussing in this post where a dude suggested me to share a better solution. So I decided to have a little work on that. Here’s my report.

The main two most encounted problems when implementing a jump feature would be:

  1. Preventing object from jumping in midair.
  2. Preventing user to keep pressing the jump key, resulting in a series of sticky event firings.

For the former problem. the solution is fairly simple: just give a check to this.app.keyboard.wasPressed before applying the impulse & everything would be fine.

For the latter problem, we need to maintain a integer variable which counts for the number of collided rigid bodies. Further more, if reaction forces are also taken into account, then that variable have to be upgraded into a Set. Maintenance can be done when collisionstart or collisionend event fires.

The final script asset would be like:

{
    const Jump = pc.createScript('Jump');
    Jump.attributes.add('impulse', { type: 'number', default: 5, min: 0 });

    Object.entries({
        initialize() {
            this.collisions = new Set();
            const keyboard = this.app.keyboard;
            keyboard.on('keydown', e => {
                if(e.key !== pc.KEY_SPACE)
                    return;
                if(!this.collisions.size)
                    return;
                if(!keyboard.wasPressed(pc.KEY_SPACE))
                    return;
                this.jump();
                e.event.preventDefault();
            });
            this.entity.collision.on('collisionstart', collision_info => this.collisions.add(collision_info.other));
            this.entity.collision.on('collisionend', other => this.collisions.delete(other));
        },
        jump(impulse = this.impulse) {
            this.entity.rigidbody.applyImpulse(0, impulse, 0);
            const inverse_impulse = -impulse / this.collisions.size;
            for(const entity of this.collisions) {
                const other_jump = entity?.script?.Jump;
                if(!other_jump)
                    continue;
                other_jump.jump(inverse_impulse);
            }
        }
    }).forEach(([key, value]) => Jump.prototype[key] = value);
}

Note that there’s a line where operator ?. is used. It can work normally, but PlayCanvas editor keeps complaining.

You can add it to whatever rigid body you’d like to make jumpable. Just make sure that the rigid body must have a collision component.

The project can be viewed here.

2 Likes