[solved, kinda] Strange mouse lock behavior

i’m making a game that is first person, and am running into some issues getting the camera to rotate properly with mouse movement.

my first thought (and the one that is usedin the " First-person 3D camera and movement" example) is to use the dx and dy variables. however, when the mouse snaps back to the center of the screen after the actual input, it records that as an input, too, meaning that i can’t just sum the variables to determine player rotation since it’ll be canceled out the next on_input call. this results in the player being able to turn if they continue moving the mouse, but they’ll snap back to their original orientation once they stop.

annoyed by this, i then did something a bit more manual, comparing screen_x and screen_y against the results of window.get_size to determine if the player had moved the mouse outside the center of the screen. this seemed to work better than the first method, but there’s this very annoying behavior, where it feels like the mouse gets “stuck” in a certain location that isn’t the center of the screen, resulting in you having to fight the mouse input for a few seconds before it goes back to normal and your inputs are properly taken again. worst of all, i can’t seem to reliably replicate the behavior besides just “move the mouse around enough”

how do i get proper mouse looking working?

I have a tutorial on FPS control and you can also download the project too: FPS Tutorial

If I had to summarize it:

On the template, I have this on Camera GO’s on_input function:

if pointer.locked and action.dx and action.dy then
		self.y_rot = self.y_rot - action.dx * self.rotation_speed
		self.x_rot = self.x_rot + action.dy * self.rotation_speed
		self.x_rot = math.max(MIN_X_ROT, math.min(MAX_X_ROT, self.x_rot))
	end

And, on the update, I lerp the values to get a smooth effect:

self.current_y_rot = vmath.lerp(0.15, self.current_y_rot, self.y_rot)
self.current_x_rot = vmath.lerp(0.15, self.current_x_rot, self.x_rot)

local q_y_rot = vmath.quat_rotation_y(math.rad(self.current_y_rot))
local q_x_rot = vmath.quat_rotation_x(math.rad(self.current_x_rot))
local camera_rot = q_y_rot * q_x_rot

go.set_rotation(camera_rot)

To follow the player (On update):

local player_pos = go.get_position(self.player_id)
local base_camera_pos = player_pos + vmath.vector3(0, self.camera_height, 0)

local vel = go.get("/Player/Body#collisionobject", "linear_velocity") or vmath.vector3()
local horizontal_speed = math.sqrt(vel.x * vel.x + vel.z * vel.z)
go.set_position(base_camera_pos)

On the player GO, I have my camera as direction object and I use this to follow the direction camera is pointing at:

if vmath.length_sqr(dir) > 0 then
		local cam_y_rot = math.rad(go.get(self.direction_object, "euler.y"))
		local cam_q = vmath.quat_rotation_y(cam_y_rot)
		dir = vmath.rotate(cam_q, dir)

		local mult = self.input.run and 3 or 1
		local speed = self.move_speed * mult
		vel.x, vel.z = dir.x * speed, dir.z * speed
		physics.wakeup("#collisionobject")
	else
		vel.x, vel.z = 0, 0
	end
	go.set("#collisionobject", "linear_velocity", vel)

1 Like

found the cause.
engine bug: calling window.set_mouse_lock with true while the mouse is already locked will force the position into the center and register an additional input. this was very noticeable since i was calling it every frame.

associated github issue

1 Like