2
-- <Sprite.collide>, <Group.collide>
4
Collision = Class:extend
7
-- Default grid size used when checking collisions. Roughly speaking, this
8
-- should be two times as big as your most common sprite size, but tweaking
9
-- this number can yield better performance.
13
-- Checks for collisions between <Sprite>s and <Group>s. If you call
14
-- Collision:check(a, b, c), it will check for collisions between a and b,
15
-- a and c, but not b and c.
17
-- It spatially hashes all objects into a grid, then checks for collisions via
18
-- <Sprite.overlap()>. If an overlap is detected, then the collision is recorded.
19
-- As a final step, this calls collidedWith() on sprites that collided, but in
20
-- descending order of overlap. (This normally only triggers the onCollide() handler
21
-- for the sprite, but <Map>s take advantage of this indirection.)
22
-- The sequence of collision resolutions is important, so that displacement occurs
23
-- in an order that prevents sprites from getting stuck on walls made of multiple sprites.
26
-- a - primary <Sprite> or <Group>
27
-- ... - any number of <Sprite>s or <Group>s to check for collisions against
28
-- the A sprite or group
33
check = function (self, a, ...)
34
-- coerce all arguments into a flat list of sprites,
35
-- recording where the first argument (or A group) sits in the list
38
local list = self:solidSprites(a)
41
for _, item in pairs(args) do
42
self:solidSprites(item, list)
45
-- build a spatial hash from the list, recording the grid
46
-- cells that each of the sprites fits inside.
47
-- we also record which cells each of the sprites in the A group
51
local gridSize = a.gridSize or self.gridSize
57
for i, spr in ipairs(list) do
58
if not gridded[spr] then
59
local startX = math.floor(spr.x / gridSize)
60
local endX = math.floor((spr.x + spr.width) / gridSize)
61
local startY = math.floor(spr.y / gridSize)
62
local endY = math.floor((spr.y + spr.height) / gridSize)
64
-- Special casing of very large sprites (as defined as >= 25 cells in
65
-- area). If they are in the A list, we just reserve them
66
-- for later and check them against all sprites in the other lists.
67
-- If they are not in the A list, we defer them to the very end of the gridding
68
-- process, only to cells that have already been gridded by virtue of another
69
-- sprite being in them.
71
-- The assumption here is that huge sprites are probably big tilemaps, and
72
-- thus likely to collide with every sprite anyway.
74
if (endX - startX) * (endY - startY) > 25 then
76
table.insert(aHuge, spr)
78
table.insert(deferred, { spr = spr, startX = startX, endX = endX, startY = startY, endY = endY })
85
for x = startX, endX do
86
grid[x] = grid[x] or {}
88
for y = startY, endY do
89
grid[x][y] = grid[x][y] or {}
90
table.insert(grid[x][y], spr)
93
table.insert(aCells[spr], grid[x][y])
103
-- Grid all huge sprites we just deferred. Unlike above,
104
-- we only add them to grid cells that already have sprites in them.
106
for _, d in pairs(deferred) do
107
for x = d.startX, d.endX do
109
for y = d.startY, d.endY do
111
table.insert(grid[x][y], d.spr)
118
-- aCells now is a table whose keys are sprites in this group,
119
-- and whose values are a table of grid cells that the sprite is in.
120
-- We now build a list of collisions based on that.
122
local collisions = {}
123
local alreadyCollided = {}
125
for aSpr, cells in pairs(aCells) do
126
alreadyCollided[aSpr] = alreadyCollided[aSpr] or {}
128
for _, cell in pairs(cells) do
129
for _, bSpr in pairs(cell) do
130
if aSpr ~= bSpr and not alreadyCollided[aSpr][bSpr] then
131
local xOverlap, yOverlap = bSpr:overlap(aSpr.x, aSpr.y, aSpr.width, aSpr.height)
133
if xOverlap ~= 0 or yOverlap ~= 0 then
134
table.insert(collisions, { area = xOverlap * yOverlap, x = xOverlap, y = yOverlap, a = aSpr, b = bSpr })
137
-- record that we've already checked this collision
139
alreadyCollided[bSpr] = alreadyCollided[bSpr] or {}
140
alreadyCollided[aSpr][bSpr] = true
141
alreadyCollided[bSpr][aSpr] = true
147
-- Run through the huge sprites in the A list, if any, adding
148
-- collisions to the existing list.
150
for _, aSpr in pairs(aHuge) do
151
alreadyCollided[aSpr] = alreadyCollided[aSpr] or {}
153
for i = aLength + 1, #list do
156
if aSpr ~= bSpr and not alreadyCollided[aSpr][bSpr] then
157
local xOverlap, yOverlap = bSpr:overlap(aSpr.x, aSpr.y, aSpr.width, aSpr.height)
159
if xOverlap ~= 0 or yOverlap ~= 0 then
160
table.insert(collisions, { area = xOverlap * yOverlap, x = xOverlap, y = yOverlap, a = aSpr, b = bSpr })
163
alreadyCollided[bSpr] = alreadyCollided[bSpr] or {}
164
alreadyCollided[aSpr][bSpr] = true
165
alreadyCollided[bSpr][aSpr] = true
170
-- collisions now is a list, each item containing these properties:
171
-- a - sprite colliding
172
-- b - second sprite colliding
175
-- area - square area of overlap
177
-- we now sort the table and resolve collisions in descending order of overlap area.
179
table.sort(collisions, self.sortCollisions)
181
for _, col in ipairs(collisions) do
182
col.a:collidedWith(col.b, col.x, col.y)
183
col.b:collidedWith(col.a, col.x, col.y)
187
-- Method: solidSprites
188
-- Returns a table of <Sprite>s belonging to an object that
189
-- should be collided with others, descending into groups.
192
-- source - <Sprite> or <Group>
193
-- existing - existing table to add to
196
-- table of <Sprite>s
198
solidSprites = function (self, source, existing)
199
local result = existing or {}
202
if source.x and source.y and source.width and source.height then
203
table.insert(result, source)
204
elseif source.sprites then
205
for _, spr in pairs(source.sprites) do
207
result = self:solidSprites(spr, result)
208
elseif spr.solid then
209
table.insert(result, spr)
218
-- Method: sortCollisions
219
-- This sorts a table of collisions in descending order of overlap,
220
-- suitable for use in conjunction with table.sort().
223
-- a - one collision table
224
-- b - one collision table
227
-- whether a should be sorted before b
229
sortCollisions = function (a, b)
230
return a.area > b.area