Adding Mouse Controls to InfiniteShooter: an update

Hi again! Last time (like way back, on the old Stuff and Things), I was talking about how I went about adding mouse controls to my game in the form of a general guide for any 2.5d game using the Godot engine.

It worked great… but there was a slight problem. When I implemented it in my game, the player’s acceleration felt like it was interpolated twice. And it was! Not only were mouse movements being interpolated, but so was the velocity of the player, leading to this weird double-acceleration effect that makes you feel like you’re walking on ice. On top of that, I still had the hack for fixing my TrackPoint, which I found equally as annoying as the problem it was trying to solve.

So I decided to look to other projects to see how they implemented it. First, I had a look at OpenTyrian’s implementation, but at first it wasn’t really that helpful:

mouse_x = ev.motion.x;
mouse_y = ev.motion.y;
mapWindowPointToScreen(&mouse_x, &mouse_y);

if (mouseRelativeEnabled && windowHasFocus)
{
	mouseWindowXRelative += ev.motion.xrel;
	mouseWindowYRelative += ev.motion.yrel;
}

// Show system mouse pointer if outside screen.
SDL_ShowCursor(mouse_x < 0 || mouse_x >= vga_width ||
               mouse_y < 0 || mouse_y >= vga_height ? SDL_TRUE : SDL_FALSE);

if (ev.motion.xrel != 0 || ev.motion.yrel != 0)
	mouseInactive = false;

Until I looked closer. But this realization came after playing the original Doom with the Crispy Doom source port. Since this source port uses code from the original game, without any patches for full mouselook, that means there’s no modern interpretation that’s too complicated since at that time every bit of optimization would have had to count.

Here’s what I noticed in this game and in OpenTyrian: there’s no interpolation. When I was moving my mouse really slowly, there was still some degree of jank, but it was unnoticeable because it didn’t affect the gameplay of either game. Now games like Half-Life actually do offer options for mouse interpolation, but if older games can still get it right there might be something I’m missing. As I concluded, there were a few things I was missing, namely that both games only handle mouse input when the mouse moves.

So I had to update my code. There’s two major things I had to consider here: (1) that mouse movement is only handled when the mouse moves, and not in _process() or _physics_process(), and (2), that I remove interpolation. I was having doubts about if the second part would work, but after I finished implementing mouse controls, controls felt surprisingly fluid! When I finished rewriting my code, here’s what I had to say in my commit’s description:

It's actually easier than you think [about it] (and I got the epiphany for it while playing Doom and thinking about what it would have had for mouse controls): actions with mouse controls are only done when the mouse moves, and it's not interpolated because it doesn't need to be. Take a look at OpenTyrian's controls: it's using SDL to say that when the mouse is moved, map its coordinates to a position on the screen and do whatever is usually done for controls there. So as it turns out, all I needed to do was write code that moves the player with a mouse only in an event when the mouse was moved, and as it turned out that actually ended up solving the issues I was having with controls.

So without any further ado, here’s what my updated code looks like for mouse controls:

extends Node

signal mouse_moved(mouse_intensity)

export(float) var sensitivity = 1.0

export(int) var max_mouse_speed = 500 # Maximum movement in pixels/second

export(bool) var pointer_lock_enabled = false

export(int) var frames_until_mouse_reset = 1

var timer = frames_until_mouse_reset

var mouse_moved : bool = false

var mouse_intensity : Vector2 setget ,get_mouse_intensity

var _mouse_intensity : Vector2

# _input: Calculate the intensity of each mouse movement and multiply it by -1 because for some reason down is positive for Godot???
func _input(event):
	# Pointer lock
	if pointer_lock_enabled == true and event is InputEventMouseButton and event.pressed == true:
		if event.button_index == 1:
			Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
		else:
			Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
	
	if event is InputEventMouseMotion:
		mouse_moved = true
		var mouse_speed = event.relative / get_process_delta_time() * -1
		var radius = clamp(abs(mouse_speed.length() * sensitivity), 0, max_mouse_speed) / max_mouse_speed # Divides by max_mouse_movement (and clamps) to get a value out of 1 (that can't be higher than 1)
		_mouse_intensity = Vector2(radius, 0).rotated(mouse_speed.angle())
		emit_signal("mouse_moved", _mouse_intensity)

func get_mouse_intensity():
	return _mouse_intensity if mouse_moved == true else Vector2(0, 0)

func _process(delta):
	if mouse_moved == true and timer == 0:
		mouse_moved = false
		timer = frames_until_mouse_reset
	elif mouse_moved == true:
		timer -= 1
Code language: PHP (php)

And of course, here’s some footage of my game with mouse controls

And that’s about it! Have a good one. I’ll see you in the next post!

You can get InfiniteShooter through itch.io or GitHub.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *