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