7
\$\begingroup\$

I have a school assignment where I have to draw a 2D map from one of the first zelda games, along with some animated sprites. Now I got the sprites to work, Im having trouble drawing the map. This is what it should look like:

enter image description here

This is the TileSet that we have to use to draw the map (each tile is 32x32 pixels):

enter image description here

We work in a custom game engine that our teacher made for us.

To draw a bitmap we use this command: GAME_ENGINE.DrawBitmap([Name], x.cord (on the canvas), y.cord (on the canvas), x.cord(on the Tileset), y.cord(on the Tileset), amount of pixels you want to cut out of the Tileset (Width), amount of pixels you want to cut out of the Tileset (Height).

If I use this command I would draw the staircase tile (top left) on the canvas: GAME_ENGINE.DrawBitmap([Name], 200, 200, 0, 0, 32, 32);

So I found out that with a nested for loop you can draw a 2D map on the canvas, this is the code I used for that:

for (int y = 0; y < 10; y++)
{
    for (int x = 0; x < 16; x++)
    {
        GAME_ENGINE.DrawBitmap(Link_Level, 128 + (x * 32), 0 + (y * 32), 0, 0, 32, 32);
    }
}

But obviously then it draws a 16 x 10 map consisting of only one tile.

My question is: How can I use that same for loop but somehow let it know which tiles to draw on the correct position?

Any help would be appreciated!

\$\endgroup\$
  • \$\begingroup\$ Store each tile under an integer id, then its x and y. So you could have a file like: 1,2,2 new line 4,23,44 new line 6, 55,23 etc. \$\endgroup\$ – Krythic Oct 26 '16 at 13:13
  • \$\begingroup\$ Then load and store that file in a List/Vector/ArrayList, and simply query the list using the array indexer. \$\endgroup\$ – Krythic Oct 26 '16 at 13:19
  • \$\begingroup\$ You could also try to make a parametric function denoting index of time. Granted, that would incredibly difficult to write with pure arithmetic. \$\endgroup\$ – The Great Duck Oct 26 '16 at 15:10
3
\$\begingroup\$

This is where your tilemap will come to use, you create an array of tiles, for example

int level[5][5] = {
    1, 1, 1, 1, 1
    1, 0, 0, 0, 1
    1, 0, 0, 0, 1
    1, 0, 0, 0, 1
    1, 1, 1, 1, 1
}

Where 0 could be grass, and 1 could be water:

Using your loop then:

for (int y = 0; y < 10; y++)
{
    for (int x = 0; x < 16; x++)
    {
       switch(level[y][x])
       {
       case 0: // Grass
            GAME_ENGINE.DrawBitmap(GRASS, 128 + (x * 32), 0 + (y * 32), 0, 0, 32, 32);
            break;
       case 1: // Water
            GAME_ENGINE.DrawBitmap(WATER, 128 + (x * 32), 0 + (y * 32), 0, 0, 32, 32);
            break;
       }
    }
}

\$\endgroup\$
  • \$\begingroup\$ I think your code is missing the actual coordinates of the selected tile. \$\endgroup\$ – Tyyppi_77 Oct 26 '16 at 13:21
  • 4
    \$\begingroup\$ @Tyyppi_77 That is correct but I can fill that in myself :) \$\endgroup\$ – S. Neut Oct 26 '16 at 13:48
  • \$\begingroup\$ I like this method the most to be honest although I do get an error at the switch function. I get this error: Wrong number of indices inside []; 2 expected \$\endgroup\$ – S. Neut Oct 26 '16 at 13:50
  • 5
    \$\begingroup\$ The redundancy of having the draw call in every case is ugly. You should at least reduce the switch to only affect a single variable describing the tile (though there are still better alternatives). \$\endgroup\$ – CodesInChaos Oct 26 '16 at 15:15
  • 1
    \$\begingroup\$ I think this would be a better answer if it had var tile = null; ... case 0: tile = GRASS; break; case 1: tile = WATER; break; ... GAME_ENGINE.DrawBitmap(tile, 128 + (x * 32), 0 + (y * 32), 0, 0, 32, 32); \$\endgroup\$ – sirdank Oct 26 '16 at 20:08
7
\$\begingroup\$

I would recommend you to store tile data (in this case the id 0 for top left and 1 to the right and so on) in an array. Use the id to also determine which sprite will you use. For example:

int[,] map = new int[4, 5]{{ 0,  0,  0,  0, 0 },
                           { 0, 19, 19, 19, 0 },
                           { 0, 19, 19, 19, 0 },
                           { 0,  0,  0,  0, 0 }};

for (int y = 0; y < 4; y++) {
    for (int x = 0; x < 5; x++) {
        GAME_ENGINE.DrawBitmap(Link_Level, 128 + (x * 32), 0 + (y * 32), (map[y, x] % 12) * 32, (map[y, x] / 12) * 32, 32, 32);
    }
}

12 comes from how many tiles do you have horizontally in your tileset. Code above should draw 5x4 map of stairs surrounding water.

EDIT: Though, i'm not really sure what is the "Link_Level" name is.

\$\endgroup\$
  • \$\begingroup\$ Link_Level refers to the variable that loads the bitmap from the assets folder of the project. First you create the variable using this command: private Bitmap [Variable_Name] = null. Then you load it with this command: [Variable_Name] = new Bitmap("[Name of the png file"); Thanks for the quick reaction by the way! \$\endgroup\$ – S. Neut Oct 26 '16 at 13:22
  • \$\begingroup\$ @S.Neut Ah lol okay, that makes sense now. And you're welcome :D \$\endgroup\$ – Greffin28 Oct 26 '16 at 13:29
3
\$\begingroup\$

A small addition to the answers provided: if you have very large maps and you don't have more than 256 types of tiles per map (including the empty tile), it might save you a lot of ram to use byte instead of int as the array type.

So instead of

int[,] map = new int[6400, 8000]

you would use

byte[,] map = new byte[6400, 8000]

But then if you have such huge maps, you might also look into loading map chunks into memory instead of the whole map. Another note: chunked loading may not work for you if there's stuff constantly going on over the whole map and you need to process all of it all the time. But that's a whole other topic for consideration when designing a tiled map game.


On drawing large maps.

There may come a time when your framerate drops, and it's probably because you have a pretty big map and you're drawing it whole, with large portions of it going off-screen, but still using up your hardware resources. To avoid that, calculate the visibility rectangle and only draw the tiles, indexes of which correspond with the insides of that rectangle.

\$\endgroup\$
2
\$\begingroup\$

This depends on how flexible you need the system to be.

What most games do is that they store the map data in an external level data file. This might using a pre-existing level editor and its level file format (Tiled) or a custom editor (could be just a text editor) with a custom format, be that format XML, JSON or a custom one.

For loading the map data you would read trough the file and store the contents of the file in a 2D array that you can then access.

A simpler way for a small project would be to just hardcoded the map 2D array to the source code of the game.

Anyways, once you have your map data in a 2D array, all you need to do is to lookup the tile type at the tile you are currently rendering.

for (int y = 0; y < 10; y++)
{
    for (int x = 0; x < 16; x++)
    {
        int xTile = MapData[y][x].x * 32;
        int yTile = MapData[y][x].y * 32;
        GAME_ENGINE.DrawBitmap(Link_Level, 128 + (x * 32), 0 + (y * 32), xTile , yTile , 32, 32);
    }
}

Here MapData is a 2D array consisting of an object that has x and y members that define the 2-dimensional "index" of the tile in the tileset. Meaning that you would define a tile to be the fifth tile on the second row for an example.

Now, while this approach is quite simple, it might get a little tedious or repetitive. So you might want to define the tile types in a class (or just refer to the tiles as integers and calculate the correct tile position based on them). A simple utility class with fields such as static global Vec2 StairCase = new Vec2(0, 0); should make defining the map easier.

To summarize, for a small project like this hardcoding a 2D array that defines that map look should be just fine. If you plan to expand the game later, I would recommend using a external data file.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.