UI Iteration

General Infrastructure

Input

Spoiler: SHOW Code: Select all interface IButton { bool IsPressed { get; } bool IsHeld { get; } bool IsReleased { get; } void Update(); } interface IAxis { float Value { get; } float Deadzone { get; } bool IsInverted { get; } void Update(); } class ButtonToAxis : IAxis { private IButton button; // ... } class AxisToButton : IButton { private IAxis axis; // ... }

, and ends world hunger

Input.GetButtonPressed(Button.Space)

Misc

Integrated FMOD for audio support.

Attached very quiet cantina music to asteroids and watched Lindsey go slightly insane trying to figure out why she has Star Wars stuck in her brain.

Sniped revisions 1000 and 1024 from Josh and made really boring commits.

Played around with tooling for merging / viewing / debugging mods.

Moved in with my girlfriend.

Got a puppy!

Gave said puppy a trial run as head of the Office Happiness Enhancement Department

(she'll be back for a second round of trials in March).

Allllright. It's been a little while since I've rambled on the forum so I figure it's time for an update. Plus, with Lindsey down for maintenance and Josh defragging the system someone's gotta keep y'all in the loop, right?It looks like PAX was a little more draining than we expected. I'm certainly not complaining, because we had atime jamming on the demo and having so many excited people hanging out at the booth all weekend. Committing to writing only gameplay code for a week and getting to see just how well all the moving parts are operating together has been a massive kick in the pants in terms of motivation and excitement. We were able to absolutely slam through features and have everything Just Work.That said, I think everyone is finishing up a little recharge. December and January have been hectic. Between travelling for Christmas, jamming for PAX, presenting at PAX, the Global Game Jam, and moving we're all taking it a little bit easy right now.What have I been up to since you last heard from me, you ask? Well, let's see..Since the original implementation of the dev UI I've made some really nice simplifications and improvements. In fact, Josh decided to move forward with the dev UI and the final gameplay UI. That meant I had to do some more hammering to ensure it was up to snuff. I massively simplified how focus and dragging are handled. I made sure that the UI does sane things when widgets are removed at runtime. I added tons of polish: keyboard and gamepad navigation, ScrollViews always keep the current selection in view (even if you were to nest multiple ScrollViews inside each other), fixed the last few subtle 'off-by-a-pixel' type issues, and started pulling over Josh's shader-based UI drawing code. That's right, just because it's programmer art right now doesn't mean it's staying that way. It's super easy to swap out the drawing code with proper prettified versions.I tend to alternate between big tasks and little tasks to keep my mind fresh so I frequently bounce around making little things better. We now have proper signal handling in the engine. In unexpected/unsupported situations the engine simply aborts. Previously this meant the game just printed a message and closed. If you had a debugger attached you'd have a chance to walk up the call stack and determine what happened, but sometimes you don't have the debugger running (or never, if you're a masochist like Josh). Now we can easily register handlers from any system that will run any time the game crashes. A particular pain point was Lua. Being a dynamic language it's super easy to write silly bugs that will cause a crash (like type the wrong variable name when calling into the engine and accidentally passing a null pointer instead of, say, the mesh you want to render). Now we're able to automatically print the entire Lua stack when something goes wrong which instantly cut our bug fixing turn around time by an order of magnitude. 9 out of 10 times we know exactly which line caused the problem without having to think about it.I also went through the engine to standardize and organize some of it a bit better. We had various APIs with unintuitive names to avoid name collisions with other common libraries. So things like StrUtil, MemUtil, and LibMath became PhxString, PhxMemory, and PhxMath. I also ripped out some templates in favor of good old function overloading.Before we had a solid Lua foundation we used a second C++ project called scratch where we did explorations and wrote test applications that stressed specific aspects of the engine. In particular, that's where my (fairly large) BSP testing application was. Now that we have robust Lua tooling we write our tests in Lua based applications that actually live in the LT project and we're able to easily choose which application to launch. That scratch project has been sitting there unused and collecting dust so I rewrote the BSP tests in Lua and nuked scratch entirely.I also added some niceties to our Windows build environment. I massively trimmed the output so it doesn't spam the console with things we don't care about, added a brightly colored message for build success/failure, profiled build times in an attempt to cut them down (turns out, vcvarsall.bat takes longer than compiling and optimizing over 16,000 significant lines of code, thanks Microsoft!), and output colorized warnings and errors in realtime. I think I finally learned my lesson this time: Command Prompt / Batch files suck. I'm done with them for good this time. I'm going to pick a new language for tools.Here's where I've been spending most of my time recently and where I think it would be fun to dive in a little.Input is something I've wanted to sit down and really solve for quite a while now. There's a lot of subtlety surrounding input and finding a way to handle the complexity while maintaining a level of elegance is a worthy challenge. In my last game I dealt with input as 2 separate type: Buttons and Axes. Buttons were a binary thing, either pressed or not. Axes were an analog thing, a value in the range [-1.0, 1.0]. The biggest issue here is that you have to decide in advance what is a button and what is an axis and you have to stick to that. You can't treat the same input as a button in some places and an axis in others. For example, the left analog stick on a controller wants to be treated like an axis when you're moving your character, but in the main menu you want to translated it to button presses for Up, Down, Left, Right. My way around it at point was to have wrappers that converted between input types in the actual bindings. The types ended up looking something like this:Obviously this was back when I was a lot OOP-ier (read: wronger). There are lots of things I don't like about this now: bindings are doing actual input processing instead of just mapping values to actions, it's a polling interface, it has interfaces, inheritance, and virtual functions...The polling style is the biggest issue. You end up checking every possible action every frame. And what happens when a button is pressed twice in the same frame (unlikely, but possible with low framerate when typing text)? How would you record and replay input to implement a replay feature for gameplay or debugging purposes? If you're serializing bindings in order to save them you now have to store information about this ButtonToAxis/AxisToButton business. It doesn't feel like the right solution.What if, instead of asking if a button is pressed, we stick events in a queue for every change in input. Then gameplay loops over the input queue and runs the necessary logic for each button press or axis change (I stole this idea from the Quake engine). That feels like a step in the right direction.So my first attempt at improving our input handling was to implement an InputStream API. Here, everytime the engine detected an input change it stuck an InputEvent into the stream. From the gameplay side we'd loop over all events at the top level of the game loop and call OnInput on everything that was currently consuming input. LuaJIT was not a big fan of this loop. And it didn't solve the button vs axis issue. But, we did get gamepad support as part of this change. And we no longer missed multiple presses in the same frame or a press and a release in a single frame. However we still had to treat buttons and axes differently and we were shoving extra information into the events like the mouse position/scroll and the current modifier keys. Still, that's a net win and this is the system we used at PAX.Then, Josh gave me 2 small nudges to see the full solution: 1) why are we sending input events to everyone instead of the just the place that uses it, and 2) what if everything was an axis? I took 2 and ran with it a bit: what if everything was both a button and an axis? All input, regardless of its source starts off as an analog value. Then we take that analog value and interpret it as a button also. Let's say when the value is greater than 0.5 it's pressed and less than 0.4 it's released. We store both and let gameplay decide which representation it wants.At this point, we still needed a proper solution for remappable bindings, support for more than just the Xbox 360 controller we used a PAX as well, HOTAS support, and haptic feedback. However, we still wanted to keep the ability to read input without setting up bindings and to query input directly without using the event loop. When we're writing a test application or we're prototyping gameplay we don't want to have to think about bindings and events we just want to do the braindead thing and ask if the space bar is pressed. We needed to keep all the existing functionality of the 'inferior' input APIs (at this point we're up to 3 different input APIs)Thus, for the past few days I've been rewriting the input system to incorporate all these ideas into the One True Input API that deprecates all the others, solves every problem. I think I've accomplished the goal (well, am currently accomplishing. There's a little more to go). I ended up splitting the input system into 2 parts: a low level API (Input) and a high level API (InputBindings).The low level API is as simple as possible. It reads input from the system, keeps track of everything as both buttons and axes, and pushes events into a queue. Because it stores a copy of the current input we're able to do the direct queries:but it also stores the number of button transitions during the last update so it won't miss double presses or press and release in the same frame. It leverages SDLs ability to virtually map all controllers to a standard Xbox 360-like layout so just about any controller you plug in will Just Work. It ensures press and release events are perfectly paired (even if your controller is remapped while holding a button, much to Josh's annoyance). This API is extremely robust and basically complete. Everything LT related has been migrated over. It needs slight improvements to the way modifiers are handled, haptic feedback support, and to be tested with HOTAS / unmapped controllers.The high level API is where bindings and the callback magic happen. Multiple inputs from any device can be bound to an action. Actions 'eat' the input so only the most recently added action will be triggered which allows us to push sets of actions onto a stack. Callbacks for specific actions (e.g. OnPressed or OnValueChanged) can be registered for from Lua and code will be called directly rather than have to query or process events. The high level API is meant to mirror the low level API as much as possible (making it painless to move from the low level API to the high level one) so it supports both direct query and an event queue in addition to the callback mechanism. The API is mostly implemented but we need to flesh out the callback mechanism, the syntax for creating bindings/registering callbacks, and explicit device assignment.Honestly, I'm quite proud of how this is turning out. I feel like I've 'solved' input and will be able to use this approach for future projects. At the very least it's nice to have reached a point where we have one simple, unified way of dealing with input.Other stuff I've been up to:Phew. I've glossed over so much detail (like the ridiculously awesome procedural sound that's on the horizon) and this is still a wall of text. How about a picture of Tess to make up for it?