10 years ago by Joncom
There appears to be a bug in the ImpactJS collision-map code wherein entities may pass through solid tiles in the rightward and downward directions.
Here it is being called in the entity update function:
The bug occurs any time
Example:
Fix:
ig.game.collisionMap.trace
is used to check for collisions against the world and returns an object containing the "final resting position" after accounting for such collisions ( .trace documentation).Here it is being called in the entity update function:
/* entity.js update function */ var mx = this.vel.x * ig.system.tick; var my = this.vel.y * ig.system.tick; var res = ig.game.collisionMap.trace( this.pos.x, this.pos.y, mx, my, this.size.x, this.size.y );
The bug occurs any time
mx
or my
is a factor of ig.game.collisionMap.tilesize
.Example:
/* - Suppose our collision map tilesize is 8. - Suppose our collision map is 4x4 tiles wide/tall. - Suppose our collision map is complelely solid, except for tile (0,0). - Suppose our entity is 8x8 pixels wide/tall. - Suppose our entity occupies position (0,0) the only walkable tile. - Suppose our entity has a vel.x of 480 (moving rightward). - Suppose a perfect 60 frames per second, meaning ig.system.tick is 1/60. */ // First a call is made to trace. /* entity.js update function */ var mx = this.vel.x * ig.system.tick; // = 480 * (1/60) // = 8; var my = this.vel.y * ig.system.tick; // = 0 * (1/60) // = 0; var res = ig.game.collisionMap.trace( this.pos.x, // 0 this.pos.y, // 0 mx, // 8 my, // 0 this.size.x, // 8 this.size.y // 8 ); // The trace function determines the number of times we will call // _traceStep. /* collision-map.js trace function */ var steps = Math.ceil(Math.max(Math.abs(vx), Math.abs(vy)) /this.tilesize); // Math.ceil(Math.max(Math.abs(8), Math.abs(0)) / 8); // Math.ceil(Math.max(8, 0) / 8); // Math.ceil(8 / 8); // Math.ceil(1); // 1; // Therefore _traceStep will be called 1 time. // The first thing _traceStep does is update the position assuming no // collision. (There is code later on that corrects the position if a // collision is detected.) res.pos.x += vx; // 0 + 8 == 8 res.pos.y += vy; // 0 + 0 == 0 // So res.pos == { x: 8, y: 0 }; // Now let's handle collision for the x-axis, // starting by finding a few important values: var pxOffsetX = (vx > 0 ? width : 0); // ^ of entity // = (8 > 0 ? 8 : 0); // = 8; var tileOffsetX = (vx < 0 ? this.tilesize : 0); // = (8 < 0 ? 8 : 0); // = 0; var tileX = Math.floor( (res.pos.x + pxOffsetX) / this.tilesize ); // = Math.floor( (8 + 8) / 8 ); // = Math.floor( 16 / 8 ); // = Math.floor( 2 ); // = 2; // So pxOffsetX == 8, tileOffsetX == 0, and tileX == 2, // We've arrived at the bug. It's tileX. // The player was at (0,0). // We decided to move rightward. // We called trace. // And the first collision tile that will be checked is (2,0)?! // Seems we skipped tile (1,0). // Remember also that (0,2) is a solid tile, so a collision // should occur for that, and it does. The following line is used // to resolve the position when a collision occurs: x = res.pos.x = tileX * this.tilesize - pxOffsetX + tileOffsetX; // = 2 * 8 - 8 + 0 // = 16 - 8 // = 8 // The above line is supposed to update the position such that // our entity is "hugging" the solid tile, but not overlapping it. // Instead, we are now positioned at the tile we skipped earlier. // This means our entity position at the end of the trace call is // (8,0). In other words, we moved 1 entire tile right, despite // the fact that the tile is solid. // TODO: Walk through a normal case and show how it was supposed to work. // TODO: Explain why this bug only affects rightward and downward movement.
Fix:
/* collision-map.js _traceStep function */ // After: var tileX = Math.floor( (res.pos.x + pxOffsetX) / this.tilesize ); // Add the following: if( tileX * this.tilesize - pxOffsetX === x + this.tilesize ) { tileX--; } // And... // After: var tileY = Math.floor( (res.pos.y + pxOffsetY) / this.tilesize ); // Add the following: if( tileY * this.tilesize - pxOffsetY === y + this.tilesize ) { tileY--; }