360-degree joystick movement

I have the up/down/left/right (x, y axis) detection working fine with the left analog stick using the “axis” event, but I am not sure if it is possible to detect other angles.  I used RoamingGamer’s simplekeydumper app to see if I could see anything that would tell me if the stick is in one of the diagonal positions, but I couldn’t see anything.  Anyone know if/how this can be done?

Are you using:  Runtime:addEventListener(“key”,…) or Runtime:addEventListener(“axis”, …)?

This week’s blog only covered buttons.  Most controllers map the left analog stick to the d-pad buttons for you and in this case they generate key events for the up, down, left and right keys.   We are going to blog about the axis events where you can get the actual distance the stick has been pressed in a future blog post.  This week’s blog spot is already committed, so it will be at least two weeks before I can write it up.    In the mean time you have to use the “axis” event to get the live data.  Each stick has two axis’s it generates data for an X axis and a Y axis.  You will get values between 1 and -1 depending on how far along that axis the stick has been pressed.  So you might get X = -0.75 and Y = 0.25 meaning the stick is in the North West position but more to the west (I’m guessing, I know the value ranges, but I haven’t figured out the direction yet).

Thanks for the response, Rob.  I am using Runtime:addEventListener( “axis” ), and you get some information, like the axis type and the normal or raw values (min -1, max 1) , but since you only get 1 axis type, (x or y) it doesn’t appear to be possible to tell if the stick is being pushed along both axes.

You should get two events, one with the X and one with the Y values generated as they move.

I am processing all the axis events, filtering on x and y, and moving a circle displayObject, but once the axis.type == “x”, it will stay stuck on “x” until moving the stick more drastically, which isn’t what I would expect.  Also, I notice I am getting 1.52587890625e-05 as the event.normalizedValue when the stick is not pushed at all.  I would have expected 0.  I am also seeing some general flakiness with movement to the left.  I don’t yet know if it’s a controller problem, OUYA problem, Corona problem or my code problem.  I would like to see others’ results.  Also, any feedback on what I might be doing wrong is much appreciated as always.

-- 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 = 0 local maxSpeed = 20 local function updateCursor() -- stop moving if the stick is no longer pushed in any direction if ( axisType == "" ) or ( axisValue == 0 ) or ( axisValue \< -1 ) or ( axisValue \> 1 ) then return end -- print( axisType .. ": " .. tostring( axisValue ) ) axisText.text = axisType .. ": " .. tostring( axisValue ) -- move the cursor based on how far stick is pushed cursor[axisType] = cursor[axisType] + maxSpeed \* axisValue -- 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.type == "x" ) or ( axis.type == "y" ) then axisType = axis.type axisValue = event.normalizedValue return true end return false end local function init() axisText = display.newText( "", 0, 0, native.systemFont, 24 ) -- inside TV safe zone axisText.x = 200 axisText.y = 50 cursor = display.newCircle( display.contentCenterX, display.contentCenterY, 10 ) Runtime:addEventListener( "axis", axisListener ) Runtime:addEventListener( "enterFrame", updateCursor ) end init()

I’ve not had a chance to try your code, but in talking to the engineers and with my own observations (and experience), analog inputs are tricky to work with.  They are naturally “noisey”.  That is the sticks don’t always return to 0.  There are springs that push the stick back and depending on the spring they may not settle correctly.  It may improve with break in, it may get worse.  On my controller my left 2 trigger is really noisey.  The rest seem to settle to 0 pretty good.   You will have to program in a dead zone to cover the variance.

I also notice that sometimes I don’t get x and y, but something like “rotation” for the type of axis.  This is one reason this didn’t make last week’s blog post include axis information.  I need more time to figure them out.  I also need to try and come up with a good way to help map the axis information and calculate movement better. 

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:

Are you using:  Runtime:addEventListener(“key”,…) or Runtime:addEventListener(“axis”, …)?

This week’s blog only covered buttons.  Most controllers map the left analog stick to the d-pad buttons for you and in this case they generate key events for the up, down, left and right keys.   We are going to blog about the axis events where you can get the actual distance the stick has been pressed in a future blog post.  This week’s blog spot is already committed, so it will be at least two weeks before I can write it up.    In the mean time you have to use the “axis” event to get the live data.  Each stick has two axis’s it generates data for an X axis and a Y axis.  You will get values between 1 and -1 depending on how far along that axis the stick has been pressed.  So you might get X = -0.75 and Y = 0.25 meaning the stick is in the North West position but more to the west (I’m guessing, I know the value ranges, but I haven’t figured out the direction yet).

Thanks for the response, Rob.  I am using Runtime:addEventListener( “axis” ), and you get some information, like the axis type and the normal or raw values (min -1, max 1) , but since you only get 1 axis type, (x or y) it doesn’t appear to be possible to tell if the stick is being pushed along both axes.

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.

You should get two events, one with the X and one with the Y values generated as they move.

I am processing all the axis events, filtering on x and y, and moving a circle displayObject, but once the axis.type == “x”, it will stay stuck on “x” until moving the stick more drastically, which isn’t what I would expect.  Also, I notice I am getting 1.52587890625e-05 as the event.normalizedValue when the stick is not pushed at all.  I would have expected 0.  I am also seeing some general flakiness with movement to the left.  I don’t yet know if it’s a controller problem, OUYA problem, Corona problem or my code problem.  I would like to see others’ results.  Also, any feedback on what I might be doing wrong is much appreciated as always.

-- 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 = 0 local maxSpeed = 20 local function updateCursor() -- stop moving if the stick is no longer pushed in any direction if ( axisType == "" ) or ( axisValue == 0 ) or ( axisValue \< -1 ) or ( axisValue \> 1 ) then return end -- print( axisType .. ": " .. tostring( axisValue ) ) axisText.text = axisType .. ": " .. tostring( axisValue ) -- move the cursor based on how far stick is pushed cursor[axisType] = cursor[axisType] + maxSpeed \* axisValue -- 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.type == "x" ) or ( axis.type == "y" ) then axisType = axis.type axisValue = event.normalizedValue return true end return false end local function init() axisText = display.newText( "", 0, 0, native.systemFont, 24 ) -- inside TV safe zone axisText.x = 200 axisText.y = 50 cursor = display.newCircle( display.contentCenterX, display.contentCenterY, 10 ) Runtime:addEventListener( "axis", axisListener ) Runtime:addEventListener( "enterFrame", updateCursor ) end init()

I’ve not had a chance to try your code, but in talking to the engineers and with my own observations (and experience), analog inputs are tricky to work with.  They are naturally “noisey”.  That is the sticks don’t always return to 0.  There are springs that push the stick back and depending on the spring they may not settle correctly.  It may improve with break in, it may get worse.  On my controller my left 2 trigger is really noisey.  The rest seem to settle to 0 pretty good.   You will have to program in a dead zone to cover the variance.

I also notice that sometimes I don’t get x and y, but something like “rotation” for the type of axis.  This is one reason this didn’t make last week’s blog post include axis information.  I need more time to figure them out.  I also need to try and come up with a good way to help map the axis information and calculate movement better. 

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.