Lift and Separate, User Input


When I was initially designing LINK I was focused on building a free form note taking application so my priorities were pointed towards being able to place and edit notes and make sure they could be saved and loaded.

Everything else was secondary.

I wasn’t worried about the best way to keep my input logic from interfering with the application logic so I would squeeze a new feature in wherever it made sense and implemented it with whatever I had available.

At the time, it was the right move. I was trying to build a prototype to test out an idea. It didn’t have to be well written, it just had to work.

When the focus shifts from testing out an idea to working out how the underlying tech implements them, things don’t get finished.

As my continuing series of posts have pointed out, these initial choices have been rearing its ugly head and if I want to continue forward with LINK, I have to do something about it.

The Problem

When dealing with input from the mouse all I really have is the variable input.mouse_click which is set to “false” if there’s no input, “pressed” if the button has just been pressed, and “down” if the button is still being pressed after one frame.

This goes back to when I was primarily developing action games using either keyboard or gamepad input. I didn’t need anything else.

The problem is, mouse input is a little more complicated than that, mostly due to how we expect things to happen. While I’ve messed around with projects that did handle more complex input, I never had a need to add it to my engine’s source code.

So, I created it on the fly.

For awhile this was fine. Mixing input and application logic was manageable and allowed me to focus on more important things.

However, as the program grew and grew I couldn’t keep track of every place I had a special piece of code that I’d need to make changes to when I added a new feature.

Random bugs started popping up and things that had been working suddenly were broken and I’m still not sure why.

At this point managing input is such a tangled mess I’ve been doing everything in my power to keep from moving away from the note-base framework I’ve built because then I’d have to face the fact that it’s barely functioning.

I saw the writing on the wall which is largely the reason I shifted [Battle Commander][battle_commander] from a game inspired by Battleship to a tech demo for an improved input handling system.

So Battle Commander’s loss is LINK’s gain.

Separating Input and Features

In an ideal world, application logic should never be aware of how the user went about doing it.

Sure, LINK is mouse driven but there’s no reason why it couldn’t work on a touch screen, with a gamepad, or just a keyboard. Granted, entering text with a gamepad is tricky, but possible. As it stands, everything is very much hardwired to only work with a mouse.

Trying to solve this in Battle Commander, the basic flow of things goes like so:

  • Find the object we’re interacting with.

Simply put, we’re just doing basic collision tests with the different objects in the game. Wherever the mouse pointer is hovering over is the object we’re interacting with. This doesn’t handle objects that overlap with each other, but I’ll be dealing with that soon.

  • Get mouse button’s action: “hover”, “press”, “down”, “activate”, or “release”.

Remember, my original input system only deals with “press”, “down”, or “false”. The extra actions are important for mouse input and I didn’t even include a “double click” option yet.

“Hover” is simply the default state, basically the same as “false”.

The “release” action happens the first frame the input returns “false” after being “pressed” or “down”.

“Activate” is an interesting action that really only pertains to UI buttons, like ones found on a dialog box.

In order to work properly, a program needs to keep track of the object the mouse is pointing to when pressed and compare that to the object the mouse is pointing to when released. If the button is released and the objects are different, we don’t run the “activate” action. This is a nice handy way of canceling the action after you’ve clicked it. Works on mobile too!

  • Update game state based on the object and the action being performed.

Once I know where the mouse is and what action is being performed I can then move on to the next bit of code, which I have a handy template for:

local itype = "template"             -- object
local istate = input_state[itype]    -- what
if istate then
	------ ![    HOVER    ]! ------

	if     istate == "press" then
		-- ![   PRESS    ]! -------

	elseif istate == "down" then
		-- ![    DOWN    ]! -------

	elseif istate == "activate" then
		-- ![  ACTIVATE  ]! -------

		istate = "release" --do release next
	end
	if istate == "release" then
		-- ![  RELEASE   ]! -------
	end
end

In Battle Commander a lot of the logic in the game sits right here. This is very similar to how UI toolkits work except instead of if-else or switch blocks, they’d be callbacks to functions: “onHover”, “onPress”, etc.

  • Finish any updates based on the new game state.

Admittedly, Battle Commander doesn’t really do much here because of the logic being in the previous section.

For LINK I’m looking to set the state in the previous section and process the application logic here.

As an example, anytime you click on the window in LINK, you’re effectively grabbing something until you release the button. This has been greatly simplified, but it’d basically look like this:

if istate then
	------ ![    HOVER    ]! ------

	if     istate == "press" then
		-- ![   PRESS    ]! -------
		grab = true

		prev_x = mouse.x
		prev_y = mouse.y
		curr_x = mouse.x
		curr_y = mouse.y

	elseif istate == "down" then
		-- ![    DOWN    ]! -------
		
		curr_x = mouse.x
		curr_y = mouse.y

	elseif istate == "activate" then
		-- ![  ACTIVATE  ]! -------

		istate = "release" --do release next
	end
	if istate == "release" then
		-- ![  RELEASE   ]! -------

		grab = false
	end
end

Then later in the code I would have this.

if grab then
	local diff_x = curr_x - prev_x
	local diff_y = curr_y - prev_y

	if diff_x ~= 0 or diff_y ~= 0 then
		if object == "camera" then
			--move camera
		elseif object == "note" then
			--move note
		elseif object == "resize_handle" then
			--resize note
		end
	end
end

With the two separated I could use the behavior to move the camera, note, or resize a note with the arrow keys.

do
	local x, y = 0, 0
	if input.left then
		x = -1
	end
	if input.right then
		x = 1
	end
	if input.up then
		y = -1
	end
	if input.down then
		y = 1
	end
	if x ~= 0 or y ~= 0 then
		grab = true
		prev_x, prev_y = 0, 0
		curr_x, curr_y = x, y
	end
end

When it reaches the if grab then ... end block, it’ll run just the same as if the mouse had done it.

Neat!

If you’d like to see it in action, feel free to download Battle Commander and take a look at the source yourself (see: lowrezjam2021.agpack/main.lua).

It’s far from perfect but it gives me a good base to work from.

The Plan

Since this will require me to basically rewrite all my input code (read: pretty much the entire program) I’ll need to be careful.

I still would call this a refactor instead of a rewrite because as I do this I plan on having the existing code working along side the new code.

I will not be opening up an empty file and starting fresh. That’s a fast way to start adding “nice-to-have” features that will bring my work to a grinding halt.

Now, I’ve done this a few other times, most notably when I changed up how I handled text editing. I found this to be a very fast way of swapping out large features.

By running them both in parallel, I can compare the end results of my old code with my new code. I could even switch between the two until I’m satisfied with the result.

As far as disadvantages go? I don’t know. If I run into any I’ll be sure let you know in a future post.

What’s Next?

Most of this past week was spent trying to figure out how to go about adding plugin support in the cleanest way possible.

With the more flexible input system it should be relatively easy to add basic plugin support. I won’t know for sure until I wrap things up but it should be as easy as adding a different input mechanism, if the example above is any indication.

I might also need to overhaul how I store all the different objects on screen for more advanced plugin support and it might be necessary for the UI support I have planned. As luck would have it, before working on LINK, I was working on this very problem for an unreleased game of mine but more on that later.

Alright. I think I’m ready.

Get LINK!

Leave a comment

Log in with itch.io to leave a comment.