Attempting to add jumping to the FPS starter kit, but can't seem to get it working

Hi everyone!

So this is pretty much as the title states. I’ve been trying to use the documentation for FPS Character controller to implement jumping support in the FPS starter kit available for forking.

I feel like I might need to modify the _CheckGround function to work with the FPS starter kit? If I wrap if (this.onGround) { around the move function, the movement keys stop working, which is what makes think that _CheckGround might not be working as intended. I have no idea with how to modify the function to get it to work with the demo though or if it’s even the culprit.

Sorry if anything I say doesn’t make sense/is wrong, just trying to become accustomed! Thanks for any help in advanced :smile:.

Here are the modifications I’ve done to fps_character_controller.js and player_input.js:


pc.script.create('fps_character_controller', function (context) {
    var MOVE_SPEED = 10;
    var JUMP_IMPULSE = 400;
    var origin = new pc.Vec3();
    var groundCheckRay = new pc.Vec3(0, -0.51, 0);
    var rayEnd = new pc.Vec3();
    var FpsCharacterController = function (entity) {
        this.entity = entity;
        this.velocity = new pc.Vec3();
        this.jumpImpulse = new pc.Vec3(0, JUMP_IMPULSE, 0);
        this.onGround = true;
        this.initialPosition = new pc.Vec3();
    FpsCharacterController.prototype = {
        initialize: function () {
            //pc.math.vec3.copy(this.entity.getPosition(), this.initialPosition);
            // TODO: this API needs to change
            this.damagable = this.entity.script.instances['damagable'].instance;
            this.damagable.on("killed", this.onKilled, this);
        update: function (dt) {
            this.entity.rigidbody.linearVelocity = pc.Vec3.ZERO;
        move: function (dir) {
                //pc.math.vec3.scale(dir, MOVE_SPEED, this.velocity);
                this.entity.rigidbody.linearVelocity = this.velocity;
        jump: function () {
            if (this.onGround) {
                this.entity.rigidbody.applyImpulse(this.jumpImpulse, origin);
                this.onGround = false;                
        onKilled: function (killer) {
        reset: function () {
        _checkGround: function () {
            var self = this;
            var pos = this.entity.getPosition();
            rayEnd.add2(pos, groundCheckRay);            
            self.onGround = false;

            // Fire a ray straight down to just below the bottom of the rigid body, 
            // if it hits something then the character is standing on something.
  , rayEnd, function (result) {
                self.onGround = true;

   return FpsCharacterController;


pc.script.create('player_input', function (context) {
    var PlayerInput = function (entity) {
        this.entity = entity;
        this.heading = new pc.Vec3();
        // Euler angles used to do camera look
        this.ex = 0;
        this.ey = 0;
        this.ez = 0;
    PlayerInput.LEFT = 'left';
    PlayerInput.RIGHT = 'right';
    PlayerInput.FORWARD = 'forward';
    PlayerInput.BACK = 'back';
    PlayerInput.JUMP = 'jump';
    PlayerInput.prototype = {
        initialize: function () {
            context.controller = new pc.input.Controller(document);
            context.controller.registerKeys(PlayerInput.LEFT, [pc.input.KEY_A, pc.input.KEY_Q, pc.input.KEY_LEFT])
            context.controller.registerKeys(PlayerInput.RIGHT, [pc.input.KEY_D, pc.input.KEY_RIGHT])
            context.controller.registerKeys(PlayerInput.FORWARD, [pc.input.KEY_W, pc.input.KEY_Z, pc.input.KEY_UP])
            context.controller.registerKeys(PlayerInput.BACK, [pc.input.KEY_S, pc.input.KEY_DOWN])
            context.controller.registerKeys(PlayerInput.JUMP, [pc.input.KEY_SPACE, pc.input.KEY_J])
            // Disabling the context menu stops the browser displaying a menu when 
            // you right-click the page
            // Catch mousemove and mousedown events
            context.mouse.on(pc.input.EVENT_MOUSEMOVE, this.onMouseMove, this);
            context.mouse.on(pc.input.EVENT_MOUSEDOWN, this.onMouseDown, this);

            // This API needs to change!
            this.character = this.entity.script.fps_character_controller;
            this.heading = new pc.Vec3();
            this.right = new pc.Vec3();
            this.direction = new pc.Vec3();
   = this.entity.findByName("Camera");
        update: function (dt) {
  , this.ey, 0);
            //pc.math.vec3.set(this.direction, 0, 0, 0);
            if (context.controller.isPressed(PlayerInput.LEFT)) {
                this.direction = this.getLeft();
            } else if (context.controller.isPressed(PlayerInput.RIGHT)) {
                this.direction = this.getRight();
            if (context.controller.isPressed(PlayerInput.FORWARD)) {
                //pc.math.vec3.add(this.direction, this.getHeading(), this.direction);
            } else if (context.controller.isPressed(PlayerInput.BACK)) {
                //pc.math.vec3.add(this.direction, this.getReverseHeading(), this.direction);
            if (context.controller.isPressed(PlayerInput.JUMP)) {
            if (this.direction.length()) {
                //pc.math.vec3.normalize(this.direction, this.direction);
        getHeading: function () {
            var f =;
            return this.heading.set(f.x, 0, f.z).normalize();
//            pc.math.vec3.copy(, this.heading);
//            this.heading[1] = 0;
//            return pc.math.vec3.normalize(this.heading, this.heading);            
        getReverseHeading: function () {
            return this.getHeading().scale(-1);
            //return pc.math.vec3.scale(this.heading, -1, this.heading);
        getRight: function () {
            var r =;
            return this.right.set(r.x, 0, r.z).normalize();
//            pc.math.vec3.copy(, this.right);
//            this.right[1] = 0;
//            return pc.math.vec3.normalize(this.right, this.right);
        getLeft: function () {
            return this.getRight().scale(-1);
            //return pc.math.vec3.scale(this.right, -1, this.right);
        onMouseMove: function (event) {
            // Update the current Euler angles, clamp the pitch.
            this.ex -= event.dy / 5;
            this.ex = pc.math.clamp(this.ex, -90, 90);
            this.ey -= event.dx / 5;
        onMouseDown: function (event) {

   return PlayerInput;

I think since the velocity is set to zero every update the apply impulse in jump will immediately be nullified the next update.

You may find answers by forking the tutorials project here :
and open the FPS example scene - it has jumping

I’m in a streaming mood so I might stream debugging this and post a link to the video in a few min!

For me, it ended up being a combination of changing the ray length (because for me it was always saying you’re NOT on the ground!) and removing the velocity from update. I’m not sure why that happened though - possibly because of where the ray is actually casting from?

Okay this might be suuuper painful to watch me rambling but here you go :smile:
(about 10 minutes left for processing at the time of posting this post)


Wow, Crefossus, you went to a lot of trouble to investigate this! You win the PlayCanvas Community Member of the Year award. :smile:

Awesome thanks a lot. This will really help :). I’ll let you know if I manage to get it to work!

@will I agree. A well deserved award.