[edit 7/16/04]
-Added a description on Why build tile based worlds
Spectre, If it would help later on I might make frogger the example game that we will create after disecting how to build our mapmaker, I could also create a commando style game. Let me know if you would like that.
User Defined Types
User defined types are very useful. I will touch upon them here and we will revisit them during the platform game developement portion. I just feel it is necessary to mention them briefly here because I use them in the mapmaker. The main strength of user defined types is that it gives us an abilityt to group variables together. The second stron point is, that we know arrays can only store the same 'type' of data, for example all ints, all floats, all strings; but waht if we need to have an array that contains ints and floats. Well we can create a user defined type that has an int variable and a float variable, and have an array of that user type. I'll show an example of this later on. For now lets just see how a basic user type is defined and used.
In the mapmake program I have buttons for the GUI. To have the buttons respond to clicks I need to check if the mousepointer is over the button. So, I define a type called rect that has four integer variables, one for the top-left-X coordinate, 1 for the top-left-Y coordinate, 1 for the Bottom-right-X coordinate, and 1 for the bottom-right-Y coordinate. I then creat variables of this type for each button on my GUI. So let's see how to define and use our own types so that we can move on to the fun stuff. Geez! I thought we'd never get there!
User Define Type Decleration
type rect
tlx
tly
brx
bry
endtype
note: because i didn't specify the type of the variables (tlx,tly,brx,bry) the compiler assumes them to be ints.
So that's all there is to the definition. Now we can use this type just like you can use a built in type. For example just like you can define a variable to be a string "name as string" you can use our defined type "button1 as rect":
button1 as rect
That was easy. Now let's assume that button 1 is top-left-x coordinate and top-left-Y coordinate is (100,100) and the bottom-right-X and bottom-right-Y coordinate is (140,140); here is how we would place thpose values into button1
button1.trx=100
button1.try=100
button1.brx=140
button1.bry=140
Ok, so you may be asking, "how does this help us?" Well say for instance later on in the program design we decide that we need to move button1 to topleft(90,90) & bottomright(130,130); if we used the defined type, we only need to change the 4 values at the beginning of the program, and we are done. If we just used 100,100,140,140 all throughout our program we would have to search for every instance of it and make sure to change them. It's not as easy as an 'automatic search and replace' because there may be other 100's or 140's that have nothing to do with button1. Don't worry if it doesn't make sense just yet, I believe that it will when we disect the mapmaker program.
Some last minute things to tie-up before we get to the game-stuff
I can almost smell those 2D games cooking...
the last things I want to cover before building our own 2D tile-map editor is:
What are tile-based games and why should I bother?
How do 3-dimensional arrays apply to tile-based games?
Reading-in data to arrays - From data statements
What is this thing called a game loop?
This will complete our crash-course first chapter. Chapter 2 we will start building or tile-mapmaker. Can you feel it?
What are tile-based games and why should I bother?
Well first I will say that many 2D games past and present are built upon tile graphics, especially games for the NES, SNES era's because let's face it, 2D platformers/rpg's/top-down were more prevalent then. Though some games still work better in 2D like R.T.S.'s (Real-Time Strategy) and T.S.R's (No. Not Terminal Stay Resident; rather Turn-Based-Stategy) anyone enjoyed Advanced Wars? Some examples of tile-based games are: Super Mario Bros., early Zeldas, the early Castlevania's, the early Final Fantasy's, The original Metroid, The orignal Mega-Man, as well as numerous games past & present. Some games used tiles that were diamond shape, giving a more 3D feel to them, These are referred to as ISO and ISO/HEX-tile-based games. I'll cover them in another tutorial. In 'top-down' tile-based games, the worlds/levels are made up of numerous tiles of a certain size. Some of the common sizes are (16*16, 32*32, 64*64) usually powers of 2 for graphical reasons.
So now that you may recognize some tile-based games, you might still be thinking, "Isn't it better to just draw my backgrounds as large bitmaps instead of building up the screen out of small tiles?" This question is not always simple to answer, and to be honest - with certain games it may be best to hand-draw all of your backgounds (though you will still likely use some sort of logical collision grid); But the most practical answers for using tiles to build the worlds are:
Why Build a world out of tiles?
When designing levels like this as 2D programmer has a few choices:
1)The first one that comes to mind is to create a large bitmap for the level, scrolling it as the player moves.
2)Another method is to use small tiles (like a mosaic) to build thhe large world. Using tiles to create the world is similar to using pixels to draw a large picture in a paint program, but rather than painting with a single pixel, we are painting with a bitmap usually a size of (32*32, 64*64*, 128*128).
Now between these 2 methods; why is method 2 preferred over method 1?
There are primarily three reasons: Memory conservation, graphic reuse, and dynamic map creation.
Let's take a look at each.
(credited to author Todd Barron)
Memory Conservation
Let's say we we needed a level 100 * 100 tiles; with each tile being 64pixels * 64pixels. That would equal 10,000 tiles total. Lets calculate how much memory this single map would use if we tried to create it using one large bitmap, instead of using tiles. so,
100 tiles * 100 tiles = 10,000 tiles
64pixels * 64 pixels = 4,096 pixels per tile
10,000 tiles * 4,096 pixels * 1 byte(8bits) = 40,960,000 bytes (w.256 colors)
10,000 tiles* 4,096 pixels * 4 bytes(32-bit) = 163,840,000 bytes
So a simple 100 * 100 map would require 163 mb in storage if we used 32-bit color; even if we chose tro use 256 colors it would require 41 mb of storage space - for one map.
Now let's compare how much memory it would take to store the map if we use tiles to create the larger world.
100 tiles * 100 tiles = 10,000 tiles
64 pixels * 64 pixels= 4,096 pixels
100 tiles * 4,096 pixels * 4 bytes= 1,638,400
10,000 tiles * 1 byte per tile = 10,000 bytes
10,000 bytes + 1,638,400 = 1,648,400 bytes total
So, using a set of 100 tiles, you can create the 100 * 100 map using opnly 2 mb of memory. Even if you used a tile set of 1000 tiles it would only use less than 20 mb.
And that's if map had no repeating tiles and each tile was used once.
Graphic reuse
This one is practically a no-brainer. If we chose to make each level from a large bitmap we would have to draw every level by hand, if we want to add a level, we will have to start from scratch and draw the level from scratch. If we want to create a new game, we start over drawing levels.
Now creating your own tiles does take time, it isn't an easy process, but once complete, you can reuse the tiles, over and over again. Yu can create new levels with them, in fact you can even use a good tileset for a completely different game, or for hundreds of completely different games.
[Dynamic Content]
Say you want your game to create random maps or levels on the fly. If your world is built from tiles you can write an algorithm to place tiles in a way to make them appealing. Hence, a random map generator.
To do this with a world that is one large bitmap would be a programming nightmare. You would have to write an algorithm that draws a world pixel byu pixel and creates something playable. I don't even want to think about it.
------
Ok I hope that you are convinced as to why tile-based worlds are the way to go. Now I can answer the Data question
So, lets imagine that we want to draw a Super Mario Brothers level. Let's say we have a tile that looks like bricks and its a bitmap that's 32 pixels by 32 pixels.
This means that our level is going to made up of multiple tiles 32*32 placed into a grid, like working with mosaics. Let's say our screen resolution is 640pixels * 480pixels, and that our world will be two screens wide by one screen high (1280pixels * 480pixels). Since we have tiles that are 32pixels * 32 Pixels in order for us to fill two screens we will need to have a 40 tiles * 15 tiles map.
To Store the 40 * 15 map we will use a 2d Array. (it may help you to keep in mind that a 640 pixel * 480 pixel screen is basically a 2d array. dim Screen640x480(640,480) where each x,y pair refers to a specific pixel. Our map will work the same way but rather than x,y referiing to a single pixel it will refer to a tile that is 32pixels*32pixels.)
dim OurMap(40,15)
So now we have a 2d array that can old our map. But we still have no idea how to get the 32*32 tiles to the screen. That's where our data statement comes in. The idea is we will give each tile a numeric identifier. For example or brick tile will be identified by "1" lets also we have an all blue tile that represents the sky and is identified by "0". Now we have to get these values into our 2d array. later when we want to draw the map we will look at the coordinates in our array, any time we run into a 0 we will draw a 32*32 blue tile; any time we run into a 1 we will draw a 32*32 brick tile. So how do we get our map's data into the array. Well there are a few methods. first we can do this
OurMap(0,0)=0: OurMap(1,0)=0: OurMap(2,0)=0: OurMap(3,0)=1 ....
As you can see this is not a good method. So to make our life easier we can use data statements.
What are data statements. Basically it's a way for us to include directly in our program, as opposed to having to open a .txt file, .ini file, .dat file, or even our own custom .map file. The data can be integers, floats, characters, strings etc. The only thing we need to make sure of is that when we read from the data statement that we have the appropriate variable type to hold the data we are reading.
Keep in mind that for our map data 0=to blue sky (32*32 tile), 1=to a brick (32*32 tile)
so lets create or map with data statements
we need a 40*15 map. So I will write the data statements to coincide with that. We will have 15 data lines; eachline will have 40 values.
I could write it all in one single data statement, but by doing it the other way I can sort of see what the map will look like.
data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
data 0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0
data 0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0
data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
data 0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
data 0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1,1,1,0
data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0
data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
data 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
data 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
It my be hard to see but these data statements represent a level that looks like this
------------------------------------------ Pixels Tiles OurMap Y val
| | 32 1 0
| | 64 2 1
| | 96 3 2
| | 128 4 3
| | 160 5 4
| ###### ####### | 192 6 5
| ###### ####### | 224 7 6
| | 266 8 7
| | 298 9 8
| ######### #### ## #### | 320 10 9
| ######### #### ## #### | 352 11 10
| | 384 12 11
| | 416 13 12
|########################################| 448 14 13
|########################################| 480 15 14
------------------------------------------
Pixels
3691112223334445556667778888999111111111
2462692582581481470470360369269000111222
8024680246802468024680246802258258148
468024680
Tiles
1234567891111111111222222222233333333334
0123456789012345678901234567890
OurMap X values
0123456789111111111122222222223333333333
012345678901234567890123456789
Now we need to read these intgers into our world array
for y=0 to 14
for x=0 to 39
tile=read number
OurMap(x,y)= tile
next x
next y
How Do 3-dimensional arrays apply to tile-based games
This is the "Meat & Potatoes", the "Nuts 'n' Bolts"; so to speak! This is where we make the leap. I know that so far I described multi-dimensional arrays, and you probably thought "Oh great, another tutorial showing me how to average numbers!" Don't fear, I wouldn't just leave you there. I understand it's not enough to just describe a data structure, I need to show how to implement it - and implement with a game design. That's why we are all here, and that's why we purchased DB/DBpro, not to write a grade averager!
It was important to introduce the structure in a reasonable straight-foward manner, but now we need to step-up a notch and make a conceptual leap. It's one thing to desrcibe a data structure, it's another to put it to use. It's kind of like describing a paintbrush, and then describing how to paint a masterpiece. So without further ado I will try to help you understand how the 3-dimensional array is the heart of tile-based games.
Here is a figure to help us conceptualize.
figure 1.4
Sorry the image is a little small, but I'm trying to keep things to a minimum.
Here's an explantion of the figure. The picture shows a 4*4*3 tile-based cut-away of a tile-based world.
The first layer is layer 0 and is oulined in black. This is the base terrain layer. All tiles placed on this layer the character can walk-over. Although I placed water on this layer (normally not walkable for mortals), I made sure to place water at the edge of the land (on layer 1) to provide a barrier so the character sprite can't move there.
The second layer is layer 1 and is outlined in red. This is the layer that the character is rendered on. Any tiles placed on this layer the character sprite will collide into. eg. water, items (like the Novell computer).
The third layer is layer 2 and is outlined in yellow. This layer would be used for things that the player walks under. eg. tree limbs, bridges, etc..
In our actual games we will usually use 5 layers. It made it easier to shoew the figure with only 3.
You are probably noticing something. This figure looks a lot like the description of a 3-dimensional array. dim world(3,3,2). And that's exactly the point. The logic, and tile-placement of the world, collision, rendering, is all handled by the structure of a 3-dimensional array. Now how do we put in information into an integer array to give it the logic to control tile images. That's our next step, and here is where we will talk about reading in data statements.