This is a reposting of a blog entry I contributed to the official Corona Labs blog back in November 2013. You can view the original post here.
I finally got around to purchasing an OUYA last week, thrilled by the possibility of creating the sort of controller-driven games I enjoyed back in the halcyon days of the NES and Sega Master System. I’ve only had a few hours of hands-on time with the OUYA, but so far I’m impressed. The hardware seems solid, the interface runs smoothly, and of course, since Corona has built-in support for OUYA and its controller using the “key events” API, I’m well-positioned to create the next OUYA runaway hit!
Test Environment
I do all of my development work on a Mac, and the Corona Simulator does not currently support controller input like it does on Windows (OS X is generally not very “joystick-friendly”). So, to properly test changes to my OUYA-targeted games, I’d typically need to create a build, transfer the APK to the OUYA, move from my computer to my TV, and pray that there isn’t some silly little bug in my code that requires me to start the entire process over. One of my favorite things about Corona is that I can see changes in my code reflected instantly in the simulator — but it seems this isn’t possible if I want to use a controller. What to do?
Introducing the “simulatorController” Library
Another thing I like about Corona is that the SDK is flexible enough to allow us to “code around” issues like this controller conundrum. There is a proud tradition in the Corona community of developers sharing libraries to expand the SDK’s functionality — Director Class being one of the most recognized. In that tradition, I sat down and whipped up a simulatorController library that adds Mac Simulator support for USB or Bluetooth controllers with just one line of code!
Before you start using simulatorController, here are a few quick notes:
- If you use Corona for Windows, you don’t even need it!
- You must be a Corona Pro subscriber using a daily build later than #1217, because that’s when keyboard and mouse events were first made available in the Mac Simulator. UPDATE: The Key Events API made it into public builds as of build number 2013.2076 (11/14/2013).
- You will need a properly-configured controller mapping application (more on that below).
- It will not work with your controller’s analog sticks (although it will detect when you click them straight down).
- The library should work with any standard HID (Human Interface Device) controller. However, pairing an OUYA controller with a Mac results in your mouse pointer drifting endlessly to one corner of the screen, rendering the mouse somewhat useless. There are unofficial drivers floating around that claim to fix this, but you should use them at your own risk. I personally use a PS3 controller via Bluetooth and it works great.
How it Works
Before we get into the code of the simulatorController library, I recommend that you read the Introduction to Game Controllers tutorial. Not only does it provide a great explanation of how Corona handles controller input, but it features a terrific sample event handler function that plays nice with my simulatorController library.
The way simulatorController works is by capturing keyboard keystrokes and translating them into the same Runtime “key” events generated by a game controller — and those simulated Runtime events are captured by the onKeyEvent() function from the tutorial (or your own controller listener function if you write a custom one). What you do from that point is entirely up to you, but the good news is that if you’re already using the demo listener function to handle controller inputs, all you need to do is drop simulatorController.lua into your project’s root folder and require() it inside main.lua:
simulatorController = require( "simulatorController" )
With this in place, we need a little help from a controller mapping application. There are plenty of them available, but I use Joystick Mapper which does the job nicely and is affordable ($4.99 in the Mac App Store). Joystick Mapper converts controller button presses into keystrokes which are then “intercepted” by simulatorController and passed onward as controller inputs to your simulated Corona app. Remember that your Simulator window must have focus and be the “active” application in OS X, or the keystrokes will not be passed to nor reported by the Simulator.
Configuring Joystick Mapper is a simple process of pressing the buttons on your controller one at a time and assigning a keystroke to each button. I’ve created a handy-dandy preset file which you can download and copy into the Joystick Mapper “Presets” folder, but if you really want to map the buttons yourself, here are the default keystroke configurations for simulatorController:
Controller Button | Mapped Keystroke |
D-Pad Up | 1 |
D-Pad Down | 2 |
D-Pad Left | 3 |
D-Pad Right | 4 |
Button A (O on OUYA, X on PS3) | a |
Button B (A on OUYA, circle on PS3) | b |
Button X (U on OUYA, square on PS3) | x |
Button Y (Y on OUYA, triangle on PS3) | y |
Mode (OUYA/PS3 logo button) | 0 |
L1 Shoulder Button 1 | 6 |
L2 Shoulder Button 2 | 7 |
R1 Shoulder Button 1 | 8 |
R2 Shoulder Button 2 | 9 |
L3 Left Analog Click | l |
R3 Right Analog Click | r |
Select Button | [ (left bracket) |
Start Button | ] (right bracket) |
Library Options
Once you require the simulatorController library into your project, it will run silently in the background, capture keystrokes from your keyboard or controller, and translate them into HID key events. In addition, I have included a couple of functions which you can call to adjust things on the fly:
- simulatorController:disable() — this function stops controller input from reaching your app, as if the controller is unplugged.
- simulatorController:enable() — this function restores controller input to your app, as if the controller is plugged in.
- simulatorController:show() — this function puts a visual indicator on the screen whenever you press a controller button so you can confirm that it’s working.
- simulatorController:hide() — this function hides the visual indicator if you had previously turned it on.
The Library Code
You don’t need to delve into the library’s code to use it, but I invite you to open it, pick it apart, or even make improvements to share with the community. It’s not an especially complicated chunk of code and hopefully it’s organized well enough to speak for itself. I’m making the code available under the standard MIT license, so use it as you wish.
-- simulatorController for Corona SDK -- Copyright (c) 2013 Jason Schroeder -- https://www.jasonschroeder.com -- http://www.twitter.com/schroederapps --[[ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- -- create our "simulator controller" display group: local simulatorController = display.newGroup() simulatorController.x = 160 simulatorController.y = display.contentHeight-50 simulatorController.isVisible = false local displayBG = display.newRoundedRect(0,0,280,50,12) displayBG:setFillColor(1,1,1,.5) displayBG:setStrokeColor(1,1,1,1) displayBG.strokeWidth = 5 displayBG.x, displayBG.y = 0, 0 simulatorController:insert(displayBG) local displayText = display.newText("", 0, 0, native.systemFont, 24) displayText.x, displayText.y = 0, 0 displayText:setTextColor(0,0,0) simulatorController:insert(displayText) -- align our mapped keystrokes to the corresponding button keyName (these must match up with the button map in Joystick Mapper): local buttonMap = { ["1"] = "up", -- D-Pad up ["2"] = "down", -- D-Pad down ["3"] = "left", -- D-Pad left ["4"] = "right", -- D-Pad right ["a"] = "buttonA", -- Ouya: O, PS3: X ["b"] = "buttonB", -- Ouya: A, PS3: circle ["x"] = "buttonX", -- Ouya: U, PS3: square ["y"] = "buttonY", -- Ouya: Y, PS3: triangle ["0"] = "buttonMode", -- console logo button (power on/off) ["6"] = "leftShoulderButton1", -- L1 ["7"] = "leftShoulderButton2", -- L2 ["8"] = "rightShoulderButton1", -- R1 ["9"] = "rightShoulderButton2", -- R2 ["l"] = "leftJoyStickButton", -- L3 (left analog click) ["r"] = "rightJoyStickButton", -- R3 (right analog click) ["["] = "buttonSelect", -- Ouya: n/a, PS3: select ["]"] = "buttonStart", -- Ouya: n/a, PS3: start } -- listen for mapped keystrokes and repeat them as if they were button presses: local function controllerListener(event) -- check to make sure key event corresponds to a mapped button: if buttonMap[event.keyName]~=nil then -- update event to HID-compatible keyName event.keyName = buttonMap[event.keyName] -- update on-screen display: if event.phase == "down" then displayText.text = event.keyName else displayText.text = "" end displayText.x, displayText.y = 0, 0 end end -- turn on the simulator controller: function simulatorController:enable() Runtime:addEventListener("key", controllerListener) end -- turn off the simulator controller: function simulatorController:disable() Runtime:removeEventListener("key", controllerListener) end -- show the simulator controller: function simulatorController:show() simulatorController.isVisible = true end -- hide the simulator controller: function simulatorController:hide() simulatorController.isVisible = false end -- start the simulator controller and return the simulatorController display group: simulatorController:enable() return simulatorController
Sample Project
I created a very basic sample project so you can see the library at work in the Simulator. This sample is just the library and a main.lua file. It draws a square in the center of the screen that can be moved using the D-Pad. Pressing any other button changes the square’s color at random. It’s not much to look at, but it beats having to build for the device just to test an actual controller.
In Summary
I hope that some of you can use this library to speed up your development pace while creating apps for the OUYA or other controller-driven environments. As I write this, there are just over 500 games available for OUYA, so now is the time for developers to enter that marketplace — before it becomes as saturated as the larger app stores. Imagine the competitive edge you’ll have!
Leave a Reply