in libs/game/physics.ts [349:588]
protected tilemapCollisions(movingSprite: MovingSprite, tm: tiles.TileMap) {
const s = movingSprite.sprite;
// if the sprite is already clipping into a wall,
// allow free movement rather than randomly 'fixing' it
if (s.flags & sprites.Flag.IsClipping) {
if (!tm.isOnWall(s)) {
s.flags &= ~sprites.Flag.IsClipping;
}
}
if (!s.isStatic()) s.setHitbox();
const hbox = s._hitbox;
const tileScale = tm.scale;
const tileSize = 1 << tileScale;
const xDiff = Fx.sub(
s._x,
s._lastX
);
const yDiff = Fx.sub(
s._y,
s._lastY
);
if (!(s.flags & SPRITE_NO_WALL_COLLISION)) {
if (xDiff !== Fx.zeroFx8) {
const right = xDiff > Fx.zeroFx8;
const x0 = Fx.toIntShifted(
Fx.add(
right ?
Fx.add(hbox.right, Fx.oneFx8)
:
Fx.sub(hbox.left, Fx.oneFx8),
Fx.oneHalfFx8
),
tileScale
);
const collidedTiles: sprites.StaticObstacle[] = [];
// check collisions with tiles sprite is moving towards horizontally
for (
let y = Fx.sub(hbox.top, yDiff);
y < Fx.iadd(tileSize, Fx.sub(hbox.bottom, yDiff));
y = Fx.iadd(tileSize, y)
) {
const y0 = Fx.toIntShifted(
Fx.add(
Fx.min(
y,
Fx.sub(
hbox.bottom,
yDiff
)
),
Fx.oneHalfFx8
),
tileScale
);
if (tm.isObstacle(x0, y0)) {
const obstacle = tm.getObstacle(x0, y0);
if (!collidedTiles.some(o => o.tileIndex === obstacle.tileIndex)) {
collidedTiles.push(obstacle);
}
}
}
if (collidedTiles.length) {
const collisionDirection = right ? CollisionDirection.Right : CollisionDirection.Left;
s._x = Fx.sub(
right ?
Fx.sub(
Fx8(x0 << tileScale),
hbox.width
)
:
Fx8((x0 + 1) << tileScale),
hbox.ox
);
for (const tile of collidedTiles) {
if(!(s.flags & SPRITE_NO_WALL_COLLISION)) {
s.registerObstacle(collisionDirection, tile, tm);
}
}
if (s.flags & sprites.Flag.DestroyOnWall) {
s.destroy();
} else if (s._vx === movingSprite.cachedVx) {
// sprite collision event didn't change velocity in this direction;
// apply normal updates
if (s.flags & sprites.Flag.BounceOnWall) {
if ((!right && s.vx < 0) || (right && s.vx > 0)) {
s._vx = Fx.neg(s._vx);
movingSprite.xStep = Fx.neg(movingSprite.xStep);
movingSprite.dx = Fx.neg(movingSprite.dx);
}
} else {
movingSprite.dx = Fx.zeroFx8;
s._vx = Fx.zeroFx8;
}
} else if (Math.sign(Fx.toInt(s._vx)) === Math.sign(Fx.toInt(movingSprite.cachedVx))) {
// sprite collision event changed velocity,
// but still facing same direction; prevent further movement this update.
movingSprite.dx = Fx.zeroFx8;
}
}
}
if (yDiff !== Fx.zeroFx8) {
const down = yDiff > Fx.zeroFx8;
const y0 = Fx.toIntShifted(
Fx.add(
down ?
Fx.add(hbox.bottom, Fx.oneFx8)
:
Fx.sub(hbox.top, Fx.oneFx8),
Fx.oneHalfFx8
),
tileScale
);
const collidedTiles: sprites.StaticObstacle[] = [];
// check collisions with tiles sprite is moving towards vertically
for (
let x = hbox.left;
x < Fx.iadd(tileSize, hbox.right);
x = Fx.iadd(tileSize, x)
) {
const x0 = Fx.toIntShifted(
Fx.add(
Fx.min(
x,
hbox.right
),
Fx.oneHalfFx8
),
tileScale
);
if (tm.isObstacle(x0, y0)) {
const obstacle = tm.getObstacle(x0, y0);
if (!collidedTiles.some(o => o.tileIndex === obstacle.tileIndex)) {
collidedTiles.push(obstacle);
}
}
}
if (collidedTiles.length) {
const collisionDirection = down ? CollisionDirection.Bottom : CollisionDirection.Top;
s._y = Fx.sub(
down ?
Fx.sub(
Fx8(y0 << tileScale),
hbox.height
)
:
Fx8((y0 + 1) << tileScale),
hbox.oy
);
for (const tile of collidedTiles) {
if(!(s.flags & SPRITE_NO_WALL_COLLISION)) {
s.registerObstacle(collisionDirection, tile, tm);
}
}
if (s.flags & sprites.Flag.DestroyOnWall) {
s.destroy();
} else if (s._vy === movingSprite.cachedVy) {
// sprite collision event didn't change velocity in this direction;
// apply normal updates
if (s.flags & sprites.Flag.BounceOnWall) {
if ((!down && s.vy < 0) || (down && s.vy > 0)) {
s._vy = Fx.neg(s._vy);
movingSprite.yStep = Fx.neg(movingSprite.yStep);
movingSprite.dy = Fx.neg(movingSprite.dy);
}
} else {
movingSprite.dy = Fx.zeroFx8;
s._vy = Fx.zeroFx8;
}
} else if (Math.sign(Fx.toInt(s._vy)) === Math.sign(Fx.toInt(movingSprite.cachedVy))) {
// sprite collision event changed velocity,
// but still facing same direction; prevent further movement this update.
movingSprite.dy = Fx.zeroFx8;
}
}
}
}
if (!(s.flags & SPRITE_NO_TILE_OVERLAPS)) {
// Now that we've moved, check all of the tiles underneath the current position
// for overlaps
const overlappedTiles: tiles.Location[] = [];
for (
let x = hbox.left;
x < Fx.iadd(tileSize, hbox.right);
x = Fx.iadd(tileSize, x)
) {
const x0 = Fx.toIntShifted(
Fx.add(
Fx.min(
x,
hbox.right
),
Fx.oneHalfFx8
),
tileScale
);
for (
let y = hbox.top;
y < Fx.iadd(tileSize, hbox.bottom);
y = Fx.iadd(tileSize, y)
) {
const y0 = Fx.toIntShifted(
Fx.add(
Fx.min(
y,
hbox.bottom
),
Fx.oneHalfFx8
),
tileScale
);
// if the sprite can move through walls, it can overlap the underlying tile.
if (!tm.isObstacle(x0, y0) || !!(s.flags & sprites.Flag.GhostThroughWalls)) {
overlappedTiles.push(tm.getTile(x0, y0));
}
}
}
if (overlappedTiles.length) {
this.tilemapOverlaps(s, overlappedTiles);
}
}
}