Difference between revisions of "Professional controls"

m (Fixed bugs in two examples)
m (Improved the code (corrected deadzone, mapped analogue to digital, added tap functionality, added analogue constrain to circle))
Line 18: Line 18:
 
update = function() -- Call this in love.update()
 
update = function() -- Call this in love.update()
 
-- Reset controls
 
-- Reset controls
control.horiz, control.vert, control.left, control.up, control.down, control.right = 0,0,false,false,false,false
+
for k,v in pairs(control.keys) do control[k] = false end
control.jump, control.attack, control.menu = false,false,false
 
 
 
 
-- Check both key sets
 
-- Check both key sets
Line 26: Line 25:
 
 
 
-- Check joystick if it's enabled
 
-- Check joystick if it's enabled
if useJoystick then -- Replace "useJoystick" with whatever variable you want to use
+
if options.useJoystick then -- Replace "options.useJoystick" with whatever variable you want to use
 
control.horiz = love.joystick.getAxis(0, control.joyaxes.horiz)
 
control.horiz = love.joystick.getAxis(0, control.joyaxes.horiz)
 
control.vert = love.joystick.getAxis(0, control.joyaxes.vert)
 
control.vert = love.joystick.getAxis(0, control.joyaxes.vert)
 
for k,v in pairs(control.joybtns) do control[k] = control[k] or love.joystick.isDown(0, v) end
 
for k,v in pairs(control.joybtns) do control[k] = control[k] or love.joystick.isDown(0, v) end
 
local deadzone = .1 -- Deadzone prevents control creep
 
local deadzone = .1 -- Deadzone prevents control creep
if control.horiz<deadzone and control.horiz>deadzone*-1 then control.horiz = 0 end
+
if control.horiz<deadzone and control.horiz>deadzone*-1 and control.vert<deadzone and control.vert>deadzone*-1 then
if control.vert<deadzone and control.vert>deadzone*-1 then control.vert = 0 end
+
control.horiz,control.vert = 0,0
 +
end
 +
else control.horiz, control.vert = 0,0
 
end
 
end
 +
control.horiz, control.vert = math.min(math.max(-1, control.horiz), 1), math.min(math.max(-1, control.vert), 1)
 
 
-- Impose digital movement onto analogue movement
+
-- Impose digital movement onto analogue movement (and vice-verca)
 
if control.up then control.vert = control.vert-1 end
 
if control.up then control.vert = control.vert-1 end
 
if control.down then control.vert = control.vert+1 end
 
if control.down then control.vert = control.vert+1 end
 
if control.left then control.horiz = control.horiz-1 end
 
if control.left then control.horiz = control.horiz-1 end
 
if control.right then control.horiz = control.horiz+1 end
 
if control.right then control.horiz = control.horiz+1 end
control.horiz, control.vert = math.min(math.max(-1, control.horiz), 1), math.min(math.max(-1, control.vert), 1)
+
if control.horiz < 0 then control.left = true elseif control.horiz > 0 then control.right = true end
 +
if control.vert < 0 then control.up = true elseif control.vert > 0 then control.down = true end
 +
 +
-- offer detection for inputs being tapped, instead of held
 +
for k,v in pairs(control.keys) do
 +
if control[k] then
 +
if control.tap[k] == nil then control.tap[k] = true
 +
elseif control.tap[k] == true then control.tap[k] = false
 +
end
 +
else control.tap[k] = nil
 +
end
 +
end
 +
 +
-- Constrain horiz and vert to a unit circle (comment this out if you want square instead of circular analogue direction)
 +
local l=(x*x+y*y)^.5
 +
if l==0 then local nh, nv = 0, 0
 +
else local nh, nv = control.horiz/l, control.vert/l
 +
end
 +
control.horiz, control.vert = math.abs(control.horiz) > math.abs(nh) and nh or control.horiz, math.abs(control.vert) > math.abs(nv) and nv or control.vert
 
end,
 
end,
 
}
 
}

Revision as of 22:46, 9 August 2011

Introduction

Nearly all professional computer games since the nineties have offered configurable controls in a similar way: Two sets of keys and a joystick, all interchangeable, and an option to disable the joystick. It's pretty straightforward to add this scheme to your own games in Love2D, and will help your game look better.

This article will assume your game needs controls for a single player moving horizontally and vertically, and buttons for jump, attack, and pause menu. The joystick bindings will be made for a standard Xbox controller (left stick for movement, A to jump, B to attack, and > (Start) to go to the menu). Changing the bindings and available controls is quite simple, as is discussed below.

Adding it to your game

Here's the code you'll need. It would be best to put this under love.load.

-- Use the first joystick
love.joystick.open(0)

control = {
	keys = { up="w", left="a", down="s", right="d", jump=" ", attack="lctrl", menu="escape" },
	keys2 = { up="up", left="left", down="down", right="right", jump="z", attack="rctrl", menu="escape" },
	joyaxes = { horiz=0, vert=1 },			-- Defaults for Xbox controller
	joybtns = { jump=0, attack=1, menu=7 },
	
	update = function()		-- Call this in love.update()
		-- Reset controls
		for k,v in pairs(control.keys) do control[k] = false end
		
		-- Check both key sets
		for k,v in pairs(control.keys) do control[k] = control[k] or love.keyboard.isDown(v) end
		for k,v in pairs(control.keys2) do control[k] = control[k] or love.keyboard.isDown(v) end
		
		-- Check joystick if it's enabled
		if options.useJoystick then		-- Replace "options.useJoystick" with whatever variable you want to use
			control.horiz = love.joystick.getAxis(0, control.joyaxes.horiz)
			control.vert = love.joystick.getAxis(0, control.joyaxes.vert)
			for k,v in pairs(control.joybtns) do control[k] = control[k] or love.joystick.isDown(0, v) end
			local deadzone = .1		-- Deadzone prevents control creep
			if control.horiz<deadzone and control.horiz>deadzone*-1 and control.vert<deadzone and control.vert>deadzone*-1 then
				control.horiz,control.vert = 0,0
			end
		else control.horiz, control.vert = 0,0
		end
		control.horiz, control.vert = math.min(math.max(-1, control.horiz), 1), math.min(math.max(-1, control.vert), 1)
		
		-- Impose digital movement onto analogue movement (and vice-verca)
		if control.up then control.vert = control.vert-1 end
		if control.down then control.vert = control.vert+1 end
		if control.left then control.horiz = control.horiz-1 end
		if control.right then control.horiz = control.horiz+1 end
		if control.horiz < 0 then control.left = true elseif control.horiz > 0 then control.right = true end
		if control.vert < 0 then control.up = true elseif control.vert > 0 then control.down = true end
		
		-- offer detection for inputs being tapped, instead of held
		for k,v in pairs(control.keys) do
			if control[k] then
				if control.tap[k] == nil then control.tap[k] = true
				elseif control.tap[k] == true then control.tap[k] = false
				end
			else control.tap[k] = nil
			end
		end
		
		-- Constrain horiz and vert to a unit circle (comment this out if you want square instead of circular analogue direction)
		local l=(x*x+y*y)^.5
		if l==0 then local nh, nv = 0, 0
		else local nh, nv = control.horiz/l, control.vert/l
		end
		control.horiz, control.vert = math.abs(control.horiz) > math.abs(nh) and nh or control.horiz, math.abs(control.vert) > math.abs(nv) and nv or control.vert
	end,
}

Now, in love.update, simply add the line control.update().

Usage and customization

Checking if a control is pressed

In this scheme, each control will be updated as control.[control name]. For example, in the code above, control.jump would be true if space, Z, and/or the joystick's A button is pressed, or false otherwise. Here's a demonstration:

function love.update(dt)
	control.update()
	if control.menu then openPauseMenu() end
	if control.jump and not control.attack then player.doNormalJump() end
end

Movement

Digital directions are handled just like regular controls (for instance, if control.right then player.x = player.x+2). Analogue directions are handled as control.horiz (left and right) and control.vert (up and down), which are decimals between -1 and 1. If no movement keys are pressed and the joystick is centered, control.horiz and control.vert will be 0. Left and up are negative numbers, while right and down are positive. So in its simplest form, one could use:

function love.update(dt)
	control.update()
	local speed = 20		-- Player moves at 20 pixels per second
	player.x = player.x + control.horiz * speed * dt
	player.y = player.y + control.vert * speed * dt
end

Please note that digital movement controls are imposed onto the analogue controls, so for example, holding the left button will subtract 1 from control.horiz.

Changing bindings

For key bindings, simply change the keyconstant for the control. For joystick buttons and axes, change the number of the control. Examples:

control.keys.attack = "q"
control.joybtns.jump = 3

Adding controls

Of course, your game may not have only jump and attack controls, so you'll need to add your own. To do this, change your bindings, like this:

keys = { up="w", left="a", down="s", right="d", menu="escape",  inventory="i", useItem="return" },
keys2 = { up="up", left="left", down="down", right="right", menu="escape", inventory="tab", useItem="e" },
joyaxes = { horiz=0, vert=1 },			-- Defaults for Xbox controller
joybtns = { menu=7, inventory=3, useItem=2 },

And change the first lines of the control.update() function appropriately:

control.horiz, control.vert, control.left, control.up, control.down, control.right = 0,0,false,false,false,false
control.inventory, control.useItem = false,false

Now, control.[control name] will be updated appropriately. Following this example, one could check for control.inventory and control.useItem being pressed.

Xbox controllers

Microsoft has decided that Xbox controllers are the official gamepad for Windows, and as such, are the most common controller a player may have. Therefore, it's advisable to tailor your game's controls to work with them.

One important caveat about Xbox controllers: Both of their triggers (L2 and R2 for those of you more familiar with Playstations) are treated as ONE axis, rather than two buttons!