Funded with Kickstarter

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à!

Copyright © 2012– Retro Game Crunch.