Help please I need a jump feature

https://playcanvas.com/project/721032/overview/test

I have gotten the WASD movement in but I don´t know how to code so I’m not able to add a jump feature how can I do this.

1 Like

Hi @wolfshadow726 and welcome,

Try doing a search in the forums with “how to jump”, you will get several relevant posts to read through.

I did but I did not find anything that had both was and jump for first-person and anything I did find broke it so I more help.

It’s not easy to make a jump feature and it depends on your game your wishes and your abilities.

The way I do that in my game is that I move the character up for a short while and then I move the character down, until the character reaches the ground. But I also check how far the player wants to jump and apply that in the jump by moving the character forward at the same time.

But how you do this is completely dependent on the rest of the game.

1 Like

ok, I would do that if I knew how but I have no idea how to code at all.

A good start is to follow the PlayCanvas tutorial. Than you learn the basics that you need to make a game.

https://developer.playcanvas.com/en/tutorials/keepyup-part-one/

3 Likes

ok thank you very much I will try it

hey so do you think you could try and add a jump feature to my code because I want a momentum-based game.

var FirstPersonMovement = pc.createScript('firstPersonMovement');

FirstPersonMovement.attributes.add('camera', {
    type: 'entity',
    description: 'Optional, assign a camera entity, otherwise one is created'
});

FirstPersonMovement.attributes.add('power', {
    type: 'number',
    default: 2500,
    description: 'Adjusts the speed of player movement'
});

FirstPersonMovement.attributes.add('lookSpeed', {
    type: 'number',
    default: 0.25,
    description: 'Adjusts the sensitivity of looking'
});

// initialize code called once per entity
FirstPersonMovement.prototype.initialize = function() {
    this.force = new pc.Vec3();
    this.eulers = new pc.Vec3();

    var app = this.app;

    // Listen for mouse move events
    app.mouse.on("mousemove", this._onMouseMove, this);

    // when the mouse is clicked hide the cursor
    app.mouse.on("mousedown", function () {
        app.mouse.enablePointerLock();
    }, this);

    // Check for required components
    if (!this.entity.collision) {
        console.error("First Person Movement script needs to have a 'collision' component");
    }

    if (!this.entity.rigidbody || this.entity.rigidbody.type !== pc.BODYTYPE_DYNAMIC) {
        console.error("First Person Movement script needs to have a DYNAMIC 'rigidbody' component");
    }
};

// update code called every frame
FirstPersonMovement.prototype.update = function(dt) {
    // If a camera isn't assigned from the Editor, create one
    if (!this.camera) {
        this._createCamera();
    }

    var force = this.force;
    var app = this.app;

    // Get camera directions to determine movement directions
    var forward = this.camera.forward;
    var right = this.camera.right;


    // movement
    var x = 0;
    var z = 0;

    // Use W-A-S-D keys to move player
    // Check for key presses
    if (app.keyboard.isPressed(pc.KEY_A) || app.keyboard.isPressed(pc.KEY_Q)) {
        x -= right.x;
        z -= right.z;
    }

    if (app.keyboard.isPressed(pc.KEY_D)) {
        x += right.x;
        z += right.z;
    }

    if (app.keyboard.isPressed(pc.KEY_W)) {
        x += forward.x;
        z += forward.z;
    }

    if (app.keyboard.isPressed(pc.KEY_S)) {
        x -= forward.x;
        z -= forward.z;
    }

    // use direction from keypresses to apply a force to the character
    if (x !== 0 && z !== 0) {
        force.set(x, 0, z).normalize().scale(this.power);
        this.entity.rigidbody.applyForce(force);
    }

    // update camera angle from mouse events
    this.camera.setLocalEulerAngles(this.eulers.y, this.eulers.x, 0);
};

FirstPersonMovement.prototype._onMouseMove = function (e) {
    // If pointer is disabled
    // If the left mouse button is down update the camera from mouse movement
    if (pc.Mouse.isPointerLocked() || e.buttons[0]) {
        this.eulers.x -= this.lookSpeed * e.dx;
        this.eulers.y -= this.lookSpeed * e.dy;
    }
};

FirstPersonMovement.prototype._createCamera = function () {
    // If user hasn't assigned a camera, create a new one
    this.camera = new pc.Entity();
    this.camera.setName("First Person Camera");
    this.camera.addComponent("camera");
    this.entity.addChild(this.camera);
    this.camera.translateLocal(0, 0.5, 0);
};

    this.x = new pc.Vec3();
    this.z = new pc.Vec3();
    this.heading = new pc.Vec3();
    this.magnitude = new pc.Vec2();

    this.azimuth = 0;
    this.elevation = 0;

    // Calculate camera azimuth/elevation
    var temp = this.camera.forward.clone();
    temp.y = 0;
    temp.normalize();
    this.azimuth = Math.atan2(-temp.x, -temp.z) * (180 / Math.PI);

    var rot = new pc.Mat4().setFromAxisAngle(pc.Vec3.UP, -this.azimuth);
    rot.transformVector(this.camera.forward, temp);
    this.elevation = Math.atan(temp.y, temp.z) * (180 / Math.PI);

    this.forward = 0;
    this.strafe = 0;
    this.jump = false;
    this.cnt = 0;

    app.on('firstperson:forward', function (value) {
        this.forward = value;
    }, this);
    app.on('firstperson:strafe', function (value) {
        this.strafe = value;
    }, this);
    app.on('firstperson:look', function (azimuthDelta, elevationDelta) {
        this.azimuth += azimuthDelta;
        this.elevation += elevationDelta;
        this.elevation = pc.math.clamp(this.elevation, -90, 90);
    }, this);
    app.on('firstperson:jump', function () {
        this.jump = true;
    }, this);
};

FirstPersonView.prototype.postUpdate = function(dt) {
    // Update the camera's orientation
    this.camera.setEulerAngles(this.elevation, this.azimuth, 0);

    // Calculate the camera's heading in the XZ plane
    this.z.copy(this.camera.forward);
    this.z.y = 0;
    this.z.normalize();

    this.x.copy(this.camera.right);
    this.x.y = 0;
    this.x.normalize();

    this.heading.set(0, 0, 0);

    // Move forwards/backwards
    if (this.forward !== 0) {
        this.z.scale(this.forward);
        this.heading.add(this.z);
    }

    // Strafe left/right
    if (this.strafe !== 0) {
        this.x.scale(this.strafe);
        this.heading.add(this.x);
    }

    if (this.heading.length() > 0.0001) {
        this.magnitude.set(this.forward, this.strafe);
        this.heading.normalize().scale(this.magnitude.length());
    }

    if (this.jump) {
        this.entity.script.characterController.jump();
        this.jump = false;
    }

    this.entity.script.characterController.move(this.heading);

    var pos = this.camera.getPosition();
    this.app.fire('cameramove', pos);
};


////////////////////////////////////////////////////////////////////////////////
//  FPS Keyboard Controls (Movement Only - Work Alongside Mouse Look Script)  //
////////////////////////////////////////////////////////////////////////////////
var KeyboardInput = pc.createScript('keyboardInput');

KeyboardInput.prototype.initialize = function() {
    var app = this.app;

    var updateMovement = function (keyCode, value) {
        switch (keyCode) {
            case 38: // Up arrow
            case 87: // W
                app.fire('firstperson:forward', value);
                break;
            case 40: // Down arrow
            case 83: // S
                app.fire('firstperson:forward', -value);
                break;
            case 37: // Left arrow
            case 65: // A
                app.fire('firstperson:strafe', -value);
                break;
            case 39: // Right arrow
            case 68: // D
                app.fire('firstperson:strafe', value);
                break;
        }
    };

    var keyDown = function (e) {
        if (!e.repeat) {
            updateMovement(e.keyCode, 1);

            if (e.keyCode === 32) { // Space
                app.fire('firstperson:jump');
            }
        }
    };

    var keyUp = function (e) {
        updateMovement(e.keyCode, 0);
    };

    // Manage DOM event listeners
    var addEventListeners = function () {
        window.addEventListener('keydown', keyDown, true);
        window.addEventListener('keyup', keyUp, true);
    };
    var removeEventListeners = function () {
        window.addEventListener('keydown', keyDown, true);
        window.addEventListener('keyup', keyUp, true);
    };

    this.on('enable', addEventListeners);
    this.on('disable', removeEventListeners);
    
    addEventListeners();
};


////////////////////////////////////////////////////////////////////////////////
//                         FPS Mouse Look Controls                            //
////////////////////////////////////////////////////////////////////////////////
var MouseInput = pc.createScript('mouseInput');

MouseInput.prototype.initialize = function() {
    var app = this.app;
    var canvas = app.graphicsDevice.canvas;

    var mouseDown = function (e) {
        if (document.pointerLockElement !== canvas && canvas.requestPointerLock) {
            canvas.requestPointerLock();
        }
    };

    var mouseMove = function (e) {
        if (document.pointerLockElement === canvas) {
            var movementX = event.movementX || event.webkitMovementX || event.mozMovementX || 0;
            var movementY = event.movementY || event.webkitMovementY || event.mozMovementY || 0;

            app.fire('firstperson:look', -movementX / 5, -movementY / 5);
        }
    };

    // Manage DOM event listeners
    var addEventListeners = function () {
        window.addEventListener('mousedown', mouseDown, false);
        window.addEventListener('mousemove', mouseMove, false);
    };
    var removeEventListeners = function () {
        window.removeEventListener('mousedown', mouseDown, false);
        window.removeEventListener('mousemove', mouseMove, false);
    };

    this.on('enable', addEventListeners);
    this.on('disable', removeEventListeners);
    
    addEventListeners();
};

// Utility function for both touch and gamepad handling of deadzones
// Takes a 2-axis joystick position in the range -1 to 1 and applies
// an upper and lower radial deadzone, remapping values in the legal
// range from 0 to 1.
function applyRadialDeadZone(pos, remappedPos, deadZoneLow, deadZoneHigh) {
    var magnitude = pos.length();
 
    if (magnitude > deadZoneLow) {
        var legalRange = 1 - deadZoneHigh - deadZoneLow;
        var normalizedMag = Math.min(1, (magnitude - deadZoneLow) / legalRange);
        var scale = normalizedMag / magnitude; 
        remappedPos.copy(pos).scale(scale);
    } else {
        remappedPos.set(0, 0);
    }
}

////////////////////////////////////////////////////////////////////////////////
//                 Dual Virtual Stick FPS Touch Controls                      //
////////////////////////////////////////////////////////////////////////////////
var TouchInput = pc.createScript('touchInput');

TouchInput.attributes.add('deadZone', {
    title: 'Dead Zone',
    description: 'Radial thickness of inner dead zone of the virtual joysticks. This dead zone ensures the virtual joysticks report a value of 0 even if a touch deviates a small amount from the initial touch.',
    type: 'number',
    min: 0,
    max: 0.4,
    default: 0.3
});
TouchInput.attributes.add('turnSpeed', {
    title: 'Turn Speed',
    description: 'Maximum turn speed in degrees per second',
    type: 'number',
    default: 150
});
TouchInput.attributes.add('radius', {
    title: 'Radius',
    description: 'The radius of the virtual joystick in CSS pixels.',
    type: 'number',
    default: 50
});
TouchInput.attributes.add('doubleTapInterval', {
    title: 'Double Tap Interval',
    description: 'The time in milliseconds between two taps of the right virtual joystick for a double tap to register. A double tap will trigger a jump.',
    type: 'number',
    default: 300
});

TouchInput.prototype.initialize = function() {
    var app = this.app;
    var graphicsDevice = app.graphicsDevice;
    var canvas = graphicsDevice.canvas;

    this.remappedPos = new pc.Vec2();
    
    this.leftStick = {
        identifier: -1,
        center: new pc.Vec2(),
        pos: new pc.Vec2()
    };
    this.rightStick = {
        identifier: -1,
        center: new pc.Vec2(),
        pos: new pc.Vec2()
    };
    
    this.lastRightTap = 0;

    var touchStart = function (e) {
        e.preventDefault();

        var xFactor = graphicsDevice.width / canvas.clientWidth;
        var yFactor = graphicsDevice.height / canvas.clientHeight;

        var touches = e.changedTouches;
        for (var i = 0; i < touches.length; i++) {
            var touch = touches[i];
            
            if (touch.pageX <= canvas.clientWidth / 2 && this.leftStick.identifier === -1) {
                // If the user touches the left half of the screen, create a left virtual joystick...
                this.leftStick.identifier = touch.identifier;
                this.leftStick.center.set(touch.pageX, touch.pageY);
                this.leftStick.pos.set(0, 0);
                app.fire('leftjoystick:enable', touch.pageX * xFactor, touch.pageY * yFactor);
            } else if (touch.pageX > canvas.clientWidth / 2 && this.rightStick.identifier === -1) {
                // ...otherwise create a right virtual joystick
                this.rightStick.identifier = touch.identifier;
                this.rightStick.center.set(touch.pageX, touch.pageY);
                this.rightStick.pos.set(0, 0);
                app.fire('rightjoystick:enable', touch.pageX * xFactor, touch.pageY * yFactor);
                
                // See how long since the last tap of the right virtual joystick to detect a double tap (jump)
                var now = Date.now();
                if (now - this.lastRightTap < this.doubleTapInterval) {
                    app.fire('firstperson:jump');
                }
                this.lastRightTap = now;
            }
        }
    }.bind(this);

    var touchMove = function (e) {
        e.preventDefault();

        var xFactor = graphicsDevice.width / canvas.clientWidth;
        var yFactor = graphicsDevice.height / canvas.clientHeight;
        
        var touches = e.changedTouches;
        for (var i = 0; i < touches.length; i++) {
            var touch = touches[i];

            // Update the current positions of the two virtual joysticks
            if (touch.identifier === this.leftStick.identifier) {
                this.leftStick.pos.set(touch.pageX, touch.pageY);
                this.leftStick.pos.sub(this.leftStick.center);
                this.leftStick.pos.scale(1 / this.radius);
                app.fire('leftjoystick:move', touch.pageX * xFactor, touch.pageY * yFactor);
            } else if (touch.identifier === this.rightStick.identifier) {
                this.rightStick.pos.set(touch.pageX, touch.pageY);
                this.rightStick.pos.sub(this.rightStick.center);
                this.rightStick.pos.scale(1 / this.radius);
                app.fire('rightjoystick:move', touch.pageX * xFactor, touch.pageY * yFactor);
            }
        }
    }.bind(this);

    var touchEnd = function (e) {
        e.preventDefault();

        var touches = e.changedTouches;
        for (var i = 0; i < touches.length; i++) {
            var touch = touches[i];

            // If this touch is one of the sticks, get rid of it...
            if (touch.identifier === this.leftStick.identifier) {
                this.leftStick.identifier = -1;
                app.fire('firstperson:forward', 0);
                app.fire('firstperson:strafe', 0);
                app.fire('leftjoystick:disable');
            } else if (touch.identifier === this.rightStick.identifier) {
                this.rightStick.identifier = -1;
                app.fire('rightjoystick:disable');
            }
        }
    }.bind(this);

    // Manage DOM event listeners
    var addEventListeners = function () {
        canvas.addEventListener('touchstart', touchStart, false);
        canvas.addEventListener('touchmove', touchMove, false);
        canvas.addEventListener('touchend', touchEnd, false);
    };
    var removeEventListeners = function () {
        canvas.removeEventListener('touchstart', touchStart, false);
        canvas.removeEventListener('touchmove', touchMove, false);
        canvas.removeEventListener('touchend', touchEnd, false);
    };

    this.on('enable', addEventListeners);
    this.on('disable', removeEventListeners);
    
    addEventListeners();
};

TouchInput.prototype.update = function(dt) {
    var app = this.app;
    
    // Moving
    if (this.leftStick.identifier !== -1) {
        // Apply a lower radial dead zone. We don't need an upper zone like with a real joypad
        applyRadialDeadZone(this.leftStick.pos, this.remappedPos, this.deadZone, 0);

        var strafe = this.remappedPos.x;
        if (this.lastStrafe !== strafe) {
            app.fire('firstperson:strafe', strafe);
            this.lastStrafe = strafe;
        }
        var forward = -this.remappedPos.y;
        if (this.lastForward !== forward) {
            app.fire('firstperson:forward', forward);
            this.lastForward = forward;
        }
    }

    // Looking
    if (this.rightStick.identifier !== -1) {
        // Apply a lower radial dead zone. We don't need an upper zone like with a real joypad
        applyRadialDeadZone(this.rightStick.pos, this.remappedPos, this.deadZone, 0);

        var lookLeftRight = -this.remappedPos.x * this.turnSpeed * dt;
        var lookUpDown = -this.remappedPos.y * this.turnSpeed * dt;
        app.fire('firstperson:look', lookLeftRight, lookUpDown);
    }
};


////////////////////////////////////////////////////////////////////////////////
//                 Dual Analog Stick FPS Gamepad Controls                     //
////////////////////////////////////////////////////////////////////////////////
var GamePadInput = pc.createScript('gamePadInput');

GamePadInput.attributes.add('deadZoneLow', { 
    title: 'Low Dead Zone',
    description: 'Radial thickness of inner dead zone of pad\'s joysticks. This dead zone ensures that all pads report a value of 0 for each joystick axis when untouched.',
    type: 'number',
    min: 0,
    max: 0.4,
    default: 0.1
});
GamePadInput.attributes.add('deadZoneHigh', {
    title: 'High Dead Zone',
    description: 'Radial thickness of outer dead zone of pad\'s joysticks. This dead zone ensures that all pads can reach the -1 and 1 limits of each joystick axis.',
    type: 'number',
    min: 0,
    max: 0.4,
    default: 0.1
});
GamePadInput.attributes.add('turnSpeed', {
    title: 'Turn Speed',
    description: 'Maximum turn speed in degrees per second',
    type: 'number',
    default: 90
});

GamePadInput.prototype.initialize = function() {
    var app = this.app;

    this.lastStrafe = 0;
    this.lastForward = 0;
    this.lastJump = false;

    this.remappedPos = new pc.Vec2();
    
    this.leftStick = {
        center: new pc.Vec2(),
        pos: new pc.Vec2()
    };
    this.rightStick = {
        center: new pc.Vec2(),
        pos: new pc.Vec2()
    };

    // Manage DOM event listeners
    var addEventListeners = function () {
        window.addEventListener("gamepadconnected", function(e) {});
        window.addEventListener("gamepaddisconnected", function(e) {});
    };
    var removeEventListeners = function () {
        window.removeEventListener("gamepadconnected", function(e) {});
        window.removeEventListener("gamepaddisconnected", function(e) {});
    };

    this.on('enable', addEventListeners);
    this.on('disable', removeEventListeners);
    
    addEventListeners();
};

GamePadInput.prototype.update = function(dt) {
    var app = this.app;

    var gamepads = navigator.getGamepads ? navigator.getGamepads() : [];

    for (var i = 0; i < gamepads.length; i++) {
        var gamepad = gamepads[i];

        // Only proceed if we have at least 2 sticks
        if (gamepad && gamepad.mapping === 'standard' && gamepad.axes.length >= 4) {
            // Moving (left stick)
            this.leftStick.pos.set(gamepad.axes[0], gamepad.axes[1]);
            applyRadialDeadZone(this.leftStick.pos, this.remappedPos, this.deadZoneLow, this.deadZoneHigh);

            var strafe = this.remappedPos.x;
            if (this.lastStrafe !== strafe) {
                app.fire('firstperson:strafe', strafe);
                this.lastStrafe = strafe;
            }
            var forward = -this.remappedPos.y;
            if (this.lastForward !== forward) {
                app.fire('firstperson:forward', forward);
                this.lastForward = forward;
            }

            // Looking (right stick)
            this.rightStick.pos.set(gamepad.axes[2], gamepad.axes[3]);
            applyRadialDeadZone(this.rightStick.pos, this.remappedPos, this.deadZoneLow, this.deadZoneHigh);

            var lookLeftRight = -this.remappedPos.x * this.turnSpeed * dt;
            var lookUpDown = -this.remappedPos.y * this.turnSpeed * dt;
            app.fire('firstperson:look', lookLeftRight, lookUpDown);

            // Jumping (bottom button of right cluster)
            if (gamepad.buttons[0].pressed && !this.lastJump) {
                app.fire('firstperson:jump');
            }
            this.lastJump = gamepad.buttons[0].pressed;
        }
    }
};

ps i have no idea how this works

Hello @wolfshadow726! Did you already follow the PlayCanvas tutorial? This tutorial has also a kind of jumping by keeping the ball up and you will learn the first things that you need to create your game. If you already have a project with the character movement and envorniment, I would like to see it so that I can help you better.

Ok, I have the link here. https://playcanvas.com/project/721032/overview/reclaw

i normally jump by checking if the player is at a certain height, say,

if(this.entity.getPosition.y < //put your max height here){
this.entity.rigidbody.applyImpulse(0,10,0);
}

does that help???

I’m not sure because I don’t know where to add it in my code

Also sorry for taking so long my computer had broke and I’m still fixing it but I can still add code

With this solution you have to be careful if you have for example a level with different floors.

put the code in the update function.

I have changed it a little bit

if(this.entity.getPosition.y < 2){
this.entity.rigidbody.applyImpulse(0,40,0);
}

you can put it anywhere in the update function

i hope that helped

Little correction in your if statement:

if (this.entity.getPosition().y < 2) {
    this.entity.rigidbody.applyImpulse(0,40,0);
}