handle

Introduction

View rolling is a great way to enhance the feedback of FPS controllers. It increases the sense of momentum when you are strafing left and right. However lots of "Quake-Like" controllers that include view roll do not seem to take velocity into account. Instead they simply roll the player's view left and right based on input. There's nothing inherently wrong with this. It still delivers the same sensation of speed and it's very easy to program. But we can do better.

Instead we can base view rolling on the player's velocity. Using velocity to calculate view roll directly correlates with the player's momentum. This will result in nice autonomous behaviours such as the player's view snapping suddenly if they collide against a wall quickly. If view roll is purely based on input then the view will forever lean left or right as long as the button is held and the sensation of colliding with objects is reduced. If you are using velocity there is also a nice little side-effect where the camera will sway subtley when you rapidly change direction.

Now there are issues here and there with using velocity based view rolling. If the player's velocity changes rapidly it may cause the view to shake rapidly. This is currently the case with my character controller when you try to run up a steep slope. It looks a little unprofessional and buggy, but everything else in my controller is rock solid so it's a negligable issue for now.

Additionally, what appeared to be a bug with view rolling was a blessing in disguise for my character controller. Whenever the player was moving against flat walls, their view would violently jerk left or right. This was happening due to velocity being inconsistently clipped when using Godot's "move_and_slide" function. This forced me to figure out what was going on and eventually solve the issue by implementing a continuous collision detection loop for much smoother velocty clipping.

handle

Code Breakdown

Quake's source code contains a very nice function to calculate velocity based view-rolling. So instead of reinventing the wheel, why not simply convert it?
Here is the function converted from QuakeC to GDscript:

	        	func CalcRoll (velocity, angle, speed):
	var s : float
	var side : float
				
	side = velocity.dot(-get_global_transform().basis.x)
				
	s = sign(side)
	side = abs(side)
		
	if (side < speed):
		side = side * angle / speed
	else:
		side = angle
		
	return side * s
		

Let's breakdown the code and see what's going on here.

We want to get the dot product of the player's velocity vector and the player's right vector (i.e. a vector that points to the right of the player's forward facing direction). The dot product will tell us how much the player's velocity vector is moving in relation to their current forward facing direction.

side = velocity.dot(-get_global_transform().basis.x)


Then we want to get the sign of the dot product which gives a value that is either -1 or 1, left or right respectively. We store this in a variable for later use. Most standard maths libraries will have a sign function, however it can be easily implemented using a ternary operation.

s = sign(side)
	OR
s = -1 if side < 0 else 1


Next we want to get the absolute value of the "side" variable since the next calculation will require a multiplication of the speed. Speed should always positive as it's not signifying a direction but instead a magnitude. A car in real life reversing at 5km isn't travelling at -5km.

side = abs(side)


Next we want to check if the roll amount is greater than the speed parameter. If the roll amount is greater then we simply use the angle parameter as our roll amount. If the roll amount is less than the speed param we multiply side by the angle param then divide by the speed param. This gives us a value that has a nice angle to speed ratio. There's probably a proper name for this calcluation. I don't know what it is.

if (side < speed):
	side = side * angle / speed
else:
	side = angle


Lastly we return our roll amount multiplied by the sign value we calculated earlier ("s"). This will give our final roll amount a positive or negative value, corresponding to left/right movement. You add the roll calculation to the player's camera/head z-axis rotation to make the player's view roll left and right while strafing. This value can also be added to any other item that exists in view, such as weapons or hud elements, to give your controller an extra layer of feedback.

camera.rotation_degrees.z += CalcRoll(player.velocity, rollangle, rollspeed)


You can find my implementation of view rolling in github repo linked below inside the view.gd script.

The project is licensed under GNU v3 since it uses a significant amount of Quake source code. If this article and/or the project on github has helped in anyway I would appreciate a credit.

https://github.com/Btan2/Q_Move