Virtual Joystick help needed

The current project I’m stuck on consists of a Third-Person controller with animations embedded for movements. I have it successfully working multiplayer wise on another fork however I’ve came to a standstill once I’ve added virtual joysticks for mobile players.

Link to fork project here

Note: This virtual joystick is only visible from mobile browsers.

There are 2 main scripts that correlate with each other for it to work that being (PlayerMovement.js & virtualJoystick.js). The x, z values of Joystick needed to change the character position are from virtualJoystick.js yet I’ve created an event to send those values to PlayerMovement.js to actually make the change.

The problem is that the character is not moving in the correct direction as well as the proper animations needed are not playing. I also have a PlayerAnimationHandler.js script which applies the proper animations based on the direction the player is going but again this only seems to be working on Desktop and I would like for it to work on mobile browsers as well with the virtual joystick that I have embedded. The left stick is the root of the problem, the right stick simply controls the camera rotation.

PlayerMovement.js - Bottom of Script

  // Desktop Movement of Player
    if (x !== 0 || z !== 0) {
        var pos = new pc.Vec3(x * dt, 0, z * dt);
        pos.normalize().scale(this.speed);
        pos.add(this.entity.getPosition());

        var targetY = this.cameraScript.eulers.x + 180;
        var rot = new pc.Vec3(0, targetY, 0);

        this.entity.rigidbody.teleport(pos, rot);
    }
    
    // Touch JoyStick Movement of Player Event
    var onPlayerTouchMove = function (x, z) { // z is really y
        var touchSpeed = 0.0001;

        // Left Joystick movement
        if (x !== 0 || z !== 0) {
            var pos = new pc.Vec3(-x * dt, 0, -z * dt);
            pos.normalize().scale(touchSpeed);
            pos.add(this.entity.getPosition());

            var targetY = this.cameraScript.eulers.x + 180;
            var rot = new pc.Vec3(0, targetY, 0);
            
            this.entity.rigidbody.teleport(pos, rot);
        }
    };
    
    // listen for the player touch move event
    this.entity.script.touch.on('touchPlayerMove', onPlayerTouchMove);
};

I attempted to fork many similar projects that have a virtual joystick yet I’m having trouble figuring out how to execute this, I’ve been working at it for a week now and it has pressed me to move to another engine. I wouldn’t like to only because it’s a small issue but implementing it seems so much of a challenge.

I’m asking for proper guidance or anyone who can simply implement the fix. I’m usually one to do it on my own however there is a forum for a reason so why not utilize it? Thanks ahead of time, it’s much appreciated!

Hi @khaelou and welcome,

You need to use the forward vector of your player entity as the base direction for applying the joystick input for movement. That way movement will always face the direction your player is facing.

Here is how I’ve modified your code for that, also I’ve moved the input handler in your initialize method, it’s considered a bad practice for many reasons to bind an event handler on each and every frame:

var PlayerMovement = pc.createScript('playerMovement');

PlayerMovement.attributes.add('speed', { type: 'number', default: 0.085 });

PlayerMovement.prototype.initialize = function () {
    var app = this.app;
    var camera = app.root.findByName('Camera');
    this.cameraScript = camera.script.cameraMovement;
        
    var terrain = this.app.root.findByName('Terrain');
    this.groundCheckRay = new pc.Vec3(0, -1, 0);
    this.rayEnd = new pc.Vec3();
    this.groundNormal = new pc.Vec3();
    this.onGround = true;
    this.jumping = false;
    
    this.touchInput = new pc.Vec3();
    
    // Touch JoyStick Movement of Player Event
    var onPlayerTouchMove = function (x, z) { // z is really y        
        this.touchInput.set(x,0,z);        
    }.bind(this);
    
    // listen for the player touch move event
    this.entity.script.touch.on('touchPlayerMove', onPlayerTouchMove);    
};

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

    var forward = this.entity.forward;
    var right = this.entity.right;

    var x = 0;
    var z = 0;
            
    var posForJump = this.entity.getPosition();
    this.rayEnd.add2(posForJump, this.groundCheckRay);

    // 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.
    var result = this.app.systems.rigidbody.raycastFirst(posForJump, this.rayEnd);
    this.onGround = !!result;
    if (result) {
        this.groundNormal.copy(result.normal);
    }

    if (app.keyboard.isPressed(pc.KEY_A)) {
        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;
    }
    
    if (app.keyboard.isPressed(pc.KEY_SPACE)) {
         if (this.onGround && !this.jumping && this.entity.children[0].script.playerAnimationHandler.direction == 'Jump') {            
            setTimeout(function () {
                this.entity.rigidbody.applyImpulse(0, 14, 0);
                this.jumping = false;
            }.bind(this), 300);
        }
    }
    
    if (app.keyboard.isPressed(pc.KEY_SHIFT)) {
        // Sprint/Boost
        this.speed = 0.175;
    } else {
        this.speed = 0.085;
    }
    
    if (app.keyboard.isPressed(pc.KEY_TAB)) {
        
    }
    
    if (app.keyboard.isPressed(pc.KEY_OPEN_BRACKET)) {
        //this.entity.parent.findByName('Camera Axis').children[0].camera.fov = 1;
    }
    
    if (app.keyboard.isPressed(pc.KEY_CLOSE_BRACKET)) {
        this.entity.parent.findByName('Camera Axis').children[0].camera.fov = 45;
    }
    
    if (app.keyboard.isPressed(pc.KEY_UP)) {
        // camera move up
        this.cameraScript.eulers.y -= 1.5;
    }

    if (app.keyboard.isPressed(pc.KEY_DOWN)) {
       // camera move down
       this.cameraScript.eulers.y += 1.5;
    }

    if (app.keyboard.isPressed(pc.KEY_LEFT)) {
       // camera move left
       this.cameraScript.eulers.x -= 1.5;
    }

    if (app.keyboard.isPressed(pc.KEY_RIGHT)) {
       // camera move right
       this.cameraScript.eulers.x += 1.5;
    }
    
    // Desktop Movement of Player
    if (x !== 0 || z !== 0) {
        var pos = new pc.Vec3(x * dt, 0, z * dt);
        pos.normalize().scale(this.speed);
        pos.add(this.entity.getPosition());

        var targetY = this.cameraScript.eulers.x + 180;
        var rot = new pc.Vec3(0, targetY, 0);

        this.entity.rigidbody.teleport(pos, rot);
    }
    
    // Left Joystick movement
    if (this.touchInput.x !== 0 || this.touchInput.z !== 0) {
        var touchSpeed = 0.1;
        
        var pos = new pc.Vec3().copy(this.entity.forward);
        pos.x += -this.touchInput.x * dt;
        pos.z += -this.touchInput.z * dt;
        pos.scale(touchSpeed);
        pos.add(this.entity.getPosition());

        var targetY = this.cameraScript.eulers.x + 180;
        var rot = new pc.Vec3(0, targetY, 0);

        this.entity.rigidbody.teleport(pos, rot);
    }    
    
    this.touchInput.copy(pc.Vec3.ZERO);
};

For the animations to work you will have to add the joystick input logic to the PlayerAnimationHandler.js script.

2 Likes

Thanks so much for this, I’ll consider this when implementing new ideas in the game. I’m going to try reimplementing this with the fix you referred. Thanks as your explanation cleared some air I was having.
I will update if this works as expected!

1 Like

@Leonidas the code you provided is indeed better however the player now can only move forward with the left joystick which is not expected behavior. I know this has something to do with the forward vector but again this is hard to understand. I have vector code changes when either WASD is pressed, I can’t figure out how to make this correlation with the joystick. Implementing the animation I think wouldn’t be a huge issue, it’s just getting the player to move towards the direction of their joystick movement that is. How would I improve the code you provided to make the player move in the other directions and not just forward. May you test the project on mobile here to replicate the new problem?

Upon inspection, I see that due to the code only utilizing the forward vector and not the right vector as well may also be the problem.

How would I properly map the touch input direction to the two vectors like the code below:

    if (app.keyboard.isPressed(pc.KEY_A)) {
        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;
    }

It’s even now more frustrating because the player is only moving in a single direction which is not how a joystick should work. The player should move in the direction of the joystick movement and not just forward. If the player moves the stick up then he should go up, same for down and left and right.

@yaustar May you further assists this issue?

Give some time to forum members to take a look at your issue before posting again :wink:

1 Like

I gotcha, just really want to solve this.

Here is one way to solve this:

    // Left Joystick movement
    if (this.touchInput.x !== 0 || this.touchInput.z !== 0) {
        var touchSpeed = 5;

        var posTouch = this.entity.getPosition().clone();
        posTouch.add(this.entity.forward.scale(-this.touchInput.z * touchSpeed));
        posTouch.add(this.entity.right.scale(this.touchInput.x * touchSpeed));
        
        var targetYTouch = this.cameraScript.eulers.x + 180;
        var rotTouch = new pc.Vec3(0, targetYTouch, 0);

        this.entity.rigidbody.teleport(posTouch, rotTouch);
    }    

For the animations as I’ve said you need to add the joystick input logic to the PlayerAnimationHandler.js file. It can’t work automatically.

I’d suggest doing some vector math tutorials in general, it will help you here.

2 Likes

@Leonidas You’re a life saver, it works as expected! Now on to implementing the animations and learning vector math :sweat_smile:! It’s highly appreciated that you adjusted the speed and scale values as well as those were other minor issues I was facing.

Will consider your method when adding animations for joystick movements! :sunglasses:

1 Like

@Leonidas I’ve been trying to see how to go about adding the touch input to the animation handler properly and I’m stuck yet again. My method has been trying to determine wether the joystick coordinates simulate a keypress. For example if the stick is up and the player is moving forward this should fire an event say WKeyPress so that the handler knows.

    var w = app.keyboard.isPressed(pc.KEY_W);
    var a = app.keyboard.isPressed(pc.KEY_A);
    var s = app.keyboard.isPressed(pc.KEY_S);
    var d = app.keyboard.isPressed(pc.KEY_D);
    var space = app.keyboard.isPressed(pc.KEY_SPACE);
    
    var touchW;
    var touchA;
    var touchS;
    var touchD;

    this.entity.animation.loop = true;

    if (w && !s) {
        if (a && !d) {
            this.direction = 'Run Forward Left';
        } else if (d && !a) {
            this.direction = 'Run Forward Right';
        } else {
            this.direction = 'Run Forward';
        }
    }

This is basically how the animations are determined considering this project is built from a fork of the Third-Person controller example. I need a way to determine a key press based on the directional forward and right vectors but they never stay the same when moving around and rotating the camera.

Is there a way to determine just from the forward/right vectors or input x, z values from Touch input?

if (forward.x <= 0 && forward.z >= 0 && right.x <=0 && right.z >= 0) {
   console.log('W');
}

is how I’m attempting to simulate a keypress.

If you add the following statements into a separate variable then you will have the forward/right movement values:

var forwardMovement = this.entity.forward.scale(-this.touchInput.z * touchSpeed);
var rightMovement = this.entity.right.scale(this.touchInput.x * touchSpeed);

posTouch.add(forwardMovement);
posTouch.add(rightMovement);

You can then pass them to the animation script and use them in a similar manner to the WASD keypresses.

1 Like

@Leonidas I’ve managed to get animations to play based on the vector direction considering your method of passing the movements to the animation script. Now my only problem seems to be that upon rotation of the camera and touch input movement the animation becomes inaccurate. I apologize for my troubles as you’ve helped much already.

Updated fork link here

PlayerAnimationHandler.js - located in PlayerAnimationHandler.prototype.initialize()

// updateMobilePlayerAnim
    var updateMobilePlayerAnim = function (forward, right) {
        //console.log('Forward:', forward);
        //console.log('Right:', right);
        
        var tempDirection = this.direction;
        this.entity.animation.loop = true;
        
        if (forward.x < 0 && forward.z > 0 && right.x >= 0 && right.z >= 0) {
            this.direction = 'Run Forward Left';
        } else if (forward.x < 0 && forward.z > 0 && right.x <= 0 && right.z <= 0) {
            this.direction = 'Run Forward Right';
        } else if (forward.x > 0 && forward.z > 0) {
            this.direction = 'Run Forward';
        } else if (forward.x > 0 && forward.z < 0) {
            this.direction = 'Run Backward';
        } else {
            this.direction = 'Idle';
        }
        
        if (tempDirection !== this.direction) {
            this.setDirection(this.direction);
        }
    }.bind(this);
    
    // listen for the player movement direction to determine animation
    this.entity.parent.script.playerMovement.on('mobilePlayerAnim', updateMobilePlayerAnim);  

Will I need to tie the camera rotation as well in order to maintain the proper animations? I feel like this has do to with the rotation of the player though I have this value in PlayerMovemvent.js file or the vector directions I’m using in the if-else clause is a bit off.

Test via mobile here

Never mind, I found an existing project which already implemented such touch controls including camera without the need of joysticks here. I will implement this into my existing project!

1 Like