[SOLVED] How do I make a 3D jumping mechanic?

How can I make the character only jump when it is touching something with the tag ‘solid’ and preferably only on a vertical collision?

var PlayerController = pc.createScript('playerController');

PlayerController.attributes.add('power', { type: 'number' });

PlayerController.attributes.add('jumpHeight', { type: 'number' });

PlayerController.attributes.add('modelEntity', { type: 'entity' });

PlayerController.prototype.initialize = function() {
    this._anim = this.modelEntity.anim;
    this._angle = 0;
    this.baseAngle = 0;
    this.yVel = 0;
};

PlayerController.prototype.update = function(dt) {
    var x = 0;
    var z = 0;
    if (this.yVel > 0) {
        this.yVel -= 3;
    }
    if (this.app.keyboard.isPressed(pc.KEY_W)) {
        x -= 1;
    }
    if (this.app.keyboard.isPressed(pc.KEY_S)) {
        x += 1;
    }
    if (this.app.keyboard.isPressed(pc.KEY_A)) {
        z -= 1;
    }
    if (this.app.keyboard.isPressed(pc.KEY_D)) {
        z += 1;
    }
    /*if (this.app.keyboard.isPressed(pc.KEY_LEFT)) {
        this.baseAngle += 10;
        this.fire('base angle', this.baseAngle);
    }
    if (this.app.keyboard.isPressed(pc.KEY_RIGHT)) {
        this.baseAngle -= 10;
        this.fire('base angle', this.baseAngle);
    }*/
    if (this.app.keyboard.wasPressed(pc.KEY_SPACE) && this.entity.rigidbody.linearVelocity.y === 0) {
        this.yVel = this.jumpHeight;
    }
    this.entity.rigidbody.applyForce(0, this.yVel, 0);
    let movement = new pc.Vec3(-x * Math.sin(this.baseAngle * Math.PI / 180) - z * Math.cos(this.baseAngle * Math.PI / 180), 0, -x * Math.cos(this.baseAngle * Math.PI / 180) - z * Math.sin(this.baseAngle * Math.PI / 180)).normalize().scale(dt * this.power);
    this.entity.rigidbody.applyForce(movement);
    if(x !== 0 || z !== 0) {
        this._angle = pc.math.lerpAngle(this._angle, 180 + (Math.atan2(z, x) * pc.math.RAD_TO_DEG), 0.4) % 360;
        this.modelEntity.setEulerAngles(0, this._angle, 0);
    }
    else {
        this._angle = pc.math.lerpAngle(this._angle, this.baseAngle, 0.4) % 360;
        this.modelEntity.setEulerAngles(0, this._angle, 0);
    }
    this.modelEntity.anim.setFloat('Speed', Math.abs(x+z));
    this.modelEntity.anim.setFloat('Jump', this.yVel > 0 ? 1 : 0);
};

Hi @P0werman1 and welcome!

Maybe the topic below can help you with this.

I managed to get it working from that for jumping from the ground, but it won’t let me jump if I’m on an obstacle. Any idea why?

It should also work on obstacles. Can you share the editor link of your print?

https://playcanvas.com/project/1036368/overview/game

Can you explain your logic?

As far I understand your jump function need to be inside result.

if (result) {
    // able to jump
}

I have a y velocity value, that decreases so long as the value is greater than 0. The player is always moved up by the current y velocity. The jump function sets the y velocity to a larger number.

I think the logic will be something like this:

if (raycast result) {
    // be able to jump 
    if (player want to jump) {
        // jump
    }
}
else {
    // fall
}

It’s basically a worse written version of that.
EDIT: I changed it to follow that instead, looks cleaner and works better now, but I can still only jump from the ground
EDIT2: This way makes me see what’s wrong with the system, but I don’t know how to fix it. The raycast only sees the ground. I don’t know why.

As long as you don’t check for ‘ground’ tags it should work the same on obstacle entities, because this is basically the same as a ground entity. I will check your project when I’m home.

    const downDir = pc.Vec3.DOWN.clone();
    const endPos = new pc.Vec3().copy(playerPos).add(downDir.scale(100));
    const result = this.app.systems.rigidbody.raycastFirst(playerPos, endPos);

I don’t see any tag checks here, but I don’t fully understand what’s going on.

Do you update playerPos somewhere?

const playerPos = this.entity.getPosition();

This is done right before, just forgot to copy that part

Okay, I will check your project when I’m home.

Cool, thank you.

I figured out the problem, but I still can’t fix it. I’m using raycastFirst, but I think it returns all intersections. It wouldn’t work on the obstacles because it detected the obstacle and the ground. How can I make it only return the first intersection?
EDIT: I don’t know if it’s returning all of the intersections, but something in my code doesn’t work when it returns more than 1.

image

Here the player is only be able to jump when there is no result, so no ground.

Is that correct?

It does that, and I don’t know why this works, but when I’m touching the ground it returns null. That was the only way I could find to say that you were touching the ground. Do you know a better way? I tried what was done in the post you linked, but it didn’t work.

The problem is that the start position of the raycast is too low. The origin of the player is on the ground, so the start position of the raycast is on the ground too. The raycast should start a little bit higher, above the ground. With the code below I solve this problem.

const playerPos = this.entity.getPosition();
// clone the world DOWN vector and scale it by 100 units
// then add it to the player position to get a new position 100 units down in world space
const downDir = pc.Vec3.DOWN.clone();
const startPos = new pc.Vec3(playerPos.x, playerPos.y + 1, playerPos.z);
const endPos = new pc.Vec3().copy(playerPos).add(downDir.scale(1));
const result = this.app.systems.rigidbody.raycastFirst(startPos, endPos);

if (result) {
    if (this.yVel < 0) {
        this.yVel = 0;
    }
    if (this.app.keyboard.wasPressed(pc.KEY_SPACE)) {
        this.yVel = this.jumpHeight;
    }
}
else {
    this.yVel -= 9.8;
}

You still need to apply the correct animation.

You can render a line to make a raycast visible, this makes it easier to debug problems like this.

this.app.drawLine(startPos, endPos);