/ld27

To get this branch, use:
bzr branch http://9ix.org/bzr/ld27
1 by Josh C
zoetrope 1.4
1
-- Class: Group
2
-- A group is a set of sprites. Groups can be used to
3
-- implement layers or keep categories of sprites together.
4
--
5
-- Extends:
6
--		<Class>
7
--
8
-- Event: onUpdate
9
-- Called once each frame, with the elapsed time since the last frame in seconds.
10
--
35 by Josh C
cluke009 zoetrope + my spritebatch changes
11
-- Event: onStartFrame
1 by Josh C
zoetrope 1.4
12
-- Called once each frame like onUpdate, but guaranteed to fire before any others' onUpdate handlers.
13
--
14
-- Event: onEndFrame
15
-- Called once each frame like onUpdate, but guaranteed to fire after all others' onUpdate handlers.
16
17
Group = Class:extend
18
{
19
	-- Property: active
20
	-- If false, none of its member sprites will receive update-related events.
21
	active = true,
22
23
	-- Property: visible
24
	-- If false, none of its member sprites will be drawn.
25
	visible = true,
26
27
	-- Property: solid
28
	-- If false, nothing will collide against this group, nor will this group
29
	-- displace any other sprite. This does not prevent collision checking
30
	-- against individual sprites in this group, however.
31
	solid = true,
32
33
	-- Property: sprites
35 by Josh C
cluke009 zoetrope + my spritebatch changes
34
	-- A table of member sprites, in drawing order. You can iterate over this
35
	-- table with <members()>.
1 by Josh C
zoetrope 1.4
36
	sprites = {},
37
38
	-- Property: timeScale
39
	-- Multiplier for elapsed time; 1.0 is normal, 0 is completely frozen.
40
	timeScale = 1,
41
42
	-- Property: translate
43
	-- This table's x and y properties shift member sprites' positions when drawn.
44
	-- To draw sprites at their normal position, set both x and y to 0.
45
	translate = { x = 0, y = 0 },
35 by Josh C
cluke009 zoetrope + my spritebatch changes
46
1 by Josh C
zoetrope 1.4
47
	-- Property: translateScale
48
	-- This table's x and y properties multiply member sprites'
49
	-- positions, which you can use to simulate parallax scrolling. To draw
50
	-- sprites at their normal position, set both x and y to 1.
51
	translateScale = { x = 1, y = 1 },
52
35 by Josh C
cluke009 zoetrope + my spritebatch changes
53
	-- Property: scale
54
	-- Zooms in or out all drawing operations in the group, centering them with
55
	-- respect to the <origin> property.
56
	scale = 1,
57
58
	-- Property: distort
59
	-- Distorts the group's scale, similar to <Sprite.distort>.
60
	distort = { x = 1, y = 1 },
61
62
	-- Property: origin
63
	-- Sets the center point for all scaling operations.
64
	origin = { x = 0, y = 0 },
65
1 by Josh C
zoetrope 1.4
66
	-- Method: add
67
	-- Adds a sprite to the group.
68
	--
69
	-- Arguments:
70
	--		sprite - <Sprite> to add
71
	--
72
	-- Returns:
35 by Josh C
cluke009 zoetrope + my spritebatch changes
73
	--		the sprite added, so you can write things like
74
	-- 		self.player = self:add(Player:new())
1 by Josh C
zoetrope 1.4
75
76
	add = function (self, sprite)
77
		assert(sprite, 'asked to add nil to a group')
78
		assert(sprite ~= self, "can't add a group to itself")
35 by Josh C
cluke009 zoetrope + my spritebatch changes
79
1 by Josh C
zoetrope 1.4
80
		if STRICT and self:contains(sprite) then
81
			local info = debug.getinfo(2, 'Sl')
82
			print('Warning: adding a sprite to a group it already belongs to (' ..
83
				  info.short_src .. ' line ' .. info.currentline .. ')')
84
		end
85
86
		table.insert(self.sprites, sprite)
35 by Josh C
cluke009 zoetrope + my spritebatch changes
87
		return sprite
1 by Josh C
zoetrope 1.4
88
	end,
89
90
	-- Method: remove
91
	-- Removes a sprite from the group. If the sprite is
92
	-- not in the group, this does nothing.
35 by Josh C
cluke009 zoetrope + my spritebatch changes
93
	--
1 by Josh C
zoetrope 1.4
94
	-- Arguments:
95
	-- 		sprite - <Sprite> to remove
35 by Josh C
cluke009 zoetrope + my spritebatch changes
96
	--
1 by Josh C
zoetrope 1.4
97
	-- Returns:
98
	-- 		nothing
99
100
	remove = function (self, sprite)
35 by Josh C
cluke009 zoetrope + my spritebatch changes
101
		for i, spr in self:members() do
1 by Josh C
zoetrope 1.4
102
			if spr == sprite then
103
				table.remove(self.sprites, i)
104
				return
105
			end
106
		end
35 by Josh C
cluke009 zoetrope + my spritebatch changes
107
1 by Josh C
zoetrope 1.4
108
		if STRICT then
109
			local info = debug.getinfo(2, 'Sl')
110
			print('Warning: asked to remove a sprite from a group it was not a member of (' ..
111
				  info.short_src .. ' line ' .. info.currentline .. ')')
112
		end
113
	end,
114
115
	-- Method: moveToFront
116
	-- Moves a sprite in the group so that it is drawn on top
117
	-- of all other sprites in the group.
118
	--
119
	-- Arguments:
120
	--		sprite - <Sprite> to move, should already be a member of the group
121
	--
122
	-- Returns:
123
	--		nothing
124
125
	moveToFront = function (self, sprite)
35 by Josh C
cluke009 zoetrope + my spritebatch changes
126
		for i, spr in self:members() do
1 by Josh C
zoetrope 1.4
127
			if spr == sprite then
128
				table.remove(self.sprites, i)
129
				table.insert(self.sprites, sprite)
130
				return
131
			end
132
		end
133
134
		if STRICT then
135
			print('Warning: asked to move sprite to front of group, but is not a member: ' .. sprite)
136
		end
137
	end,
138
139
	-- Method: moveToBack
140
	-- Moves a sprite in the group so that it is drawn below
141
	-- all other sprites in the group.
142
	--
143
	-- Arguments:
144
	--		sprite - <Sprite> to move, should already be a member of the group
145
	--
146
	-- Returns:
147
	--		nothing
35 by Josh C
cluke009 zoetrope + my spritebatch changes
148
1 by Josh C
zoetrope 1.4
149
	moveToBack = function (self, sprite)
35 by Josh C
cluke009 zoetrope + my spritebatch changes
150
		for i, spr in self:members() do
1 by Josh C
zoetrope 1.4
151
			if spr == sprite then
152
				table.remove(self.sprites, i)
153
				table.insert(self.sprites, 1, sprite)
154
				return
155
			end
156
		end
157
158
		if STRICT then
159
			print('Asked to move sprite to back of group, but is not a member: ' .. sprite)
160
		end
161
	end,
162
163
	-- Method: sort
164
	-- Sorts members into a new draw sequence.
165
	--
166
	-- Arguments:
167
	--		func - function to perform the sort. This will receive two <Sprite>s as arguments;
168
	--			   the function must return whether the first should be drawn below the second.
169
	--
170
	-- Returns:
171
	--		nothing
172
173
	sort = function (self, func)
174
		table.sort(self.sprites, func)
175
	end,
176
35 by Josh C
cluke009 zoetrope + my spritebatch changes
177
	-- Method: members
178
	-- A convenience method that iterates over all member sprites.
179
	--
180
	-- Arguments:
181
	--		none
182
	--
183
	-- Returns:
184
	--		order, sprite
185
186
	members = function (self)
187
		return ipairs(self.sprites)
188
	end,
189
1 by Josh C
zoetrope 1.4
190
	-- Method: collide
191
	-- Collides all solid sprites in the group with another sprite or group.
192
	-- This calls the <Sprite.onCollide> event handlers on all sprites that
193
	-- collide with the same arguments <Sprite.collide> does.
194
	--
195
	-- It's often useful to collide a group with itself, e.g. myGroup:collide().
196
	-- This checks for collisions between the sprites that make up the group.
197
	--
198
	-- Arguments:
199
	-- 		... - any number of <Sprite>s or <Group>s to collide with. If none
200
	--			  are specified, the group collides with itself.
35 by Josh C
cluke009 zoetrope + my spritebatch changes
201
	--
1 by Josh C
zoetrope 1.4
202
	-- Returns:
203
	--		nothing
204
	--
205
	-- See Also:
206
	--		<Sprite.collide>
207
208
	collide = function (self, ...)
209
		local list = {...}
210
211
		if #list > 0 then
212
			if STRICT then
213
				for _, other in pairs(list) do
214
					assert(other:instanceOf(Group) or other:instanceOf(Sprite), 'asked to collide non-group/sprite ' ..
215
						   type(other))
216
				end
217
			end
218
219
			Collision:check(self, ...)
220
		else
221
			Collision:check(self, self)
222
		end
223
	end,
224
225
	-- Method: setEffect
226
	-- Sets a pixel effect to use while drawing sprites in this group.
227
	-- See https://love2d.org/wiki/PixelEffect for details on how pixel
228
	-- effects work. After this call, the group's effect property will be
229
	-- set up so you can send variables to it. Only one pixel effect can
230
	-- be active on a group at a time.
231
	--
232
	-- Arguments:
233
	--		filename - filename of effect source code; if nil, this
234
	--				   clears any existing pixel effect.
235
	--		effectType - either 'screen' (applies the effect to the entire
236
	--					 group once, via an offscreen canvas), or 'sprite'
237
	--					 (applies to the effect to each individual draw operation).
238
	--					 Screen effects use more resources, but certain effects
239
	--					 need to work on the entire screen to be effective.
240
	--
241
	-- Returns:
242
	--		whether the effect was successfully created
243
244
	setEffect = function (self, filename, effectType)
245
		effectType = effectType or 'screen'
246
35 by Josh C
cluke009 zoetrope + my spritebatch changes
247
		if love.graphics.isSupported('shader') and
1 by Josh C
zoetrope 1.4
248
		   (effectType == 'sprite' or love.graphics.isSupported('canvas'))then
249
			if filename then
35 by Josh C
cluke009 zoetrope + my spritebatch changes
250
				self.effect = love.graphics.newShader(Cached:text(filename))
1 by Josh C
zoetrope 1.4
251
				self.effectType = effectType
252
			else
253
				self.effect = nil
254
			end
255
256
			return true
257
		else
258
			return false
259
		end
260
	end,
261
262
	-- Method: count
263
	-- Counts how many sprites are in this group.
35 by Josh C
cluke009 zoetrope + my spritebatch changes
264
	--
1 by Josh C
zoetrope 1.4
265
	-- Arguments:
266
	--		subgroups - include subgroups?
35 by Josh C
cluke009 zoetrope + my spritebatch changes
267
	--
1 by Josh C
zoetrope 1.4
268
	-- Returns:
269
	--		integer count
270
271
	count = function (self, subgroups)
272
		if subgroups then
273
			local count = 0
274
35 by Josh C
cluke009 zoetrope + my spritebatch changes
275
			for _, spr in self:members() do
1 by Josh C
zoetrope 1.4
276
				if spr:instanceOf(Group) then
277
					count = count + spr:count(true)
278
				else
279
					count = count + 1
280
				end
281
			end
282
283
			return count
284
		else
285
			return #self.sprites
286
		end
287
	end,
288
289
	-- Method: die
290
	-- Makes the group totally inert. It will not receive
291
	-- update events, draw anything, or be collided.
292
	--
293
	-- Arguments:
294
	--		none
295
	--
296
	-- Returns:
297
	-- 		nothing
298
299
	die = function (self)
300
		self.active = false
301
		self.visible = false
302
		self.solid = false
303
	end,
304
305
	-- Method: revive
306
	-- Makes this group completely active. It will receive
307
	-- update events, draw itself, and be collided.
308
	--
309
	-- Arguments:
310
	--		none
311
	--
312
	-- Returns:
313
	-- 		nothing
314
315
	revive = function (self)
316
		self.active = true
317
		self.visible = true
318
		self.solid = true
319
	end,
320
321
	-- Method: contains
322
	-- Returns whether this group contains a sprite.
323
	--
324
	-- Arguments:
325
	--		sprite - sprite to look for
326
	--		recurse - check subgroups? defaults to true
327
	--
328
	-- Returns:
329
	--		boolean
330
331
	contains = function (self, sprite, recurse)
332
		if recurse ~= false then recurse = true end
333
35 by Josh C
cluke009 zoetrope + my spritebatch changes
334
		for _, spr in self:members() do
1 by Josh C
zoetrope 1.4
335
			if spr == sprite then return true end
336
337
			if recurse and spr:instanceOf(Group) and spr:contains(sprite) then
338
				return true
339
			end
340
		end
341
342
		return false
343
	end,
344
345
	-- Method: loadLayers
346
	-- Loads layers from a Lua source file (as generated by Tiled -- http://mapeditor.org).
347
	-- Each layer is created as a <Group> belonging to this one and added to preserve its
348
	-- ordering. Tile layers are created as <Map> instances; object layers will try to create
349
	-- instances of a class named by the object's name property. If no class exists by
350
	-- this name, or the object has no name property, a gray fill will be created instead,
351
	-- as a placeholder. If the object has a property named _the, then this will set
352
	-- the.[whatever] to it.
353
	--
354
	-- Arguments:
355
	--		file - filename to load
356
	--		tileClass - class to create tiles in tile layers with; constructor
357
	--				    will be called with properties: image, width,
358
	--			 	    height, imageOffset (with x and y sub-properties)
359
	--
360
	-- Returns:
361
	--		nothing
362
35 by Josh C
cluke009 zoetrope + my spritebatch changes
363
    loadLayers = function (self, file, tileClass)
364
        local ok, data = pcall(loadstring(Cached:text(file)))
365
        local _, _, directory = string.find(file, '^(.*[/\\])')
366
        directory = directory or ''
367
368
        if ok then
369
            -- store tile properties by gid
370
371
            local tileProtos = {}
372
373
            for _, tileset in pairs(data.tilesets) do
374
                for _, tile in pairs(tileset.tiles) do
375
                    local id = tileset.firstgid + tile.id
376
377
                    for key, value in pairs(tile.properties) do
378
                        tile.properties[key] = tovalue(value)
379
                    end
380
381
                    tileProtos[id] = tile
382
                    tileProtos[id].width = tileset.tilewidth
383
                    tileProtos[id].height = tileset.tileheight
384
                end
385
            end
386
387
            for _, layer in pairs(data.layers) do
388
                if self.prototype[layer.name] then
389
                    error('The class you are loading layers into reserves the ' .. layer.name .. ' property for its own use; you cannot load a layer with that name')
390
                end
391
392
                if STRICT and self[layer.name] then
393
                    local info = debug.getinfo(2, 'Sl')
394
                    print('Warning: a property named ' .. layer.name .. ' already exists in this group (' ..
395
                          info.short_src .. ', line ' .. info.currentline .. ')')
396
                end
397
398
                if layer.type == 'tilelayer' then
399
                    local map = Map:new{ spriteWidth = data.tilewidth, spriteHeight = data.tileheight }
400
                    map:empty(layer.width, layer.height)
401
402
                    -- load tiles
403
404
                    for _, tiles in pairs(data.tilesets) do
405
                        map:loadTiles(directory .. tiles.image, tileClass or Tile, tiles.firstgid)
406
407
                        -- and mix in properties where applicable
408
409
                        for id, tile in pairs(tileProtos) do
410
                            if map.sprites[id] then
411
                                map.sprites[id]:mixin(tile.properties)
412
                            end
413
                        end
414
                    end
415
416
                    -- load tile data
417
418
                    local x = 1
419
                    local y = 1
420
421
                    for _, val in ipairs(layer.data) do
422
                        map.map[x][y] = val
423
                        x = x + 1
424
425
                        if x > layer.width then
426
                            x = 1
427
                            y = y + 1
428
                        end
429
                    end
430
431
                    self[layer.name] = map
432
                    self:add(map)
433
                elseif layer.type == 'objectgroup' then
434
                    local group = Group:new()
435
436
                    for _, obj in pairs(layer.objects) do
437
                        -- roll in tile properties if based on a tile
438
439
                        if obj.gid and tileProtos[obj.gid] then
440
                            local tile = tileProtos[obj.gid]
441
442
                            obj.name = tile.properties.name
443
                            obj.width = tile.width
444
                            obj.height = tile.height
445
446
                            for key, value in pairs(tile.properties) do
447
                                obj.properties[key] = tovalue(value)
448
                            end
449
450
                            -- Tiled tile-based objects measure their y
451
                            -- position at their lower-left corner, instead
452
                            -- of their upper-left corner as usual
453
454
                            obj.y = obj.y - obj.height
455
                        end
456
457
                        -- create a new object if the class does exist
458
459
                        local spr
460
461
                        if _G[obj.name] then
462
                            obj.properties.x = obj.x
463
                            obj.properties.y = obj.y
464
                            obj.properties.width = obj.width
465
                            obj.properties.height = obj.height
466
467
                            spr = _G[obj.name]:new(obj.properties)
468
                        else
469
                            spr = Fill:new{ x = obj.x, y = obj.y, width = obj.width, height = obj.height, fill = { 128, 128, 128 } }
470
                        end
471
472
                        if obj.properties._the then
473
                            the[obj.properties._the] = spr
474
                        end
475
476
                        group:add(spr)
477
                    end
478
479
                    self[layer.name] = group
480
                    self:add(group)
481
                else
482
                    error("don't know how to create a " .. layer.type .. " layer from file data")
483
                end
484
            end
485
        else
486
            error('could not load layers from file: ' .. data)
487
        end
488
    end,
489
1 by Josh C
zoetrope 1.4
490
491
	-- passes startFrame events to member sprites
492
493
	startFrame = function (self, elapsed)
494
		if not self.active then return end
495
		elapsed = elapsed * self.timeScale
35 by Josh C
cluke009 zoetrope + my spritebatch changes
496
497
		for _, spr in self:members() do
1 by Josh C
zoetrope 1.4
498
			if spr.active then spr:startFrame(elapsed) end
499
		end
500
501
		if self.onStartFrame then self:onStartFrame(elapsed) end
502
	end,
503
504
	-- passes update events to member sprites
505
506
	update = function (self, elapsed)
507
		if not self.active then return end
508
		elapsed = elapsed * self.timeScale
509
35 by Josh C
cluke009 zoetrope + my spritebatch changes
510
		for _, spr in self:members() do
1 by Josh C
zoetrope 1.4
511
			if spr.active then spr:update(elapsed) end
512
		end
513
514
		if self.onUpdate then self:onUpdate(elapsed) end
515
	end,
516
517
	-- passes endFrame events to member sprites
518
519
	endFrame = function (self, elapsed)
520
		if not self.active then return end
521
		elapsed = elapsed * self.timeScale
522
35 by Josh C
cluke009 zoetrope + my spritebatch changes
523
		for _, spr in self:members() do
1 by Josh C
zoetrope 1.4
524
			if spr.active then spr:endFrame(elapsed) end
525
		end
526
527
		if self.onEndFrame then self:onEndFrame(elapsed) end
528
	end,
529
530
	-- Method: draw
531
	-- Draws all visible member sprites onscreen.
532
	--
533
	-- Arguments:
534
	--		x - x offset in pixels
535
	--		y - y offset in pixels
536
537
	draw = function (self, x, y)
538
		if not self.visible then return end
539
		x = x or self.translate.x
540
		y = y or self.translate.y
35 by Josh C
cluke009 zoetrope + my spritebatch changes
541
1 by Josh C
zoetrope 1.4
542
		local scrollX = x * self.translateScale.x
543
		local scrollY = y * self.translateScale.y
544
		local appWidth = the.app.width
545
		local appHeight = the.app.height
35 by Josh C
cluke009 zoetrope + my spritebatch changes
546
		local scaled = self.scale ~= 1 or self.distort.x ~= 1 or self.distort.y ~= 1
1 by Josh C
zoetrope 1.4
547
548
		if self.effect then
549
			if self.effectType == 'screen' then
550
				if not self._canvas then self._canvas = love.graphics.newCanvas() end
551
				self._canvas:clear()
552
				love.graphics.setCanvas(self._canvas)
553
			elseif self.effectType == 'sprite' then
35 by Josh C
cluke009 zoetrope + my spritebatch changes
554
				love.graphics.setShader(self.effect)
1 by Josh C
zoetrope 1.4
555
			end
556
		end
35 by Josh C
cluke009 zoetrope + my spritebatch changes
557
558
		if scaled then
559
			local scaleX = self.scale * self.distort.x
560
			local scaleY = self.scale * self.distort.y
561
562
			love.graphics.push()
563
			love.graphics.translate(self.origin.x, self.origin.y)
564
			love.graphics.scale(scaleX, scaleY)
565
			love.graphics.translate(- self.origin.x, - self.origin.y)
566
		end
567
568
		for _, spr in self:members() do
1 by Josh C
zoetrope 1.4
569
			if spr.visible then
570
				if spr.translate then
571
					spr:draw(spr.translate.x + scrollX, spr.translate.y + scrollY)
572
				elseif spr.x and spr.y and spr.width and spr.height then
573
					local sprX = spr.x + scrollX
574
					local sprY = spr.y + scrollY
575
576
					if sprX < appWidth and sprX + spr.width > 0 and
577
					   sprY < appHeight and sprY + spr.height > 0 then
578
						spr:draw(sprX, sprY)
579
					end
580
				else
581
					spr:draw(scrollX, scrollY)
582
				end
583
			end
584
		end
35 by Josh C
cluke009 zoetrope + my spritebatch changes
585
586
		if scaled then
587
			love.graphics.pop()
588
		end
589
1 by Josh C
zoetrope 1.4
590
		if self.effect then
591
			if self.effectType == 'screen' then
35 by Josh C
cluke009 zoetrope + my spritebatch changes
592
				love.graphics.setShader(self.effect)
1 by Josh C
zoetrope 1.4
593
				love.graphics.setCanvas()
594
				love.graphics.draw(self._canvas)
595
			end
596
35 by Josh C
cluke009 zoetrope + my spritebatch changes
597
			love.graphics.setShader()
1 by Josh C
zoetrope 1.4
598
		end
599
	end,
600
601
	__tostring = function (self)
602
		local result = 'Group ('
603
604
		if self.active then
605
			result = result .. 'active'
606
		else
607
			result = result .. 'inactive'
608
		end
609
610
		if self.visible then
611
			result = result .. ', visible'
612
		else
613
			result = result .. ', invisible'
614
		end
615
616
		if self.solid then
617
			result = result .. ', solid'
618
		else
619
			result = result .. ', not solid'
620
		end
621
622
		return result .. ', ' .. self:count(true) .. ' sprites)'
623
	end
624
}