MegaZeux Joystick Support Guide

MegaZeux 2.93 — December 31st, 2023

This guide contains all current information for joystick mapping and Robotic support for using joysticks.


  1. General Info
    1. A typical controller layout
    2. List of available actions
  2. Config Usage and Examples
    1. Examples for specific games
    2. Adding/modifying controller support
    3. Config options in versions prior to 2.92
  3. Robotic Usage and Examples
  4. Controller Layouts for Console Ports
    1. NDS and 3DS
    2. PSP
    3. Switch
    4. Wii
    5. GP2X
  5. License

General Info

Joystick support in MegaZeux is primarily handled through abstracted buttons and axes called actions. These actions correspond primarily to XInput controls in both name and general physical location on a controller. Actions can interact directly with the UI elements of MegaZeux and may be used by MegaZeux games.

Actions can be used by games in two ways. The simplest method is to bind a key to a given action via a game config file (see Config Usage and Examples). When the joystick action is pressed/released in game, MegaZeux will simulate the press/release of the bound key. Each action has a default game key bound to it initially (see the list of available actions below). Alternatively, the simulated key behavior can be disabled through Robotic and the pressed/released status of an action may be read directly with a counter (see Robotic Usage and Examples).

A Typical Controller Layout

This figure shows most available actions and a typical placement of them on controllers for most platforms:

Diagram of a typical XInput-like controller with buttons and axes labeled. lshoulder rshoulder ltrigger rtrigger axis_ltrigger axis_rtrigger up left right down select start y x b a axis_lx axis_ly lstick axis_rx axis_ry rstick

NOTE: Not all controllers have the same layout or support all of the same controls; the default layout for a given controller depends on the mapping provided by SDL or gamecontrollerdb.txt, if any. See Adding/modifying controller support for more info on getting new controllers to work in SDL and tweaking existing controllers.

List of Available Actions

The available joystick actions, their UI behavior, and their default gameplay keybindings are:

Name Title behavior Window behavior Default gameplay key
up Element/​cursor up key_up (movement)
down Element/​cursor down key_down (movement)
left Element/​cursor left key_left (movement)
right Element/​cursor right key_right (movement)
a Play game Select key_space (shoot)
b Main menu Cancel key_delete (bomb)
x Load world Select char (text) key_return (game menu)
y Reload save Backspace (text) key_s (Caverns: spells)
start Play game Select Game menu1
select Main menu Cancel Game menu1
lshoulder Settings Next element key_insert (bomb type)
rshoulder2 Settings Settings3 key_p (Caverns: altar)
ltrigger2 Load world Page up key_f3 (save game)
rtrigger2 Reload save Page down key_f4 (load save)
lstick2 Home
rstick2 End

The following actions are available only through Robotic or are mostly similar to other actions listed above:

Name Description
l_up, l_down, l_left, and l_right2 Directional actions for the left analog stick. Same behavior as the dpad actions by default.
r_up, r_down, r_left, and r_right2 Directional actions for the right analog stick. Same behavior as the dpad actions by default.
axis_lx and axis_ly2 X and Y axes of the left stick (counter only).
axis_rx and axis_ry2 X and Y axes of the right stick (counter only).
axis_ltrigger and axis_rtrigger2 Axes for the left and right triggers (counter only).

1Gives access to most UI menus such as saving, loading, exiting the game, and settings. This behavior overrides any keys assigned to this action unless both ENTER_MENU and ESCAPE_MENU are disabled. This occupies both start and select right now because some controllers may lack one of these buttons.

2The rshoulder action is replaced by some consoles with a hardcoded button to open an onscreen keyboard. The availability of some other actions may vary between consoles and controllers (for example, some controllers may have a left analog stick but no dpad, and many controllers lack the stick press lstick and rstick actions).

3This behavior currently only works on some windows, such as the main menu and game menu.

Config Usage and Examples

Creating a game config file to give a game compatibility with joystick actions is very easy and, best of all, requires no modification to older games. A game config file is a text file that shares the same filename as a world but with the ".cnf" extension instead of ".mzx" (for example, the the config file for CAVERNS.MZX would be CAVERNS.CNF). Joystick config file options can be used in this file just as they would be in the regular config file, but they'll only affect their given world.

The main config option worth noting for the purpose of configuring a game.cnf file is


where # is the joystick number (1-16), ACTION is the name of a joystick action, and KEY is either a key number or a string in the format of key_NAME (see keycodes.html for key_pressed numbers and/or key names to use here). For example:

joy1.lshoulder = key_space

would assign the space key to the lshoulder action of joystick 1. Since up to 16 joysticks can be used with MegaZeux, it may be useful to assign the same action keybinding to multiple joysticks. This can be done by substituting the joystick number with a range. For example:

joy[1,16].lshoulder = key_space

will assign the lshoulder action on every controller to the space key.

Examples for Specific Games

Forest of Ruin

The default config mostly works for this game, but it would be nice to be able to switch weapons (and start is good enough for the menu in this game, which frees up x):

joy[1,16].x = key_w


red also mostly works with the default config thanks to x being bound to key_return. For convenience, we'll go ahead and bind y to key_return too:

joy[1,16].y = key_return

Demon Earth

Demon Earth uses different keys for attacking and uses space to hold the player's current facing direction. Because of this, it's useful to put the attack keys on the face buttons and space on the shoulders. Note: because Demon Earth uses swap worlds, you may need to create a game.cnf file for each world.

joy[1,16].a = key_a joy[1,16].b = key_s joy[1,16].x = key_return joy[1,16].y = key_return joy[1,16].lshoulder = key_space joy[1,16].rshoulder = key_space


Comments can be added to explain the in-game function of each key:

# Shoot down; change to B for some consoles # Shoot right; change to A for some consoles # Shoot left; change to Y for some consoles # Shoot up; change to X for some consoles joy[1,16].a = key_s joy[1,16].b = key_d joy[1,16].x = key_a joy[1,16].y = key_w # Shoot up # Shoot down # Shoot left # Shoot right joy[1,16].r_up = key_w joy[1,16].r_down = key_s joy[1,16].r_left = key_a joy[1,16].r_right = key_d # Select menu, bomb joy[1,16].lshoulder = key_space joy[1,16].rshoulder = key_space joy[1,16].ltrigger = key_space joy[1,16].rtrigger = key_space

Adding/Modifying Controller Support

Most platforms supported by MegaZeux (excluding consoles) rely on SDL 2. Controller support in MegaZeux for these platforms is based on SDL's game controller API, and any controller supported by the game controller API is automatically detected by MegaZeux. However, many controllers are still missing from SDL and/or gamecontrollerdb.txt.

It should be possible to map most missing USB or Bluetooth controllers, and several utilities for this purpose are available at the SDL_GameControllerDB GitHub repository. When used, these utilities will produce a mapping string that can be added to MZX using the following config setting:

gamecontroller_add = 030000005e0400008e02000000007801,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,

(note: it must be a single line). This will override any existing config for your controller (in either SDL or gamecontrollerdb.txt) with your new mapping. It might also be a good idea to thoroughly test your new mapping and send it to an MZX developer or submit it to SDL or SDL_GameControllerDB yourself if it's a controller that is missing or if the mapping in the database is unusable/broken.

The way MZX uses the SDL game controller API to generate controller mappings can be further configured globally with the following settings:

gamecontroller.SDLBUTTON = act_NAME gamecontroller.+SDLAXIS = act_NAME gamecontroller.-SDLAXIS = act_NAME

The first will assign an SDL game controller button to the MZX action NAME. The second will assign the positive direction of an SDL axis to NAME; the third does the same, for the negative direction. A complete list of SDL buttons that can be customized is in the config file.

Finally, if you'd like to go back to the old and bad joystick support for some reason, you can turn off automatic mappings altogether via the config file:

gamecontroller_enable = 0

Config Options in Versions Prior to 2.92

MZX versions prior to 2.92 do not have access to any joystick counters, nor to actions in general. Only the following config options are available. The use of these options in versions 2.92 and up is not recommended.

Versions prior to 2.92 are also not capable of using joystick index ranges or binding key names and must use key_pressed values directly. They also do not distinguish global config from game config, meaning a game.cnf file that uses these settings will affect all UIs in MZX permanently until another game.cnf file is loaded.

Most port pad.config files still use these config settings as the physical location of each button/axis/hat is already known and will not change.

joyXbuttonY = A

This will assign button Y (1-256) on joystick X (1-16) to key A.

In versions 2.92 and up, A can also be a key name in the format of key_NAME or an action name in the format of act_NAME. Additionally, X can be a range, e.g. joy[Z,W]buttonY.

joyXaxisY = A, B

This will assign axis Y (1-16) on joystick X (1-16) to keys A and B. The negative direction of the axis will be assigned to A and the positive direction will be assigned to B.

In versions 2.92 and up, A and B can also be key names in the format of key_NAME or action names in the format of act_NAME. Additionally, X can be a range, e.g. joy[Z,W]axisY.

joyXhat = A, B, C, D

This will assign the hat of joystick X (1-16) to keys A, B, C, and D. Up will be assigned to A, down will be assigned to B, left will be assigned to C, and right will be assigned to D.

In versions 2.92 and up, A/B/C/D can also be key names in the format of key_NAME or action names in the format of act_NAME. Additionally, X can be a range, e.g. joy[Z,W]hat.

Robotic Usage and Examples

Joystick presses can be read directly from Robotic via counters. First, however, it might be useful to turn off the simulated key press behavior for joysticks that is enabled by default:

set "joy_simulate_keys" to 0

The joy#active counter returns 1 if a joystick is active, 0 if it is not active, and -1 if the provided joystick index is invalid. You can use it if you need to check to see how many joysticks are active or if any are active:

set "num_active" 0 loop start if "joy&loopcount&active" = 1 then "#is_active" loop for 15 * "&num_active& controllers are active." end : "#is_active" inc "num_active" by 1 goto "#return"

The joy#.NAME counter is probably more useful, however. For action NAME, this counter will return the value of that action for a given joystick number, or -1 if either the joystick number or action name are invalid. For button actions, the value is 1 if pressed and 0 if not pressed. For axis actions, the value can be anywhere between -32768 and 32767, though trigger axes are more likely to be between 0 and 32767.

The following example moves a player sprite around with an analog stick:

lockplayer set "spr0_refx" to "playerx" set "spr0_refy" to "playery" set "spr0_width" to 1 set "spr0_height" to 1 set "spr0_unbound" to 1 put c?? Sprite p00 at 316 168 : "l" inc "spr0_x" by "('joy1.axis_lx'/2500)" inc "spr0_y" by "('joy1.axis_ly'/2500)" wait for 1 goto "l"

This longer example implements a simple player robot that can move around or shoot by holding the a button and pressing a direction:

lockplayer : "l" if "joy1.a" = 1 then "shoot" if "joy1.up" = 1 then "go_up" if "joy1.down" = 1 then "go_down" if "joy1.right" = 1 then "go_right" if "joy1.left" = 1 then "go_left" wait for 1 goto "l" : "go_up" go NORTH for 1 goto "l" : "go_down" go SOUTH for 1 goto "l" : "go_right" go EAST for 1 goto "l" : "go_left" go WEST for 1 goto "l" : "shoot" if "joy1.up" = 1 "shoot_up" if "joy1.down" = 1 "shoot_down" if "joy1.right" = 1 "shoot_right" if "joy1.left" = 1 "shoot_left" wait for 1 goto "l" : "shoot_up" sfx 28 shoot NORTH wait for 2 goto "l" : "shoot_down" sfx 28 shoot SOUTH wait for 2 goto "l" : "shoot_right" sfx 28 shoot EAST wait for 2 goto "l" : "shoot_left" sfx 28 shoot WEST wait for 2 goto "l"

The above example can be modified to check the axes, too:

... if "joy1.up" = 1 then "go_up" if "joy1.l_up" = 1 then "go_up" if "joy1.r_up" = 1 then "go_up" if "joy1.down" = 1 then "go_down" if "joy1.l_down" = 1 then "go_down" if "joy1.r_down" = 1 then "go_down" if "joy1.right" = 1 then "go_right" if "joy1.l_right" = 1 then "go_right" if "joy1.r_right" = 1 then "go_right" if "joy1.left" = 1 then "go_left" if "joy1.l_left" = 1 then "go_left" if "joy1.r_left" = 1 then "go_left" ...

Ideally, a game should work with both keyboard and rather than joystick inputs alone. However, if you do make a game that is playable only with a joystick, it should probably indicate clearly that a joystick is required.

Controller Layouts for Console Ports

Diagrams of the default layouts for the console platforms supported by MegaZeux are provided here. These layouts can be further configured for each platform by modifying the "pad.config" file that comes with them.

NDS and 3DS

The NDS and 3DS ports share the same layout; the only difference is that the 3DS port has extra controls, particularly the New 3DS (note the New 3DS circle stick is reserved for future use and can't be mapped).

Neither console maps axis_rx, axis_ry, axis_ltrigger, axis_rtrigger, lstick, or rstick. The right shoulder button is hardcoded to open the onscreen keyboard, so rshoulder is unmapped as well.

Furthermore, the NDS doesn't have the circle pad (axis_lx, axis_ly) and both the NDS and original 3DS are missing ZL (ltrigger) and ZR (rtrigger).

Diagram resembling an NDS or 3DS, with buttons and axes labeled. axis_lx axis_ly up, down, left, right a b x y start select lshoulder rshoulder (keyboard) ltrigger rtrigger


The PSP port does not map axis_rx, axis_ry, axis_ltrigger, axis_rtrigger, ltrigger, rtrigger, lstick, or rstick. No button is reserved for a keyboard as the PSP port supports an infrared keyboard.

Diagram resembling a PSP, with buttons and axes labeled. lshoulder rshoulder axis_lx axis_ly up, down, left, right a b x y select start


The Nintendo Switch port uses SDL 2 and thus has mappings built into SDL. However, SDL default mappings for a, b, x, and y correspond to the XInput positions for these buttons, which Nintendo console users generally don't want. Because of this, the Switch port also ships with a default pad.config that reconfigures the mapping of a, b, x, and y globally for all SDL controllers:

gamecontroller.a = act_b gamecontroller.b = act_a gamecontroller.x = act_y gamecontroller.y = act_x

Furthermore, the Switch port may not map axis_ltrigger or axis_rtrigger, and if it does, they probably only return a value of either 0 or 32767 (this needs to be verified).

Diagram resembling a Switch with buttons and axes labeled. ltrigger lshoulder rtrigger rshoulder start select axis_lx axis_ly lstick axis_rx axis_ry rstick up down left right a b x y


Button availability for the Wii port varies given which controller or extension controller is being used. Regardless of the available controllers, however, no Wii layout maps lstick or rstick. No buttons are reserved for a keyboard in any layout as the Wii has multiple built-in USB ports. The home button is hardcoded to select when present.

Extensions not otherwise listed below are the Guitar Hero 3 extension controller (supported by MegaZeux) and the Wii Balance Board and Wii Motion Plus (not supported by MegaZeux).

Wii Remote (no extension)

The mapping for this layout is fairly arbitrary and may change in future versions (particularly if start/select become more usable by games). This layout has no axes or mappings for ltrigger, rtrigger, or start. This layout assumes the Wii Remote is held horizontally like an NES controller.

Diagram resembling a Wii Remote with buttons labeled. lshoulder rshoulder up down left right x select y b a

Wii Remote (with nunchuck)

The mapping for this layout is fairly arbitrary and may change in future versions (particularly if start/select become more usable by games). This layout has no mappings for start, axis_rx, axis_ry, axis_ltrigger, or axis_rtrigger. This layout assumes the Wii Remote is held like a remote in one hand and the nunchuck is held by the other hand.

Diagram resembling a Wii Remote and nunchuck, with buttons and axes labeled. lshoulder ltrigger axis_lx axis_ly up down left right rtrigger rshoulder y select x b a

Wii Classic Controller

This layout maps all buttons and axes aside from the aforementioned lstick and rstick. Input from the Wii Remote is ignored in this configuration (aside from pointing/clicking). Note that L/R are the triggers as they are analog and ZL/ZR are the shoulders (opposite from the New 3DS layout). This is the recommended controller for use with the Wii port.

The NES and SNES classic controllers currently select this layout as well, but (obviously) several buttons will be missing. It is unknown currently whether libogc can/will distinguish between these.

Diagram resembling a Wii Classic Controller with buttons and axes labeled. ltrigger axis_ltrigger rtrigger axis_rtrigger lshoulder rshoulder up, down, left, right select select start x y a b axis_lx axis_ly axis_rx axis_ry

Gamecube Controller

This layout maps most buttons but is notably lacking a button for select and one shoulder button. Currently lshoulder is mapped to Z and rshoulder is left unmapped, as lshoulder is arguably more useful by default.


Some buttons are unusually mapped on the GP2X due to its non-standard layout. The a, b, x, and y actions are assigned using their Nintendo positions rather than the GP2X button names. While the GP2X appears to have an analog stick, it is actually only 8-directional. However, some models have a stick press button, which is mapped to lstick. The volume buttons are also mappable and assigned to ltrigger and rtrigger (for lack of anything better to put there).

The GP2X does not map axis_lx, axis_ly, axis_rx, axis_ry, axis_ltrigger, axis_rtrigger, or rstick. No button is reserved for a keyboard as the GP2X has a built-in USB port.

Diagram resembling a GP2X with buttons and axes labeled. lshoulder rshoulder up, down, left, right, lstick ltrigger rtrigger a b x y select start


Copyright © 2019-2023 Lachesis —

Permission to use, copy, modify, and distribute this document for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.