Welcome to the first FEZ Friday! Or rather, the first FEZ FNA Friday, because it turns out Polytron used to have a FEZ Friday on their old blog. But whatever, NEW TEXT WALLS!

This series will be outlining some of the changes we've made to make the user experience a little nicer when playing FEZ 1.12, as well as things we've polished and optimized as a result of moving from MonoGame to my own reimplementation of XNA4, known as FNA:


We'll probably release a set of patch notes, but this is to be considered more of an educational resources than anything - it provides some examples of real scenarios encountered when making FEZ 1.12 as well as the processes used to overcome the obstacles associated with what might be considered small items by the general public, but are often gruelling tasks performed in the service of polishing an already-complete game.

Before we start, I have to get some disclaimers out of the way. Just standard procedure:

I am an independent contractor and NOT an employee of Polytron Corporation, and do not possess any rights to its copyrights and trademarks, including those pertaining to FEZ. While the samples provided here are from the actual upcoming 1.12 build I've been working on, the contents of these posts are not to be treated as official statements from Polytron or its associates. All content shown is a work in progress!

Anyway, let's talk about how controller support in games can be pretty terrible.

I've spoken at length about this before in FFFFFFriday...


... and it's time we revisit the subject through a different window.

There are two kinds of controller menus I truly, absolutely hate. The first is the kind that has a really ugly method of binding to a mostly illegible numbering system and doesn't account for TONS of nasty scenarios that can render a game unplayable before you even leave the main menu.


Oh. Well at least it's not that controller menu where it's just a picture and doesn't let you configure anything.


Ah, crap. How did we get here?

A lot of this comes down to historical reasons. Being a 360 game at first, the second menu was a natural result, since the Windows version could just use XInput and be done with it. The first menu, on the other hand, is where it gets kind of ugly.

MonoGame, until just recently as a matter of fact, was still on SDL 1.2, but only for gamepad support. The rest of the library used OpenTK (we'll talk about this another day), but gamepads were stuck with SDL_Joystick, which only supported DirectInput on Windows (and without hotplugging support, ack). In order to support both XInput and DirectInput, these two separate paths were formed in the FEZ MonoGame branch, ultimately giving us two menus which, combined, gave us the worst of both worlds.

You'd think this would have been more prominent for other MonoGame projects, but remember that it's only Windows that has to put up with XInput vs. DirectInput. For early versions of MonoGame-SDL2, where Linux/OSX was my only concern, we also used SDL_Joystick, but with a format I wrote called MonoGameJoystick.cfg, which bound the mysterious button/axis/hat numbers to the 360 controller layout. This allowed me to keep the one path, but still make it configurable in some way for people to make controllers usable. It also standardized it too, so the config would work for all of my MG-SDL2 titles.

Then SDL_GameController came along and made all that ugliness unnecessary for all of those situations, and that's what's in FNA today.

The new controller menu for FEZ 1.12 is probably my favorite UI-related feature in the update:


Oh, but what's this... DualShock prompts? We'll get to that in a second, but let's talk about what it took to get here.

One of the big advantages of moving all of the FEZ PC versions to FNA is that we can now make use of the SDL2 GamePad implementation I wrote, which is infinitely nicer than anything we had before. The SDL_GameController subsystem has a very wide range of hardware support, including both XInput and DirectInput at the same time, dynamically, and with hotplugging support. With the databases provided by SDL itself, Valve's Big Picture database, and Gabriel Jacobo's SDL_GameControllerDB...


... controller support is now MUCH easier to wrangle than it was in the Bad Old Days. We can essentially do the hard work of mapping the cryptic index values to a layout that's standard without arbitrarily locking out any hardware to meet that standard, then do the work of making that standard configurable knowing that the recognized devices (and the players using them) will understand what on Earth is going on. And we can do this for any game that uses this format.

So now that we've wrangled the hardware side, let's wrangle the software side.

We now have only one controller path instead of two, so which do we throw out? On one hand the configurable menu was clunky with its bindings, but on the other hand we want to be able to do more than just restrict it to a stock photo that points actions to buttons.

The solution is to take out the worst of both worlds, in order to leave us with the best of both worlds. I took out the 360 menu and revamped the controller config menu to work with the very same button glyphs that you'll see in-game.

Speaking of button glyphs, we now run into our first major problem: With configurable buttons, what will happen to the in-game prompts and tutorials now that we can change them to what we want?

The answer is nothing. But that's the problem. Consider this string that you might find in any game's content files:

"Press {A} to jump."

Well, shit. The strings are hardcoded to button values. The work it'd take to write a new button prompt system, including scrubbing all the strings (all locales, mind!), would be a nightmare to deal with.

This is where thinking like a data-oriented programmer can really save you a lot of trouble. Remember those DualShock prompts from earlier? Those were actually a really big hint at how to solve this.

In order to support those prompts (which we got from BlitWorks, thanks guys!), we need to swap out the 360 glyphs with the DS3/DS4 glyphs when one of those devices is connected. While FNA's GetGUIDEXT extension makes hardware detection easy...


... we're still left with the act of doing the glyph swapping at runtime. Remember that hotplugging is a thing that exists, so we can't just do something sloppy on boot; just like the input itself, we need to be able to adjust the game's behavior for when the appropriate hardware is connected. So given the original data transformation...

"{{BUTTON}}" -> BUTTON.png

... we now need to do a bit more work:


The secret sauce is, of course, checking the current layout when compositing the in-game text (pseudocode):

if (InputManager.GamePadLayout == DualShock4)
    glyph = ButtonToGlyphSony4[glyphstring];
else if (InputManager.GamepadLayout == DualShock3)
    glyph = ButtonToGlyphSony3[glyphstring];
else // (InputManager.GamepadLayout == Xbox360)
    glyph = ButtonToGlyph[glyphstring];

Not terribly complicated. But returning to configurable buttons, now we somehow need to get here:


The problem we're facing is that the data we're being given is no longer representative of what we actually want to transform it into at the end of the process. With the insertion of a controller configuration, the data is now just plain incorrect.

Except that's ridiculous, because it's just data. Just because we changed the problem, doesn't magically make the original data no longer representative of anything. The problem isn't the data, the transformation is. That's the whole reason we're having to even look at any of this in the first place.

A good way to reexamine what data actually represents is to just go back to the beginning and look at the original transformation of the data. So we're back to 360 Land:

"{{BUTTON}}" -> BUTTON.png

This was fine for the 360, because the game only had one configuration format and so each button linked to one action... and each action linked to one button.

The data is perfectly representative of something we want, it just happens to now have stupid names for what we want it to actually represent. The transformation can be equally represented like this:

"{{ACTION}}" -> BUTTON.png

So {A} isn't A, {A} is Jump! This not only makes just as much sense in the 360 situation, but it's now much more clear what the solution is for our situation as well, and BUTTON.png can still just as easily be BUTTON_HARDWARE_X.png for the DualShock case. The result is this (still pseudocode):

if (InputManager.GamePadLayout == DualShock4)
    glyph = ButtonToGlyphSony4[ControllerConfig[glyphstring]];
else if (InputManager.GamepadLayout == DualShock3)
    glyph = ButtonToGlyphSony3[ControllerConfig[glyphstring]];
else // (InputManager.GamepadLayout == Xbox360)
    glyph = ButtonToGlyph[ControllerConfig[glyphstring]];

glyphstring, now that it's interpreted as a mapping and not a button, can be parsed with a simple lookup of the current controller config, and the resulting button is the glyph we get at the end. All that work of making whole new string reinterpretation subsystems and templatized layout structures, which is what a LOT of programmers would have done, can simply be skipped with one extra dictionary lookup, with a couple if statements added for a bonus.

It's worth noting that this is still technically more expensive than what we were originally doing, but I've been careful to only use cycles that we've earned back from other optimizations to FEZ and FNA, which I'll be talking about in the coming weeks. So stay tuned if you're wondering how we're adding stuff like this without making the game slower than the last version.

Oh, and DualShock 4 Light Bar support is going in the Linux version officially:


Next week we'll be talking about why even a pixel art game should have a nice, beefy graphics options menu.

Further reading/viewing, until next Friday:

Mike Acton - Data-Oriented Design in C++: https://www.youtube.com/watch?v=rX0ItVEVjHc

SDL Wiki - SDL_GameController API: https://wiki.libsdl.org/CategoryGameController

DualShock 4 Light Bar Support Documentation: http://pcgamingwiki.com/wiki/Engine:FNA#DualShock_4_Light_Bar_Support
Shared publiclyView activity