/ld26

To get this branch, use:
bzr branch http://9ix.org/bzr/ld26
1 by Josh C
zoetrope 1.4
1
-- Class: Gamepad
2
-- This represents a single gamepad connected to the user's computer. This offers the 
3
-- usual functions to check on the status of buttons; you can also inspect other
4
-- controls, like analog joysticks, by checking properties like <axes> and <hats>.
5
-- (Incidentally, a hat is used for things like a digital control pad.) 
6
--
7
-- Normally, gamepad buttons are indexed by number. This class also adds virtual buttons
8
-- named 'left', 'right', 'up', 'down'. This consults the first two analog axes
9
-- and the first hat of the gamepad (if they exist) to set these.
10
--
11
-- A gamepad is only recognized if it is plugged in when LOVE starts up. There's no way
12
-- to tell if the user has unplugged it after that; it just acts inert. A gamepad object
13
-- with no connected hardware at startup will exist and respond to calls like <pressed()>,
14
-- but it will always return false. You can tell it's unplugged because its active property
15
-- will be false, and its name will always be 'NO DEVICE CONNECTED'.
16
--
17
-- Extends:
18
--		<Sprite>
19
20
Gamepad = Sprite:extend
21
{
22
	-- Property: number
23
	-- The index of the gamepad, starting at 1.
24
25
	-- Property: name
26
	-- The name of the gamepad, e.g. "XBox Controller".
27
28
	-- Property: numAxes
29
	-- The number of available axes, e.g. for analog controls.
30
31
	-- Property: numBalls
32
	-- The number of available balls.
33
34
	-- Property: numButtons
35
	-- The number of available buttons.
36
37
	-- Property: numHats
38
	-- The number of available hats, e.g. digital movement controls.
39
40
	-- Property: axes
41
	-- The state of all analog axes on the gamepad, indexed by number.
42
	-- Values range from -1 to 1, where 0 is completely neutral.
43
44
	-- Property: balls
45
	-- The amount of motion by a each ball on the gamepad, indexed by number.
46
	-- Not sure what the range of values is here.
47
48
	-- Property: hats
49
	-- The state of each hat on the gamepad, indexed by number. Each one has
50
	-- one of these values: https://love2d.org/wiki/JoystickConstant
51
52
	-- Property: deadZone
53
	-- Any motion by an analog control (from 0 to 1) less than this value is
54
	-- ignored when simulating digital controls.
55
56
	deadZone = 0.1,
57
58
	-- private property: _thisFrame
59
	-- what keys are pressed this frame
60
	-- if you are interested in this, use allPressed() instead
61
62
	_thisFrame = {},
63
64
	-- private property: _lastFrame
65
	-- what buttons were pressed last frame
66
67
	_lastFrame = {},
68
69
	new = function (self, obj)
70
		obj = self:extend(obj)
71
		assert(type(obj.number) == 'number', 'must set a gamepad number')
72
73
		obj.axes = {}
74
		obj.balls = {}
75
		obj.hats = {}
76
77
		if obj.number <= love.joystick.getNumJoysticks() then
78
			if not love.joystick.isOpen(obj.number) then love.joystick.open(obj.number) end
79
			obj.name = love.joystick.getName(obj.number)
80
			obj.numAxes = love.joystick.getNumAxes(obj.number)
81
			obj.numBalls = love.joystick.getNumBalls(obj.number)
82
			obj.numButtons = love.joystick.getNumButtons(obj.number)
83
			obj.numHats = love.joystick.getNumHats(obj.number)
84
85
			-- set initial values for axes and balls
86
			-- hat values are strings so nil comparisons are safe
87
88
			for i = 1, obj.numAxes do
89
				obj.axes[i] = 0
90
			end
91
92
			for i = 1, obj.numBalls do
93
				obj.balls[i] = { x = 0, y = 0 }
94
			end
95
		else
96
			obj.name = 'NO DEVICE CONNECTED'
97
			obj.numAxes = 0
98
			obj.numBalls = 0
99
			obj.numButtons = 0
100
			obj.numHats = 0
101
		end
102
103
		love.joystickpressed = Gamepad._dispatchPress
104
		love.joystickreleased = Gamepad._dispatchRelease
105
		if obj.onNew then obj:onNew() end
106
		return obj
107
	end,
108
109
	-- Method: pressed
110
	-- Are *any* of the buttons passed held down this frame?
111
	--
112
	-- Arguments:
113
	--		button numbers passed as individual arguments
114
	--
115
	-- Returns:
116
	-- 		boolean
117
118
	pressed = function (self, ...)
119
		local buttons = {...}
120
121
		for _, value in pairs(buttons) do
122
			if self._thisFrame[value] then
123
				return true
124
			end
125
		end
126
		
127
		return false
128
	end,
129
130
	-- Method: justPressed
131
	-- Are *any* of the buttons passed pressed for the first time this frame?
132
	--
133
	-- Arguments:
134
	--		button numbers passed as individual arguments
135
	--
136
	-- Returns:
137
	-- 		boolean
138
139
	justPressed = function (self, ...)
140
		local buttons = {...}
141
142
		for _, value in pairs(buttons) do
143
			if self._thisFrame[value] and not self._lastFrame[value] then
144
				return true
145
			end
146
		end
147
		
148
		return false
149
	end,
150
151
	-- Method: released
152
	-- Are *all* of the buttons passed not held down this frame?
153
	--
154
	-- Arguments:
155
	--		button numbers passed as individual arguments
156
	--
157
	-- Returns:
158
	-- 		boolean
159
160
	released = function (self, ...)
161
		local buttons = {...}
162
	
163
		for _, value in pairs(buttons) do
164
			if self._thisFrame[value] then
165
				return false
166
			end
167
		end
168
		
169
		return true
170
	end,
171
172
	-- Method: justReleased
173
	-- Are *any* of the buttons passed released after being held last frame?
174
	--
175
	-- Arguments:
176
	--		button numbers passed as individual arguments
177
	--
178
	-- Returns:
179
	-- 		boolean
180
181
	justReleased = function (self, ...)
182
		local buttons = {...}
183
	
184
		for _, value in pairs(buttons) do
185
			if self._lastFrame[value] and not self._thisFrame[value] then
186
				return true
187
			end
188
		end
189
		
190
		return false
191
	end,
192
193
	-- Method: allPressed
194
	-- Returns all buttons currently pressed this frame.
195
	--
196
	-- Arguments:
197
	--		none
198
	--
199
	-- Returns:
200
	--		string button descriptions; if nothing is pressed, nil
201
202
	allPressed = function (self)
203
		local result = {}
204
205
		for key, value in pairs(self._thisFrame) do
206
			if value then table.insert(result, key) end
207
		end
208
		
209
		return unpack(result)
210
	end,
211
212
	-- Method: allJustPressed
213
	-- Returns all buttons just pressed this frame.
214
	--
215
	-- Arguments:
216
	--		none
217
	--
218
	-- Returns:
219
	--		string button descriptions; if nothing is just pressed, nil
220
221
	allJustPressed = function (self)
222
		local result = {}
223
224
		for key, value in pairs(self._thisFrame) do
225
			if value and not self._lastFrame[key] then table.insert(result, key) end
226
		end
227
		
228
		return unpack(result)
229
	end,
230
231
	-- Method: allJustReleased
232
	-- Returns all buttons just released this frame.
233
	--
234
	-- Arguments:
235
	--		none
236
	--
237
	-- Returns:
238
	--		string button descriptions; if nothing is just pressed, nil
239
240
	allJustReleased = function (self)
241
		local result = {}
242
243
		for key, value in pairs(self._thisFrame) do
244
			if not value and self._lastFrame[key] then table.insert(result, key) end
245
		end
246
		
247
		return unpack(result)
248
	end,
249
250
	buttonPressed = function (self, button)
251
		self._thisFrame[button] = true
252
	end,
253
254
	buttonReleased = function (self, button)
255
		self._thisFrame[button] = false
256
	end,
257
258
	endFrame = function (self, elapsed)
259
		-- move button values to the previous frame
260
261
		for key, value in pairs(self._thisFrame) do
262
			self._lastFrame[key] = value
263
		end
264
265
		-- set values
266
267
		for i = 1, self.numAxes do
268
			self.axes[i] = love.joystick.getAxis(self.number, i)
269
		end
270
271
		for i = 1, self.numBalls do
272
			self.balls[i].x, self.balls[i].y = love.joystick.getBall(self.number, i)
273
		end
274
275
		for i = 1, self.numHats do
276
			self.hats[i] = love.joystick.getHat(self.number, i)
277
		end
278
279
		-- simulate digital controls
280
281
		self._thisFrame['up'] = false
282
		self._thisFrame['down'] = false
283
		self._thisFrame['left'] = false
284
		self._thisFrame['right'] = false
285
286
		if self.numHats > 0 then
287
			local hat = self.hats[1]
288
289
			if hat == 'u' then
290
				self._thisFrame['up'] = true
291
			elseif hat == 'd' then
292
				self._thisFrame['down'] = true
293
			elseif hat == 'l' then
294
				self._thisFrame['left'] = true
295
			elseif hat == 'r' then
296
				self._thisFrame['right'] = true
297
			elseif hat == 'lu' then
298
				self._thisFrame['up'] = true
299
				self._thisFrame['left'] = true
300
			elseif hat == 'ru' then
301
				self._thisFrame['up'] = true
302
				self._thisFrame['right'] = true
303
			elseif hat == 'ld' then
304
				self._thisFrame['down'] = true
305
				self._thisFrame['left'] = true
306
			elseif hat == 'rd' then
307
				self._thisFrame['down'] = true
308
				self._thisFrame['right'] = true
309
			end
310
		end
311
312
		if self.numAxes > 1 then
313
			local xAxis = self.axes[1]
314
			local yAxis = self.axes[2]
315
316
			if math.abs(xAxis) > self.deadZone then
317
				if xAxis < 0 then
318
					self._thisFrame['left'] = true
319
				else
320
					self._thisFrame['right'] = true
321
				end
322
			end
323
324
			if math.abs(yAxis) > self.deadZone then
325
				if yAxis < 0 then
326
					self._thisFrame['up'] = true
327
				else
328
					self._thisFrame['down'] = true
329
				end
330
			end
331
		end
332
	
333
		Sprite.endFrame(self)
334
	end,
335
336
	-- private method: _dispatchPress
337
	-- receives a Love joystickpressed callback and hands it off
338
	-- to the appropriate gamepad.
339
340
	_dispatchPress = function (number, button)
341
		if the.gamepads[number] then
342
			the.gamepads[number]:buttonPressed(button)
343
		end
344
	end,
345
346
	-- private method: _dispatchRelease
347
	-- receives a Love joystickreleased callback and hands it off
348
	-- to the appropriate gamepad.
349
350
	_dispatchRelease = function (number, button)
351
		if the.gamepads[number] then
352
			the.gamepads[number]:buttonReleased(button)
353
		end
354
	end
355
}