2
-- A group is a set of sprites. Groups can be used to
3
-- implement layers or keep categories of sprites together.
9
-- Called once each frame, with the elapsed time since the last frame in seconds.
11
-- Event: onBeginFrame
12
-- Called once each frame like onUpdate, but guaranteed to fire before any others' onUpdate handlers.
15
-- Called once each frame like onUpdate, but guaranteed to fire after all others' onUpdate handlers.
20
-- If false, none of its member sprites will receive update-related events.
24
-- If false, none of its member sprites will be drawn.
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.
34
-- A table of member sprites, in drawing order.
37
-- Property: timeScale
38
-- Multiplier for elapsed time; 1.0 is normal, 0 is completely frozen.
41
-- Property: translate
42
-- This table's x and y properties shift member sprites' positions when drawn.
43
-- To draw sprites at their normal position, set both x and y to 0.
44
translate = { x = 0, y = 0 },
46
-- Property: translateScale
47
-- This table's x and y properties multiply member sprites'
48
-- positions, which you can use to simulate parallax scrolling. To draw
49
-- sprites at their normal position, set both x and y to 1.
50
translateScale = { x = 1, y = 1 },
53
-- Adds a sprite to the group.
56
-- sprite - <Sprite> to add
61
add = function (self, sprite)
62
assert(sprite, 'asked to add nil to a group')
63
assert(sprite ~= self, "can't add a group to itself")
65
if STRICT and self:contains(sprite) then
66
local info = debug.getinfo(2, 'Sl')
67
print('Warning: adding a sprite to a group it already belongs to (' ..
68
info.short_src .. ' line ' .. info.currentline .. ')')
71
table.insert(self.sprites, sprite)
75
-- Removes a sprite from the group. If the sprite is
76
-- not in the group, this does nothing.
79
-- sprite - <Sprite> to remove
84
remove = function (self, sprite)
85
for i, spr in ipairs(self.sprites) do
87
table.remove(self.sprites, i)
93
local info = debug.getinfo(2, 'Sl')
94
print('Warning: asked to remove a sprite from a group it was not a member of (' ..
95
info.short_src .. ' line ' .. info.currentline .. ')')
99
-- Method: moveToFront
100
-- Moves a sprite in the group so that it is drawn on top
101
-- of all other sprites in the group.
104
-- sprite - <Sprite> to move, should already be a member of the group
109
moveToFront = function (self, sprite)
110
for i, spr in ipairs(self.sprites) do
111
if spr == sprite then
112
table.remove(self.sprites, i)
113
table.insert(self.sprites, sprite)
119
print('Warning: asked to move sprite to front of group, but is not a member: ' .. sprite)
123
-- Method: moveToBack
124
-- Moves a sprite in the group so that it is drawn below
125
-- all other sprites in the group.
128
-- sprite - <Sprite> to move, should already be a member of the group
133
moveToBack = function (self, sprite)
134
for i, spr in ipairs(self.sprites) do
135
if spr == sprite then
136
table.remove(self.sprites, i)
137
table.insert(self.sprites, 1, sprite)
143
print('Asked to move sprite to back of group, but is not a member: ' .. sprite)
148
-- Sorts members into a new draw sequence.
151
-- func - function to perform the sort. This will receive two <Sprite>s as arguments;
152
-- the function must return whether the first should be drawn below the second.
157
sort = function (self, func)
158
table.sort(self.sprites, func)
162
-- Collides all solid sprites in the group with another sprite or group.
163
-- This calls the <Sprite.onCollide> event handlers on all sprites that
164
-- collide with the same arguments <Sprite.collide> does.
166
-- It's often useful to collide a group with itself, e.g. myGroup:collide().
167
-- This checks for collisions between the sprites that make up the group.
170
-- ... - any number of <Sprite>s or <Group>s to collide with. If none
171
-- are specified, the group collides with itself.
179
collide = function (self, ...)
184
for _, other in pairs(list) do
185
assert(other:instanceOf(Group) or other:instanceOf(Sprite), 'asked to collide non-group/sprite ' ..
190
Collision:check(self, ...)
192
Collision:check(self, self)
197
-- Sets a pixel effect to use while drawing sprites in this group.
198
-- See https://love2d.org/wiki/PixelEffect for details on how pixel
199
-- effects work. After this call, the group's effect property will be
200
-- set up so you can send variables to it. Only one pixel effect can
201
-- be active on a group at a time.
204
-- filename - filename of effect source code; if nil, this
205
-- clears any existing pixel effect.
206
-- effectType - either 'screen' (applies the effect to the entire
207
-- group once, via an offscreen canvas), or 'sprite'
208
-- (applies to the effect to each individual draw operation).
209
-- Screen effects use more resources, but certain effects
210
-- need to work on the entire screen to be effective.
213
-- whether the effect was successfully created
215
setEffect = function (self, filename, effectType)
216
effectType = effectType or 'screen'
218
if love.graphics.isSupported('pixeleffect') and
219
(effectType == 'sprite' or love.graphics.isSupported('canvas'))then
221
self.effect = love.graphics.newPixelEffect(Cached:text(filename))
222
self.effectType = effectType
234
-- Counts how many sprites are in this group.
237
-- subgroups - include subgroups?
242
count = function (self, subgroups)
246
for _, spr in pairs(self.sprites) do
247
if spr:instanceOf(Group) then
248
count = count + spr:count(true)
261
-- Makes the group totally inert. It will not receive
262
-- update events, draw anything, or be collided.
270
die = function (self)
277
-- Makes this group completely active. It will receive
278
-- update events, draw itself, and be collided.
286
revive = function (self)
293
-- Returns whether this group contains a sprite.
296
-- sprite - sprite to look for
297
-- recurse - check subgroups? defaults to true
302
contains = function (self, sprite, recurse)
303
if recurse ~= false then recurse = true end
305
for _, spr in pairs(self.sprites) do
306
if spr == sprite then return true end
308
if recurse and spr:instanceOf(Group) and spr:contains(sprite) then
316
-- Method: loadLayers
317
-- Loads layers from a Lua source file (as generated by Tiled -- http://mapeditor.org).
318
-- Each layer is created as a <Group> belonging to this one and added to preserve its
319
-- ordering. Tile layers are created as <Map> instances; object layers will try to create
320
-- instances of a class named by the object's name property. If no class exists by
321
-- this name, or the object has no name property, a gray fill will be created instead,
322
-- as a placeholder. If the object has a property named _the, then this will set
323
-- the.[whatever] to it.
326
-- file - filename to load
327
-- tileClass - class to create tiles in tile layers with; constructor
328
-- will be called with properties: image, width,
329
-- height, imageOffset (with x and y sub-properties)
334
loadLayers = function (self, file, tileClass)
335
local ok, data = pcall(loadstring(Cached:text(file)))
336
local _, _, directory = string.find(file, '^(.*[/\\])')
337
directory = directory or ''
340
-- store tile properties by gid
342
local tileProtos = {}
344
for _, tileset in pairs(data.tilesets) do
345
for _, tile in pairs(tileset.tiles) do
346
local id = tileset.firstgid + tile.id
348
for key, value in pairs(tile.properties) do
349
tile.properties[key] = tovalue(value)
352
tileProtos[id] = tile
353
tileProtos[id].width = tileset.tilewidth
354
tileProtos[id].height = tileset.tileheight
358
for _, layer in pairs(data.layers) do
359
if self.prototype[layer.name] then
360
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')
363
if STRICT and self[layer.name] then
364
local info = debug.getinfo(2, 'Sl')
365
print('Warning: a property named ' .. layer.name .. ' already exists in this group (' ..
366
info.short_src .. ', line ' .. info.currentline .. ')')
369
if layer.type == 'tilelayer' then
370
local map = Map:new{ spriteWidth = data.tilewidth, spriteHeight = data.tileheight }
371
map:empty(layer.width, layer.height)
375
for _, tiles in pairs(data.tilesets) do
376
map:loadTiles(directory .. tiles.image, tileClass or Tile, tiles.firstgid)
378
-- and mix in properties where applicable
380
for id, tile in pairs(tileProtos) do
381
if map.sprites[id] then
382
map.sprites[id]:mixin(tile.properties)
392
for _, val in ipairs(layer.data) do
396
if x > layer.width then
402
self[layer.name] = map
404
elseif layer.type == 'objectgroup' then
405
local group = Group:new()
407
for _, obj in pairs(layer.objects) do
408
-- roll in tile properties if based on a tile
410
if obj.gid and tileProtos[obj.gid] then
411
local tile = tileProtos[obj.gid]
413
obj.name = tile.properties.name
414
obj.width = tile.width
415
obj.height = tile.height
417
for key, value in pairs(tile.properties) do
418
obj.properties[key] = tovalue(value)
421
-- Tiled tile-based objects measure their y
422
-- position at their lower-left corner, instead
423
-- of their upper-left corner as usual
425
obj.y = obj.y - obj.height
428
-- create a new object if the class does exist
433
obj.properties.x = obj.x
434
obj.properties.y = obj.y
435
obj.properties.width = obj.width
436
obj.properties.height = obj.height
438
spr = _G[obj.name]:new(obj.properties)
440
spr = Fill:new{ x = obj.x, y = obj.y, width = obj.width, height = obj.height, fill = { 128, 128, 128 } }
443
if obj.properties._the then
444
the[obj.properties._the] = spr
450
self[layer.name] = group
453
error("don't know how to create a " .. layer.type .. " layer from file data")
457
error('could not load layers from file: ' .. data)
461
-- passes startFrame events to member sprites
463
startFrame = function (self, elapsed)
464
if not self.active then return end
465
elapsed = elapsed * self.timeScale
467
for _, spr in pairs(self.sprites) do
468
if spr.active then spr:startFrame(elapsed) end
471
if self.onStartFrame then self:onStartFrame(elapsed) end
474
-- passes update events to member sprites
476
update = function (self, elapsed)
477
if not self.active then return end
478
elapsed = elapsed * self.timeScale
480
for _, spr in pairs(self.sprites) do
481
if spr.active then spr:update(elapsed) end
484
if self.onUpdate then self:onUpdate(elapsed) end
487
-- passes endFrame events to member sprites
489
endFrame = function (self, elapsed)
490
if not self.active then return end
491
elapsed = elapsed * self.timeScale
493
for _, spr in pairs(self.sprites) do
494
if spr.active then spr:endFrame(elapsed) end
497
if self.onEndFrame then self:onEndFrame(elapsed) end
501
-- Draws all visible member sprites onscreen.
504
-- x - x offset in pixels
505
-- y - y offset in pixels
507
draw = function (self, x, y)
508
if not self.visible then return end
509
x = x or self.translate.x
510
y = y or self.translate.y
512
local scrollX = x * self.translateScale.x
513
local scrollY = y * self.translateScale.y
514
local appWidth = the.app.width
515
local appHeight = the.app.height
518
if self.effectType == 'screen' then
519
if not self._canvas then self._canvas = love.graphics.newCanvas() end
521
love.graphics.setCanvas(self._canvas)
522
elseif self.effectType == 'sprite' then
523
love.graphics.setPixelEffect(self.effect)
527
for _, spr in pairs(self.sprites) do
529
if spr.translate then
530
spr:draw(spr.translate.x + scrollX, spr.translate.y + scrollY)
531
elseif spr.x and spr.y and spr.width and spr.height then
532
local sprX = spr.x + scrollX
533
local sprY = spr.y + scrollY
535
if sprX < appWidth and sprX + spr.width > 0 and
536
sprY < appHeight and sprY + spr.height > 0 then
540
spr:draw(scrollX, scrollY)
546
if self.effectType == 'screen' then
547
love.graphics.setPixelEffect(self.effect)
548
love.graphics.setCanvas()
549
love.graphics.draw(self._canvas)
552
love.graphics.setPixelEffect()
556
__tostring = function (self)
557
local result = 'Group ('
560
result = result .. 'active'
562
result = result .. 'inactive'
566
result = result .. ', visible'
568
result = result .. ', invisible'
572
result = result .. ', solid'
574
result = result .. ', not solid'
577
return result .. ', ' .. self:count(true) .. ' sprites)'