Adding Mouse Controls to InfiniteShooter: an update


Originally from my blog.

Hi again! Last time, 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

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!

Oh, and here's something I didn't write in my blog: you can try this out by going to the game's GitHub repository, cloning the feature-additions branch, and opening that in the Godot engine. Let me know what you think!

Get InfiniteShooter

Leave a comment

Log in with itch.io to leave a comment.