It’s an exciting time to be a mobile app developer. Through the magic of mobile and Bluetooth support on devices, it’s now possible to use HID (Human Interface Device) controllers to play games, in addition to the standard touchscreen and accelerometer. While this isn’t yet “utopia,” you can do some really cool things with your wireless device and modern technology.

Key event framework

For HID controllers, we decided to build upon the existing key event framework. Thus, programming for controllers won’t be too much different — no plugins are required for HID-compliant controllers, nor do you need to require() anything special.

local function onKeyEvent( event )

    local phase = event.phase
    local keyName = event.keyName

    if ( phase == "down" ) then
        if ( keyName == "buttonA" ) then
            return true
        elseif ( keyName == "buttonB" ) then
            return true
        elseif ( keyName == "buttonX" ) then
            return true
        elseif ( keyName == "buttonY" ) then
            return true
        elseif ( keyName == "up" ) then  -- D-Pad up
            return true
        elseif ( keyName == "down" ) then  -- D-Pad down
            return true
        elseif ( keyName == "left" ) then  -- D-Pad left
            return true
        elseif ( keyName == "right" ) then  -- D-Pad right
            return true
        elseif ( keyName == "buttonSelect" ) then  -- Select/Back button
            return true
        elseif ( keyName == "buttonStart" ) then  -- Start/Home button
            return true
        elseif ( keyName == "buttonMode" ) then  -- Power on/off button
            return true
        elseif ( keyName == "leftShoulderButton1" ) then  -- Top-left button
            return true
        elseif ( keyName == "rightShoulderButton1" ) then  -- Top-right button
            return true
        elseif ( keyName == "leftShoulderButton2" ) then  -- Bottom-left button
            return true
        elseif ( keyName == "rightShoulderButton2" ) then  -- Bottom-right button
            return true
        elseif ( keyName == "leftJoyStickButton" ) then  -- Pressing down on the left joystick button
            return true
        elseif ( keyName == "rightJoystickButton" ) then  -- Pressing down on the right joystick button
            return true

        -- Now deal with Android standard buttons
        elseif ( keyName == "back" ) then  -- Hardware back button
            return true
        elseif ( keyName == "volumeUp" ) then
            return true
        elseif ( keyName == "volumeDown" ) then
            return true
        end
        print( "Done with keys" )
    end

    -- IMPORTANT! Return false to indicate that this app is NOT overriding the received key
    -- This lets the operating system execute its default handling of the key
    return false
end

Runtime:addEventListener( "key", onKeyEvent )

Of course, we haven’t provided any code other than returning true because it all depends on what you want to do. The button names may also vary from controller to controller — for example, they may not always be called "buttonA" or "buttonX", so if you are working with a new controller, it’s useful to check the value of the buttons that are pressed.

Button phases

Note that buttons/keys produce two phases, much like touch handlers. Those two phases are "up" and "down", where "down" means the user has pressed the button and "up" means they have released it. Like touch handlers, you do not “continuously” get events — once you press the button and get the "down" phase, you will not get another event until the button is released, thus dispatching the "up" phase.

Similar to touch events, it’s generally best practice to use the "down" state for time-sensitive actions like movement or firing weapons. However, for UI elements that typically respond like a mouse click, or touch events that trigger when the touch is complete, the "up" state may be warranted.

Putting it together

Let’s look at an example movePlayer() function using event-based buttons:

local player1 = display.newRect( 0, 0, 10, 30 )
player1.x = 30
player1.y = display.contentCenterY
player1:setFillColor( 1, 0, 0 )
player1.isMoving = false
player1.direction = "up"

local player2 = display.newRect( 0, 0, 10, 30 )
player2.x = display.contentWidth-30
player2.y = display.contentCenterY
player2:setFillColor( 0, 0, 1 )
player2.isMoving = false
player2.direction = "up"

local moveSpeed = 2

local function movePlayer( player, event )

    if ( player == nil ) then
        return
    end

    if ( player.isMoving ) then

        if ( player.direction == "down" ) then
            player.y = player.y + moveSpeed
            if ( player.y > (display.contentHeight-20) ) then
                player.y = display.contentHeight-20
            end
        else
            player.y = player.y - moveSpeed
            if ( player.y < 20 ) then
                player.y = 20
            end
        end
    end
end

local function onKeyEvent( event )

    local thisPlayer

    if ( event.device.descriptor == "Joystick 1" ) then
        thisPlayer = player1
    else
        thisPlayer = player2
    end

    if ( event.keyName == "up" ) then

        thisPlayer.direction = "up"
        if ( event.phase == "down" ) then
            thisPlayer.isMoving = true
        else
            thisPlayer.isMoving = false
        end
        return true

    elseif ( event.keyName == "down" ) then

        thisPlayer.direction = "down"
        if ( event.phase == "down" ) then
            thisPlayer.isMoving = true
        else
            thisPlayer.isMoving = false
        end
        return true
    end

    return false
end
Runtime:addEventListener( "key", onKeyEvent )

local function onFrame()
    movePlayer( player1 )
    movePlayer( player2 )
end
Runtime:addEventListener( "enterFrame", onFrame )

Detecting controllers

Most likely, you will also need to know if the controller is attached and also know when it's detached, or if a new controller comes into play. This is handled using the inputDevice type.

-- Fetch all input devices currently connected to the system
local inputDevices = system.getInputDevices()
local controllersActive = {}

-- Traverse all input devices
for deviceIndex = 1,#inputDevices do

    -- Fetch the input device's information
    print( deviceIndex, "canVibrate", inputDevices[deviceIndex].canVibrate )
    print( deviceIndex, "connectionState", inputDevices[deviceIndex].connectionState )
    print( deviceIndex, "descriptor", inputDevices[deviceIndex].descriptor )
    print( deviceIndex, "displayName", inputDevices[deviceIndex].displayName )
    print( deviceIndex, "isConnected", inputDevices[deviceIndex].isConnected )
    print( deviceIndex, "type", inputDevices[deviceIndex].type )
    print( deviceIndex, "permenantid", tostring(inputDevices[deviceIndex].permanentId) )
    print( deviceIndex, "andoridDeviceid", inputDevices[deviceIndex].androidDeviceId )

    local index
    if ( event.device.descriptor == "Joystick 1" ) then
        index = 1
    elseif ( event.device.descriptor == "Joystick 2" ) then
        index = 2
    elseif ( event.device.descriptor == "Joystick 3" ) then
        index = 3
    elseif ( event.device.descriptor == "Joystick 4" ) then
        index = 4
    end
    controllersActive[index] = inputDevices[deviceIndex].isConnected
end

Here are a few notes regarding controller detection:

  • For multiplayer apps, the event.descriptor (i.e. "Joystick 1", "Joystick 2") can be used as a unique key that binds a particular controller to a player. This will scan for active devices and set up a table of devices that are currently connected.
  • You should watch for connections and disconnections during gameplay and handle "losing" a controller or adding a controller. You may also want to pause the game if a controller drops out.
  • You might want to hide any UI elements that are specific to one controller, or hide "touchscreen" features when there are controllers present.

And here's some sample code to handle this:

local function onInputDeviceStatusChanged( event )

    -- Handle the input device change
    if ( event.connectionStateChanged ) then

        print( event.device.displayName .. ": " .. event.device.connectionState )

        local index
        if ( event.device.descriptor == "Joystick 1" ) then
            index = 1
        elseif ( event.device.descriptor == "Joystick 2" ) then
            index = 2
        elseif ( event.device.descriptor == "Joystick 3" ) then
            index = 3
        elseif ( event.device.descriptor == "Joystick 4" ) then
            index = 4
        end
        controllersActive[index] = inputDevices[deviceIndex].isConnected
    end
end

-- Add the input device status event listener
Runtime:addEventListener( "inputDeviceStatus", onInputDeviceStatusChanged )

Note that these status changes will not register if the app is suspended. It's your responsibility to poll the controllers if you're resuming back to gameplay state.

Conclusion

Most game controllers offer both digital inputs (buttons) and analog inputs called sticks or joysticks. Because the typical joystick moves both up and down and left and right, we measure the movement based on which "axis" it's moving on. An analog stick moving up and down will be measured along its Y axis, while side-to-side movement is measured on the X axis.

Axis input will be covered in a future blog post, but it's important to know that many game controllers will map the left analog stick to the D-Pad. As such, moving the left stick will frequently generate "up", "down", "left" and "right" events. Also, the bottom shoulder buttons are considered "analog" inputs, but they will generate "button" events when they are squeezed past a certain threshold. If your app needs to know precisely what value those triggers are producing, you would use the axis method of measuring things, for fine-tuned control.


  1. This is awesome news, but will HID controller support also come to the Simulator as well soon? (ie So we can plug a gamepad into our Mac and use it to test our game in the Simulator, without having to build for the device every time we make a change?)

      • Chris & Dave as we mentioned above, we will be covering axis support (analog stick, x, y, etc.) in a future post. This was was rather long as it was and I’ve just had my OUYA for a few days, so I’ve got to get a few more ducks in a row before I can write that up. The features are there in the Daily Build API docs

        http://docs.coronalabs.com/daily/api/

        These features should be rolling into the public build pretty soon as such the regular docs will be updated with the info. If you want to take a peek before we blog about it, you can look for “axis” under the Event’s section.

  2. This is FANTASTIC NEWS! Been looking forward to when Corona can build out to OUYA. Rob – this does mean that we’ll be looking forward to an update to your ultimate config and device detection code to include the configs/etc for OUYA. Do you happen to know when the next public release will be?

  3. Downloaded the Stable build today, and tried connecting my PS3 controller to my Mac to test this out, seemed like I had my controller connected to the Mac successfully, but couldn’t figure out how to get it working with Corona. Has anyone else done this successfully? If so how did you get it to work?

  4. When will add support for keyboard control for mac simulator? Now it is very inconvenient to develop an application for android, ouya.

    Back in iOS 7 appeared maintain joysticks, whether you plan to add support?

    • Anton, we do support limited keyboard functionality on the OS-X simulator now. In fact there was a blog post last Tuesday about a way to use a Mac App to convert HID controller input to keystrokes that were compatible with code you might write for an OUYA.

      Philip, I looked up the iCade and it appears to just need Keyboard control. I don’t know if we have keyboard support for iOS yet or not.

      Rob

  5. Does anyone else experience really bad frame rate drops when using OnKeyEvent heavily? I’m building an OUYA game and everytime a button is pressed, even if it doesn’t do anything, framerate drops to 10 or so. :(

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>