I've taken a look at your repro, this is definitely an bug in excalibur. We have an issue to address tilemap collisions in the backlog, I'll move it up to target a robust fix the next release of the engine (
).
The first problem is that tilemaps currently don't zero out the component of velocity from other actors upon collision, like in the normal collision system. Because global acceleration is always on, velocity accumulates until it can update through the edge of the tilemap. The second problem is that fast moving objects can accidentally warp through walls because each frame moves the object velocity pixels/second across the screen. This is fixed for most of excalibur, except for tilemaps
I've been able to create a workaround for you (unfortunately it's not the prettiest) to temporarily solve this until we can fix it at the engine level.
import * as ex from 'excalibur';
import TiledResource from '../src';
import { Actor } from 'excalibur';
var game = new ex.Engine({
width: 500,
height: 400,
canvasElementId: 'game',
pointerScope: ex.Input.PointerScope.Canvas
});
var start = (mapFile) => {
ex.Physics.collisionResolutionStrategy = ex.CollisionResolutionStrategy.RigidBody;
// set global acceleration simulating gravity pointing down
ex.Physics.defaultMass = 100;
ex.Physics.acc.setTo(0, 700);
ex.Physics.showMotionVectors = true;
ex.Physics.checkForFastBodies = true;
// enable physics
ex.Physics.enabled = true;
var map = new TiledResource(mapFile);
var loader = new ex.Loader([map]);
game.currentScene.tileMaps = []
game.start(loader).then(function() {
map.data.tilesets.forEach(function(ts) {
console.log(ts.image, ts.imageTexture.isLoaded());
});
const tileMap: ex.TileMap = map.getTileMap();
const layer: any = map.data.layers[0];
for (let i: number = 0; i < layer.height; i++) {
for (let j: number = 0; j < layer.width; j++) {
if (layer.data[i * layer.width + j] !== 0) {
tileMap.data[i * layer.width + j].solid = true;
}
}
}
var tm = map.getTileMap();
game.add(tm);
game.addTileMap(tileMap);
let runner = new Actor(60, 60, 10, 10);
runner.collisionType = ex.CollisionType.Active;
runner.color = ex.Color.Red;
runner.body.useBoxCollision();
// Unfortunately tilemaps don't yet operate in the nice new physics system
// The root cause of the problem is that while the actor is sitting on the tile, it
// gradually accumulates velocity until it is so high that a single engine tick updates
// it over the floor. I'll pull this in to the next release milestone, the work-around for
// now is to manually set the velocity when the bottom of the box collides.
let delta = 16/1000; // default to 60fps to start
let intersection = ex.Vector.Zero;
runner.on('precollision', (evt: ex.PostCollisionEvent) => {
if (evt.side === ex.Side.Bottom && evt.other === null) {
evt.actor.vel.y = 0;
evt.actor.pos.y += evt.intersection.y;
evt.actor.pos.y -= ex.Physics.acc.y * delta * delta;
}
});
runner.on('preupdate', (evt: ex.PreUpdateEvent) => {
delta = evt.delta/1000; // convert to seconds
});
// Because the current tilemap implementation does not deal with fast acceleration well with
// small objects we need to check for skipped frames and correct.
runner.on('postupdate', () => {
if (delta > 32/1000) {
// catch frame drops that can increase update distance and reset to last know good position/velocity for 1 frame
runner.vel = runner.oldVel;
runner.pos = runner.oldPos;
console.log('reset')
}
});
game.input.keyboard.on('press', (evt) => {
if (evt.key === ex.Input.Keys.Left){
runner.vel.x = -20;
}
if (evt.key === ex.Input.Keys.Right) {
runner.vel.x = 20;
}
if (evt.key === ex.Input.Keys.Up) {
runner.vel.y = -400;
}
})
game.add(runner);
});
}
document.getElementById('select-map').addEventListener('change', (e) => {
var map = (e.target as HTMLSelectElement).value;
if (map) {
start(map);
}
return true;
})
start("test-v2.json");