Z-Ordering for Isometric Tile Maps

For a 2D, top-down view, the natural ordering of a tile map is a linear array, organized as a 2D matrix. The coordinates of any given tile can be calculated by `y * width + x` (row-major) or `x * height + y` (column-major). Conversely, you can calculate the x and y from the index of a given tile. [1] This is not the only way to represent such a map — for example, if you have an extremely large map it may be worthwhile to ‘swizzle’ the array, storing blocks of tiles sequentially rather than entire rows. [2]

The advantage of the top-down view is that order of rendering is less important. Tiles do not overlap, so the order they are rendered to the screen does not affect the final image. Therefore, rearranging the tile array has no visible effect on rendering. The matrix view of the map is easy to reason about, so this is convenient.

An isometric view, on the other hand, is a different matter.
Continue reading

FTJ2014 Post-mortem Part II – Animation

Unlike rendering, I went into this jam with no clue how to do animation. Fortunately, it turns out to be fairly simple (in part thanks to my per-entity rendering setup.)

First, a brief bit of background, for anyone unfamiliar with rendering:

Each vertex drawn has three properties: position, color, and texture coordinates. The rest of the pixels draw are determined by linear interpolation between connected vertices. Which vertices are connected depends on the primitive type defined when we send the vertex buffer to the GPU. In Majo, everything is rendered as Quads (4 consecutive vertices create a single face.) Finally, by convention, the elements of the position vector are referred to by the familiar (x, y, z), but texture coordinates are labeled (u, v, w).

So, the following code renders a 16×16 square starting at the origin (top-left of screen), textured with a 32×32 block of the current texture, starting at the top-left of the texture image:

void Foo::render(sf::Vertex * quad) const
    quad[0].position = sf::Vector2f(0, 0);
    quad[1].position = sf::Vector2f(16, 0);
    quad[2].position = sf::Vector2f(16, 16);
    quad[3].position = sf::Vector2f(0, 16);

    quad[0].texCoords = sf::Vector2f(0, 0);
    quad[1].texCoords = sf::Vector2f(32, 0);
    quad[2].texCoords = sf::Vector2f(32, 32);
    quad[3].texCoords = sf::Vector2f(0, 32);

(There’s no requirement for texture size and actual rendered size to be related, but direct multiples prevent squashing and stretching.)

What this means is we can place multiple sprites on a single texture image (a spritesheet), and slice out the portion we want to render. Which means that *animating* these sprites is just a matter of changing which slice of the spritesheet we render.

A single frame of animation, then, is simply a rectangle indicating what part of the spritesheet to render:

struct AnimationFrame
    sf::Vector2f uv;  // Offset into the texture.
    sf::Vector2f spriteSize;  // Size of the sprite on the texture.

And an animation consists of a list of frames, with some bookkeeping regarding when to switch:

class Animation
    /* Container for a list of animation frames. */
    std::vector<const AnimationFrame> m_frames;
    int m_currentFrame;

    int m_ticksPerFrame;
    int m_ticksSinceLastFrame;

    // Constructors / Accessors elided.

    /* Start the animation. */
    void start();
    /* Update call.  Advances the animation. */
    void update();
     * Render the texture coords to quad.
     * Does not touch position -- that is to be set by the entity.
    void render(sf::Vertex * quad) const;

The render method allows the owning entity to pass a quad to render the appropriate uv coordinates (while still rendering position and color itself.)

We’re not done yet!

The problem is that this class is hefty: a four-frame animation weighs in at 76 bytes, plus overhead incurred by the vector class. Copying this class as a member of every Entity that uses an animation adds up to a lot of memory moving around. (The issue is less the memory usage, and more that allocating, copying, and deallocating that memory is not free.)

The obvious solution is to cache the Animation, and let each entity store a pointer to it. However, we now have a different problem: the timing information and current frame are shared between all entities using a given animation. (Since each entity calls Animation::update, this leads to undesired behavior.)

This leads to the core lesson I learned during this jam: data and state are different things. Separate them.

Our animation frames are data: we load them into memory once, and only read from them. On the other hand, the timing information is *state*, modified on each update. These are two separate concepts, so let’s carve out the stateful bits into their own structure:

struct AnimationState
    int frameCount;
    int currentFrame;
    int ticksPerFrame;
    int ticksSinceLast;

    // Methods elided

With a bit of trivial setup, each animated Entity now owns an AnimationState, as well as a pointer to an Animation (20 + 4 bytes per Entity on x86). Now we can cache and share Animations all we want, as they are constant data. Animation::render has its signature changed to accept an AnimationState passed in: void render(sf::Vertex * quad, const AnimationState& state) const;

Over 1000 entities, this system saves ~54.6kB of memory. That’s not a lot. However, by enabling caching of Animations, we reduce the amount of initialization code involved, as well as the number of runtime copies/moves neccessary. Decreased memory usage is just a nice bonus.

Next time: design, scope, and some miscellaneous issues encountered over the week.