Controller control

If you want to add interactivity to your ROM, it’s vital to learn how to read input from your controller. There are essentially three libraries that you can use:

  • joypad.h – High-level library for controller reading. Used for handling the input of standard N64/GC controllers, best for 99% of cases. This is the one we’ll be covering in this page.
  • joybus.h – Low-level library alternative. Really should only be used for custom controllers that have bizarre input patterns.
  • controller.h – Legacy library that has been deprecated. It should still work, but it’s likely to be removed some time in the future. Check it out here.

Data structures

There are two data structures that you need to take note of. First is joypad_inputs_t which is the main controller input structure. This contains all the input that a controller provides to the N64. Within that structure is a subset called joypad_buttons_t which contains all the data for the digital buttons (everything except the joystick).

Let’s start with the main one. This one contains all the information necessary for handling controller input.

typedef struct __attribute__((packed)) joypad_inputs_s
{
	// Structure containing button presses (see next code block)
	joypad_buttons_t btn;

	// Value of the main joystick's X and Y axis
	// Can range from -128 to +127, but practically is -85 to +85
	int8_t stick_x;
	int8_t stick_y;

	// The rest of these are only for GameCube controllers
	// C-stick
	int8_t cstick_x;
	int8_t cstick_y;
	// Analogue triggers
	uint8_t analog_l;
	uint8_t analog_r;
} joypad_inputs_t;

joypad_buttons_t is a subset containing the data about the digital buttons which can be accessed either as part of joypad_inputs_t or it can be used on its own if you’re making a game that doesn’t use any analogue joystick input.

typedef union joypad_buttons_u
{
	// Raw button data as a 16-bit value.
	uint16_t raw;
	// Button data as callable bits
	struct __attribute__((packed))
	{
		unsigned a : 1;
		unsigned b : 1;
		unsigned z : 1;
		unsigned start : 1;
		unsigned d_up : 1;
		unsigned d_down : 1;
		unsigned d_left : 1;
		unsigned d_right : 1;
		unsigned y : 1; // Only for GC controllers
		unsigned x : 1; // Only for GC controllers
		unsigned l : 1;
		unsigned r : 1;
		unsigned c_up : 1;
		unsigned c_down : 1;
		unsigned c_left : 1;
		unsigned c_right : 1;
	};
} joypad_buttons_t;

Initialising/closing the subsystem

Staring and stopping the subsystem is very easy. Just call these functions to do it. Calling any joypad functions while not initialised will crash the game.

void joypad_init(void);
void joypad_close(void);

Identifying controllers

The first thing you should do before checking for any input is looking for controllers and identifying what they are.

To detect connected controllers, use the joypad_is_connected() function. It really is that simple.

typedef enum
{
	JOYPAD_PORT_1     = 0,
	JOYPAD_PORT_2     = 1,
	JOYPAD_PORT_3     = 2,
	JOYPAD_PORT_4     = 3,
} joypad_port_t;

bool joypad_is_connected(joypad_port_t port);

Next you need to figure out what is connected to that port. This is done with the joypad_get_style() to get a general idea of what is inserted or with the joypad_get_identifier() function to get a more precise answer.

// Get a general idea of what is in the controller port
typedef enum
{
	JOYPAD_STYLE_NONE = 0,
	JOYPAD_STYLE_N64,
	JOYPAD_STYLE_GCN,
	JOYPAD_STYLE_MOUSE,
} joypad_style_t;

joypad_style_t joypad_get_style(joypad_port_t port);

// Get the specific identifier for what is inserted into the port
joybus_identifier_t joypad_get_identifier(joypad_port_t port);

// This will return one of the following:
#define JOYBUS_IDENTIFIER_UNKNOWN               0x0000
#define JOYBUS_IDENTIFIER_NONE                  0xFFFF
#define JOYBUS_IDENTIFIER_N64_VOICE_RECOGNITION 0x0001
#define JOYBUS_IDENTIFIER_N64_RANDNET_KEYBOARD  0x0002
#define JOYBUS_IDENTIFIER_64GB_LINK_CABLE       0x0003
#define JOYBUS_IDENTIFIER_GBA_LINK_CABLE        0x0004
#define JOYBUS_IDENTIFIER_CART_RTC              0x0010
#define JOYBUS_IDENTIFIER_CART_EEPROM_4KBIT     0x0080
#define JOYBUS_IDENTIFIER_CART_EEPROM_16KBIT    0x00C0
#define JOYBUS_IDENTIFIER_N64_CONTROLLER        0x0500
#define JOYBUS_IDENTIFIER_N64_MOUSE             0x0200

// Gamecube controllers
#define JOYBUS_IDENTIFIER_MASK_PLATFORM         0x1800
#define JOYBUS_IDENTIFIER_PLATFORM_GCN          0x0800
#define JOYBUS_IDENTIFIER_MASK_GCN_CONTROLLER   0x0100
#define JOYBUS_IDENTIFIER_MASK_GCN_NORUMBLE     0x2000
#define JOYBUS_IDENTIFIER_MASK_GCN_WIRELESS     0x8000

Controller accessories

You can also run some additional checks to see if there are any accessories plugged into the controller (Controller Pak, Rumble Pak, etc). This is done pretty straightforwardly with the joypad_get_accessory_type() function.

typedef enum
{
	JOYPAD_ACCESSORY_TYPE_NONE = 0,
	JOYPAD_ACCESSORY_TYPE_UNKNOWN,
	JOYPAD_ACCESSORY_TYPE_CONTROLLER_PAK,
	JOYPAD_ACCESSORY_TYPE_RUMBLE_PAK,
	JOYPAD_ACCESSORY_TYPE_TRANSFER_PAK,
	JOYPAD_ACCESSORY_TYPE_BIO_SENSOR,
	JOYPAD_ACCESSORY_TYPE_SNAP_STATION,
} joypad_accessory_type_t;

joypad_accessory_type_t joypad_get_accessory_type(joypad_port_t port);

Reading from the controllers

This is the most important part since it is what will be in the main loop of your game and it’s what you’ll be interacting with most of the time.

These first two functions return a data structure with the current state of the controller. You can pick either the whole controller or just the buttons as described in the data structures section above.

// Get all controller info
joypad_inputs_t joypad_get_inputs(joypad_port_t port);
// Get button data only
joypad_buttons_t joypad_get_buttons(joypad_port_t port);

The next six functions are a bit more like three sets of two. They return the structure with:

  • Presses (buttons that are active that perviously werent)
  • Holds (buttons that are active that were also active previously)
  • Releases (buttons that were active, but are no longer active)

Each of these modes can then be applied to both of these:

  • Buttons – using the joypad_buttons_t structure
  • Axis read – you input one (or more) of the ‘D-pads’, and get a direction of the action in return

Reading the axis

For axis reads, you need to pick the ‘axis’ to read. This can be interpreted as either the D-pad on the left of the controller, the joystick or the C-buttons.

Determining 8-way direction in any pair of axes

Used in this function:

joypad_8way_t joypad_get_direction(joypad_port_t port, joypad_2d_t axes);

To pick the pair of axes we’ll be reading from to determine direction, we need to use this enum:

typedef enum
{
	// Analog stick 2D axes.
	JOYPAD_2D_STICK = (1 << 0),
	// D-Pad 2D axes.
	JOYPAD_2D_DPAD  = (1 << 1),
	// C buttons 2D axes
	JOYPAD_2D_C    = (1 << 2),
	// Left-Hand 2D axes: Analog stick or D-Pad.
	JOYPAD_2D_LH    = (JOYPAD_2D_STICK | JOYPAD_2D_DPAD),
	// Right-Hand 2D axes: Analog stick or C buttons.
	JOYPAD_2D_RH    = (JOYPAD_2D_STICK | JOYPAD_2D_C),
	// Any 2D axes: Analog stick, D-Pad, or C buttons.
	JOYPAD_2D_ANY   = (JOYPAD_2D_STICK | JOYPAD_2D_DPAD | JOYPAD_2D_C),
} joypad_2d_t;

The return value is going to be much simpler. It’s just an int of value 0-7 that points to the direction of movement where 0 is right and then it goes counter-clockwise from there. No button pressed would return a negative value.

typedef enum
{
	JOYPAD_8WAY_NONE	   = -1,
	JOYPAD_8WAY_RIGHT	  = 0,
	JOYPAD_8WAY_UP_RIGHT   = 1,
	JOYPAD_8WAY_UP		 = 2,
	JOYPAD_8WAY_UP_LEFT	= 3,
	JOYPAD_8WAY_LEFT	   = 4,
	JOYPAD_8WAY_DOWN_LEFT  = 5,
	JOYPAD_8WAY_DOWN	   = 6,
	JOYPAD_8WAY_DOWN_RIGHT = 7,
} joypad_8way_t;

Determining 2-way direction in any single axis

Used in these functions:

int joypad_get_axis_pressed(joypad_port_t port, joypad_axis_t axis);
int joypad_get_axis_released(joypad_port_t port, joypad_axis_t axis);
int joypad_get_axis_held(joypad_port_t port, joypad_axis_t axis);

And to determine which axis we’re choosing from, we need to use this enum:

typedef enum
{
	JOYPAD_AXIS_STICK_X  = offsetof(joypad_inputs_t, stick_x),
	JOYPAD_AXIS_STICK_Y  = offsetof(joypad_inputs_t, stick_y),
	JOYPAD_AXIS_CSTICK_X = offsetof(joypad_inputs_t, cstick_x),
	JOYPAD_AXIS_CSTICK_Y = offsetof(joypad_inputs_t, cstick_y),
	JOYPAD_AXIS_ANALOG_L = offsetof(joypad_inputs_t, analog_l),
	JOYPAD_AXIS_ANALOG_R = offsetof(joypad_inputs_t, analog_r),
} joypad_axis_t;

The return value will be:

  • -1 for left/down
  • 0 for neutral
  • +1 for right/up

The reading functions

With that in mind, here are the functions, in button/axis pairs, grouped by press type.

// Button pressed on this frame
joypad_buttons_t joypad_get_buttons_pressed(joypad_port_t port);
int joypad_get_axis_pressed(joypad_port_t port, joypad_axis_t axis);

// Button held since the previous frame
joypad_buttons_t joypad_get_buttons_held(joypad_port_t port);
int joypad_get_axis_released(joypad_port_t port, joypad_axis_t axis);

// Button released on this frame
joypad_buttons_t joypad_get_buttons_released(joypad_port_t port);
int joypad_get_axis_held(joypad_port_t port, joypad_axis_t axis);

// Returns the counter-clockwise direction in a 2-way axis
joypad_8way_t joypad_get_direction(joypad_port_t port, joypad_2d_t axes);

Rumble Pak

If you’re using a Rumble Pak, there is some functionality that helps you control it. Unlike future controllers, the N64 rumbling feature has only an on or off setting, so there isn’t a way of controlling how much it shakes, unless you activate it intermittently.

This can be done with the following functions. They’re fairly self-expanatory.

// Check if rumbling works on that port
bool joypad_get_rumble_supported(joypad_port_t port);

// Check whether rumble is active
bool joypad_get_rumble_active(joypad_port_t port);

// Activate/deactivate rumble. Set the 2nd parameter to true/false
void joypad_set_rumble_active(joypad_port_t port, bool active);

To use Control Paks, refer to the saving section on Controller Paks. For other accessories, you’ll have to use the joybus inteface which is out of scope for this page.

Search

Subscribe to the mailing list

Follow N64 Squid

  • RSS Feed
  • YouTube

Random posts