The moment I’ve been waiting for

This is it: the one feature I’ve been looking forward to implementing since I started this project over two months ago. It’s a map overlay that fills itself in as you explore the game world.

A good map is the single most important feature of an exploration game. With a good map, you might not know where you’re supposed to go, but you won’t ever be truly lost. A decent mapping system is the biggest difference between a game that is mostly loved out of nostalgia (Metroid) and one that is still discovered and enjoyed by newcomers (Super Metroid). Here’s my first pass at such a system.

Right now the map is completely blank when the game first runs, so you’re filling in empty, unexplored space with pink (explored) rooms. Soon there will be mapping stations that fill in your map with places you haven’t been yet. These unexplored but mapped rooms will be blue. A prime motivator for many players in this genre, myself included, is to fill in that map, turning the blue rooms pink.

One thing you might find strange is that the rooms on the map diagram have the same aspect ratio as the screen itself, 16:9. This is an intentional decision, and reflects my commitment to making the map correspond perfectly, 1:1, with the game world. One map space equals one screen of the game world. Period, no exceptions.

One of the things that bothered me the most about Shadow Complex, a recent entry in the exploration genre, is that despite the modern aspect ratio, the rooms were all squares on the map, and one square often did not equal one screen worth of space.

This map lies.

It might seem like a minor nit to pick, but a map with a constantly shifting scale can be a very frustrating experience, and there were several points in the game where the map actually hindered my exploration, when one square of the map actually corresponded to areas full screen widths apart, separated by walls. It just convinced me further that the designers of modern exploration games don’t understand what makes their games fun to play, what actually motivates players to put in the hours.

Since I’m already talking on the subject, another thing that really bothered me about Shadow Complex’s map is that there are no hidden rooms. 100% of the map is described by mapping stations that are on the main game path, so there is never a moment when you are exploring an area that isn’t mapped in advance for you. Not only are there no secrets that you have to discover without the aid of a template, you never have to figure out how to get to your next objective. It’s hand-holding, nanny-designer game design at its worst. To be clear, I loved Shadow Complex in spite of its flaws, and it’s not the only offender on these points — some of the more recent of Nintendo’s Metroid series were just as egregious.

As another example, consider Insanely Twisted Shadow Planet, another recent game. There are no mapping stations in that game, so you’re always exploring uncharted territory and adding it to your map, which beautifully describes the game world.

This slutty map leaves nothing to the imagination.

I give a lot of credit to the designers of ITSP for taking on the exploration genre with a flying mechanic, which adds a lot of challenges to the level design. One thing that got lost in their implementation, sadly, is secret areas. That super-detailed map is pretty to look at, but it leaves absolutely nothing to the imagination: there’s no room there for secret passages, so after exploring an area just once you’re assured that only well-marked locked doors separate you from other unexplored regions. There’s never a reason to come back to an area, except when you’re forced to backtrack or you want to blow away a lock to find a new area, usually little more than a cubbyhole with a prize at the end.

Compare this to one of the most well-executed maps in the history of the genre, Super Metroid. Here’s what it looks like as you’re exploring.

There could be secret passages off of any of those rooms

Notably, Super Metroid only gives you part of the map. There are lots of unmapped secret passages, including ones you must find to reach your main objectives, and the only real guide you have to finding them is knowing where they cannot occur — you can take faith that if your map shows a room to the right of the one you’re in, there won’t be another, secret room squished in between them. Again, it seems like a small difference, but that sort of attention to detail and dedication to accuracy really sets the mediocre games in this genre apart from the great ones.

I have lots more to say about map design, but it can probably wait for another day.

More camera work and padded tilesets

I did some more work on the camera today, since it was suffering from a serious lack of smoothness. It definitely looks a lot nicer now:

I also took some time to correct an issue that’s been bugging me for quite a while now: graphical artifacts from drawing a tile sheet. A tile sheet is a big image composed of many smaller discrete images (the tiles) that are all the same size. When you draw from one, you tell the graphics card which portion of the sheet you’re referencing and where on the screen you want it to go. Unfortunately, graphics cards are fundamentally floating-point machines, and if you have sub-pixel smoothing turned on (which you really want), then you run the risk of drawing a tiny portion of the adjacent tile. This looks terrible.

Those little lines just look like ass

The solution to this problem is to pad each tile by surrounding it with the same color pixels as its own edges. This problem is ubiquitous, so I was very surprised that I couldn’t find any pre-rolled software that solved it for me. I ended up writing my own, which you can download here. It probably needs the .NET runtime to work, but I haven’t tried it on another computer yet. In case that doesn’t work for whatever reason, the algorithm to pad the tiles looks like so:

Image tilesheet = Bitmap.FromFile(_inputFileString);
int numCols = tilesheet.Width / tileWidth;
int numRows = tilesheet.Height / tileHeight;
Image padded = new Bitmap(tilesheet.Width + numCols * 2, tilesheet.Height + numRows * 2);

using ( Graphics g = Graphics.FromImage(padded) ) {
    for ( int row = 0; row < numRows; row++ ) {
        for ( int col = 0; col < numCols; col++ ) {

            int xOffset = col * tileWidth;
            int yOffset = row * tileHeight;
            int destXOffset = xOffset + col * 2 + 1;
            int destYOffset = yOffset + row * 2 + 1;

            // draw the main body of the image
            Rectangle srcRect = new Rectangle(xOffset, yOffset, tileWidth, tileHeight);
            Rectangle destRect = new Rectangle(destXOffset, destYOffset, tileWidth, tileHeight);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            // then a strip on each edge

            // left
            srcRect = new Rectangle(xOffset, yOffset, 1, tileHeight);
            destRect = new Rectangle(destXOffset - 1, destYOffset, 1, tileHeight);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            // right
            srcRect = new Rectangle(xOffset + tileWidth - 1, yOffset, 1, tileHeight);
            destRect = new Rectangle(destXOffset + tileWidth, destYOffset, 1, tileHeight);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            // top
            srcRect = new Rectangle(xOffset, yOffset, tileWidth, 1);
            destRect = new Rectangle(destXOffset, destYOffset - 1, tileWidth, 1);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            // bottom
            srcRect = new Rectangle(xOffset, yOffset + tileHeight - 1, tileWidth, 1);
            destRect = new Rectangle(destXOffset, destYOffset + tileHeight, tileWidth, 1);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            // then a pixel for each corner
            srcRect = new Rectangle(xOffset, yOffset, 1, 1);
            destRect = new Rectangle(destXOffset - 1, destYOffset - 1, 1, 1);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            srcRect = new Rectangle(xOffset + tileWidth - 1, yOffset, 1, 1);
            destRect = new Rectangle(destXOffset + tileWidth, destYOffset - 1, 1, 1);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            srcRect = new Rectangle(xOffset + tileWidth - 1, yOffset + tileHeight -1, 1, 1);
            destRect = new Rectangle(destXOffset + tileWidth, destYOffset + tileHeight, 1, 1);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);

            srcRect = new Rectangle(xOffset, yOffset + tileHeight - 1, 1, 1);
            destRect = new Rectangle(destXOffset - 1, destYOffset + tileHeight, 1, 1);
            g.DrawImage(tilesheet, destRect, srcRect, GraphicsUnit.Pixel);
        }
    }
}

padded.Save(_outputFileString);

Now I can take the tile sets output by Pyxel Edit, run them through this tool, then feed them to Tiled Map Editor. But since that would have been too easy, I discovered that the Tiled map reader library I'm working with didn't support offset, padded tile sets, which made everything look like this:

It's not supposed to look like that. It's not that kind of game.

So I had to add support for this feature, which could have been much worse than it was. You can download the updated version of the library here. It also includes a bug fix for drawing rotated or flipped tiles.

More work on camera control

I ripped out most of the guts of the camera system today and started thinking seriously about how I want it to work, especially for instances when the character’s vertical position on the screen changes during a transition between rooms. I’ve got it looking a lot better, with one major hiccup still remaining.

At the end of the video above, you can see the problem: when the character enters a room where the camera position is no longer constrained by a floor, his vertical position causes the camera to immediately pan downward. Obviously this is jarring. I have a couple ideas on how to fix it.

  • Smoothly pan from the initial vertical position to the lower one. Easy enough, but would likely involve changing the camera system from immediately panning when the character steps outside a box in the center of the screen to a goal-based system where it’s always trying to align itself with a target. Might be worth trying just to see what it’s like.
  • Lock the camera’s Y position until the character jumps or falls, then do the above.

I have a feeling there are a whole host of camera issues for me to deal with that I haven’t even imagined yet, so count on this topic coming up again frequently.

Populating the ship

In the very first drafts of the game story, back before any work had begun on the actual game itself, the protagonist accidentally kills all of his friends in a depressurization event at the beginning of the game. I now think this was a terrible way to get a cheap laugh. I may change my mind on this once more, but I think having living relationships with friends on the ship is important in developing an emotionally affecting story. They can also reflect upon the protagonist through their dialogue, filling in his backstory and shedding light on his character.

The protagonist has a name now, by the way: Lieutenant Roark. Roark is also the name of the motion capture actor and the creative consultant for this game.

Today I set to work creating some secondary characters for the ship, including the main villain for the story. I decided to name them all after obscure nautical terminology: Captain Purchase, Chef Hawser, Ensigns Forecastle and Taffrail, etc. The exception is the villain, Professor Iapetus (another moon of Saturn).

I realized that the NPC system I had hacked together earlier was pretty functionally minimal, so I had to expand it quite a bit to get something worth looking at. First was the problem of formatting text to the screen, since I didn’t want to have to insert \n characters all throughout my dialogue by trial and error. Feel free to use the following algorithm I whipped up, which automatically wraps the text to however wide you want and makes sure it’s all on the screen.

Vector2 displayPosition = GetBaseDisplayPosition();

// split the string with newlines to get the width right
StringBuilder sb = new StringBuilder();
string text = GetConversation()[_conversationLineNum];
int left = 0, right = text.Length - 1;
while ( left < right ) {
    string substring = text.Substring(left, right - left + 1);
    float width = Font.MeasureString(substring).X;
    while ( width > MaxDialogWidth ) {
        // look for a space to break the text up
        for ( int i = right; i > left; i-- ) {
            if ( text[i] == ' ' ) {
                right = i - 1;
                break;
            }
        }
        substring = text.Substring(left, right - left + 1);
        width = Font.MeasureString(substring).X;
    }
    sb.Append(substring).Append("\n");
    left = right + 2;
    right = text.Length - 1;
}

Vector2 stringSize = Font.MeasureString(sb);
displayPosition -= stringSize / 2;

// If we're still drawing off of the screen, nudge the draw boundary
int screenWidth = spriteBatch.GraphicsDevice.Viewport.Bounds.Width;
float cameraScreenCenter = GetCameraCenter();
float leftMargin = cameraScreenCenter - screenWidth / 2 + HorizontalMargin;
float rightMargin = cameraScreenCenter + screenWidth / 2 - HorizontalMargin;
if ( displayPosition.X < leftMargin ) {
    displayPosition.X = leftMargin;
} else if ( displayPosition.X + stringSize.X > rightMargin ) {
    displayPosition.X = rightMargin - stringSize.X;
}

// Finally, draw the text shadowed. This involves drawing the text twice, darker then lighter.
Color shadow = Color.Lerp(_color, Color.Black, .5f);
spriteBatch.DrawString(Font, sb, displayPosition + new Vector2(3), shadow);
spriteBatch.DrawString(Font, sb, displayPosition, _color);

I also noticed that the text was a bit hard to read against some of the background, so I added a drop-shadow effect to it (the last lines of code above). It definitely looks a lot nicer, although I might eventually place the text on a dark backdrop during the conversation to give it even more contrast.

Just noticed I made a typo in this dialogue.

Finally, I needed to animate the characters to bring them to life. They all walk back and forth in a predefined box, randomly stopping, starting, and changing direction every now and then. It looks reasonably convincing, if I do say so myself!

Tomorrow I need to draw the scenery for the engine room and correct the lingering camera issues, then I can move on to scripting the climactic shock of the ship’s crash to the surface of Enceladus and the beginning of the game proper.

Exploring the ship to Enceladus

I had a bit of a crisis of confidence over the last few days as I realized I was going to have to draw a bunch of original art assets. One major obstacle to this task is that I don’t know how to draw. But I calmed down when I realized that, 1) the graphics don’t really matter all that much to the market I’m pursuing, and 2) I wasn’t ever going to learn how to draw unless I started doing it. After all, I didn’t know C# or XNA when I started this project, and now I’m less than horrible with each. So I shoved my doubts aside and got to work producing some truly… interesting pixel art to furnish the ship.

Not many pixels for that much work

The idea behind this art, besides the pixel aesthetic, is that the images are meant to be iconic representations, rather than aiming for realistic reproductions of objects. So I didn’t really draw a couch; I drew the idea of a couch, which is immediately recognizable despite its lack of detail. My wife didn’t realize that the lighted monoliths were supposed to be super computers, but once I animate them to make the lights blink on and off I think it should be obvious enough.

I’ve now furnished five rooms of the ship plus the hallway, leaving only the engine room to draw. Here’s what it looks like way zoomed out:

A digital dollhouse

If you’re feeling patient, you can take this full-sized guided tour around the ship. It starts in the cockpit, then moves onto the lounge, through the hallway, on to the dormitory, then the kitchen, then the brig, and ends up in the cargo hold.

There’s still work to be done on the camera system to accommodate the transitions between rooms, and then to place NPCs and write their dialog. There’s also a door in the base of the ship, which is how you exit once it’s on the surface of the moon, but I haven’t bothered to get horizontal doors working yet.

Be sure to leave a comment to let me know what you think of the art so far — and if anything looks too terrible to live!

First original map artwork

“Artwork” might be a bit of a generous term in this case, but here it is. I’m working on the first map of the game, on the ship going to Enceladus, drawing the tiles necessary to convincingly depict an interplanetary vessel. Here’s the outline of the ship as it appears zoomed way out in the map editor:

Not very ship shaped at the moment

And here’s what it looks like in the game:

I’m using Pyxel Edit to draw the tiles, then laying them down with Tiled. It’s a reasonable workflow for the most part, except that I have to make sure to never, ever change the order of the tiles in the exported tile sheet or else face starting over entirely. Also, I’ve found some annoying, but not deal-breaking, issues with Tiled’s auto-mapping feature, which I’m using to automatically fill in the collision layer with whatever is in the foreground layer. The system is convoluted and confusing, there’s no concept of a wildcard, and I just learned that each orientation of a tile counts as a different tile in the rule set. Even for the very limited tile set so far, I have a huge auto mapping rule.

This is already getting out of hand.

This map also exposed some buggy behavior in the camera controls during room transitions (very obvious in the video) that I’ll have to sort out. I’m a bit torn how to fix this issue in the general case. The problem is that the character is moving between two rooms with different height ceilings, so his vertical position on the screen will have to change. It would be easy enough to make that a smooth transition, but I’m left asking myself a question that I ask myself embarrassingly often: What Would Super Metroid Do? I have a gut feeling that Super Metroid would design the level map so that the ceiling height of two adjacent rooms is the same. I’ll have to sleep on it.

Rotation and mirroring (flipping) with Tiled Map Editor and XNA

Update: the library is now hosted on GitHub: https://github.com/zachmu/tiled-xna

I’ve had very good luck with Tiled Map Editor and the very simple XNA library I found to work with it, written by Kevin Gadd and Stephen Belanger. I’ve since tweaked the hell out of it to work with my own game engine, but it never needed any major overhauls until I started using a tile set creation tool called Pyxel Edit. Pyxel Edit is great for making quick mockups of a tileset (although it desperately needs a selection tool), and it has a great feature where you can rotate a tile into whatever orientation you want. The only issue is that the tile sheets it exports don’t contain duplicate rotated tiles for each orientation you need, so I thought that I couldn’t easily use it with Tiled, because you can’t rotate tiles in Tiled.

Except you can rotate tiles in the latest version of Tiled, even if there’s no UI for it. But it works as advertised. Pushing Z will rotate a tile, while X and Y mirror the tile horizontally or vertically.

Once I figured this out, I just needed to add support for it to the above XNA library. I found the TMX Map Format documentation to be very useful in this process, especially since it contained pseudo code examples. And I got to write some bit-masking code, which I always enjoy.

// Bits on the far end of the 32-bit global tile ID are used for tile flags
const unsigned FLIPPED_HORIZONTALLY_FLAG = 0x80000000;
const unsigned FLIPPED_VERTICALLY_FLAG   = 0x40000000;
const unsigned FLIPPED_DIAGONALLY_FLAG   = 0x20000000;

Since this was a bit tricky to get right, especially the drawing, I figured I should share it back. So here it is: a simple XNA Tiled library with support for flipping and rotating tiles. Enjoy!

Implementing a shatter destruction effect using Box2D and XNA

I’ve had a couple requests to share how I implemented the shattering effect in these two videos:

It’s pretty straightforward to accomplish, and can easily be tweaked to accommodate your aesthetic and performance needs. I’m assuming a basic familiarity with Farseer (the XNA port of Box2D) and the XNA graphics libraries.

To begin, dispose of whatever Body you were using to track the unshattered object, if any. We’re going to replace it with a bunch of smaller bodies that comprise the shards of the original object. Each of those shards will be encapsulated with a simple container class that tracks its Body and its coordinates on the source image.

private class Piece {
    public Piece(Body body, int x, int y) {
        Body = body;
        X = x;
        Y = y;
    }

    public Body Body { get; private set; }
    public int X { get; private set; }
    public int Y { get; private set; }
}

We do a bit of math to determine where each of these pieces should be centered, according to the simulation position of the original image’s center. ConvertUnits is a Box2D design pattern that knows how to convert a measurement from screen space to simulation space and back. There’s a complete implementation you can use in the Farseer sample code. This implementation uses the same number of cuts in the vertical and horizontal directions, which will result in non-square shards if you feed it a non-square image.

/// 
/// Begins a new destruction animation for the image given.
/// 
/// The simulation world
/// The image to destroy
/// The rectangle on the texture to split up and draw
/// The center of the original image
/// The number of horizontal and vertical pieces to split the image into
/// The maximum velocity of any individual piece
public ShatterAnimation(World world, Texture2D image, Rectangle? originRectangle, Vector2 position, int numPieces, float maxVelocity) {
    _image = image;
    _originRectangle = originRectangle ?? new Rectangle(0, 0, 0, 0);
    _pieces = new Piece[numPieces * numPieces];
    _maxVelocity = maxVelocity;

    int width = originRectangle == null ? _image.Width : originRectangle.Value.Width;
    int height = originRectangle == null ? _image.Height : originRectangle.Value.Height;

    _displayPieceWidth = width / numPieces;
    float simPieceWidth = ConvertUnits.ToSimUnits(_displayPieceWidth);
    _displayPieceHeight = height / numPieces;
    float simPieceHeight = ConvertUnits.ToSimUnits(_displayPieceHeight);

    int displayWidthOffset = width / 2;
    float simWidthOffset = ConvertUnits.ToSimUnits(displayWidthOffset);
    int displayHeightOffset = height / 2;
    float simHeightOffset = ConvertUnits.ToSimUnits(displayHeightOffset);

    int i = 0;
    for ( int x = 0; x < numPieces; x++ ) {
        for ( int y = 0; y < numPieces; y++ ) {
            float posx = position.X - simWidthOffset + (x * simPieceWidth + simPieceWidth / 2);
            float posy = position.Y - simHeightOffset + (y * simPieceHeight + simPieceHeight / 2);
            Body body = BodyFactory.CreateRectangle(world, simPieceWidth * 3f / 4f, simPieceHeight * 3f / 4f, 1);
            body.CollidesWith = Arena.TerrainCategory;
            body.CollisionCategories = Arena.TerrainCategory;
            body.Position = new Vector2(posx, posy);
            body.IsStatic = false;
            body.FixedRotation = false;
            body.Restitution = .6f;
            AssignRandomDirection(body);
            _pieces[i++] = new Piece(body, x, y);
        }
    }
}

private void AssignRandomDirection(Body body) {
    double linearVelocity = random.NextDouble() * _maxVelocity;
    double direction = random.NextDouble() * Math.PI * 2;
    Vector2 velocity = new Vector2((float) (Math.Cos(direction) * linearVelocity),
        (float) (Math.Sin(direction) * linearVelocity));
    body.LinearVelocity = velocity;
}

To draw the effect, we take the position and rotation of the simulated Body for each shard, translate it to screen space, and tell XNA which slice of the source image to draw on top.

public void Draw(SpriteBatch spriteBatch) {
    float alpha = 1f - (float) _timeAlive / (float) TimeToLiveMs;
    int xOffset = _originRectangle.X;
    int yOffset = _originRectangle.Y;
    foreach ( Piece piece in _pieces ) {
        Vector2 displayPosition = ConvertUnits.ToDisplayUnits(piece.Body.Position);
        Vector2 origin = new Vector2(_displayPieceWidth / 2, _displayPieceHeight / 2);
        float rotation = piece.Body.Rotation;
        spriteBatch.Draw(_image,
                            new Rectangle((int) displayPosition.X, (int) displayPosition.Y, _displayPieceWidth,
                                        _displayPieceHeight),
                            new Rectangle(xOffset + _displayPieceWidth * piece.X, yOffset + _displayPieceHeight * piece.Y, _displayPieceWidth,
                                        _displayPieceHeight),
                            Color.White * alpha, rotation, origin,
                            SpriteEffects.None, 0);
    }
}

My implementation uses a fade-away effect. To get this to work, you need to track how long the animation has been running so you can calculate the alpha value to draw.

public void Update(GameTime gameTime) {
    _timeAlive += gameTime.ElapsedGameTime.Milliseconds;
}

Finally, don't forget to Dispose of the Body's once the effect is over!

There are lots of ways you can tweak the effect to make it more suitable for whatever you're doing with it. The most important parameters are the number of pieces, what they collide with, the restitution (bounciness) of the pieces, their speed, and their margin. The code that controls these factors is reproduced below.

Body body = BodyFactory.CreateRectangle(world, simPieceWidth * 3f / 4f, simPieceHeight * 3f / 4f, 1);
body.CollidesWith = Arena.TerrainCategory;
body.CollisionCategories = Arena.TerrainCategory;
body.Restitution = .6f;
AssignRandomDirection(body);

In my implementation, I chose relatively bouncy pieces with a reasonable amount of padding around them (1/4 the piece size). Less bouncy pieces with less padding (and with a lower velocity) tend to clump together and fall over, which looks kind of neat on free-roaming enemies but strange when applied to blocks. It's also possible to make the shards collide with whatever you want in your game world, or with nothing at all. My current implementation has them collide with each other and the terrain, but nothing else. Because Farseer performance depends heavily on the number of simultaneous contacts, making them not collide with one another is an easy way to boost performance, along with decreasing the number of shards.

Let me know in the comments if you have any questions, or show off how you're using this in your own games!