360-degree joystick movement

I get that at the native level you just get x and y axis events and pass them along.  I was merely suggesting that it would be a Really Useful Thing if Corona had an event designed as I suggested.

I understand the second part of your response, but as you can see above, we already know that each controller is different and thus it is is the developer’s duty to build a library of control handlers for Corona.  It would still be really useful if there were some pre-made samples for most common controllers.  I have read all of the Corona/OUYA developer docs and got the virtual controller sample working but it is completely unrelated to the problem I am describing.  I can easily map the OUYA controller buttons and get the axis events just fine.

The problem is that axis events are either not being reported correctly to Corona or Corona is not reporting them correctly.  If anyone is able to demonstrate working corona code that moves a cursor around the screen to match 360-degree movement of the left thumbstick, then I would love to see it.  I am not seeing how it is possible when the axis events do not report BOTH x and y axis events as the thumbstick is moved.

I understand what you’re trying to say, which is you’re supposed to figure all this out based on an x and y axis value, but if I am only getting one of them as the stick is moved around, it won’t work.  Do you understand what I am seeing?  Do you have an actual OUYA box and controller and have you been able to make a cursor move around the screen smoothly using the axis events from Corona?

An axis event gets raised when the axis value has changed.  That is, an axis event will never provide the same value that you received in the prior event.  This is an optimization and it matches the “touch” and “mouse” event behavior.

For example, if you are able to move the joystick along the x-axis without moving it along the y-axis, then you’ll only receive an axis event for the x-axis and not the y-axis because the y value has been unchanged.  So, you’re right, you won’t always receive the joystick’s axis events in pairs.  You will only receive axis values when movement has been detected.  This is by design.  It’s up to you to store the last axis values received.

Thanks for clarifying that this is by design, Joshua.  It looks like there is some amount of work to be done to store old values and then relate new values as they come in to the old values.  Should be fun!

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

Also, now that I’m more learned…  You don’t want to depend on the stings “x” and “y”  (or “z” and “Zrotation”) because they are not guaranteed to be consistently named.  You should use event.axis.number to identify each axis.  Now which axis is what may also vary from controller to controller.

For instance on the OUYA controller, I see 6 axes:  1, 2 is the left stick, 3 is the left trigger, 4, 5 is the right stick and 6 is the right trigger.  Yet my Sony PS3 dual shock is reporting 14 axes.  1, 2 is the left stick, 3, 4 is the right stick, I see 9, 10, and 11 for the D-Pad buttons.  Oddly I don’t get axis values on the left button…  The Up, Down and Right button generate Axis events.  13 is the left trigger and 14 is the right trigger.  

It’s important to know the controllers you plan to support and come up with a way to map them.

That’s interesting.  I was hoping that there would be some way to reference the most common controls across all controllers.  If they’re not consistently named and the axis number varies for each controller, we’ll probably need to start building a library of controllers for the axis number mappings.

Mapping the axis numbers to something usable is going to be a challenge.  It’s kind of the last piece I need to figure out for the axis blog post.

We are looking forward to this blog post :slight_smile: The latest post about IAPs was awesome!

I know you are able to figure it out :wink:

It´s amazing how everything fits into our release schedule… IAPs, Ouya plugin, etc… I can´t wait to announce our current project on Wednesday… *teaser*

Max

I’m glad to know it wasn’t just me finding it a challenge!   :slight_smile:

I’m still at a loss for how to translate the information coming from the axis event into a usable 360-degree direction.  I am seeing stickiness in the axis.type and axis.number values when the stick is moved around, and that doesn’t seem right.  Any progress on your end, Rob? 

I’ve got most of the blog post done for Axis stuff.  I have to make a few changes that Engineering wanted, but the one thing that’s not working to my satisfaction is this exact question, which is how to map the X, Y to an angle.    But while typing this, I decided to put in the time and solve it.  I think this should work:

local function calculateAngle(sidex, sidey) &nbsp;&nbsp;&nbsp; if sidex == 0 or sidey == 0 then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return nil &nbsp;&nbsp;&nbsp; end &nbsp;&nbsp;&nbsp; local tanx = math.abs(sidey) / math.abs(sidex); &nbsp;&nbsp;&nbsp; local atanx = math.atan(tanx); -- (result in radians) &nbsp;&nbsp;&nbsp; local anglex = atanx \* 180 / math.pi; -- converted to degrees &nbsp;&nbsp;&nbsp; if sidey \< 0 then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; anglex = anglex \* -1 &nbsp;&nbsp;&nbsp; end &nbsp;&nbsp;&nbsp; print("sidex",sidex, "sidey", sidey, "anglex", anglex, "tanx", tanx, "atanx", atanx) &nbsp;&nbsp;&nbsp; if sidex \< 0 and sidey \> 0 then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; anglex = 270 + math.abs(anglex) &nbsp;&nbsp;&nbsp; elseif sidex \< 0 and sidey \< 0 then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; anglex = 270 - math.abs(anglex) &nbsp;&nbsp;&nbsp; elseif sidex \> 0 and sidey \< 0 then &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; anglex = 90 + math.abs(anglex) &nbsp;&nbsp;&nbsp; else &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; anglex = 90 - anglex &nbsp;&nbsp;&nbsp; end &nbsp;&nbsp;&nbsp; print("New anglex", anglex) &nbsp;&nbsp;&nbsp; return anglex end print(calculateAngle(1, 2)) print(calculateAngle(1, -2)) print(calculateAngle(-1, -2)) print(calculateAngle(-1, 2))

If you think of the axis controller as a Cartesian coordinate system where 0,0 is center +X is to the right, -X to the left, +Y up and -Y down, then we can start doing some right angle math.   Lets look at two arbitrary numbers:

x = 0.5

y = 0.5

Now in our minds we can picture a graph from 0,0, to 0.5, 0.5 as being a 45 degree line.   Using the formula above, x of 0.5 is the length of side a, y of 0.5 is the length of side b, and the math formula above.  It will generate an angle of 45.  But if I make either or both of those numbers negative, it also comes up with 45, so you have to do some mischief to add some amount of degrees to the calculated angle depending on which quadrant the hypotenuse falls in.   To compound things, I want an angle of 0 to be “North” instead of “East” like we would normally think of it.  If you remember your protractor from math class, an angle of 0 was a straight horizontal line, an angle of 90 was a vertical line upward.  But in our circle math, we tend to think of a rotation of 0 being the right orientation and a rotation of 90 is a clock-wise rotation of 90 degrees.  All this weirdness is to map the calculated right triangle angle (since it can only be 0 - 90 in this case) to a clockwise rotation amount.

I’ve not tested this with a controller, but with passing various values in, it produces the angles I would expect to be able to rotate to.

Thanks for the code snippet, Rob.  I think you definitely need to try with a controller, as I am finding that the actual behavior does not seem to allow for detection of both an “x” and “y” value at the same time.  The axis events generated only provide 1 axis at a time and often I am seeing that rotating the stick around the edges results in events where the x and y axis don’t change as they should (i.e. if you start with the stick pushed far left and rotate it upwards towards the top, you just get x axis events until some tip-over point and then you sometimes get y axis events.

This is visible easily when using a controller with SimpleKeyDumper (RoamingGamer.com) or when dumping axis event information via print statements.

For analog sticks, wouldn’t it make sense if the event generated returned some kind of indicator that it is a 360-degree control event and the degree at which the stick is pointed, with the values the same as they are today?  If that is too much to ask, we will definitely need both x and y values instead of just the 1 value provided in the axis event.

Tony,

At the native level, there is no such thing as a joystick event.  They are all received as individual axis input events and Corona passes them to you in Lua as-is.  This is also how it works on Windows via DirectInput, which Corona doesn’t support yet but I’ve had experience with in the past.  So, that’s just how it is and it’s exactly how all games (including PCs, Xbox 360s, etc.) receive joystick input events.  If you Google search it, then you can see what I’m talking about.

If the input device is of type “joystick”, then it’s usually safe to assume that the first 2 axes are the x and y of the primary joystick/thumbstick.  That is a de facto standard.  Unfortunately, there is no standard for which axis inputs represent the right thumbstick on the gamepad… or even the analog triggers.  That is, gamepad manufacturers do no map axis numbers to the same axis inputs as other manufacturers.  There is no standard.  This also means that axis “type” name that Corona provides might actually be wrong, which is why we document it as unreliable.  That’s just how it is.  This also makes a “joystick” event impossible, because Corona has no means of knowing which axis inputs belong to the right thumbstick.

The only way to truly know which axis input is which is by providing a gamepad/joystick mapping screen just like how PC games do it (and that’s exactly why PC games provide screens like this).  If you looked at the sample projects made by Ouya, notice that one of their sample projects shows you how to set up a gamepad mapping screen to handle this very issue.

The other way to do it is to look at the input device’s name and automatically map its axis inputs to the appropriate game inputs on your end.  This means you would have to identify which axis inputs map to which thumbstick or trigger yourself for an Ouya gamepad, PS3 controller, Xbox 360 controller, etc.  That’s a common technique used by game developers which they would call “gamepad profiles”.  Some games go so far as to show the matching button image or button letter/number for the gamepad, like how PC games would do it with an Xbox 360 or PS3 controller.

Now, on Ouya, it may be okay to assume that end-users only use Ouya gamepads.  In which case, it’s okay to assume which axis inputs are which.  That would greatly simplify the code on your end if you’re willing to not support any other type of gamepad, such as a PS3 controller.  That said, I know the Ouya dev team would still like you to add the option to map input to other types of controllers, because that’s why they went out of their way to make that gamepad mapping sample project, but that’s up to you.

I get that at the native level you just get x and y axis events and pass them along.  I was merely suggesting that it would be a Really Useful Thing if Corona had an event designed as I suggested.

I understand the second part of your response, but as you can see above, we already know that each controller is different and thus it is is the developer’s duty to build a library of control handlers for Corona.  It would still be really useful if there were some pre-made samples for most common controllers.  I have read all of the Corona/OUYA developer docs and got the virtual controller sample working but it is completely unrelated to the problem I am describing.  I can easily map the OUYA controller buttons and get the axis events just fine.

The problem is that axis events are either not being reported correctly to Corona or Corona is not reporting them correctly.  If anyone is able to demonstrate working corona code that moves a cursor around the screen to match 360-degree movement of the left thumbstick, then I would love to see it.  I am not seeing how it is possible when the axis events do not report BOTH x and y axis events as the thumbstick is moved.

I understand what you’re trying to say, which is you’re supposed to figure all this out based on an x and y axis value, but if I am only getting one of them as the stick is moved around, it won’t work.  Do you understand what I am seeing?  Do you have an actual OUYA box and controller and have you been able to make a cursor move around the screen smoothly using the axis events from Corona?

An axis event gets raised when the axis value has changed.  That is, an axis event will never provide the same value that you received in the prior event.  This is an optimization and it matches the “touch” and “mouse” event behavior.

For example, if you are able to move the joystick along the x-axis without moving it along the y-axis, then you’ll only receive an axis event for the x-axis and not the y-axis because the y value has been unchanged.  So, you’re right, you won’t always receive the joystick’s axis events in pairs.  You will only receive axis values when movement has been detected.  This is by design.  It’s up to you to store the last axis values received.

Thanks for clarifying that this is by design, Joshua.  It looks like there is some amount of work to be done to store old values and then relate new values as they come in to the old values.  Should be fun!