8
\$\begingroup\$

I'm thinking about making a simple isometric game with HTML5 Canvas, and wondering what's the fastest way to render the tiles.

Since the tiles are diamond shaped, but drawImage draws rectangles, I have to leave out the corners (the black parts in the following):

sample diamond tile

I think that leaves me with three options:

  1. Use Image objects with alpha channels (.png). I'm afraid this might kill performance.
  2. Use a clipping path. If the renderer is optimized this could be pretty fast.
  3. Prerender square tiles, like this:

prerendered square tiles

I'd have the black square as one actual tile in memory, and I'd draw such a tile for the green field, and all fields next to it, or above and below. The diagonal (blue) fields would be make up of the corners of the rectangular tiles. This would avoid clipping or alpha channels, but I would have to prerender all possible combinations of tiles, and it seems like overkill.

What's the best or fastest way to do isometric tiles? What do other games, like FarmVille, use?

\$\endgroup\$
10
  • 1
    \$\begingroup\$ I think "best technique" is kind of loaded, and will depend on the requirements of the game, and technology being used and the developer creating the game. \$\endgroup\$
    – House
    Jan 14, 2013 at 18:04
  • 1
    \$\begingroup\$ What problem are you actually having? It seems like you've provided three answers to your own question inside the question text? \$\endgroup\$ Jan 14, 2013 at 21:10
  • 1
    \$\begingroup\$ This doesn't have enough votes: BEST BEST BESSSSSSST! argh? \$\endgroup\$
    – Jimmy
    Jan 15, 2013 at 2:47
  • 1
    \$\begingroup\$ Guess you should follow the advice found here: meta.gamedev.stackexchange.com/a/638/7191 \$\endgroup\$
    – House
    Jan 15, 2013 at 3:41
  • 1
    \$\begingroup\$ So you don't know that this is actually a performance problem? You're just trying to optimise the code before it's written? In that case, -1 and voting to close. \$\endgroup\$ Jan 15, 2013 at 20:40

4 Answers 4

11
\$\begingroup\$

Using transparency (alpha channel) is the way to go, I recommend.

This means that when you want a vertical object on the tile like this:

tile with tree

Then you can do it easily if your renderer draws the tiles back-to-front i.e. painters algorithm.

IMAGE CREDIT: Reiner's tileset.

\$\endgroup\$
9
\$\begingroup\$

While the method described by sws and MarkR is also what I prefer, I would like to present an alternative approach.

A hackish option for creating an isometric look with minimal effort is to actually use orthogonal tiles, and use context.transform to set a projection matrix which makes the map look isometric (or a combination of context.rotate and context.scale when you don't know how projection matrices work).

See the specification for canvas transformation methods for details.

Tile image:

base tile image

Drawing code:

    for (var x = 0; x < 5; x++) {
        for (var y = 0; y < 5; y++) {
            ctx.drawImage(img, x * img.width, y * img.height);
        }
    }

Result before matrix application:

without transformation matrix applied

Same code with same tile image after applying this transformation matrix:

    ctx.transform(  1,   0.5,
                   -1,   0.5,
                  160,   0    );

with transformation matrix

With the dashed grid removed from the tile image and changed the tile offset in the drawing code to img.width - 1 and img.height - 1 to get rid of the gaps caused by the transformation. Suddenly the tile looks half as ugly:

with matrix and some tweaks

The main drawback of this method is that when you design your tiles in a graphic editor, they won't really be what-you-see-is-what-you-get. You will also encounter problems when you want to draw any objects which are not on the floor but standing upright. For these you can switch off the transformation matrix before drawing them, but then you will have to calculate the position yourself. You can use these formulas for that:

var xScreen = xWorld * 1   + yWorld * -1  + 160;
var yScreen = xWorld * 0.5 + yWorld * 0.5 + 0;  

(note how the numbers from the transformation matrix reappear in these formulas - you are doing the matrix multiplication yourself here).

So why should I do this?

This method is good when you:

  1. aren't experienced with designing isometric tiles, but you have orthogonal ones
  2. don't want to spend much time on developing an isometric graphic engine, which is somewhat more difficult than an orthogonal one.

Another interesting feature is that when you know your way around matrix calculation, you can modify the projection matrix between frames to zoom, tilt and rotate the map in real-time for some nice fake-3d effects (try to do THAT with isometric tiles).

But when you know how to handle isometric tiles, both artistically and technically, and you don't need any fake-perspective trickery I would rather suggest you to go with diamond-shaped tiles with transparency.

\$\endgroup\$
5
  • \$\begingroup\$ It is certainly possible to do this. This way you can have "flat" or "top-down" tiles. However, it means that any "3d" elements in the tiles can't be done easily. I prefer to do this transformation ahead of time. \$\endgroup\$
    – MarkR
    Jan 14, 2013 at 20:33
  • \$\begingroup\$ +1 if not only for the expert explanation! Well done. \$\endgroup\$
    – Luceos
    Jan 15, 2013 at 6:40
  • \$\begingroup\$ @MarkR: I expanded my answer a lot after you made your comment, you might want to take a look if it still applies. \$\endgroup\$
    – Philipp
    Jan 15, 2013 at 8:17
  • 1
    \$\begingroup\$ Thanks for the detailed write-up! That's a very interesting technique, I didn't think about doing that before. However, as I'm starting from scratch (and am equally bad at drawing isometric and square tiles ;-)), I'll go for true isometric tiles. Also, this seems to be not very fast. I did a small benchmark, and blitting tiles without a transformation matrix to integer pixel positions is about 3x faster than with the transformation on my PC. Still I might use this for some special effects. \$\endgroup\$
    – jdm
    Jan 16, 2013 at 9:19
  • \$\begingroup\$ Any way to add some z level to this map? \$\endgroup\$
    – Tarion
    May 10, 2014 at 16:52
3
\$\begingroup\$
  1. You're using context.drawImage to copy pixel data from one source (Image, or offscreen canvas) to another (offscreen or onscreen canvas), which handles alpha gracefully out-of-the-box. Canvas is hardware-accelerated, so testing will allow you to determine whether there's a discernible difference in rendering speeds w.r.t. alpha- vs opaque-pixels.

  2. Clipping will require you to push/pop the context's state when defining a clipping path once per tile, which could be a costly operation considering isometric's overdraw.

  3. Prerendered tiles would, as you state, require an immense number of 'connector' tiles to be drawn, which may or may not be feasable. (I'm leaning more toward "may not".)

A fourth solution would be to adopt a pre-rendered "chunk" (PRC) of tiles, generated once to offscreen canvases, and then cover the screen with the PRCs once per frame. There would still be overdraw, but building the PRC once and rendering it with an offset determined by the player character's (or view camera's) position relative to the PRC should be a relatively simple operation. This would allow you to combine rendering with option #1, which is IMO the best option if performance weren't a consideration (since it's simplest to implement).

\$\endgroup\$
3
\$\begingroup\$

A little alpha channel doesn't hurt much, but if you want to avoid it consider using two quarter tiles, this also gives you some room for making nice tile transitions without doing a plethora of different images, that is probably the biggest advantage:

Rectangular sub-tile pattern

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .