hid-vendorsIt’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 controllers to play games, in addition to the standard touchscreen and accelerometer.

To be clear, this isn’t yet utopia, but you can do some really cool things with your wireless device and modern technology. Before we get to the goodies, let’s talk about what we can do today, and what we will be able to do soon.

Current HID Devices

At the time of this writing, game controllers reside in the world of Android using a host of HID (Human Interface Device) controllers that are compatible with Android 3.1 or later. This isn’t just game controllers, but keyboard, mice, etc. That means that a hardware manufacturer could build a child-friendly controller/case inside which an Android phone or tablet could fit.

Yet the big news, to date, has come from four vendors: OUYA, Green Throttle, GameStick, and NVIDIA SHIELD. Green Throttle allows you to connect certain HDMI-compatible Android devices to a TV and turn them into a gaming console. You can even run it on some Android devices that do not connect to a TV, like the Google Nexus 7, to develop on or even use as a small console. In contrast, the OUYA, NVIDIA SHIELD, and GameStick are standalone gaming consoles. The OUYA is a small cube no more than 3 inches in size that plugs into your TV’s HDMI port and talks to HID-compatible controllers, including the OUYA-specific controller. GameStick goes even smaller, with the entire gaming console in a form factor not much bigger than a USB memory stick — it just plugs into and draws its power from the TV. All of these devices run a customized version of Android designed around a gaming console interface and they support an open development platform that lets indie developers and small studios build games to be played on them.

Corona Support

And now for the best part, at least from the Corona development standpoint: you can now build your own games for these platforms! Even non-game apps could be created for these devices. Over the past few months, Corona’s engineers have been rolling out HID support in phases as part of Daily Builds, available to Pro and Enterprise subscribers.

Green Throttle

Let’s start with Green Throttle who does a proprietary implementation for their controllers. To use this system, you need to include the Green Throttle plugin in your build.settings file:

settings =
{
   plugins =
   {
      --key is the name passed to Lua's 'require()'
      ["plugin.greenthrottle"] =
      {
         --required
         publisherId = "com.greenthrottle"
      },
   },
}

Then in your program, simply require the module:

local greenthrottle = require( "plugin.greenthrottle" )

Finally, code your input system similar to the following. Note that Green Throttle uses a polling system where you need to check for various button and stick states and then react to them.

local player1 = display.newRect( 0, 0, 10, 30 )
player1.x = 30
player1.y = display.contentCenterY
player1:setFillColor( 255, 0, 0 )

local player2 = display.newRect( 0, 0, 10, 30 )
player2.x = display.contentWidth - 30
player2.y = display.contentCenterY
player2:setFillColor( 0, 0, 255 )

local moveSpeed = 2

local function movePlayer( player, controller )

   if ( greenthrottle.getConnectedState( controller ) == false or player == nil ) then
      return
   end

   if ( greenthrottle.getButtonState( greenthrottle.BUTTON_STATE_HELD, controller, greenthrottle.BUTTON_DOWN ) ) then
      player.y = player.y + moveSpeed
      if ( player.y > (display.contentHeight-20) ) then
         player.y = display.contentHeight-20
      end
   elseif ( greenthrottle.getButtonState( greenthrottle.BUTTON_STATE_HELD, controller, greenthrottle.BUTTON_UP ) ) then
      player.y = player.y - moveSpeed
      if ( player.y < 20 ) then
         player.y = 20
      end
   end
end

local function onFrame()
   greenthrottle.startFrame()
   movePlayer( player1, greenthrottle.CONTROLLER_1 )
   movePlayer( player2, greenthrottle.CONTROLLER_2 )
end
Runtime:addEventListener( "enterFrame", onFrame )

OUYA, NVIDIA SHIELD, and GameStick

For OUYA, SHIELD, GameStick and other HID controllers, we decided to build upon the existing key framework that Android users have been using to listen for the hardware “back” button and volume controls. Thus, programming for controllers won’t be too much different — no plugins are required for HID compliant controllers, nor do you have to require() anything special.

local function onKeyEvent( event )

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

   if ( keyName == "buttonA" and phase == "down" ) then  --A button (O on OUYA, X on PS3)
      return true
   elseif ( keyName == "buttonB" and phase == "down" ) then  --B Button (A on OUYA, Circle on PS3)
      return true
   elseif ( keyName == "buttonX" and phase == "down" ) then  --X button (U on OUYA, Square on PS3)
      return true
   elseif ( keyName == "buttonY" and phase == "down" ) then  --Y button (Y on OUYA, Triangle on PS3)
      return true
   elseif ( keyName == "up" and phase == "down" ) then  --dPad Up
      return true
   elseif ( keyName == "down" and phase == "down" ) then  --dPad Down
      return true
   elseif ( keyName == "left" and phase == "down" ) then  --dPad Left
      return true
   elseif ( keyName == "right" and phase == "down" ) then  --dPad Right
      return true
   elseif ( keyName == "buttonSelect" and phase == "down" ) then  --Select/Back Button (not used on OUYA)
      return true
   elseif ( keyName == "buttonStart" and phase == "down" ) then  --Start/Home Button (not used on OUYA)
      return true
   elseif ( keyName == "buttonMode" and phase == "down" ) then  --Power On/Off button
      return true
   elseif ( keyName == "leftShoulderButton1" and phase == "down" ) then  --Top Left button on the front of the controller, sometimes called L1
      return true
   elseif ( keyName == "rightShoulderButton1" and phase == "down" ) then  --Top Right button on the front of the controller, sometimes called R1
      return true
   elseif ( keyName == "leftShoulderButton2" and phase == "down" ) then  --Bottom Left button on the front of the controller, sometimes called L2
      return true
   elseif ( keyName == "rightShoulderButton2" and phase == "down" ) then  --Bottom Right button on the front of the controller, sometimes called R2
      return true
   elseif ( keyName == "leftJoyStickButton" and phase == "down" ) then  --pressing down on the left Joystick Button
      return true
   elseif ( keyName == "rightJoystickButton" and phase == "down" ) then  --pressing down on the right Joystick button
      return true

   --now deal with Android standard buttons
   elseif ( "back" == keyName and phase == "down" ) then
      --handle your hardware back button here
      return true
   end

   if ( keyName == "volumeUp" and phase == "down" ) then
      --handle the volume buttons with your code here
      return true
   elseif ( keyName == "volumeDown" and phase == "down" ) then
      --handle the volume buttons with your code here
      return true
   end
   print( "done with keys" )
   --it's important to return false so the OS will handle keys you did not process
   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”. 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 each of these buttons produces two phases, much like touch handlers. Those phases are “up” and “down”, where “down” means the player has pressed the button and “up” means they have released it. Like touch handlers, you do not continue to get events — once you press the button and get the “down” phase, you will not get another event until the button is released, queuing 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 would be warranted.

Putting it Together

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

local player1 = display.newRect( 0, 0, 10, 30 )
player1.x = 30
player1.y = display.contentCenterY
player1:setFillColor( 255, 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, 255 )
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
   --it's important to return false so the OS will handle keys you did not process
   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 up during play. This is handled using the inputDevice APIs.

--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.

On-Screen Button Graphics

Both OUYA and Green Throttle provide graphics to developers for each of the respective device’s buttons. It’s important when building an “in-game tutorial” to include an image of the button that should be pressed for a particular action. While this concept may seem foreign to app developers adapting from the touchscreen, it shows the user which button to press at a quick glance. Note that both vendors offer usability documents about how buttons are expected to behave.

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 dPad. 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.

And that concludes today’s tutorial. While this just touches on the basics of HID controllers, you can see why it’s an exciting time to be using Corona SDK!

  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=""> <strike> <strong>