Joystick input correction code

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

Joystick input correction code

Post by ahv » Sat Mar 28, 2015 1:31 pm

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)
	if (extent < deadzone) then
		s.x, s.y = 0, 0 -- x and y are the rectified inputs
	else
		extent = math.min(1, (extent - deadzone) / (1 - deadzone))
		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.

Image

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

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

Re: Useful joystick input code

Post by qubodup » Sat Mar 28, 2015 2:51 pm

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)

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

Re: Useful joystick input code

Post by ahv » Sat Mar 28, 2015 8:20 pm

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)
	if (extent < deadzone) then
		s.x, s.y = 0, 0 -- x and y are the rectified inputs
	else
		extent = math.min(1, (extent - deadzone) / (1 - deadzone))
		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
Image
moi

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

Re: Useful joystick input code

Post by qubodup » Sun Mar 29, 2015 11:08 am

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)

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

Re: Useful joystick input code

Post by ahv » Mon Mar 30, 2015 4:31 am

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)
	if (extent < deadzone) then
		s.x, s.y = 0, 0 -- x and y are the rectified inputs
	else
		extent = math.min(1, (extent - deadzone) / (1 - deadzone))
		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 :P

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

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

Re: Joystick input correction code

Post by ahv » Sat Apr 04, 2015 3:55 pm

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

Post Reply

Who is online

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