11
\$\begingroup\$

I am working on a 2D RPG, which will feature the usual dungeon/town maps (pre-generated).

I am using tiles, that I will then combine to make the maps. My original plan was to assemble the tiles using Photoshop, or some other graphic program, in order to have one bigger picture that I could then use as a map.

However, I have read on several places people talking about how they used arrays to build their map in the engine (so you give an array of x tiles to your engine, and it assemble them as a map). I can understand how it's done, but it seems a lot more complicated to implement, and I can't see obvious avantages.

What is the most common method, and what are advantages/disadvantages of each?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ The main issue is memory. A texture that fills up a 1080p screen once is about 60mb. So an image uses an int per pixel, a tile uses a int per (pixel / (tilesize * tilesize)). So if the tiles are 32,32 you can represent a map 1024 times as large by using tiles vs a pixel. Also texture swapping times are going to hurt pushing that much memory around etc... \$\endgroup\$ Mar 28, 2012 at 22:24

3 Answers 3

19
\$\begingroup\$

First off, let me say that 2D RPGs are near and dear to my heart and working with old DX7 VB6 MORPG engines (don't laugh, it was 8 years ago, now :-) ) is what first got me interested in game development. More recently, I started converting a game I worked on in one of those engines to use XNA.

That said, my recommendation is that you use a tile-based structure with layering for your map. With any graphics API you use, you're going to have a limit on the size of textures you can load. Not to mention the graphics card texture memory limits. So, considering this, if you want to maximize the size of your maps while not only minimizing the amount and size of textures you load into memory but also decreasing the size of your assets on the user's hard drive AND the load times, you're definitely going to want to go with tiles.

As far as implementation goes, I've gone into detail on how I handled it on a few questions here on GameDev.SE and on my blog (both linked below), and that's not exactly what you're asking so I'll just go into the basics here. I'll also make note of the features of tiles that make them beneficial over loading several large pre-rendered images. If anything is not clear, let me know.

  1. The first thing you need to do is create a tilesheet. This is just a big image that contains all your tiles aligned in a grid. This (and maybe an extra one depending on the number of tiles) will be the only thing you need to load. Just 1 image! You could load 1 per map or one with every tile in the game; whatever organization works for you.
  2. Next, you need to understand how you can take that "sheet" and translate each tile into a number. This is pretty straightforward with some simple math. Note that the division here is integer division, so the decimal places are dropped (or rounded down, if you prefer). How to convert cells to coordinates and back.
  3. OK, now that you've broken the tilesheet into a series of cells (numbers), you can take those numbers and plug them into whatever container you like. For the sake of simplicity, you can just use a 2D array.

    int[,] mapTiles = new int[100,100]; //Map is 100x100 tiles without much overhead
    
  4. Next, you want to draw them. One of the ways you can make this a LOT more efficient (depending on map size) is to calculate only the cells that the camera is currently viewing and loop through those. You can do this by fetching the map tile array coordinates of the camera's top-left (tl) and bottom-right (br) corners. Then loop from tl.X to br.X and, in a nested loop, from tl.Y to br.Y to draw them. Example code below:

    for (int x = tl.X; x <= br.X;x++) {
        for (int y = tl.Y; y <= br.Y;y++) {
            //Assuming tileset setup from image
            Vector2 tilesetCoordinate = new Vector2((mapTiles[x,y] % 8) * 32,(mapTiles[x,y] / 8) * 32);
            //Draw 32x32 tile using tilesetCoordinate as the source x,y
        }
    }
    
  5. Jackpot! That's the basics of the tile engine. You can see that it's easy to have even a 1000x1000 map with not much overhead. Also, if you have less than 255 tiles, you could use a byte array cutting down the memory by 3 bytes per cell. If a byte is too small, a ushort would probably suffice for your needs.

Note: I left out the concept of world coordinates (which is what your camera's position will be based on) since that, I think, is outside the scope of this answer. You can read up on that here on GameDev.SE.

My Tile-Engine Resources
Note: All of these are targeted at XNA, but it pretty much applies to anything – you just need to change the draw calls.

  • My answer to this question outlines how I handle the map cells and layering in my game. (See third link.)
  • My answer to this question explains how I store the data in a binary format.
  • This is the first (well, technically second, but first "technical") blog post about the development of the game I was working on. The whole series contains information about things like pixel shaders, lighting, tile behaviours, movement and all that fun stuff. I actually updated the post to include the content of the my answer to the first link I posted above, so you may want to read it instead (I may have added things). If you have any questions, you can drop a comment there or here and I'd be happy to help.

Other Tile-Engine Resources

  • The tile engine tutorial from this site gave me the basis I used for creating my maps.
  • I haven't actually watched these video tutorials yet because I haven't had the time, but they're probably helpful. :) They may be outdated though, if you're using XNA.
  • This site has some more tutorials that (I think) are based on the above videos. It may be worth checking out.
\$\endgroup\$
4
  • \$\begingroup\$ Waow, that's twice more info than I asked, and pretty much answers all the questions I didn't ask, great, thanks :) \$\endgroup\$ Mar 29, 2012 at 12:12
  • \$\begingroup\$ @Mikalichov - Glad I could help! :) \$\endgroup\$ Mar 29, 2012 at 12:28
  • \$\begingroup\$ Typically with a 2D array you want to use the first dimension as your Y and the next as your X. In memory an array is going to be sequentially 0,0-i then 1,0-i. If you do nested loops with X first hen you are actually jumping back and forth in memory instead of following a sequential path of reading. Always for (y) then for (x). \$\endgroup\$
    – Brett W
    Mar 29, 2012 at 19:20
  • \$\begingroup\$ @BrettW - A valid point. Thanks. It got me wondering how 2D arrays are stored in .Net (Row-major order. Learned something new today! :-) ). However, after updating my code, I realized that it's exactly the same as what you're describing, just with the variables switched. The difference is in what order the tiles get drawn. My current example code draws top-to-bottom, left-to-right whereas what you describe would draw left-to-right, top-to-bottom. So, for the sake of simplicity, I decided to revert it back to the original. :-) \$\endgroup\$ Mar 29, 2012 at 20:39
6
\$\begingroup\$

Both tile based systems and a static model/texture system can be used to represent a world and each has different strengths. Whether one is better than the other boils down to how you use the pieces, and what works best for your skillset and needs.

That being said both systems can be used separately, or also together.

A standard tile based system has the following strengths:

  • Simple 2D Array of tiles
  • Each tile has a single material
    • This material may be a single texture, multiple, or blended from surrounding tiles
    • Can re-use textures for all tiles of that type, reducing asset creation and rework
  • Each tile can be passable or not (collision detection)
  • Easy to render and identify parts of the map
    • Character position and map position are absolute coordinates
    • Simple to loop through relevant tiles for screen region

The disadvantage to tiles is that you have to create a system for building this tile based data. You could create an image that uses each pixel as a tile, thus creating your 2D array from a texture. You could also create a proprietary format. Using bitflags you can store a large number of data per tile in a fairly small space.

The primary reason most people do tiles is because it allows them to create small assets and re-use them. This allows you to create a much larger picture will smaller pieces. It reduces rework because you are not changing the entire world map to make a small change. For example if you wanted to change the shade of all the grass. In a large image you would have to repaint all the grass. In a tile system you just update the grass tile(s).

All-in-all you will find yourself likely doing a lot less rework with a tile based system than a large graphical map. You may end up using a tile based system for collision even if you don't use it for your ground map. Just because you use a tile internally doesn't mean you can't use models to represent the objects of the environment which may use more than 1 tile for their space.

You would need to provide more specifics on your environment/engine/language to provide code examples.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks! I'm working under C#, and am going for a similar (but less talented) version of Final Fantasy 6 (or basically most Square SNES RPG), so floor tiles AND structure tiles (i.e. houses). My biggest concern is that I don't see how to build the 2D array without spending hours checking "there is a grass corner there, then three straight horizontal, then a house corner, then..", with tons of possible mistakes along the way. \$\endgroup\$ Mar 29, 2012 at 2:20
  • \$\begingroup\$ Looks like Richard has you covered in terms of a code specifics.I personally would use an enum of tiletypes and use bitflags to identify the value of the tile. This allows you to store 32+ values in an average integer. You will also be able to store multiple flags per tile. So you can say Grass=true, Wall=true, Collision=true, etc. without separate variables. Then you simply assign "themes" which determines the tilesheet for the graphics for that particular map region. \$\endgroup\$
    – Brett W
    Mar 29, 2012 at 19:26
3
\$\begingroup\$

Drawing the map: Tiles are easy on the engine, because then the map can be represented as a giant array of tile indices. Draw code is just a nested loop:

for i from min_x to max_x:
    for j from min_y to max_y:
        Draw(Tiles[i][j], getPosition(i,j))

For large maps, one huge bitmap image for the entire map could take up a lot of space in memory, and could be larger than what a graphics card supports for a single image size. But you won't have any issues with tiles because you only allocate graphics memory once for each tile (or one total, optimally, if they're all on one sheet)

It's also easy to reuse the tile information for e.g. collisions. for example, to get the terrain tile at the top-left corner of the character's sprite:

let x,y = character.position
let i,j = getTileIndex(x,y) // <- getTileIndex is the opposite function of getPosition
let tile = Tiles[i][j]

Suppose you need to check for collisions against rocks/trees etc. You can get the tile at the position of the (character position + character sprite size + current direction) and check if it's marked as walkable or not walkable.

\$\endgroup\$

You must log in to answer this question.

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