## Joystick input correction code

General discussion about LÖVE, Lua, game development, puns, and unicorns.
ahv
Prole
Posts: 7
Joined: Mon Dec 02, 2013 4:36 pm
Location: Helsinki, Finland

### Joystick input correction code

The input from the sticks isn't as accurate as the uninitiated might think.
This program demonstrates and solves the inaccuracy.

Code: Select all

function love.load()
deadzone = 0.25 -- adjustable, my pretty worn controller needs to have this as high as 0.3
love.graphics.setBackgroundColor(30, 30, 30)
joystick = love.joystick.getJoysticks()[1]
s = {}
end

function love.update()
s.ax, s.ay = joystick:getAxes() -- ax and ay are the actual raw values from the controller
local extent = math.sqrt(math.abs(s.ax * s.ax) + math.abs(s.ay * s.ay))
local angle = math.atan2(s.ay, s.ax)
s.x, s.y = 0, 0 -- x and y are the rectified inputs
else
s.x, s.y = extent * math.cos(angle), extent * math.sin(angle)
end
end

function love.draw()
-- bullseye
love.graphics.setColor(20, 10, 20)
love.graphics.circle("fill", 250, 250, 200, 48)
love.graphics.setColor(30, 30, 30)
love.graphics.circle("fill", 250, 250, 200 * deadzone, 24)
-- actual values indicator line
love.graphics.setColor(160, 80, 160)
love.graphics.circle("fill", 250, 250, 5, 8)
love.graphics.setColor(200, 100, 200)
love.graphics.line(250, 250,  250 + (s.ax * 200), 250 + (s.ay * 200))
-- fixed location indicator circle
love.graphics.setColor(150, 120, 150)
love.graphics.circle("line", 250 + (s.x * 200), 250 + (s.y * 200), 10, 12)
end
The purple line originating from the middle shows the raw input data from the controller.
The darkened area in the middle is the deadzone that is associated with the raw input.
The lighter circle is the rectified input.

When you roll your stick around a full circle, you can see that it doesn't make a smooth circle; it makes this puffy pillowy shape -- which isn't how it should be, so it needs some code to rectify that.
Last edited by ahv on Sat Apr 04, 2015 3:49 pm, edited 1 time in total.
moi

qubodup
Inner party member
Posts: 775
Joined: Sat Jun 21, 2008 9:21 pm
Location: Berlin, Germany
Contact:

### Re: Useful joystick input code

When you roll your stick around a full circle, you can see that it doesn't make a smooth circle; it makes this puffy pillowy shape -- which isn't how it should be, so it needs some code to rectify that.
Nice tool! Not sure what you mean though. This is what it looks like on my machine:
lg.newImage("cat.png") -- made possible by lg = love.graphics
-- Don't force fullscreen (it frustrates those who want to try your game real quick) -- Develop for 1280x720 (so people can make HD videos)

ahv
Prole
Posts: 7
Joined: Mon Dec 02, 2013 4:36 pm
Location: Helsinki, Finland

### Re: Useful joystick input code

Oh it works, I just mean that without the code, the "circle" you make is not a circle. You can see what the shape would be if you make a full circle with the stick and look at what the end of the purple line traces. I was just being redundant with the last paragraph... I don't know why.

EDIT:
I added some code that draws a trace to clarify, press a (or button 1) to clear it.

Code: Select all

function love.load()
deadzone = 0.25 -- adjustable, my pretty worn controller needs to have this as high as 0.3
love.graphics.setBackgroundColor(30, 30, 30)
joystick = love.joystick.getJoysticks()[1]
s = {}
trace = {}
end

function love.update()
s.ax, s.ay = joystick:getAxes() -- ax and ay are the actual raw values from the controller
local extent = math.sqrt(math.abs(s.ax * s.ax) + math.abs(s.ay * s.ay))
local angle = math.atan2(s.ay, s.ax)
s.x, s.y = 0, 0 -- x and y are the rectified inputs
else
s.x, s.y = extent * math.cos(angle), extent * math.sin(angle)
end

-- trace
table.insert(trace, 1, 250 + (200*s.ay))
table.insert(trace, 1, 250 + (200*s.ax))
end

function love.draw()
-- bullseye
love.graphics.setColor(20, 10, 20)
love.graphics.circle("fill", 250, 250, 200, 48)
love.graphics.setColor(30, 30, 30)
love.graphics.circle("fill", 250, 250, 200 * deadzone, 24)
-- actual values indicator line
love.graphics.setColor(160, 80, 160)
love.graphics.circle("fill", 250, 250, 5, 8)
love.graphics.setColor(200, 100, 200)
love.graphics.line(250, 250,  250 + (s.ax * 200), 250 + (s.ay * 200))
-- fixed location indicator circle
love.graphics.setColor(150, 120, 150)
love.graphics.circle("line", 250 + (s.x * 200), 250 + (s.y * 200), 10, 12)

--trace
if #trace >= 4 then	love.graphics.line(trace) end
end

function love.joystickpressed(joystick, button)
if button == 1 then trace = {} end
end
moi

qubodup
Inner party member
Posts: 775
Joined: Sat Jun 21, 2008 9:21 pm
Location: Berlin, Germany
Contact:

### Re: Useful joystick input code

Interesting.

I'm not sure what to think though... controllers are unreliable when it comes to diagonal coordinates? Apparently on my Speedlink SL-6556-BK XEOX PRO ANALOG GAMEPAD - USB, only the top right corner is unreliable...
lg.newImage("cat.png") -- made possible by lg = love.graphics
-- Don't force fullscreen (it frustrates those who want to try your game real quick) -- Develop for 1280x720 (so people can make HD videos)

ahv
Prole
Posts: 7
Joined: Mon Dec 02, 2013 4:36 pm
Location: Helsinki, Finland

### Re: Useful joystick input code

Yes, when you make a circle on your controller stick, it would make sense to expect the input to make at least roughly a circle too - which it doesn't. Even if a brand new controller was able to make a perfect square with it's circular movement the raw input data would still have to be processed a little bit to make it useable in a game (assuming that accuracy of the stick input is important in the specific game).

Code: Select all

function love.load()
deadzone = 0.25 -- adjustable, my pretty worn controller needs to have this as high as 0.3
scale, ix, iy = 50, 50, 450 -- adjustable (scale == radius of input circle)
love.graphics.setBackgroundColor(30, 30, 30)
joystick = love.joystick.getJoysticks()[1]
s = {}
trace = {}
trace.show = true
cursor = { x = 250, y = 250, speed = 200, useRawInput= false }
end

function love.update(delta)
s.ax, s.ay = joystick:getAxes() -- ax and ay are the actual raw values from the controller
local extent = math.sqrt(math.abs(s.ax * s.ax) + math.abs(s.ay * s.ay))
local angle = math.atan2(s.ay, s.ax)
s.x, s.y = 0, 0 -- x and y are the rectified inputs
else
s.x, s.y = extent * math.cos(angle), extent * math.sin(angle)
end
table.insert(trace, 1, iy + (scale*s.ay))
table.insert(trace, 1, ix + (scale*s.ax))
-- move cursor
local dx, dy = 0, 0
if cursor.useRawInput then dx, dy = s.ax, s.ay
else dx, dy = s.x, s.y end
cursor.x = cursor.x + (delta * cursor.speed * dx)
cursor.y = cursor.y + (delta * cursor.speed * dy)
-- prevent cursor from leaving screen
if cursor.x <= 0 then  cursor.x = 0 end
if cursor.y <= 0 then  cursor.y = 0 end
if cursor.x >= 500 then  cursor.x = 500 end
if cursor.y >= 500 then  cursor.y = 500 end
end

function drawInputIndicator(drawTrace)
-- bullseye
love.graphics.setColor(20, 10, 20)
love.graphics.circle("fill", ix, iy, scale, 48)
love.graphics.setColor(30, 30, 30)
love.graphics.circle("fill", ix, iy, scale * deadzone, 24)
-- actual input values indicator line
if drawTrace then love.graphics.setColor(160, 80, 160) -- visual feedback for if trace is being shown or nay
else love.graphics.setColor(100, 160, 100) end
love.graphics.circle("fill", ix, iy, scale / 40, 8)
love.graphics.setColor(200, 100, 200)
love.graphics.line(ix, iy,  ix + (s.ax * scale), iy + (s.ay * scale))
-- interpreted input indicator circle
love.graphics.setColor(150, 120, 150)
love.graphics.circle("line", ix + (s.x * scale), iy + (s.y * scale), scale / 20, 12)
if drawTrace and #trace >= 4 then love.graphics.line(trace) end
end

function love.draw()
drawInputIndicator(trace.show)
--cursor
if cursor.useRawInput then love.graphics.setColor(240, 120, 120)
else love.graphics.setColor(200, 240, 120) end -- green cursor when using interpreted input
love.graphics.line(cursor.x - 5, cursor.y, cursor.x + 5, cursor.y)
love.graphics.line(cursor.x, cursor.y - 5, cursor.x, cursor.y + 5)
end

function love.joystickpressed(joystick, button)
if button == 1 then
trace = {}
trace.show = true
elseif button == 2 then
trace.show = not trace.show
elseif button == 3 then
cursor.useRawInput = not cursor.useRawInput
end
end
Button 1 (a) - Reset indicator trace
Button 2 (b) - Show/hide trace
Button 3 (x) - Switch between raw and processed input

Red cursor means that it's using the raw controller input to transform it's position, green uses the processed values. You can notice the cursor moving in a much less consistent manner when using the raw mode. If this was some sort of a diagonal running race game, the winner would be the one with the newer controller

You can use the processed input values to move a character (or a cursor, in this example) and expect it to move in a consistent way with these two lines:

cursor.x = cursor.x + (delta * cursor.speed * s.x)
cursor.y = cursor.y + (delta * cursor.speed * s.y)
moi

ahv
Prole
Posts: 7
Joined: Mon Dec 02, 2013 4:36 pm
Location: Helsinki, Finland

### Re: Joystick input correction code

I meant to post this simplified code snippet earlier:

Code: Select all

local inDZ, outDZ = 0.25, 0.1 --deadzones
function correctStick ( x, y ) --raw x, y axis data from stick
local len = math.sqrt ( x * x + y * y )
if len <= inDZ
then x, y = 0, 0
elseif len + outDZ >= 1 then
x, y = x / len, y / len
else
len = ( len - inDZ ) / ( 1 - inDZ - outDZ )
x, y = x * len, y * len
end
return x, y -- corrected input that you can use directly in transformations
end
moi

### Who is online

Users browsing this forum: Google [Bot] and 5 guests