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)
109
161
-- Method: collide
110
162
-- Collides all solid sprites in the group with another sprite or group.
111
163
-- This calls the <Sprite.onCollide> event handlers on all sprites that
115
167
-- This checks for collisions between the sprites that make up the group.
118
-- other - <Sprite> or <Group> to collide with, default self
170
-- ... - any number of <Sprite>s or <Group>s to collide with. If none
171
-- are specified, the group collides with itself.
121
-- boolean, whether any collision was detected
124
177
-- <Sprite.collide>
126
collide = function (self, other)
127
other = other or self
130
assert(other:instanceOf(Group) or other:instanceOf(Sprite), 'asked to collide non-group/sprite ' ..
134
if not self.solid or not other.solid then return false end
137
if other.sprites then
138
local grid = self:grid()
139
local gridSize = self.gridSize
141
for _, othSpr in pairs(other.sprites) do
142
local startX = math.floor(othSpr.x / gridSize)
143
local endX = math.floor((othSpr.x + othSpr.width) / gridSize)
144
local startY = math.floor(othSpr.y / gridSize)
145
local endY = math.floor((othSpr.y + othSpr.height) / gridSize)
147
for x = startX, endX do
149
for y = startY, endY do
151
for _, spr in pairs(grid[x][y]) do
152
hit = spr:collide(othSpr) or hit
160
for _, spr in pairs(self.sprites) do
161
hit = spr:collide(other) or hit
169
-- Displaces a sprite or group by all solid sprites in this group. This will *not* cause
170
-- any onCollide event handlers to be called.
173
-- other - <Sprite> or <Group> to collide with, default self
174
-- xHint - force horizontal displacement in one direction, uses direction constants, optional
175
-- yHint - force vertical displacement in one direction, uses direction constants, optional
183
displace = function (self, other, xHint, yHint)
184
other = other or self
187
assert(other:instanceOf(Group) or other:instanceOf(Sprite), 'asked to displace non-group/sprite ' ..
191
if not self.solid or not other.solid then return false end
193
if other.sprites then
194
-- group displacing group
196
local grid = self:grid()
197
local gridSize = self.gridSize
199
for _, othSpr in pairs(other.sprites) do
200
local startX = math.floor(othSpr.x / gridSize)
201
local endX = math.floor((othSpr.x + othSpr.width) / gridSize)
202
local startY = math.floor(othSpr.y / gridSize)
203
local endY = math.floor((othSpr.y + othSpr.height) / gridSize)
204
local displacers = {}
206
for x = startX, endX do
208
for y = startY, endY do
210
for _, spr in pairs(grid[x][y]) do
211
if spr ~= othSpr and spr.solid and spr:intersects(othSpr.x, othSpr.y, othSpr.width, othSpr.height) then
212
table.insert(displacers, spr)
220
-- see Map:subdisplace() for how this is done
222
if #displacers > 0 then
226
while hit and loops < 3 do
230
local xVotes, yVotes = 0, 0
231
local minChangeX, minChangeY, absMinChangeX, absMinChangeY
232
local origX, origY = othSpr.x, othSpr.y
234
for _, spr in pairs(displacers) do
236
local xChange = othSpr.x - origX
237
local yChange = othSpr.y - origY
240
xVotes = xVotes + math.abs(xChange)
243
if not minChangeX or math.abs(xChange) < absMinChangeX then
245
absMinChangeX = math.abs(xChange)
250
yVotes = yVotes + math.abs(yChange)
253
if not minChangeY or math.abs(yChange) < absMinChangeY then
255
absMinChangeY = math.abs(yChange)
259
-- restore sprite to original position
266
if xVotes > 0 and xVotes > yVotes then
267
othSpr.x = othSpr.x + minChangeX
268
elseif yVotes > 0 then
269
othSpr.y = othSpr.y + minChangeY
276
-- group displacing sprite
278
local displacers = {}
280
for _, spr in pairs(self.sprites) do
281
if spr ~= other and spr:intersects(other.x, other.y, other.width, other.height) then
282
table.insert(displacers, spr)
286
-- see Map:subdisplace() for how this is done
288
if #displacers > 0 then
292
while hit and loops < 3 do
296
local xVotes, yVotes = 0, 0
297
local minChangeX, minChangeY, absMinChangeX, absMinChangeY
298
local origX, origY = other.x, other.y
300
for _, spr in pairs(displacers) do
302
local xChange = other.x - origX
303
local yChange = other.y - origY
306
xVotes = xVotes + math.abs(xChange)
309
if not minChangeX or math.abs(xChange) < absMinChangeX then
311
absMinChangeX = math.abs(xChange)
316
yVotes = yVotes + math.abs(yChange)
319
if not minChangeY or math.abs(yChange) < absMinChangeY then
321
absMinChangeY = math.abs(yChange)
325
-- restore sprite to original position
332
if xVotes > 0 and xVotes > yVotes then
333
other.x = other.x + minChangeX
334
elseif yVotes > 0 then
335
other.y = other.y + minChangeY
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)
464
-- Creates a table indexed by x and y dimensions, with each
465
-- cell a table of sprites that touch this grid element. For
466
-- example, with a grid size of 50, a sprite at (10, 10) that
467
-- is 50 pixels square would be in the grid at [0][0], [1][0],
468
-- [0][1], and [1][1].
470
-- This can be used to speed up work that involves checking
471
-- for sprites near each other, e.g. collision detection.
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.
474
-- existing - existing grid table to add sprites into,
475
-- optional. Anything you pass must have
476
-- used the same size as the current call.
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)
481
grid = function (self, existing)
482
local result = existing or {}
483
local size = self.gridSize
485
for _, spr in pairs(self.sprites) do
487
local oldSize = spr.gridSize
488
spr.gridSize = self.gridSize
489
result = spr:grid(result)
490
spr.gridSize = oldSize
492
local startX = math.floor(spr.x / size)
493
local endX = math.floor((spr.x + spr.width) / size)
494
local startY = math.floor(spr.y / size)
495
local endY = math.floor((spr.y + spr.height) / size)
497
for x = startX, endX do
498
if not result[x] then result[x] = {} end
500
for y = startY, endY do
501
if not result[x][y] then result[x][y] = {} end
502
table.insert(result[x][y], spr)
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)
511
461
-- passes startFrame events to member sprites