Third person controller with keyboard + joystick (No mouse)

For days I’ve been trying to create already a third person player controller for react-three-fiber and @react-three/rapier such as in experiences like https://summer-afternoon.vlucendo.com and https://coastalworld.com/ Both of them seem to have the same implementation which works with keyboard and a joystick, but I cannot seem to figure out how this is clearly implemented.

Would anyone have an idea on how these actually work, and how can I create one?

I’ve tried two methods, but every time I failed, despite trying each in different ways.

Things I’ve tried so far:

1.- Creating a pivot for the camera
2.- Rotating the camera AND the player

My observations:
1.- Camera follows (?) the player
2.- Camera rotates on Y axis when player goes left or right
3.- Rigidbody handles its own rotation

I might be confused, but maybe the camera is the controller and the player just aesthetics?

I’d be extremely grateful for someone to explain me how these third person controllers work, and it would be even of greater help to see some example.

1 Like

I’m sorry to hear this is such an issue… you can use regular old orbit controls and just apply them to your character…slap WSAD controls onto a finite state machine and your done…
Recommend watching videos gone by SimonDev on YouTube for more info

Hey! Thank you for the answer.

I am afraid to say that it’s a bit more complicated than that. The user, when goes to the right or left, he goes in a circle, it seems. That’s the tricky part for me. I simply cannot understand how that is done, but I assume it has a relationship with the camera. I believe there are a lot of things going on in the code, but that’s what I’m trying to figure out.

As for the OrbitControls, I thought that they were mostly used to control the camera, and the documentation from three.js says The camera to be controlled. The camera must not be a child of another object, unless that object is the scene itself.

Yea…your right about that…I don’t know how mine works…but I’m working on mine right now, so when I get a working example I will send you the most basic form to create what you need

That would be heavily appreciated! You did manage to get the character going in a circle? :dizzy_face:

Hi I hope this helps, try Threeverse projects, it supports, first person, third person and orbit camera that follows around the player’s avatar, or try directly Threeverse demo.

Hello! Thank you for your answer :smile:

I checked your project! It’s interesting, and thank you for sharing!

Unfortunately, my project does not seek two joysticks :frowning:

Still, I might keep it in mind for another project :pray:t2:

1 Like

Threeverse also uses keyboard, u can just remove the joystick if u wish, u can modify the keyboard controls at assets/js/touchcontrols.js

Yup. Still, what mostly concerns me is how the player goes in a circle when going right or left in the projects i quoted. I dont know how can i implement that

See if this code helps;

if(!playerControlsParameters.keyboard){
  		playerControlsParameters.keyboard={shift:false,walk:0,stir:0,yaw:0,elev:0}
  		function desktopKeyDown(dK){
  			if(dK.keyCode===16)playerControlsParameters.keyboard.shift=true // Shift start
  			if(dK.keyCode===87||dK.keyCode===38&&playerControlsParameters.keyboard.walk==0)playerControlsParameters.keyboard.walk=18
  			if(dK.keyCode===83||dK.keyCode===40&&playerControlsParameters.keyboard.walk==0)playerControlsParameters.keyboard.walk=-18
  			processMemory.redStir=(playerControlsParameters.keyboard.walk<0?playerControlsParameters.keyboard.walk*-1:playerControlsParameters.keyboard.walk)/200+1
  			if(dK.keyCode===65||dK.keyCode===37&&playerControlsParameters.keyboard.stir==0)playerControlsParameters.keyboard.stir=modelsParameters.driveVehicleIndex[playerParameters.index]!=null?-50/processMemory.redStir:-10
  			if(dK.keyCode===68||dK.keyCode===39&&playerControlsParameters.keyboard.stir==0)playerControlsParameters.keyboard.stir=modelsParameters.driveVehicleIndex[playerParameters.index]!=null?50/processMemory.redStir:10
  			if(dK.keyCode===81||dK.keyCode===46&&playerControlsParameters.keyboard.yaw==0)playerControlsParameters.keyboard.yaw=-14
  			if(dK.keyCode===69||dK.keyCode===34&&playerControlsParameters.keyboard.yaw==0)playerControlsParameters.keyboard.yaw=14
  			if(dK.keyCode===219||dK.keyCode===35&&playerControlsParameters.keyboard.elev==0)playerControlsParameters.keyboard.elev=-5
  			if(dK.keyCode===221||dK.keyCode===36&&playerControlsParameters.keyboard.elev==0)playerControlsParameters.keyboard.elev=5
  			// Shift
  			if(playerControlsParameters.keyboard.shift){
  				if(playerControlsParameters.keyboard.walk>0&&playerControlsParameters.keyboard.walk<50)playerControlsParameters.keyboard.walk=50
  				if(playerControlsParameters.keyboard.walk<0&&playerControlsParameters.keyboard.walk>-50)playerControlsParameters.keyboard.walk=-50
  				if(modelsParameters.driveVehicleIndex[playerParameters.index]==null){
  					if(playerControlsParameters.keyboard.stir>0&&playerControlsParameters.keyboard.stir<20)playerControlsParameters.keyboard.stir=20
  					if(playerControlsParameters.keyboard.stir<0&&playerControlsParameters.keyboard.stir>-20)playerControlsParameters.keyboard.stir=-20
  				}
  				if(playerControlsParameters.keyboard.yaw<0&&playerControlsParameters.keyboard.yaw>-50)playerControlsParameters.keyboard.yaw=-50
  				if(playerControlsParameters.keyboard.yaw>0&&playerControlsParameters.keyboard.yaw<50)playerControlsParameters.keyboard.yaw=50
  				if(playerControlsParameters.keyboard.elev<0&&playerControlsParameters.keyboard.elev>-14)playerControlsParameters.keyboard.elev=-14
  				if(playerControlsParameters.keyboard.elev>0&&playerControlsParameters.keyboard.elev<14)playerControlsParameters.keyboard.elev=14
  			}
  			// Increment
  			if(playerControlsParameters.keyboard.walk!=0)playerControlsParameters.keyboard.walk+=playerControlsParameters.keyboard.walk/50
  			if(playerControlsParameters.keyboard.stir!=0)playerControlsParameters.keyboard.stir+=playerControlsParameters.keyboard.stir/50
  			if(playerControlsParameters.keyboard.yaw!=0)playerControlsParameters.keyboard.yaw+=playerControlsParameters.keyboard.yaw/50
  			if(playerControlsParameters.keyboard.elev!=0)playerControlsParameters.keyboard.elev+=playerControlsParameters.keyboard.elev/50
  			// Filter
  			if(playerControlsParameters.keyboard.walk>100)playerControlsParameters.keyboard.walk=100
  			if(playerControlsParameters.keyboard.walk<-100)playerControlsParameters.keyboard.walk=-100
  			if(playerControlsParameters.keyboard.stir>100)playerControlsParameters.keyboard.stir=100
  			if(playerControlsParameters.keyboard.stir<-100)playerControlsParameters.keyboard.stir=-100
  			if(playerControlsParameters.keyboard.yaw>100)playerControlsParameters.keyboard.yaw=100
  			if(playerControlsParameters.keyboard.yaw<-100)playerControlsParameters.keyboard.yaw=-100
  			if(playerControlsParameters.keyboard.elev>100)playerControlsParameters.keyboard.elev=100
  			if(playerControlsParameters.keyboard.elev<-100)playerControlsParameters.keyboard.elev=-100
  			if(dK.keyCode===86)opStickedgeRIGHT(true) // Switch camera
  			if(dK.keyCode===(modelsParameters.driveVehicleIndex[playerParameters.index]!=null?82:32))opSticktopRIGHT(true) // Jump
  			if(dK.keyCode===(modelsParameters.driveVehicleIndex[playerParameters.index]!=null?32:82)){ // Waving start
  				if(!playerControlsParameters.waving)opSticksideLEFT(true)
  				playerControlsParameters.waving=true
  			}
  		}
  		function desktopKeyUp(dK){
  			if(dK.keyCode===16)playerControlsParameters.keyboard.shift=false // Shift end
  			if(dK.keyCode===87||dK.keyCode===38||dK.keyCode===83||dK.keyCode===40)playerControlsParameters.keyboard.walk=0
  			if(dK.keyCode===65||dK.keyCode===37||dK.keyCode===68||dK.keyCode===39)playerControlsParameters.keyboard.stir=0
  			if(dK.keyCode===81||dK.keyCode===69||dK.keyCode===46||dK.keyCode===34)playerControlsParameters.keyboard.yaw=0
  			if(dK.keyCode===221||dK.keyCode===219||dK.keyCode===36||dK.keyCode===35)playerControlsParameters.keyboard.elev=0
  			if(dK.keyCode===(modelsParameters.driveVehicleIndex[playerParameters.index]!=null?32:82)){ // Waving end
  				playerControlsParameters.waving=undefined
  				opSticksideLEFT(false)
  			}
  		}
  		window.addEventListener('keydown',desktopKeyDown,false)
  		window.addEventListener('keyup',desktopKeyUp,false)
  	}

To make circular turns, just add small forward steps inbetween turns.

Instead of: turn → turn → turn
Do: turn → step → turn → step → turn → step

The size of the turn and the size of the step control how big the arc is.

Untitled Diagram

3 Likes

Yep, but I also believe the camera heavily influences the movement for some reason. I believe there is some sort of desiredDirection type of reference which might be calculated based on diatance from the camera.

Basically, having an object in front of the camera, and making the player follow that point (not sure if im explaining it correctly).

Imagine it as a circle, in the center there is a pivot, camera is up by Y, reference in front by Z, and the reference is where should the player be (?)

1 Like

I had a quick look at the navigation in “Summer afternoon”. If you do not come up with a solution, I will try tomorrow to see whether such navigation is easy or hard to do. I appears to be not so hard – the character moves towards the pointer and the camera moves towards the character – like a person going after a cat, that goes after a laser pointer controlled by the person.

2 Likes

Thats actually a clever way of looking at it, but im not so sure.

Lets not forget they also got keyboard controls and the behavior is the same.

I assume the keyboards and joystick are somehow connected to one another. I somehow achieved to integrate the keyboard with the joystick, but my implementation was not pretty at all.

I believe their mouse movement is just a dynamic joystick.

1 Like

Here you go → a cat and a tail, because a cat needs a tail. Click in the canvas and then use the keyboard (only the arrow keys). It is not absolutely the same as in Summer Afternoon, but at least it shows smooth third person navigation.

https://codepen.io/boytchev/full/rNQwzaQ

1 Like

Actually, this is a great starting point! If further polished, it’s great and exactly some starting point im looking for.

The million dollar question is, how would this work with physics? I mean, how should forces or linear velocity be implemented?

Pavel, I just studied the code.

Man, you’re truly an inspiration.

Thank you so much! :tada: