Joysticks tutorial

This is going to be a fairly brief tutorial on handling joystick input. AGen enumerates all joysticks attached to the user's system and stores them in a global Lua table named "joysticks". The first device from this table is also stored in a global reference named "joystick". If the global "joystick" reference is nil then we can assume that there are no working controllers attached to the user's system.

Joystick axes

Each joystick object has an xaxis, yaxis and zaxis property. These axes represent the state of the controller's d-pad or analog sticks. The value of each axis property is a decimal number ranging between -1 to 1. To demonstrate how the axes are used, we will write a short script that continually polls the global joystick object.

display:create ( "Joysticks Tutorial", 800, 600, 32, true ) 

assert ( joystick, "No joystick detected" )

-- draw a sprite with a blue square
square = Sprite ( )
square.canvas:rectangle ( 30, 30 )
square.canvas:set_fill_style ( BLUE, 1 )
square.canvas:fill ( )
display.viewport:add_child ( square )

timer = Timer ( )
timer:start ( 16, true )
timer.on_tick = function ( timer )
  local d = timer:get_delta ( )

  -- move the square using the x and y joystick axis
  square:change_position ( joystick.xaxis * d, joystick.yaxis * d )

  -- rotate the square using the z axis
  square:change_rotation ( joystick.zaxis * d )
end
Download:  joysticks.lua




Buffering joystick input

So how do you program a combo system in your game? First, your script will need to keep a list of the buttons which the player has entered. This is called 'buffering'. Usually, axis movements are also stored in the input buffer. You can then iterate the buffer and look for a combo sequence involving both axis movements and joystick presses.

Joystick buttons are identified in AGen by numeric values stored in the globals named JBUTTON_1 to JBUTTON_16. In addition, there are four extra values representing the POV hat (or D-pad) of the joystick: JDPAD_EAST, JDPAD_NORTH, JDPAD_WEST and JDPAD_SOUTH.

Notice that moving the analog axis of a joystick is not reported as a button press. We will change this in Lua by introducing six globals: JAXIS_XPLUS, JAXIS_XMINUS, JAXIS_YPLUS, JAXIS_YMINUS, JAXIS_ZPLUS and JAXIS_ZMINUS. These new globals will represent movements of the joystick's analog axis. Each of them is assigned a number starting from 132 since values between 112 and 131 are already used for joystick buttons and the POV hat.

-- input buffer
joystick.buffer = {}

-- buttons states
joystick.state = {}

-- button string names (for logging)
buttons =
{
    'button 1', 'button 2', 'button 3', 'button 4',
    'button 5', 'button 6', 'button 7', 'button 8',
    'button 9', 'button 10', 'button 11', 'button 12',
    'button 13', 'button 14', 'button 15', 'button 16',
    'pov north', 'pov east', 'pov south', 'pov west',
    'x-axis+', 'x-axis-',
    'y-axis+', 'y-axis-',
    'z-axis+', 'z-axis-'
}

-- axes implemented as buttons
JAXIS_XPLUS = 132
JAXIS_XMINUS = 133
JAXIS_YPLUS = 134
JAXIS_YMINUS = 135
JAXIS_ZPLUS = 136
JAXIS_ZMINUS = 137

Each joystick object has an "is_down" function that tells you if one of its button is currently pressed. We will be writing our own version called "is_down2" which will also check if a particular axis is held down.

-- joystick axes null zone
nullzone = 0.5

-- tests if an axis or a button is pressed
joystick.is_down2 = function ( joystick, button )
  if button == JAXIS_XPLUS then
    return joystick.xaxis > nullzone
  elseif button == JAXIS_XMINUS then
    return joystick.xaxis < -nullzone
  elseif button == JAXIS_YPLUS then
    return joystick.yaxis > nullzone
  elseif button == JAXIS_YMINUS then
    return joystick.yaxis < -nullzone
  elseif button == JAXIS_ZPLUS then
    return joystick.zaxis > nullzone
  elseif button == JAXIS_ZMINUS then
    return joystick.zaxis < -nullzone
  end
  return joystick:is_down ( button )
end

The last part of the script polls the joystick and stores the state information for each button. A state of "true" means that the associated button is currently pressed and "false" means that it is released. Using this information, the script detects any state changes and print them on the screen.

timer = Timer ( )
timer:start ( 16, true )
timer.on_tick = function ( timer )
  -- update buffer and button states
  for i = JBUTTON_1, JAXIS_ZMINUS, 1 do
    local state = joystick:is_down2 ( i )
    if state == true and joystick.state[i] == false then
      local event = { 'press', i }
      table.insert ( joystick.buffer, event )
    elseif state == false and joystick.state[i] == true then
      local event = { 'release', i }
      table.insert ( joystick.buffer, event )
    end
    joystick.state[i] = state
  end

  -- limit the joystick buffer to 20 events
  while #joystick.buffer > 20 do
    table.remove ( joystick.buffer, 1 )
  end

  -- log the contents of the buffer on screen
  sprite.canvas:clear ( )
  for i, v in pairs ( joystick.buffer ) do
    local button = buttons[v[2] - JBUTTON_1 + 1]
    sprite.canvas:move_to ( -400, 300 - i * 16 )
    sprite.canvas:write ( v[1] .. ':' .. button )
  end
end
Download:  joysticks2.lua