KeyMap - A Keymapping Module with Controller Support

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
User avatar
richardperkins
Prole
Posts: 9
Joined: Tue Jan 07, 2014 6:20 am

KeyMap - A Keymapping Module with Controller Support

Post by richardperkins »

Tired of constantly calling love.keyboard.isDown(key)? Want to check for more than one key with a single line of code?

I have just the thing for you!

As a part of my project, I felt like I spent entirely too much time screwing around with detecting keys. When you need more than one input to do one thing (WSAD, or arrow keys, or gamepad), it is a real pain. So, I spent some time and developed "KeyMap." With it, there are only a few functions to get it running, and with those few functions, you can have as many keys as you want bound to as many mappings as you want. I've attached a demo program so you can see how it's done.

DEMO
Check out the binding demo I've made for you! It shows all of the states of each mapping, what keys are bound, and even lets you bind and unbind each mapping.
KeyMap.love
Binding Demo
(2.29 KiB) Downloaded 275 times
In the attachment is the module as well as a demo program. It has five built-in mappings, and displays all of the states. Click the "BIND" button next to the mapping you wish to bind to. Press escape to cancel, click the button again to unbind all mappings, or press any key, button, or analog stick to bind.

Here is just the module with a tad bit of documentation. It is fairly straightforward.

KeyMap.lua

Code: Select all

-- @module keymap

local keymap = {}

local _keyCheck = false
local _keyCheckFor = false
local _keyLast = false
local _map = {}
local joystick = love.joystick.getJoysticks()[1]
local lastPushed = nil
love.keyboard.setKeyRepeat( true )

-- Add a new mapping to keymap
function keymap:addMapping( maps )
	for _ , m in pairs( maps ) do
		
		_map[m] = { held=false , pushed=false , keys={} , joy={} }
		
	end
end

-- Retreive mapping from id
function keymap:getMapping(id)
	if id then
		return _map[id]
	else
		local mapList = {}
		for i , m in pairs(_map) do
			mapList[i] = m
		end
		return mapList
	end
end

-- Retreive a mapping from key
local function findMap(keyType, key)
	
	for i , map in pairs(_map) do
		if keyType == "key" then
			for j , k in pairs(map.keys) do
				if k == key then
					return map
				end
			end
		else
			for j , k in pairs(map.joy) do
				if k == key then
					return map
				end
			end
		end
	end
end

-- Check if system is checking for keys to bind
function keymap:isBinding()
	return _keyCheck
end

-- Call this in your main code.
function keymap:update( dt )
	if _keyCheck then
		if _keyLast then
			self:bind(_keyCheckFor,_keyLast)
			_keyCheck = false
			_keyLast = nil
			_keyCheckFor = nil
		end
	else
		for i,m in pairs(_map) do
			if m.pushed then
				m.pushed = false
				m.held = true
			end
		end
		
	end
end

--Check if any key in the given mapping is either being pushed or held.
function keymap:isDown( map )
	if map ~= nil then
		return _map[map].held or _map[map].pushed
	end
end

-- Check if any key in the given mapping has been recently pushed
function keymap:isPushed( map )
	if map ~= nil then
		if _map[map].pushed then
			--_map[map].pushed = false
			return true
		end
		return false
	end
end


-- Check if key is being held down
function keymap:isHeld( map )
	return _map[map].held
end

-- Bind key pushed ("id") to mapping ("map")
function keymap:bind( map , id )
	if id[1] == "joy" then
		for i , m in pairs( id ) do
			if i > 1 then
				table.insert( _map[map].joy , id[i] )
			end
		end
	else
		for i , m in pairs( id ) do
			if i > 1 then
				table.insert( _map[map].keys , id[i] )
			end
		end
	end
end

-- Remove all Bindings from mapping
function keymap:unbind( map )
	_map[map].keys = {}
	_map[map].joy = {}
	_keyLast = false
	_keyCheck = false
	_keyCheckFor = false
end


-- Binds the next key pushed to given mapping
function keymap:addBind( map )
	_keyCheck = true
	_keyCheckFor = map
end




-- KEYBOARD SUPPORT --
function love.keypressed( key , isrepeat )
	if not _keyCheck then
		local k = findMap("key", key)
		if k then
			if not isrepeat then
				k.pushed = true
				k.held = false
			else
				k.pushed = false
				k.held = true
			end
			return
		end		
	elseif key == "escape" then
	
		_keyCheck = false
		_keyLast = nil
		_keyCheckfor = nil
	
	else
		print("key")
		_keyLast = { "key" , key }
	end
end

function love.keyreleased( key )
	local k = findMap("key", key)
	if not _keyCheck and k then
			k.held = false
			k.pushed = false
	end
end


-- JOYSTICK SUPPORT --
function love.gamepadpressed( joystick, button )
	local k = findMap("joy", button)
	if not _keyCheck and k then
		k.held = true
		k.pushed = true
	else
		_keyLast = { "joy" , button }
	
	end
end

function love.gamepadreleased( joystick, button )
	local k = findMap("joy", button)
	if not _keyCheck and k then
		k.pressed = false
		k.held = false
	end
end

function love.joystickaxis( joystick, axis, value )
	if not _keyCheck then
		for i , m in pairs ( _map ) do
			for j , k in pairs( m.joy ) do
				if axis  == 1 then
					if k == "left" then
						if value < -0.25 then
							m.pushed = true
							m.held = true
						else
							m.pushed = false
							m.held = false
						end
					end
					if k == "right" then
						if value > 0.25 then
							m.pushed = true
							m.held = true
						else
							m.pushed = false
							m.held = false
						end
					end
				end
				
				if axis  == 2 then
					if k == "up" then
						if value < -0.25 then
							m.pushed = true
							m.held = true
						else
							m.pushed = false
							m.held = false
						end
					end
					if k == "down" then
						if value > 0.25 then
							m.pushed = true
							m.held = true
						else
							m.pushed = false
							m.held = false
						end
					end
				end
			
			end
		end
		
	else
	
		if axis == 1 then 
			if value == -1 then
				_keyLast = { "joy" , "left" }
			elseif value < 1 then
				_keyLast = { "joy" , "right" }
			end
		end
		
		if axis == 2 then 
			if value == -1 then
				_keyLast = { "joy" , "up" }
			elseif value == 1 then
				_keyLast = { "joy" , "down" }
			end
		end
	
	end
end

return keymap
Setup

First off, copy KeyMap.lua into the root directory of your project.

At the top of your main.lua file, add in the following line of code:

Code: Select all

require "KeyMap"
To add in a mapping (a list of keys to look for), use the following line of code in your love.load function:

Code: Select all

KeyMap:addMapping({"up","down","left","right","fire"})
We just added 5 mappings to KeyMap. Now, there are no keys assigned to it. We need to bind a key to a mapping. There are two ways of doing this.
If you know what KeyConstants you want to assign, we can go ahead and assign them manually like so:

Code: Select all

KeyMap:bind("up",{"key","w","up"})
Here, we told KeyMap that we want to bind the "W" key and the up arrow on the keyboard to the "up" mapping. "key" is the KeyType, which will be either "key" for keyboard or "joy" for joystick. These are separated because some KeyConstants are the same as some GamepadButton constants, such as 'a' being both the "A" key on the keyboard and the "A" button on the gamepad.

The other way we can bind to a mapping is to call the pass in what mapping we want to bind to with the following function. It will take whatever key, button, or analog stick that is pressed.

Code: Select all

KeyMap:addBind("up")
Next, you will need to call the following in your love.update(dt) method (NOTE: call this after your detection codes, or isPressed method will not ever return true):

Code: Select all

KeyMap:update(dt)
That is all you need to get set up!

All that is left is to check if our bindings worked. Anywhere you need to check for keypresses, use the following line of code to check whether the button is down:

Code: Select all

KeyMap:isDown("up")
This function will return true if any of the keys mapped to "up" are being pressed, and obviously false otherwise.

Now, what if we want to see if the button has just been pressed? Normally, we would need to check for that in love.keypressed(key,isrepeat), but now we just do:

Code: Select all

KeyMap:isPushed("up")
Additionally, we can check to see if the button is being held down. It does not return true when the mapped key is just being pressed.

Code: Select all

KeyMap:isHeld("up")

I hope you all find this to be useful! I know I have :3

EDIT 01/15/14: Updated code to be an actual module rather than whatever it was before.
User avatar
togFox
Party member
Posts: 764
Joined: Sat Jan 30, 2021 9:46 am
Location: Brisbane, Oztralia

Re: KeyMap - A Keymapping Module with Controller Support

Post by togFox »

I know this is an old thread but I think this topic is very relevant for most (or all) projects and I've yet to see anything so simple. Except I can't make it work. :(

The author was active on this forum for just 3 weeks back in 2014 but the demo provided actually does work as is without adjustment. That's promising. However, the only documentation is in this thread (no github) and the demo has very little relevance to how this might be implemented in a game. I'm seeing if anyone with equal curiosity about this forgotten thread can work this out.

I believe I've followed all the instructions but * shrug *:

Code: Select all

KeyMap = require("KeyMap")

function love.load()
	KeyMap:addMapping({"NW","N","NE","E","SE","S","SW","W","TAKE","INV","USE"})
	
       -- 8 cardinal directions for keypad and WASD etc
	KeyMap:bind("N", {"key","kp8","w"})
	KeyMap:bind("NE", {"key","kp9","e"})
	KeyMap:bind("E", {"key","kp6","d"})
	KeyMap:bind("SE", {"key","kp3","c"})
	KeyMap:bind("S", {"key","kp2","x"})
	KeyMap:bind("SW", {"key","kp1","z"})
	KeyMap:bind("W", {"key","kp4","a"})
	KeyMap:bind("NW", {"key","kp7","q"})
end

function love.update(dt)
	KeyMap:update(dt)
end
function love.keypressed( key, scancode, isrepeat )
	if KeyMap:isPushed("N") then
               print("Hello world")
	end	
end
When I press N to travel north on keypad or WASD then I expect a greeting in the console but nothing happens. All this works in the demo but the demo does things that are not documented here. I think this simple module can become a part of my toolkit if I can work out what it's doing.
Current project:
https://togfox.itch.io/backyard-gridiron-manager
American football manager/sim game - build and manage a roster and win season after season
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: KeyMap - A Keymapping Module with Controller Support

Post by ReFreezed »

A quick look at the code reveals that the library overwrites love.keypressed and some other event handlers. Instead of calling KeyMap:isPushed() in love.keypressed you should do it in love.update and let the library handle all keyboard+gamepad input.

In some other libraries you have to do something like this for the library to work:

Code: Select all

function love.keypressed(key, isRepeat)
	TheLibrary:keypressed(key, isRepeat)
end
...but here you just have to call KeyMap:update(dt) in love.update.
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot] and 21 guests