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 »

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 »

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 »

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 »

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 »

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 »

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
[email protected]
Prole
Posts: 23
Joined: Sat Jun 17, 2017 1:23 pm

Re: Joystick input correction code

Post by [email protected] »

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.
Post Reply

Who is online

Users browsing this forum: 麻猫和黄猫 and 61 guests