Page 1 of 1

Joystick input correction code

Posted: Sat Mar 28, 2015 1:31 pm
by ahv
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.

Re: Useful joystick input code

Posted: Sat Mar 28, 2015 2:51 pm
by qubodup
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:

Re: Useful joystick input code

Posted: Sat Mar 28, 2015 8:20 pm
by ahv
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

Re: Useful joystick input code

Posted: Sun Mar 29, 2015 11:08 am
by qubodup
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...

Re: Useful joystick input code

Posted: Mon Mar 30, 2015 4:31 am
by ahv
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)

Re: Joystick input correction code

Posted: Sat Apr 04, 2015 3:55 pm
by ahv
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

Re: Joystick input correction code

Posted: Thu Jan 23, 2020 11:16 am
by dj--alex@ya.ru
big thanks but i using modified code for game (warning my game using is only 4 movement modes.
Player can go only 4 directions and fires two methods.

joystick_is_gamepad=joystick:isGamepad() ;
deadzone = 0.25 -- adjustable
s = {}; ss= {} ;
s.ax, s.ay,leftfirejs,ss.ax,ss.ay,rightfirejs = joystick:getAxes(); -- check all 2 sticks and both fire sticks.
joystickPL1name=joystick:getName();
if (s.ax>0)and(s.ax<deadzone) then s.x=0; s.ax=0; else s.x=s.ax; end
if (s.ay>0)and(s.ay<deadzone) then s.y=0;s.ay=0; else s.y=s.ay; end
if (s.ax<0)and(s.ax>-deadzone) then s.x=0;s.ax=0; else s.x=s.ax; end
if (s.ay<0)and(s.ay>-deadzone) then s.y=0; s.ay=0;else s.y=s.ay; end
if (s.y>0) then movePL1="down"; end; --player move keys
if (s.y<0) then movePL1="up"; end;
if (s.x<0) then movePL1="left"; end;
if (s.x>0) then movePL1="right"; end;
if (rightfirejs>0) then ammoKEYPL1="ammo"; end;
if (leftfirejs>0) then ammoKEYPL1="ice"; end;
if (ss.ay<0) then camerakey="p"; end; --camera move keys
if (ss.ay>0) then camerakey=";"; end;
if (ss.ax<0) then camerakey="["; end;
if (ss.ax>0) then camerakey="]"; end;

end

using this extant and "angle " code broke funktionality in my game M2k and Reskue
i remove this elements from code.