© 1998 Hung Phan
This tutorial is not to be reproduced without the permission of the author.
2D Logic and Basics
"2D Logic" is thinking and designing in a 2D world. Lets say you're playing Raptor™ and you're moving your plane while the scenery parallaxed scrolled behind ya. Besides worrying what'll peg your ass, something else should have crossed your mind... 'How the hell is the game done'? Besides that, 'How do they come up with this shit'??
To save you some brain teasing, the background of a 2D game is made up of tiles (or at least most of them. I've seem some games where chunks of bitmaps are put together). Back in the days when mode13h (320x256x256) was standard, tiles were 16x16 pixels big. Now as 640x480 (SVGA, dammit) a standard, tiles should be at least 32x32 pixels. Advantages of a 32x32 tile is repetitiveness and some bitch ass artwork. But you could make the tile another size. A 40x40 tile is good, because it divides evenly with 640&480 and so does 800&600. But it becomes a bitch when you go to 1024&768. Nevertheless, it just depends on your target video mode.
You got the tile thingy down, what next? Displaying your graphics on the screen should be of a consideration. In graphical terminology, something called Blit Block Transferring (blitting) was developed to shift chunks of an image to and fro. Cool, you might think. Just blit tiles on the screen and you got yourself a "world". Not quite. You got to have some sort of representation for your maps. Something that is organized and can be manipulated easily. This is where your BASIC days should come into play. If you declare a two-dimensional array, you'll notice that it makes a perfect box (duh..) and what are maps? Exactly, a damn box or rectangle.
Before jumping into maps, global map coordinates should be justified. Lets just say an array called MAP has been defined. And if our screen can only be 5x5, we need an x and y value to control what we do from the current map location to the limited display size. So with this, we can make a function that takes map coordinates and display the world 'til we hit our video display limit.
Also note that whatever you want to have in a 2D world, they must obey the coordinate "laws". This means that you have to worry how the object can be controlled and projected on the screen respectively with the map. So along with map coordinates (which is strictly for the tiles BTW), world coordinates will come into play. World coordinates starts at (0, 0) and goes on til you reach your map limit. Whenever you move the object, you increase it or decrease it. If it goes pass the screen limit, fuck it. Keep on moving the world coordinates but just don't draw it. 2D Maps
You might think that a 2D map can be held together with a two-dimensional array. And if you were told to have multiple layers, you'll say just make three-dimensional. Yeah, you probably think you're Superman by now. But what about animated tiles? This is where you drop the old school stuff and hit the big boys, Structures and Classes.
First off, you need a structure to hold all the data a tile can hold. Stuff like the bitmap number, attributes, and other shit. This allows more flexibility than an integer array. You could even have more graphic layers without upping the array subscript. But for this tutorial's sake, I'll keep the structure simple enough to hold only the bitmap information.
unsigned int base,
Speaking of graphics layers, they are simply just tiles of graphics on top one another. They're basically there for the graphical and simplicity effect. When you have a game, and have a part of the map that is grass and the other side is dirt or sand, you dont want to draw each damn tile of when and where the grass fades to dirt or vice versa. An easier method is to have a base tile with a masked (a blitting function that skips a pre-determined palette number to show what's behind the masked-blitted image) tile over it. This way it can be flexible to have a texture faded to another texture. For animated tiles, all you do is blit the frame number every certain timeframe.
Now instead of having just an integer array, you can have a structure array. But you don't want to make the array global. That's kinda letting it get unorganized. Since you're bound to have map blitting functions, loading and saving functions (for the map editor and the game as well), just shove everything (functions and array) in a class. This is to prevent any other bad ass function from altering it. When you design your tile class, keep in mind to have an assignment function for the tiles. The assignment function basically takes in a bitmap number and inserts it into the map array, respective to the map location you specified. Buffering and Scrolling
To have smooth scrolling, you got to have a buffer that is somewhat larger than the drawing screen. When I say buffer, I mean an extra slice of video memory to draw on. This is where you use the map coordinates talked about earlier. You got to have some globals to keep track of what map number you're starting to draw with, an x offset, and a y offset. The x offset is the amount of room that is left on the left side of the screen and the y offset is the room on the top.
Let's just say that the screen resolution is 640x480 and you boxed out the last 160 pixels of the screen to make the drawing screen a nice 480x480 square and your tiles are 32x32. To have smooth scrolling you need a buffer of at least 544x544 (which leave room for a 17x17 map of 32x32 tiles). This will leave room for a "scrolling tile" on each side of the buffer.
Some may disagree with the following method but it's the easiest of 'em all. To start the whole process, just make a tile algorithm that blits the structure array onto the buffer. Of course you got to have map coordinates. I'll call it mapX and mapY. Create a function that takes in these two map coordinate and blit from there. Something like this:
void DrawMap(int x, int y)
register int i, j, MX, MY;
/* assigning map coord for base level*/
MX = x;
MY = y;
/* base level blitting */
for (i = 0; i < 17; i++)
for (j = 0; j < 17; j++)
blit((MX * 32) + xoff, (MY * 32) + yoff, image, screen);
MX = x;
DrawMap(int, int) will take in starting map coordinates and use them to blit a 17x17 tile buffer. The x & y offsets are there to shift the map correctly into place. So, what you're really doing is increasing the x_offset and y_offset every time you press the appropriate key. To make this accurate every time, you got to redraw the map and Chechen the x & y offsets every frame. By checking the offsets, I mean by if the x or y offset is greater than 32 or less then zero, you have to decrease the map or increase the global map coordinates, respectively.
Confused? I'll explain why. Just say you have a 64x64 picture and have a 80x80 picture. When the 80x80 is centered with the 64x64, the offsets of both x and y are -8 (extra 8 pixels on all sides; for x axis: 64+(8+8) = 80; for y axis: 64+(8+8) = 80). When you move the 80x80 pict down 8 and right 8, the y offset that was -8 becomes 0 and the x offset that was -8 also becomes 0. Now you have no more buffer (on the left and top that is, the right and the bottom have offsets of +16 now) and it is time to remake the offsets. Since you moved down and right, the both map coordinates have to be subtracted by one. This is because you moved down and right and the buffer that was there is now being shown and a new buffer have to take its place. If the pict is moved up and left, you do exactly the opposite.
Also, when you update the x & y offsets, also update the map world x & y coordinates of the map. You do this to draw the objects on the map relative to the player's position. Don't get map coordinates confused with world map coordinates. Map coordinates are alone the grid section you're in. World map coordinates are the actual coordinates that you're located. Which leads us into our next topic... Controlling Objects
Controlling objects are simple. All you have to do is keep tracc of their world x & y coordinates. And to draw them at the correct place is just a simple formula.
x = object.worldx - map.worldx;
y = object.worldy - map.worldy;
This works well because lets say the map's x & y is at (0, 0) and the object's x & y is at (100, 100). And when you move RIGHT 5 pixels, the map's coordinates is now (5, 0). Instead of drawing the object at (100, 100), you now draw the it at (95, 100). Look at the formula to justify this.
Reprinted with permission