Archive for the ‘Roguelike design’ Category

Diagonal movement with gamepads


I have been experimenting with joystick/gamepad support in TSL, starting with the recent 0.40. I don’t really expect anyone to play it using only a gamepad but it raises some interesting questions about interface design.

There has been much discussion over this the last few years. POWDER has been around for a long time and many more roguelikes are being adapted – or developed specifically for – handheld devices. They are limited to (or blessed with?) touchscreens, d-pads and a few high-visibility buttons. Developing control methods that do not require a keyboard – without dumbing the gameplay down – is a challenge.

I recently learned there is a TSL package for the Pandora (unfortunately not the latest version after I switched to the Allegro library, which there is currently no Pandora port of), so these concerns don’t seem entirely irrelevant.

TSL is designed as a very traditional roguelike that uses separate keys for everything; around 57 (!) last time I checked. Not all of them are strictly required to play the game though, and some are redundant. I have done some work already to reduce the need for memorization and make things more intuitive: the new inventory browser, on-screen shortcuts and various tweaks so the game will most of the time accept keys that just “make sense”.

TSL has had remappable keys for a long time (I use a Dvorak-like keyboard layout myself). It is also capable of reading input from both curses and Allegro, each with their quirks and incompatibilities (for example, I use hardcoded values for numpad in curses – the constants simply do not work on my setup!).

To make this somewhat manageable each input handler converts non-alphanumeric input to an internal “key token” format. This is the most “raw” form the game logic is ever exposed to, and most of the time it is passed directly to the keymap handler to look up what in-game action it is bound to.

This allows some useful abstraction. For example: escape, space and numpad zero are all assigned to the “cancel” action, and all prompts that can be cancelled will accept any of those keys. There are a few clashes where the raw key tokens take precedence, but almost everything is confined to the keymap code.

Reading joystick input in Allegro is really easy; I just need to catch a joystick event instead of a keyboard event. It was trivial to add key tokens for joystick buttons and bind them to actions within the game.

Joystick axes, however, are another matter. Joysticks have separate X and Y values that can be polled for the position of the stick, remaining at 0 when the stick is centered and yielding positive and negative values when swung either way. Remember the calibration screens in DOS games? They were there to detect the extremes of the analog joystick signal.

Even with modern operating systems and the introduction of digital hardware this scheme remains largely intact. A typical gamepad has a d-pad instead of a stick but will still pretend to have two axes. They are digital in the sense that they move between discrete values of -1, 0 and +1 without cable interference, but the computer treats it the same way it would an analog joystick.

The most basic solution would be to catch the events for the axes reaching max or min value and return these to the game logic as a “direction north/east/south/west” key token. However, since axis events arrive sequentially this would only allow movement in cardinal directions. TSL tactics often require extensive use of diagonals (I have long considered a special item or status that would restrict the player to only four directions – or making it a conduct!).

I came up with two solutions to this:

  • Trigger on button release. I call this “sloppy” mode. The event handler will keep a record of which directions have been pressed and merge these into a key token once the axes return to neutral position. For example: if you press left, let go, you will move west. If you press left and up, let go, you will move northwest. It also has a small tweak: if you press left and up, change your mind and release the left – wait a little while (about a second), the handler will forget that you had pressed left and only move you north.
  • You use the d-pad to aim. Pressing two directions will overlay them and a separate “step” button will trigger the movement. Releasing the d-pad does nothing. This might be prefered over the previous method, since only tapping out a long sequence of directions is both faster and causes less strain since you don’t need to move the thumb as much.

Admittedly, both these are a bit clumsy, but they work. Sloppy keys can also be enabled (and behave identically) for the directional keys on the keyboard – this offers a middle ground for anyone with a laptop (or other keyboard without numpad) that doesn’t want to use vi-keys.

One drawback is that this operates on such low level that it is entirely hidden from the logic code: even things like menu browsing will be affected. It would be possible to set a flag and temporarily switch the input method to detect extremes, making the game react instantly when a direction is held on the gamepad. However, always switching between the two could get very confusing. On gamepads with many buttons maybe two of them (like the L/R) could be designated for menu browsing.

There are a couple of things I must get in place before this becomes really useful, such as an in-game key mapping screen. I have had plans for it a long time, but haven’t written any code yet. It should use key tokens and be mostly hardware-independent, to allow rapid adjustment on any exotic hardware.

I also need to write some JRPG-style menu screens. Every action would need to be reachable from a single menu tree, in theory reducing the required number of buttons to only two or three plus directionals.

Footnote: I have been told there are gamepads with eight separate button switches for the d-pad, but I have not tried one myself. Even if this would be the electrical layout, I would assume they still pretend to have axes when talking to the OS, since that is what most games understand.