What I didn’t provide above is the fact that each event gives you one axis, either X or Y. You have to store the last X or Y received and use that in the calculation. When we publish the axis blog, the sample in it covers this as well as offers a possible way to map controllers.
Happy to help. And I hope I didn’t come across as being difficult. I was just trying to clarify that Corona can’t really assume any of the axes belong to a joystick, so we have no reliable means of knowing which 2 axes to pair. Especially for the right thumbstick, which might not even exit on the device (think flightsticks).
We had a few design discussions about this in the past, and in the end we decided that the best approach was to provide the raw input… because a helper library can always be added on top in the future to help make this easier. Such as a community driven input mapper for making sense of axes and keys from various gamepad models. Something like that can be implemented in pure Lua. At least that’s what we were thinking. Or… perhaps in the future we could add an API which would allow you to tell Corona how to pair axis inputs into a single joystick event for different device models. In either case, I like the idea of a community driven gamepad mapper so that all Corona developers can add their favorite gamepads/joysticks to the list, including the obscure/older PC game controllers. In any case, that’s my 2 cents.
It turned out to be easy to tweak my sample above to get the desired results. Thanks again so much for providing the clue I needed to get this to work right and for explaining why things are the way they are. You did not come across as difficult – I assumed I was being dense :).
Here’s my new sample with the correct behavior:
----------------------------------------------------------------------------------------- -- -- main.lua -- ----------------------------------------------------------------------------------------- -- listen for axis events and update variables that affect a cursor and -- text display that is updated every frame local cursor = nil local axisText = nil local axisType = "" local axisValue = { x=0, y=0 } local maxSpeed = 20 local function updateCursor() -- stop moving if the stick is no longer pushed in any direction -- stop moving if the stick is no longer pushed in any direction if ( axisValue.x == 0 ) and ( axisValue.y == 0 ) then return end if ( axisValue.x \< -1 ) and ( axisValue.y \> 1 ) then return end -- print( axisType .. ": " .. tostring( axisValue ) ) axisText.text = axisType .. ": " .. tostring( axisValue.x ) .. "," .. tostring( axisValue.y ) -- move the cursor based on how far stick is pushed cursor.x = cursor.x + maxSpeed \* axisValue.x cursor.y = cursor.y + maxSpeed \* axisValue.y -- prevent cursor from going off screen if ( cursor.x \< 0 ) then cursor.x = 0 elseif ( cursor.x \> display.contentWidth ) then cursor.x = display.contentWidth elseif ( cursor.y \< 0 ) then cursor.y = 0 elseif ( cursor.y \> display.contentHeight ) then cursor.y = display.contentHeight end end local function axisListener( event ) local axis = event.axis -- filter events from left stick only (x and y axis) if ( axis.descriptor == "Joystick 1: Axis 1" ) or ( axis.descriptor == "Joystick 1: Axis 2" ) then if ( axis.number == 1 ) or ( axis.number == 2 ) then axisType = axis.type axisValue[axisType] = event.normalizedValue return true end end return false end local function init() axisText = display.newText( "", 0, 0, native.systemFont, 24 ) -- inside TV safe zone axisText.x = display.contentCenterX axisText.y = 50 cursor = display.newCircle( display.contentCenterX, display.contentCenterY, 10 ) Runtime:addEventListener( "axis", axisListener ) Runtime:addEventListener( "enterFrame", updateCursor ) end init()
Tony,
I have one other tip to give you. If the player disconnects/powers-off their gamepad and then connects a completely different gamepad, then it’ll register as “Joystick 2”… meaning the axis 1 descriptor will be “Joystick 2: Axis 1”. This is because Corona can uniquely identify the 2nd gamepad and doesn’t know if the 1st gamepad will be coming back… such as due to battery failure and the 2nd gamepad might really belong to a 2nd player. This is actually Ouya’s default behavior too and we made sure to match it. I saw that your code doesn’t handle that case, so I thought I’d let you know.
So, if you’re developing a 1 player game, then you might want to accept player input from the first available gamepad/joystick out of all input devices. We have an “inputDeviceStatus” event that’ll notify your app when an input device has been connected/disconnected.
http://docs.coronalabs.com/daily/api/event/inputDeviceStatus/index.html
We also have a system.getInputDevices() function that will retrieve all input devices that are currently connected to the system. Although I recommend that you only call this function on app startup/resume and then leverage the “inputDeviceStatus” event during gameplay for best performance.
http://docs.coronalabs.com/daily/api/library/system/getInputDevices.html
Alternatively, if it’s a 1 player game, I suppose the other way to handle it is to accept axis inputs from all gamepads connected to the system. Much like how Windows or Mac deal with multiple mice/trackpads connected to the same system, where both manipulate the same mouse cursor onscreen. Just a suggestion.
How to handle the connection/disconnection of gamepads is a bit debatable and might depend if the game is single player or multiplayer, so we opted to provide the APIs needed to let the Corona developer decide on how to best handle it.
That is a really great tip, Josh. Thanks for sharing. I do not yet have a different controller that I can connect to my OUYA, so I can’t be sure exactly what’s going to happen when a different controller is used. Based on your information, I have modified the app I am working on to just use the first joystick. I may add support for more controllers later.
Also, my application calls system.getInputDevices() on app initialization and uses the inputDeviceStatus listener, but I was also only checking for “Joystick 1” there, so I modified it along the same lines above. Here is what that listener looks like now, but I haven’t tried with multiple controllers:
-- Called when the status of an input device has changed. local function onInputDeviceStatusChanged( event ) if event.connectionStateChanged then print( event.device.displayName .. ": " .. event.device.connectionState ) if ( event.device.connectionState == "disconnected" ) then if ( event.device == ouyaController ) then -- is this the device we are using? ouyaController = nil controllerAlert = native.showAlert( "Controller disconnected", "The " .. event.device.displayName .. " is disconnected. " .. "Please reconnect the controller." ) end elseif ( event.device.connectionState == "connected" ) then if ( ouyaController == nil ) then -- if controller is disconnected local d = event.device.descriptor if ( d:find( "Joystick" ) ~= nil ) then -- joystick detected if ( ouyaController == nil ) then ouyaController = event.device -- use this device end if ( controllerAlert ~= nil ) then native.cancelAlert( controllerAlert ) controllerAlert = nil end end end end end end