Hey, GameMakers! Seth Coster from Butterscotch Shenanigans here, with another BUTTERY SMOOTH TECH BLOG! This time, we’re going to dig into something many of us take for granted: INPUTS. By “Inputs” I mean, “pushing buttons and having things happen on screen.”
There are a trillion different ways you can handle inputs in your game, but some are clearly better than others, and which one is best depends on your goals. One of our goals as a commercial game studio is to try to make our games as accessible as possible. For us, that means to launch on as many platforms as we can while supporting as many controls schemes as we can. Touch screens? We got it. Controllers? Yep. Keyboard? Yep. Flight simulator joystick? ... Maybe. Racing wheel? I MEAN YEAH.
Our new game Levelhead is a great example of this. We’re launching Levelhead simultaneously on Nintendo Switch, Xbox, PC, Android, and iOS. Each of these platforms comes with a unique combination of input devices.
- Xbox: Gamepad only, with same-screen co-op support.
- Nintendo Switch: Dynamically change between touchscreen and Joy-Cons, with same-screen co-op support using various Joy-Con configurations.
- Android & iOS: Dynamically change between touchscreen and Bluetooth gamepads.
- PC: Dynamically change between keyboard/mouse and gamepad, with same-screen co-op support, either using all gamepads, or a combination of keyboard/mouse and gamepads.
That’s a lot of different ways to play the same game! And if you can pull that off, then you, too, can launch your game in even more places, making it more accessible and more likely to succeed. So... is it possible to learn this power? It sure is!
Let’s jump right in!
HARD CODING: THE “QUICK AND DIRTY” PROBLEM
If you’ve built a game before, you probably started the same way I did: by hard-coding your input handling.
By that I mean, you’ll have a character object that is explicitly listening for certain key presses or gamepad inputs. For example, your character might have some code inside its step event that listens for a press of the “A” button on a gamepad, which makes the character jump.
I call this the “Quick and Dirty” method of handling inputs. It’s “Quick” because within minutes, you have a character that can respond to button presses, and you have a playable game. This is great for game jams and rapid prototyping, but it comes with some major drawbacks. That’s the “Dirty” part.
For starters, you now have device-specific input code mixed up in your character code. As you add more styles of inputs, like a keyboard or on-screen virtual controls, this pollutes the character code and make it harder to read.
This has the added disadvantage that it only applies to the character. What if you want to have something else in your game listening for inputs, like an interface? Or what if the character has a controllable pet? Now you’ll need to program a bunch of other gamepad, keyboard, or other input-parsing code into those objects.
Oh, but it gets worse! What if you want to have same-screen co-op, where you have multiple input devices at once? Which device does each character listen to? How do the characters decide?
In this scenario, each character needs to know what inputs the other characters are listening to, so it can pick the right ones to ignore and the right ones to listen to. So now the characters are talking to each other, further polluting their code.
Under this “Quick and Dirty” method of hard-coding your inputs, your code will continue to get more and more spaghetti-like, until eventually, you can’t change it anymore for fear of breaking it. And it definitely doesn’t allow for extra features like rebindable hotkeys.
So instead of having inputs being handled by lots of different objects, hard-coded and tangled up, we need... A brain. We need a centralized decision-maker that has all the answers. We need..... an INPUT MANAGER.
THE INPUT MANAGER
The core philosophy of the Input Manager method is to have a single object, which we’ll call o_input_manager
. This object will have two jobs:
1. Listen for new input devices and decide how to assign those devices to players.
2. Read inputs coming in from various devices and standardize them, so other objects in the game can read inputs easily without caring what kind of device they came from.
It goes something like this:
Once we have our Input Manager system working, we gain a number of advantages.
- Characters no longer have to decide how they are being controlled. They don’t care if it’s a gamepad, joystick, racing wheel, keyboard, or whatever. They just need to know what “Slot” to look at.
- Since all inputs are standardized, the Input Manager can allow for rebindable keys.
- Like an Air Traffic Controller, the Input Manager can watch for connecting/disconnecting gamepads and other input devices, and assign or unassign those to player slots as needed. No other object in the game has to care about that.
- Adding new kinds of input devices becomes trivial because you just add those input devices in one place: the Input Manager.
Now, this is all well and good in flow chart and bullet-list form, but WHAT IS THIS, A POWERPOINT? No. It’s a tech blog. But this is a special kind of tech blog. A cyborg tech blog that’s half English, and half machine. The remainder of this blog post only exists... inside the code.
I’ve compiled a sample project that contains an input manager and a player object that listens to the input manager. Inside the sample project, you will see a file called START_HERE under the “Notes” section. Start there.
I’LL SEE YOU ON THE OTHER SIDE.