[SOLVED] How To Make Player's Movements Relative To Camera

I know there’s already an FPS script that makes the player’s movements relative to the camera’s rotation, but I want to know how it accomplishes that, and how I should integrate it into my script. Here’s my player code:

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

//Variables
var acc = 20;
var frc = acc;
var dec = 4;
var limit = 400; 
var xsp = 0;
var zsp = 0;
var goingRight;
var goingFront;

// initialize code called once per entity
PlayerController.prototype.initialize = function() {
    this.app.keyboard.on(pc.EVENT_KEYDOWN, this.onKeyDown, this);
    this.app.keyboard.on(pc.EVENT_KEYUP, this.onKeyUp, this);
};

// update code called every frame
PlayerController.prototype.update = function(dt) {
    //Functions
    this.MainBase(dt);   
};

PlayerController.prototype.MainBase = function(dt) {
    //Main
    this.entity.rigidbody.linearVelocity = new pc.Vec3(xsp * dt, this.entity.rigidbody.linearVelocity.y, zsp * dt);  
    
    //Basic Movement
    if (this.app.keyboard.isPressed(pc.KEY_D)) {
        xsp += acc;
        goingRight = true;
    }
    if (this.app.keyboard.isPressed(pc.KEY_A)) {
        xsp -= acc;
        goingRight = false;
    }
    if (this.app.keyboard.isPressed(pc.KEY_W)) {
        zsp -= acc;
        goingFront = true;
    }
    if (this.app.keyboard.isPressed(pc.KEY_S)) {
        zsp += acc;
        goingFront = false;
    }
    if (!this.app.keyboard.isPressed(pc.KEY_D) && !this.app.keyboard.isPressed(pc.KEY_A)) {
        goingRight = null;
    }
    if (!this.app.keyboard.isPressed(pc.KEY_W) && !this.app.keyboard.isPressed(pc.KEY_S)) {
        goingFront = null;
    }
    
    //Deceleration / Friciton
    if (goingRight === true && xsp < 0) {
        xsp += dec;
    }
    if (goingRight === false && xsp > 0) {
        xsp -= dec;
    }
    if (goingFront === true && zsp > 0) {
        zsp -= dec;
    }
    if (goingFront === false && zsp < 0) {
        zsp += dec;
    }
    if ((xsp > 0 || xsp < 0) && goingRight === null) {
        if (xsp > 0) {
            xsp -= frc;
        }
        if (xsp < 0) {
            xsp += frc;
        }
        if (xsp < frc && xsp > -frc) {
            xsp = 0;
        }
    }
    if ((zsp > 0 || zsp < 0) && goingFront === null) {
        if (zsp > 0) {
            zsp -= frc;
        }
        if (zsp < 0) {
            zsp += frc;
        }
        if (zsp < frc && zsp > -frc) {
            zsp = 0;
        }
    }
    
    //Limit
    if (xsp > limit || xsp < -limit) {
        if (xsp > limit) {
            xsp = limit;
        }
        if (xsp < -limit) {
            xsp = -limit;
        }
    }
    if (zsp > limit || zsp < -limit) {
        if (zsp > limit) {
            zsp = limit;
        }
        if (zsp < -limit) {
            zsp = -limit;
        }
    }
    
    //Rotations
    if (goingRight === true && goingFront === true) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, -45, this.entity.getEulerAngles().z);
    }
    if (goingRight === false && goingFront === true) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, 45, this.entity.getEulerAngles().z);
    }
    if (goingRight === true && goingFront === false) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, 45, this.entity.getEulerAngles().z);
    }
    if (goingRight === false && goingFront === false) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, -45, this.entity.getEulerAngles().z);
    }
    if (goingRight === true && goingFront === null) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, 90, this.entity.getEulerAngles().z);    
    }
    if (goingRight === false && goingFront === null) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, 90, this.entity.getEulerAngles().z);
    }
    if (goingRight === null && goingFront === true) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, 0, this.entity.getEulerAngles().z);
    }
    if (goingRight === null && goingFront === false) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, 0, this.entity.getEulerAngles().z);
    }
    if (goingRight === null && goingFront === null) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, this.entity.getEulerAngles().y, this.entity.getEulerAngles().z);
    }
};

// swap method called for script hot-reloading
// inherit your script state here
// PlayerController.prototype.swap = function(old) { };

// to learn more about script anatomy, please read:
// http://developer.playcanvas.com/en/user-manual/scripting/

So how would I go about making the movements relative to the rotation of the camera?

Link To Editor: https://playcanvas.com/editor/scene/953748

Hello, anybody understand how to make the player’s movements relative to the camera?

What i understand is you are trying to rotate player as camera rotates? You can do that just by teleporting the player rotation as of the value of camera rotation in the each frame.

No, not really. And even if I did that, the player’s movement will still move on the global axis. What I want is to have the movement be relative to the camera. Meaning, like in an FPS script, the player moves front, which is usually away from the camera. If you were to rotate the camera, let’s say a to the left, the player would still move directly away from the camera if you keep going front. I want to emulate this into my script, but I honestly have no idea how the FPS script manages to have this relative based movement. Could you explain how I would make the movements of the player relative to the way the camera is facing.

I find it hard to understand what you mean. Perhaps, you can give an example from some game that handles the controls the way you want to achieve?

You know the First Person Movement example playcanvas has, right? Well, I want the player to move using the camera’s directions as well.

https://playcanvas.com/editor/project/684713

In this game, when pressing forward, you move away from the camera. When the camera is rotated, the player goes along with it. Similarly, I want the player to go along with the camera in terms of movement.

So do you understand when I’m saying @LeXXik?

Maybe you can use this.entity.rotateLocal(0, 0, 0); and this.entity.translateLocal(0, 0, 0); because that is not on the global axis.

Got it, you can attach a follow script simply on a camera and when player will move, the camera will move relatively. Here is a script code.

var Follow = pc.createScript('follow');

Follow.attributes.add('target', {
    type: 'entity',
    title: 'Target',
    description: 'The Entity to follow'
});

Follow.attributes.add('distance', {
    type: 'number',
    default: 4,
    title: 'Distance',
    description: 'How far from the Entity should the follower be'
});


Follow.attributes.add('CameraXPosition', {
    type: 'number',
    default: 0.2,
    title: 'Camera X Position',
    description: 'X position of the camera from target'
});


Follow.attributes.add('CameraYPosition', {
    type: 'number',
    default: 2,
    title: 'Camera Y Position',
    description: 'Y position of the camera from target'
});

Follow.attributes.add('CameraZPosition', {
    type: 'number',
    default: 5,
    title: 'Camera Z Position',
    description: 'Z position of the camera from target'
});

Follow.attributes.add('CameraMoveSpeed', {
    type: 'number',
    default: 0.2,
    title: 'Camera Move Speed',
    description: 'Speed of camera when it moves'
});


Follow.prototype.setPlayerFillMode = function() {
    
    if(pc.platform.mobile)
        {
            this.app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
            this.app.setCanvasResolution(pc.RESOLUTION_AUTO);
             // this.app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW,1024,790);
        }
};
// initialize code called once per entity
Follow.prototype.initialize = function() {
    this.setPlayerFillMode();
    //this.vec = new pc.Vec3();
    this.vec=new pc.Vec3(0,0,0);
};

// update code called every frame
Follow.prototype.update = function(dt) {
    if (!this.target) return;

    // get the position of the target entity
    var pos = this.target.getPosition();

    // calculate the desired position for this entity
    pos.x += this.CameraXPosition * this.distance;
    pos.y += this.CameraYPosition * this.distance;
    pos.z += this.CameraZPosition * this.distance;
    

    // smoothly interpolate towards the target position
    this.vec.lerp(this.vec, pos, 1);

    // set the position for this entity
    this.entity.setPosition(this.vec); 

      this.entity.lookAt(this.target.getPosition());

};

Also provide reference of player to this script on target entity and it will work fine.

get forward & right from camera entity, zero y both vectors, normalize. You have camera relative directions at XZ plane.

3 Likes

No, no I think you misunderstood. Also thanks for that script, I’ll save it for later, but what I meant is what @KpoKec mentioned. I’ll try out that script in the future though. Sorry for troubling any of you guys. I just need help with finishing the camera movement because it’s essential to the game I’m working on.

1 Like

    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);
    }

By any chance, can I replace this.power with the speed variables I use right now? Also, would it be ok for me to use linearVelocity directly instead of applying Forces?

I’ve got one question. I have updated the script with some code from the First Person script. When I move around, I instantly stop once I stop holding any keys without slowing down. Here’s my code, is this supposed to happen, and what can I do for it to constantly update the linearVelocity force so I can recreate the friction system:

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

//Variables
var acc = 20;
var frc = acc;
var dec = 4;
var limit = 400; 
var xsp = 0;
var zsp = 0;
var gsp = 0;
var goingRight;
var goingFront;
var jmp = 7;
var grounded = false;
var jumpCount = 0;

//Attributes
PlayerController.attributes.add("camera", { type: 'entity', title: 'Camera' });

// initialize code called once per entity
PlayerController.prototype.initialize = function() {
    this.force = new pc.Vec3();
    this.main = new pc.Vec3();
    this.app.keyboard.on(pc.EVENT_KEYDOWN, this.onKeyDown, this);
    this.app.keyboard.on(pc.EVENT_KEYUP, this.onKeyUp, this);
    this.entity.collision.on('collisionstart', this.onCollisionStart, this);
    this.entity.collision.on('collisionend', this.onCollisionEnd, this);
    this.entity.collision.on('contact', this.onContact, this);
};

// update code called every frame
PlayerController.prototype.update = function(dt) {
    //Functions
    this.MainBase(dt);   
};

PlayerController.prototype.MainBase = function(dt) {
    //Main
    this.entity.rigidbody.linearVelocity = new pc.Vec3(xsp * dt, this.entity.rigidbody.linearVelocity.y, zsp * dt);  
    
    //Basic Movement
    /*if (this.app.keyboard.isPressed(pc.KEY_D)) {
        xsp += acc;
        goingRight = true;
    }
    if (this.app.keyboard.isPressed(pc.KEY_A)) {
        xsp -= acc;
        goingRight = false;
    }
    if (this.app.keyboard.isPressed(pc.KEY_W)) {
        zsp -= acc;
        goingFront = true;
    }
    if (this.app.keyboard.isPressed(pc.KEY_S)) {
        zsp += acc;
        goingFront = false;
    }
    if (!this.app.keyboard.isPressed(pc.KEY_D) && !this.app.keyboard.isPressed(pc.KEY_A)) {
        goingRight = null;
    }
    if (!this.app.keyboard.isPressed(pc.KEY_W) && !this.app.keyboard.isPressed(pc.KEY_S)) {
        goingFront = null;
    }*/
    
    //Deceleration / Friciton
    if (goingRight === true && xsp < 0) {
        xsp += dec;
    }
    if (goingRight === false && xsp > 0) {
        xsp -= dec;
    }
    if (goingFront === true && zsp > 0) {
        zsp -= dec;
    }
    if (goingFront === false && zsp < 0) {
        zsp += dec;
    }
    if ((xsp > 0 || xsp < 0) && goingRight === null) {
        if (xsp > 0) {
            xsp -= frc;
        }
        if (xsp < 0) {
            xsp += frc;
        }
        if (xsp < frc && xsp > -frc) {
            xsp = 0;
        }
    }
    if ((zsp > 0 || zsp < 0) && goingFront === null) {
        if (zsp > 0) {
            zsp -= frc;
        }
        if (zsp < 0) {
            zsp += frc;
        }
        if (zsp < frc && zsp > -frc) {
            zsp = 0;
        }
    }
    
    //Limit
    if (xsp > limit || xsp < -limit) {
        if (xsp > limit) {
            xsp = limit;
        }
        if (xsp < -limit) {
            xsp = -limit;
        }
    }
    if (zsp > limit || zsp < -limit) {
        if (zsp > limit) {
            zsp = limit;
        }
        if (zsp < -limit) {
            zsp = -limit;
        }
    }
    
    //Rotations
    if (goingRight === true && goingFront === true) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, -45, this.entity.getEulerAngles().z);
    }
    if (goingRight === false && goingFront === true) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, 45, this.entity.getEulerAngles().z);
    }
    if (goingRight === true && goingFront === false) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, 45, this.entity.getEulerAngles().z);
    }
    if (goingRight === false && goingFront === false) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, -45, this.entity.getEulerAngles().z);
    }
    if (goingRight === true && goingFront === null) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, 90, this.entity.getEulerAngles().z);    
    }
    if (goingRight === false && goingFront === null) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, 90, this.entity.getEulerAngles().z);
    }
    if (goingRight === null && goingFront === true) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, 0, this.entity.getEulerAngles().z);
    }
    if (goingRight === null && goingFront === false) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, 0, this.entity.getEulerAngles().z);
    }
    if (goingRight === null && goingFront === null) {
        this.entity.rigidbody.teleport(this.entity.getPosition().x, this.entity.getPosition().y, this.entity.getPosition().z, this.entity.getEulerAngles().x, this.entity.getEulerAngles().y, this.entity.getEulerAngles().z);
    }
    
    //Jump
    if (this.app.keyboard.wasPressed(pc.KEY_SPACE) && jumpCount < 2) {
        this.entity.rigidbody.applyImpulse(0, jmp, 0);
        grounded = false;
    }
    if (this.app.keyboard.wasPressed(pc.KEY_SPACE)) {
        jumpCount += 1;
    }
    
    //Local Variables
    var force = this.force;

    //Camera's Right and Forward
    var forward = this.camera.forward;
    var right = this.camera.right;

    //Get XZ Info
    var x = 0;
    var z = 0;

    //Check Camera For Relativity
    if (this.app.keyboard.isPressed(pc.KEY_A)) {
        x -= right.x;
        z -= right.z;
    }

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

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

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

    gsp = 100;

    //Apply Movement
    force.set(x, 0, z).normalize().scale(gsp * dt);
    this.entity.rigidbody.linearVelocity = this.main.set(force.x, this.entity.rigidbody.linearVelocity.y, force.z);
};

PlayerController.prototype.onCollisionStart = function(result) {
    //Check Ground
    if (result.other.tags.has('ground')) {
        grounded = true;
        jumpCount = 0;
    }  
    
    //Reverse Gravity
    if (result.other.tags.has('rev_grv')) {
        this.app.systems.rigidbody.gravity.set(-this.app.systems.rigidbody.gravity.x, -this.app.systems.rigidbody.gravity.y, -this.app.systems.rigidbody.gravity.z);
        jmp = -jmp;
    }
};

PlayerController.prototype.onContact = function(result) {

};

// swap method called for script hot-reloading
// inherit your script state here
// PlayerController.prototype.swap = function(old) { };

// to learn more about script anatomy, please read:
// http://developer.playcanvas.com/en/user-manual/scripting/

Yes it is supposed to happen because you are just teleporting on the exact place on the key movement.
You can try lerp function on velocity that when any key is up/is-released then velocity lerps towards 0.

1 Like

This might take a while Dev

Well using lerp doesnt work at all. I replaced scale with lerp, but nothing happens. Am I supposed to replace normalize with lerp?

apply lerp on the linear velocity rather than scale

 var forward = ////
forward.y = 0;
forward.normalize();
var right = ///
right.y = 0;
right.normalize();

var dir = new pc.Vec3().add2( right.scale(x_input), forward.scale(y_input));

Simple, both directional, no additional computing. You must set only input ))
If You need normalize diagonal speed , use something like this:

var input = new pc.Vec2(someInput.GetX(), someInput.GetY());
if (input.lengthSq() > 1.0) input.normalize();
var x_input = input.x;
var y_input = input.y;
1 Like

Alright, so I decided to use a mixture of my own code with the First Person Movement code and I solved this problem. The movement isn’t exactly the same as before, but it still works fine. So, for now, I’m marking this as solved.

New Movement Script:

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

//Variables
var acc = 20;
var dec = 10;
var limit = 400; 
var gsp = 0;
var goingRight;
var goingFront;
var jmp = 7;
var grounded = false;
var jumpCount = 0;
var isAdding;

//Attributes
PlayerController.attributes.add("manualCamera", { type: 'entity', title: 'Manual Camera' });
PlayerController.attributes.add("camera", { type: 'entity', title: 'Camera' });

// initialize code called once per entity
PlayerController.prototype.initialize = function() {
    this.force = new pc.Vec3();
    this.main = new pc.Vec3(); 
    this.app.keyboard.on(pc.EVENT_KEYDOWN, this.onKeyDown, this);
    this.app.keyboard.on(pc.EVENT_KEYUP, this.onKeyUp, this);
    this.entity.collision.on('collisionstart', this.onCollisionStart, this);
    this.entity.collision.on('collisionend', this.onCollisionEnd, this);
    this.entity.collision.on('contact', this.onContact, this);
};

// update code called every frame
PlayerController.prototype.update = function(dt) {
    //Functions
    this.MainBase(dt);   
};

PlayerController.prototype.MainBase = function(dt) {    
    //Local Variables
    var force = this.force;

    //Camera's Right and Forward
    var forward = this.camera.forward;
    var right = this.camera.right;

    //Get XZ Info
    var x = 0;
    var z = 0;
    
    //Jump
    if (this.app.keyboard.wasPressed(pc.KEY_SPACE) && jumpCount < 2) {
        this.entity.rigidbody.applyImpulse(0, jmp, 0);
        grounded = false;
    }
    if (this.app.keyboard.wasPressed(pc.KEY_SPACE)) {
        jumpCount += 1;
    }

    //Check Camera For Relativity
    if (this.app.keyboard.isPressed(pc.KEY_A)) {
        x -= right.x;
        z -= right.z;
    }
    if (this.app.keyboard.isPressed(pc.KEY_D)) {
        x += right.x;
        z += right.z;
    }
    if (this.app.keyboard.isPressed(pc.KEY_W)) {
        x += forward.x;
        z += forward.z;
    }
    if (this.app.keyboard.isPressed(pc.KEY_S)) {
        x -= forward.x;
        z -= forward.z;
    }
    
    //Acceleration
    if (this.app.keyboard.isPressed(pc.KEY_D) || this.app.keyboard.isPressed(pc.KEY_S) || this.app.keyboard.isPressed(pc.KEY_A) || this.app.keyboard.isPressed(pc.KEY_W)) {
        gsp += acc;
    }
    
    //Deceleration
    if (isAdding === true && gsp < 0) {
        gsp += dec;
    }
    if (isAdding === false && gsp > 0) {
        gsp -= dec;
    }
    
    //Limit
    if (gsp > limit || gsp < -limit) {
        if (gsp > limit) {
            gsp = limit;
        }
        if (gsp < -limit) {
            gsp = -limit;
        }
    }
    
    //Main 
    force.set(x, 0, z).normalize().scale(gsp * dt);
    this.entity.rigidbody.linearVelocity = this.main.set(force.x, this.entity.rigidbody.linearVelocity.y, force.z);
};

PlayerController.prototype.onCollisionStart = function(result) {
    //Check Ground
    if (result.other.tags.has('ground')) {
        grounded = true;
        jumpCount = 0;
    }  
    
    //Reverse Gravity
    if (result.other.tags.has('rev_grv')) {
        this.app.systems.rigidbody.gravity.set(-this.app.systems.rigidbody.gravity.x, -this.app.systems.rigidbody.gravity.y, -this.app.systems.rigidbody.gravity.z);
        jmp = -jmp;
    }
};

PlayerController.prototype.onContact = function(result) {

};

// swap method called for script hot-reloading
// inherit your script state here
// PlayerController.prototype.swap = function(old) { };

// to learn more about script anatomy, please read:
// http://developer.playcanvas.com/en/user-manual/scripting/