Height in a 2d game
And hello to you too,
I'm gonna start with explaining what I'm wanting to do, and then decide on how...I've been planning on creating my own game for over a year now. I followed quite some books and tutorials on C++ and DirectX, but now I'm gonna make my own engine (...the story is quite far finished, so now I'll start programming).
Golden Sun...that's the first word that came into my head. Not because of the graphicsstyle of the game, the sound of the story, but the tile-engine; because in Golden Sun, you have height.
Now, I'd just like to know how they've done it, implementing height in the game. There are probably more techniques and I don't mind hearing them all (the more the better). If it sounds too hard, I know what my limits are (so don't go yelling around that it might be one of the hardest things to do or alike).
I hope you can help
-Stenny
[Note: I had 2 bad, useless posts just now... Let me try this again.]
From the title, it seems that you have decided to go straight 2d. That is the first hard choice to make.
Now you need to ask yourself: Why do I want heights? Do I want rolling hills? Do I simply want multiple floors on a building? Can objects be under levels?
Depending on your answer, there are different types of implementation.
From the title, it seems that you have decided to go straight 2d. That is the first hard choice to make.
Now you need to ask yourself: Why do I want heights? Do I want rolling hills? Do I simply want multiple floors on a building? Can objects be under levels?
Depending on your answer, there are different types of implementation.
Height like Diablo II or AOE II or height like Stronghold (much better)?
Are you having stepping levels as in like Diablo or AOE or nice smooth 'rolling hills' type terrain like Stronghold?
Are you having stepping levels as in like Diablo or AOE or nice smooth 'rolling hills' type terrain like Stronghold?
Good one guys. Ok, I have indeed decided to go 2D. Don't get me wrong, it wasn't a rushed decision, I have thought this over. Now that has been cleared up:
I want to add heights to add more intriguing and varying gameplay. Think of it, ever seen a world that's completely flat? No, there isn't. There are always cliffs, buildings and other scenery objects. And why wouldn't you be able to walk on tóp of it, that'd only make things more interesting. At least, that's how I see it.
As I've already mentioned, the game that most resembles my idea is Golden Sun. Thus, no rolling heights, just multiple layers.
I don't completely get that question but from what I understand: you're asking if level 'floors' can overlap objects? They certainly can, if that is what you mean. but it's not like it's a complete floor, just the edges. The fact that you can walk behind buildings or cliffs, but off course not completely dissappear behind them (or they must be really tall).
-Stenny
Quote:Now you need to ask yourself: Why do I want heights?
I want to add heights to add more intriguing and varying gameplay. Think of it, ever seen a world that's completely flat? No, there isn't. There are always cliffs, buildings and other scenery objects. And why wouldn't you be able to walk on tóp of it, that'd only make things more interesting. At least, that's how I see it.
Quote:Do I want rolling hills? Do I simply want multiple floors on a building?
As I've already mentioned, the game that most resembles my idea is Golden Sun. Thus, no rolling heights, just multiple layers.
Quote:Can objects be under levels?
I don't completely get that question but from what I understand: you're asking if level 'floors' can overlap objects? They certainly can, if that is what you mean. but it's not like it's a complete floor, just the edges. The fact that you can walk behind buildings or cliffs, but off course not completely dissappear behind them (or they must be really tall).
-Stenny
Now based on the response you gave, there are a couple of methods you can look more into.
The first, and I think most common, method is to have a 2D grid that represents your map. Each square (which I call a grid-node) is a struct that contains information about each level in a linked-list format. It would look something like this:
This is how you would use it:
What this gives you is a map that can support infinite number of levels (represented by BLOCK). This is a nice introductory approach that gives you a lot of flexibility.
A BLOCK is an imaginary fixed dimension cube (say 32x32x32) so if you have a grid-node that has 3 blocks on it, it would be 32x32 with a height of 96. A BLOCK can also represent open-space so in theory, you can have holes in your level so that you can see below, but still have a level above the hole. Adds for some nice effects and even split-level design.
How fluid is your movement between each grid-node? Will objects be able to move in 4 directions (N, E, S, W) 8 directions(the previous 4 + NE, NW, SE, SW), or will they be completely free (360 degrees of movement)?
Depending on your answer, you would need to expand BLOCK to provide information on how the object can move from a BLOCK to the adjacent blocks.
Most times (when limiting to 4 and 8 directions), this can be done using generic functions that determine if object A can move from block A to block B. This is generally done using a CanLeaveBlock and CanEnterBlock function that takes, as parameters, a pointer to the object, the block, and the side that the object is leaving/entering from.
The first, and I think most common, method is to have a 2D grid that represents your map. Each square (which I call a grid-node) is a struct that contains information about each level in a linked-list format. It would look something like this:
struct BLOCK{ void *tile; //Pointer to your tile-specific data void *objects; //Array of objects on this block BLOCK *nextLevel; //The level 'above' this one}struct MAP{ unsigned int width; unsigned int height; BLOCK *terrain; //Ground zero}
This is how you would use it:
MAP my_map;unsigned int mem_size;my_map.width = 128;my_map.height = 128;//Allocate the space for the ground-floor terrainmem_size = my_map.width * my_map.height * sizeof(BLOCK);my_map.terrain = (BLOCK *)malloc(mem_size);memset(my_map.terrain, mem_size, 0);
What this gives you is a map that can support infinite number of levels (represented by BLOCK). This is a nice introductory approach that gives you a lot of flexibility.
A BLOCK is an imaginary fixed dimension cube (say 32x32x32) so if you have a grid-node that has 3 blocks on it, it would be 32x32 with a height of 96. A BLOCK can also represent open-space so in theory, you can have holes in your level so that you can see below, but still have a level above the hole. Adds for some nice effects and even split-level design.
How fluid is your movement between each grid-node? Will objects be able to move in 4 directions (N, E, S, W) 8 directions(the previous 4 + NE, NW, SE, SW), or will they be completely free (360 degrees of movement)?
Depending on your answer, you would need to expand BLOCK to provide information on how the object can move from a BLOCK to the adjacent blocks.
Most times (when limiting to 4 and 8 directions), this can be done using generic functions that determine if object A can move from block A to block B. This is generally done using a CanLeaveBlock and CanEnterBlock function that takes, as parameters, a pointer to the object, the block, and the side that the object is leaving/entering from.
Wow...you're quite good with this stuff Dino[smile]. Please tell me if I get this right:
I'm quite new to the tile-based programming mind you. I've gone through some articels on GDNet, and as far as I've seen almost every tilesystem uses structs, and an array of those structs represents the map. Now, you seem to be using a struct that resembles a certain cube-area in an imaginary 3d-world. Am I right?
At this subject, I'm quite the noob, so please explain as clear ass possible.
No, wait, I think I'm starting to get it (just thinking out loud here). Those 'blocks' you're speaking of, build-up a complete level. I can for example choose the blocks to be 16*16, on the floor, and 16 into the air. And when I want to create, say, a pillar, I place e.g. 3 of those blocks on each other and make a 16*16*48 cube.
Now the 'empty' blocks make sense too. You could make bridges or other things 'hanging' in the air.
=-=-
EDIT
=-=-
Ok, now for your question; the one about the movement. As with my previous post, I don't know how hard things are to do, but 8-directional based movement sounds the best. 360 degrees is way to much (I don't need that), and 4 tends to get a bit 'blocky' (although you always get that with tiles of course).
I don't really get what you're trying to say there. Maybe I'd do if I spend more time on it, but I've got to go. I'll post later on.
-Stenny
I'm quite new to the tile-based programming mind you. I've gone through some articels on GDNet, and as far as I've seen almost every tilesystem uses structs, and an array of those structs represents the map. Now, you seem to be using a struct that resembles a certain cube-area in an imaginary 3d-world. Am I right?
At this subject, I'm quite the noob, so please explain as clear ass possible.
No, wait, I think I'm starting to get it (just thinking out loud here). Those 'blocks' you're speaking of, build-up a complete level. I can for example choose the blocks to be 16*16, on the floor, and 16 into the air. And when I want to create, say, a pillar, I place e.g. 3 of those blocks on each other and make a 16*16*48 cube.
Now the 'empty' blocks make sense too. You could make bridges or other things 'hanging' in the air.
=-=-
EDIT
=-=-
Ok, now for your question; the one about the movement. As with my previous post, I don't know how hard things are to do, but 8-directional based movement sounds the best. 360 degrees is way to much (I don't need that), and 4 tends to get a bit 'blocky' (although you always get that with tiles of course).
Quote:Depending on your answer, you would need to expand BLOCK to provide information on how the object can move from a BLOCK to the adjacent blocks.
Most times (when limiting to 4 and 8 directions), this can be done using generic functions that determine if object A can move from block A to block B. This is generally done using a CanLeaveBlock and CanEnterBlock function that takes, as parameters, a pointer to the object, the block, and the side that the object is leaving/entering from
I don't really get what you're trying to say there. Maybe I'd do if I spend more time on it, but I've got to go. I'll post later on.
-Stenny
You got the building theory right 100%.
Whenever an object moves, you need to check 2 things:
- Is the object allowed to leave the block it is on? This is useful for testing to see if the object is stuck in that block or if the path is blocked by something (like a railing)
- Is the object allowed to move onto the new block? This is useful for testing to see if the new block is fully occupied. For example, the column you mentioned would not allow someone to walk onto the base blocks. They are completely occupied by something. Another example is a block that represents a thick wall of a fortress.
Most of these checks can be done in a generic function that takes 3 parameters: a pointer to the object that is moving, a pointer to the actual block, and a value to indicate the direction.
For example:
int CanLeaveBlock(OBJECT_DATA * obj, BLOCK * block, DIRECTION exitDir);
int CanEnterBlock(OBJECT_DATA * obj, BLOCK * block, DIRECTION enterDir);
When an object is moving and it's crossing from one block to an adjacent one, you would do something like this:
if(!CanLeaveBlock(my_guy, orig_pos, east)) return;
if(!CanEnterBlock(my_guy, dest_pos, west)) return;
These 2 lines basically checks to see if my_guy can leave the block going east and if my_guy can enter the new block coming from the west.
Now, this is a very simplistic approach and you will add more detail and complexity as you build your game, but it's a start.
Whenever an object moves, you need to check 2 things:
- Is the object allowed to leave the block it is on? This is useful for testing to see if the object is stuck in that block or if the path is blocked by something (like a railing)
- Is the object allowed to move onto the new block? This is useful for testing to see if the new block is fully occupied. For example, the column you mentioned would not allow someone to walk onto the base blocks. They are completely occupied by something. Another example is a block that represents a thick wall of a fortress.
Most of these checks can be done in a generic function that takes 3 parameters: a pointer to the object that is moving, a pointer to the actual block, and a value to indicate the direction.
For example:
int CanLeaveBlock(OBJECT_DATA * obj, BLOCK * block, DIRECTION exitDir);
int CanEnterBlock(OBJECT_DATA * obj, BLOCK * block, DIRECTION enterDir);
When an object is moving and it's crossing from one block to an adjacent one, you would do something like this:
if(!CanLeaveBlock(my_guy, orig_pos, east)) return;
if(!CanEnterBlock(my_guy, dest_pos, west)) return;
These 2 lines basically checks to see if my_guy can leave the block going east and if my_guy can enter the new block coming from the west.
Now, this is a very simplistic approach and you will add more detail and complexity as you build your game, but it's a start.
Yes, Yes! It makes sense! Thank you man. I guess CanLeaveBlock would also return a false if it's a cliff (= whenever you'll fall down into a pit if you dó walk).
Now, for the tile drawing itself. What tile data should a block have included. Normally you'd only have to draw the 'top' side of the blocks. But with this heightsystem, you're going to see walls too.
-Stenny
Now, for the tile drawing itself. What tile data should a block have included. Normally you'd only have to draw the 'top' side of the blocks. But with this heightsystem, you're going to see walls too.
-Stenny
While I had a generic pointer in the BLOCK struct pointing to the tile information (void * is simply a memory pointer of no specific type), you can change that to a pointer to a specific struct. Something along the lines of this:
gfx is simply your tile image. mouse_map and height_map are two ideas often used in Isometric games (you can read more about them in the Articles & Resources section).
I would have the tile graphic be the fully rendered tile (top and sides) for your initial implementation. This way you never have to worry about when to render the sides. While it's a larger bitblt to do that, it'll save you time implementing it and I think the performance cost would be negligible.
So when you render, you would iterate through all the nodes in terrain that you are concerned with. For each node, you iterate through each block and render that block until you have no more blocks in that node.
These two functions should help you get started. I just typed them up and haven't checked them to see if they even compile. It's more to show you the theory
There are a couple of things not defined in this code:
- TILE_HEIGHT and TILE_WIDTH are the 2D dimensions of your block. I have them as constants, you can place them in the MAP struct so that your engine can use any size tile.
- The function DrawTile() is your actual drawing routine to render the tile graphics at a specified pixel point.
Now this is only an introductory version to get you started. As you work with it, you will notice that you need to expand it more. For example, I would have my destination rendering surface be a parameter of Render and RenderNode.
struct TILE{ char *gfx; char *mouse_map; char *height_map;}
gfx is simply your tile image. mouse_map and height_map are two ideas often used in Isometric games (you can read more about them in the Articles & Resources section).
I would have the tile graphic be the fully rendered tile (top and sides) for your initial implementation. This way you never have to worry about when to render the sides. While it's a larger bitblt to do that, it'll save you time implementing it and I think the performance cost would be negligible.
So when you render, you would iterate through all the nodes in terrain that you are concerned with. For each node, you iterate through each block and render that block until you have no more blocks in that node.
These two functions should help you get started. I just typed them up and haven't checked them to see if they even compile. It's more to show you the theory
/***Function: Render Node**Description: Renders a node and all of it's blocks*/void RenderNode( BLOCK * baseBlock, //The base block to render int X, int Y) //The point to render at{ BLOCK * cur_block; TILE * tile; //Loop through all the blocks within this grid node cur_block = baseBlock; while(cur_block != NULL) { //Render the tile tile = cur_block->tile; if(tile != NULL) DrawTile(tile, X, Y); //Move up 1 block and set Y to appear as if the next block is // above the one we just rendered Y -= TILE_HEIGHT; cur_block = cur_block->next; }}/***Function: Render**Description: Renders the map starting at the grid point startX, startY and ** renders the map to points endX, endY*/void Render( MAP *map //Map to render , unsigned int startX, unsigned int startY //Starting points , unsigned int endX, unsigned int endY //Ending points , int renderX, int renderY) //Point to render at{ BLOCK * grid_node, * current_row; unsigned int row_offset; unsigned int x, y; int px_x, px_y; //Calculate the pointer offset for faster array element accessing row_offset = map->width; //Get the starting row grid_node current_row = map->terrain; current_row += (startY * row_offset) + startX; //Check the rendering parameters (remember that we are base 0) if(endX >= map->width) endX = map->width - 1; if(endY >= map->height) endY = map->height - 1; //Loop until we've rendered the proper number of rows y = startY; px_y = renderY; while(y <= endY) { //Get the pointers to the blocks to render x = startX; px_x = renderX; grid_node = current_row; //Loop until we have rendered the proper number of nodes while(x <= endX) { //Render this node RenderNode(grid_node, px_x, px_y); px_x += TILE_WIDTH; //Advance to the next grid node grid_node++; x++; } //Move to the next row current_row += row_offset y++; px_y += TILE_HEIGHT; }}
There are a couple of things not defined in this code:
- TILE_HEIGHT and TILE_WIDTH are the 2D dimensions of your block. I have them as constants, you can place them in the MAP struct so that your engine can use any size tile.
- The function DrawTile() is your actual drawing routine to render the tile graphics at a specified pixel point.
Now this is only an introductory version to get you started. As you work with it, you will notice that you need to expand it more. For example, I would have my destination rendering surface be a parameter of Render and RenderNode.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement