Jump to content
  • Advertisement
Sign in to follow this  
stenny

Height in a 2d game

This topic is 4320 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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

Share this post


Link to post
Share on other sites
Advertisement
[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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
Guest Anonymous Poster


http://www.gamedev.net/reference/programming/features/mm4ihm/default.asp

Share this post


Link to post
Share on other sites
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:

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

Share this post


Link to post
Share on other sites
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:


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 terrain
mem_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.

Share this post


Link to post
Share on other sites
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).

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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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:


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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!