Cutting Corners
One thing that makes a game feel more polished is “corner rounding” or “collision softening”. Especially in a top down game like End of Line. What am I on about?
You know when you miss a single tile opening by a few pixels and your character comes to a complete stop? Corner rounding sees the slight mis-alignment and nudges the character in the right direction so they don’t get snagged on the corner.
Here’s basically how we do it: Before we resolve player collisions with the foreground we test for overlap with individual tiles. Based on the size and position of the rectangle created by the overlap, we determine which direction to nudge the player in. We also take some steps to ensure that the player doesn’t lose any velocity as a result of the forgivable collision.
Here’s (kind of) what that looks like in code:
package {
import org.flixel.*;
import org.flixel.system.FlxTile;
public class Actor extends FlxSprite {
// collision softening
public var softener:Number = 1.0; // pixels to move per frame during softening
public var totalCollisions : uint = 0;
public var softenX : Number = 0;
public var softenY : Number = 0;
public function Actor(X:Number, Y:Number,SimpleGraphic:Class=null) {
super(X, Y,SimpleGraphic);
}
override public function postUpdate():void
{
// reset for next update
totalCollisions = 0;
softenX = 0;
softenY = 0;
super.postUpdate();
}
static public function softCollide(tilemap:FlxTilemap, actor:Actor):Boolean {
if (tilemap.overlapsWithCallback(actor, actor.softenCollisions)) {
// apply softening
if (actor.totalCollisions == 1) {
actor.x += actor.softenX;
actor.y += actor.softenY;
}
// save pre-collision velocity
var oldVX:Number = actor.velocity.x;
var oldVY:Number = actor.velocity.y;
var collided:Boolean = FlxG.collide(tilemap, actor);
// restore pre-collision velocity if softening was applied
if (collided && actor.totalCollisions == 1) {
actor.velocity.x = oldVX;
actor.velocity.y = oldVY;
}
return collided;
}
return false;
}
public function softenCollisionsX(tile : FlxTile, actor : Actor, softness : Number = 6) : void {
actor; // should be this
var diffX : Number = 0;
if (x < tile.x) {
// self to left
diffX = x + width - tile.x;
if (diffX <= softness) {
softenX = -softener;
}
} else {
// self to right
diffX = tile.x + tile.width - x;
if (diffX <= softness) {
softenX = softener;
}
}
}
public function softenCollisionsY(tile : FlxTile, actor : Actor, softness : Number = 6) : void {
actor; // should be this
var diffY : Number = 0;
if (y < tile.y) {
// self above
diffY = y + height - tile.y;
if (diffY <= softness) {
softenY = -softener;
}
} else {
// self below
diffY = tile.y + tile.height - y;
if (diffY <= softness) {
softenY = softener;
}
}
}
public function softenCollisions(tile : FlxTile, actor : Actor) : Boolean {
if ((x + width > tile.x) && (x < tile.x + tile.width) && (y + height > tile.y) && (y < tile.y + tile.height)) {
if (totalCollisions < 1) {
if (velocity.x == 0) {
// moving vertically
softenCollisionsX(tile, actor);
} else if (velocity.y == 0) {
// moving horizontally
softenCollisionsY(tile, actor);
}
}
totalCollisions++;
}
return true;
}
}
}
Then in our PlayState.as update() function, instead of calling:
FlxG.collide(foreground, player);
We call:
Actor.softCollide(foreground, player);
And voilà!

