141
-- Method: subcollide
142
-- This acts as a wrapper to multiple collide() calls, as if
143
-- there really were all the sprites in their particular positions.
144
-- This is much more useful than Map:collide(), which simply checks
145
-- if a sprite or group is touching the map at all.
148
-- other - other <Sprite> or <Group>
151
-- boolean, whether any collision was detected
153
subcollide = function (self, other)
157
if other.sprites then
158
others = other.sprites
163
for _, othSpr in pairs(others) do
165
if othSpr.sprites then
166
-- recurse into subgroups
167
-- order is important here to avoid short-circuiting inappopriately
169
hit = self:subcollide(othSpr.sprites) or hit
171
local startX, startY = self:pixelToMap(othSpr.x - self.x, othSpr.y - self.y)
172
local endX, endY = self:pixelToMap(othSpr.x + othSpr.width - self.x,
173
othSpr.y + othSpr.height - self.y)
176
for x = startX, endX do
177
for y = startY, endY do
178
local spr = self.sprites[self.map[x][y]]
180
if spr and spr.solid then
181
-- position our map sprite as if it were onscreen
183
spr.x = self.x + (x - 1) * self.spriteWidth
184
spr.y = self.y + (y - 1) * self.spriteHeight
186
hit = spr:collide(othSpr) or hit
197
-- Method: subdisplace
198
-- This acts as a wrapper to multiple displace() calls, as if
199
-- there really were all the sprites in their particular positions.
200
-- This is much more useful than Map:displace(), which pushes a sprite or group
201
-- so that it does not touch the map in its entirety.
204
-- other - other <Sprite> or <Group> to displace
205
-- xHint - force horizontal displacement in one direction, uses direction constants
206
-- yHint - force vertical displacement in one direction, uses direction constants
211
subdisplace = function (self, other, xHint, yHint)
214
if other.sprites then
215
others = other.sprites
220
for _, othSpr in pairs(others) do
222
if othSpr.sprites then
223
-- recurse into subgroups
224
-- order is important here to avoid short-circuiting inappopriately
226
self:subdisplace(othSpr.sprites)
228
-- determine sprites we might intersect with
230
local startX, startY = self:pixelToMap(othSpr.x - self.x, othSpr.y - self.y)
231
local endX, endY = self:pixelToMap(othSpr.x + othSpr.width - self.x,
232
othSpr.y + othSpr.height - self.y)
236
-- We displace the target sprite along the axis that would satisfy the
237
-- most map sprites, but at the minimum distance for all of them.
238
-- xVotes and yVotes track which axis should be used; this is a
239
-- proportional vote, with sprites that have large amounts of overlap
240
-- getting more of a chance to overrule the others. We run this loop
241
-- repeatedly to make sure we end up with the target sprite not overlapping
242
-- anything in the map.
244
-- This is based on the technique described at:
245
-- http://go.colorize.net/xna/2d_collision_response_xna/
247
while hit and loops < 3 do
251
local xVotes, yVotes = 0, 0
252
local minChangeX, minChangeY, absMinChangeX, absMinChangeY
253
local origX, origY = othSpr.x, othSpr.y
255
for x = startX, endX do
256
for y = startY, endY do
257
local spr = self.sprites[self.map[x][y]]
259
if spr and spr.solid then
260
-- position our map sprite as if it were onscreen
262
spr.x = self.x + (x - 1) * self.spriteWidth
263
spr.y = self.y + (y - 1) * self.spriteHeight
265
-- displace and check to see if this displacement
266
-- would result in a smaller shift than any so far
269
local xChange = othSpr.x - origX
270
local yChange = othSpr.y - origY
273
xVotes = xVotes + math.abs(xChange)
276
if not minChangeX or math.abs(xChange) < absMinChangeX then
278
absMinChangeX = math.abs(xChange)
283
yVotes = yVotes + math.abs(yChange)
286
if not minChangeY or math.abs(yChange) < absMinChangeY then
288
absMinChangeY = math.abs(yChange)
292
-- restore sprite to original position
301
if xVotes > 0 and xVotes > yVotes then
302
othSpr.x = othSpr.x + minChangeX
303
elseif yVotes > 0 then
304
othSpr.y = othSpr.y + minChangeY
313
141
-- Method: getMapSize
314
-- Returns the size of the map in sprites.
142
-- Returns the size of the map in map coordinates.
158
-- Method: pixelToMap
159
-- Converts pixels to map coordinates.
162
-- x - x coordinate in pixels
163
-- y - y coordinate in pixels
164
-- clamp - clamp to map bounds? defaults to true
167
-- x, y map coordinates
169
pixelToMap = function (self, x, y, clamp)
170
if type(clamp) == 'nil' then clamp = true end
172
-- remember, Lua tables start at index 1
174
local mapX = math.floor(x / self.spriteWidth) + 1
175
local mapY = math.floor(y / self.spriteHeight) + 1
177
-- clamp to map bounds
180
if mapX < 1 then mapX = 1 end
181
if mapY < 1 then mapY = 1 end
182
if mapX > #self.map then mapX = #self.map end
183
if mapY > #self.map[1] then mapY = #self.map[1] end
189
-- Method: spriteAtMap
190
-- Returns the sprite at a given set of map coordinates, with
191
-- the correct pixel position for that sprite. Remember that
192
-- sprites in maps are shared, so any changes you make to one
193
-- sprite will carry over to all instances of that sprite in the map.
196
-- x - x coordinate in map units
197
-- y - y coordinate in map units
200
-- <Sprite> instance. If no sprite is present at these
201
-- coordinates, the method returns nil.
203
spriteAtMap = function (self, x, y)
204
if self.map[x] and self.map[x][y] then
205
local spr = self.sprites[self.map[x][y]]
208
spr.x = (x - 1) * self.spriteWidth
209
spr.y = (y - 1) * self.spriteHeight
213
print('Warning: asked for map sprite at ' .. x .. ', ' .. y ' but map isn\'t that big')
217
-- Method: spriteAtPixel
218
-- Returns the sprite at a given set of pixel coordinates, with
219
-- the correct pixel position for that sprite. Remember that
220
-- sprites in maps are shared, so any changes you make to one
221
-- sprite will carry over to all instances of that sprite in the map.
224
-- x - x coordinate in pixels
225
-- y - y coordinate in pixels
228
-- <Sprite> instance. If no sprite is present at these
229
-- coordinates, the method returns nil.
231
spriteAtPixel = function (self, x, y)
232
return self:spriteAtMap((x - 1) * self.spriteWidth, (y - 1) * self.spriteHeight)
235
-- This overrides a method in <Sprite>, passing along
236
-- collidedWith() calls to all sprites in the map touching a
240
-- other - other <Sprite>
245
collidedWith = function (self, other)
246
local spriteWidth = self.spriteWidth
247
local spriteHeight = self.spriteHeight
248
local startX, startY = self:pixelToMap(other.x - self.x, other.y - self.y)
249
local endX, endY = self:pixelToMap(other.x + other.width - self.x,
250
other.y + other.height - self.y)
252
-- collect collisions against sprites
254
local collisions = {}
256
for x = startX, endX do
257
for y = startY, endY do
258
local spr = self.sprites[self.map[x][y]]
260
if spr and spr.solid then
261
local sprX = self.x + (x - 1) * spriteWidth
262
local sprY = self.y + (y - 1) * spriteHeight
264
local xOverlap, yOverlap = other:overlap(sprX, sprY, spriteWidth, spriteHeight)
266
if xOverlap ~= 0 or yOverlap ~= 0 then
267
table.insert(collisions, { area = xOverlap * yOverlap, x = xOverlap, y = yOverlap,
268
a = spr, ax = sprX, ay = sprY })
274
-- sort as usual and pass off collidedWith() calls
276
table.sort(collisions, Collision.sortCollisions)
278
for _, col in ipairs(collisions) do
279
col.a.x, col.a.y = col.ax, col.ay
280
col.a:collidedWith(other, col.x, col.y)
281
other:collidedWith(col.a, col.x, col.y)
285
-- this is here mainly for completeness; it's better to specify displacement
286
-- in individual map sprites
288
displace = function (self, other, xHint, yHint)
289
if not self.solid or self == other or not other.solid then return end
290
if STRICT then assert(other:instanceOf(Sprite), 'asked to displace a non-sprite') end
292
local spriteWidth = self.spriteWidth
293
local spriteHeight = self.spriteHeight
294
local startX, startY = self:pixelToMap(other.x - self.x, other.y - self.y)
295
local endX, endY = self:pixelToMap(other.x + other.width - self.x,
296
other.y + other.height - self.y)
298
-- collect collisions against sprites
300
local collisions = {}
302
for x = startX, endX do
303
for y = startY, endY do
304
local spr = self.sprites[self.map[x][y]]
306
if spr and spr.solid then
307
local sprX = self.x + (x - 1) * spriteWidth
308
local sprY = self.y + (y - 1) * spriteHeight
310
local xOverlap, yOverlap = other:overlap(sprX, sprY, spriteWidth, spriteHeight)
312
if xOverlap ~= 0 or yOverlap ~= 0 then
313
table.insert(collisions, { area = xOverlap * yOverlap, x = xOverlap, y = yOverlap,
314
a = spr, ax = sprX, ay = sprY })
320
-- sort as usual and displace
322
table.sort(collisions, Collision.sortCollisions)
324
for _, col in ipairs(collisions) do
325
col.a.x, col.a.y = col.ax, col.ay
326
col.a:displace(other)
330
330
draw = function (self, x, y)
331
331
-- lock our x/y coordinates to integers
332
332
-- to avoid gaps in the tiles