diff --git a/docs/TODO.md b/docs/TODO.md index f5f05d8..96b6fa8 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -13,8 +13,8 @@ - Slopes: - Non-slope-traversing Entities (e.g. projectiles) collide at the wrong place - Occasional jittering at the bottom of ceiling slopes - - Jittering when a slope leads into a ceiling - - Strange behaviour when colliding with the wrong side of a slope tile + - Strange behaviour when a slope leads into a ceiling + - Strange behaviour when colliding with the "back" of a slope tile - Strange behaviour when colliding with the left/right corners of a diamond ## Tech Debt diff --git a/src/engine/game/physics/Physics.java b/src/engine/game/physics/Physics.java index b73edac..500c050 100644 --- a/src/engine/game/physics/Physics.java +++ b/src/engine/game/physics/Physics.java @@ -5,6 +5,7 @@ import engine.game.GameUtils; import engine.game.Level; import engine.game.Logic; +import engine.game.TileLayer; import engine.game.physics.Hitbox.CollisionNode; import engine.game.tiles.ForegroundTile; import engine.game.tiles.PostProcessingTile; @@ -221,15 +222,6 @@ public static CollisionResult getCollisionResult( detectCollisionsX(result, logic, hitbox.getRightNodes()); } - // Check for collisions with any PostProcessingTiles at the new - // Hitbox position; we have to check this now because after the - // y-movement is applied, the Hitbox may no longer be colliding with - // a PostProcessingTile, so we will have missed the collision! - if (dx != 0) { - detectPostProcessCollisions( - result, logic, hitbox.getAllNodes(), false); - } - // Move in the y-axis if (dy < 0) { detectCollisionsY(result, logic, hitbox.getTopNodes()); @@ -237,11 +229,59 @@ public static CollisionResult getCollisionResult( detectCollisionsY(result, logic, hitbox.getBottomNodes()); } - // Check for collisions with any PostProcessingTiles at the final - // Hitbox position + /* + * POST-PROCESSING COLLISION DETECTION. + * + * STAGE 1: + * Check for PostProcessingCollisions at the initial Hitbox + * position. + * + * It may be that the Hitbox is already intersecting a + * PostProcessingTile, which affects which collisions are permitted. + * + * EXAMPLE: + * - Hitbox is on a right slope, moving right. + * - After the x-movement is applied, the Hitbox is no longer + * intersecting the slope but is intersecting the floor tile at + * the top of the slope. + * - An x-collision is registered, but no PostProcessingCollision + * is registered, so the x-collision never gets invalidated. + */ + detectPostProcessCollisions( + result, logic, hitbox.getAllNodes(), 0, 0); + + + /* + * STAGE 2: + * Check for PostProcessingCollisions after the x-movement is + * applied. + * + * It is possible that the x-movement could move the Hitbox into a + * PostProcessingTile, but the y-movement could move the Hitbox out + * of it, so we have to check for collisions before the y-movement + * is applied. + * + * EXAMPLE: + * - Hitbox is in the empty tile "between" 2 right slopes (above + * one, and left of the other), moving right. + * - After the x-movement is applied, the Hitbox intersects the + * slope immediately to the right. + * - Were we to apply the y-movement, the bottom node of the + * Hitbox would fall into the solid block BELOW the slope, + * therefore it would never be inside the slope. + */ + if (dx != 0) { + detectPostProcessCollisions( + result, logic, hitbox.getAllNodes(), dx, 0); + } + + /* + * STAGE 3: + * Check for PostProcessingCollisions at the final Hitbox position. + */ if (dy != 0) { detectPostProcessCollisions( - result, logic, hitbox.getAllNodes(), true); + result, logic, hitbox.getAllNodes(), dx, dy); } result.finish(); @@ -320,13 +360,15 @@ private static void detectCollisionsY( * @param result CollisionResult to update after detecting collisions. * @param logic * @param nodes - * @param afterYMovement + * @param dx + * @param dy */ private static void detectPostProcessCollisions( CollisionResult result, Logic logic, Set nodes, - boolean afterYMovement) { + float dx, + float dy) { Level level = logic.getLevel(); @@ -335,10 +377,8 @@ private static void detectPostProcessCollisions( // Find the desired position of this CollisionNode // (this ignores any previously-detected collisions, since they may // be overridden by a PostProcessingCollision) - float nodeX = result.desiredNodeX(node); - float nodeY = afterYMovement - ? result.desiredNodeY(node) - : result.initialNodeY(node); + float nodeX = result.initialNodeX(node) + dx; + float nodeY = result.initialNodeY(node) + dy; // Find the tile which this node will intersect int tileX = Tile.getTileX(nodeX); diff --git a/src/engine/game/tiles/Slope.java b/src/engine/game/tiles/Slope.java index a51760e..757377a 100644 --- a/src/engine/game/tiles/Slope.java +++ b/src/engine/game/tiles/Slope.java @@ -99,21 +99,57 @@ protected boolean isCollisionValid_X( int tileXBefore = Tile.getTileX(result.initialNodeX(collision.node)); int tileXAfter = Tile.getTileX(result.desiredNodeX(collision.node)); if (tileXBefore == tileXAfter) { - // Disable x-collisions if the CollisionNode was already - // intersecting the Tile in question - // (e.g. having been on a neighbouring slope) + /* + * Disable x-collisions if the CollisionNode was already + * intersecting the Tile in question. + * + * This can happen when the Hitbox moves from 1 slope to another. + * + * EXAMPLE: + * - Hitbox is on a right slope, moving right. The slope node is + * inside the slope, but the bottom-right node is intersecting + * the solid tile "behind" the slope. + * - After the attempted movement is applied, the Hitbox is no + * longer intersecting the same slope. It is intersecting the + * next slope tile (right and up), but the slope node is now + * inside the solid block below the new slope. + * - An x-collision is generated for the bottom-right node. + * This collision would normally be valid, since the slope node + * is not yet inside the new slope. + * - Because the node was ALREADY intersecting that solid block, + * the collision is invalidated - problem solved. + */ return false; } if (result.initialNodeY(collision.node) < slopeCollision.getTileTop()) { - // Allow x-collisions triggered by CollisionNodes above the Slope - // (e.g. if a Slope leads into a wall) + /* + * Allow x-collisions triggered by CollisionNodes above the Slope + * + * This can happen if a Slope connects directly to a wall. + * + * EXAMPLE: + * - Hitbox is on a right slope, moving right. + * - The Hitbox collides with a wall at the top of the slope. + * - This collision is valid, because it is above the slope tile. + */ return true; } if (result.initialNodeY(collision.node) > slopeCollision.getTileBottom()) { - // Allow x-collisions triggered by CollisionNodes below the Slope - // (e.g. if a Slope leads to a vertical drop) + /* + * Allow x-collisions triggered by CollisionNodes below the Slope + * + * This can happen if a Slope leads to a vertical drop. + * + * EXAMPLE: + * - Hitbox moves towards a right slope. There is a solid block + * tile directly below the slope. + * - The Hitbox collides in such a way that it intersects the + * slope, but also the solid block. An x-collision is + * generated. + * - This collision is valid, because it is below the slope tile. + */ return true; }