Page 1 of 1

How to treat analog movement as a button press?

Posted: Mon Sep 24, 2018 2:56 am
by ThiefOfPringles
As the title suggests, I wanna know if there is a way to treat analog movement (on a gamepad) such as the joystick pushing against the edge of its case or the trigger going all the way down as a button press. This idea came from how Nintendo apps (specifically dev with homebrew toolchains) can do this, specifying that the joystick has been pushed up and held like a button press, but I've looked through the entire LOVE wiki only to find that there is no normal way of going about this. Can I have some pointers or examples as to how this could be achieved?

Re: How to treat analog movement as a button press?

Posted: Mon Sep 24, 2018 11:39 am
by JoshGrams
You have to do it yourself, but it's not hard. The values for an axis generally go from -1 to 1, so just check if it's above a threshold. You probably don't want to use 1 as the threshold because they might not be perfectly calibrated, or might drift over time so they hit the case before going all the way to 1.

You may also want to save the last value, and have separate on/off thresholds with a little space between them to avoid jitter (if the axis value is between the thresholds then use the old value).

Here's a quick but (I think) fairly complete demo:

Code: Select all

local function axisValue(self)
	local value
	if type(self.axis) == 'string' then
		value = self.device:getGamepadAxis(self.axis)
	elseif type(self.axis) == 'number' then
		value = self.device:getAxis(self.axis)
	else
		error("Unknown axis type.")
	end
	return value * self.dir
end

local function isAxisButtonDown(self)
	local value = axisValue(self)
	local down
	if value >= self.downThreshold then
		down = true
	elseif value <= self.upThreshold then
		down = false
	else
		-- indeterminate: use previous value.
		down = self.down
	end
	return down
end

local function updateAxisButton(self)
	local down = isAxisButtonDown(self)
	if down ~= self.down then
		self.down = down
		if self.down then self:pressed() else self:released() end
	end
end

local function newButtonFromAxis(device, axis, dir)
	return {
		device = device, axis = axis, dir = dir or 1,
		-- Call this in `love.update` to get pressed/released callbacks.
		update = updateAxisButton,
		pressed = function(button) end,
		released = function(button) end,
		-- Call this to check whether the button is currently down.
		isDown = isAxisButtonDown,
		-- Or just read this property after calling `update`.
		down = false,
		-- Leave a little space between these to avoid possible jitter.
		downThreshold = 0.9, upThreshold = 0.75
	}
end

function love.load()
	buttons = {}
end

function love.draw()
	local u = 50
	for i,b in ipairs(buttons) do
		if b:isDown() then
			love.graphics.rectangle('fill', i * u, u, 0.9 * u, 0.9 * u)
		end
	end
end

function love.joystickadded(j)
	if j:isGamepad() then
		table.insert(buttons, newButtonFromAxis(j, 'triggerleft'))
		table.insert(buttons, newButtonFromAxis(j, 'triggerright'))
	else
		for i=1,j:getAxisCount() do
			table.insert(buttons, newButtonFromAxis(j, i))
		end
	end
	print(#buttons)
end

function love.joystickremoved(j)
	for i=#buttons,1,-1 do  -- loop backwards so it's safe to remove.
		local b = buttons[i]
		if b.device == j then  table.remove(buttons, i)  end
	end
end

function love.keypressed(k, s)
	if k == 'escape' then love.event.quit() end
end

Re: How to treat analog movement as a button press?

Posted: Tue Sep 25, 2018 2:51 pm
by shru
I'll point out that there are various input libraries which can handle analog inputs this way, in addition to solving other common input issues. I'd take a look at baton and maybe even my own otokonokontroller.