Hello everyone. First post here, as I just purchased BlitzMax today, 15 days into my demo trial! :)
Anyways, I'm fairly new to real programming (used to use Clickteams Click & Create and Multimedia Fusion a few years back) and I'm not really sure if the approach I'm taking with my tile engine is particularly well crafted or efficient. Any suggestions or alterations that could help maximize the efficiency of design would be greatly appreciated!
I apologize in advance for the length of this - I figure it's just easier to explain the whole approach and how it's integrated than it is to take out bits and pieces that might not make much sense outside of the framework.
My tile engine design as of now...
A tile engine that:
Can theoretically have an unlimited number of layers (right now it's limited to 255 since I'm using a byte to represent it's layer number), and each tile can utilize from 1 to the max number of layers. (i.e. a spot on the map that only features empty grass might only need one layer, while the tile next to it might feature 4 layers...the grass, some rocks, a barrel, and a roof overhang)
Dynamically loads and unloads map regions and tilesets. If desired, I'd like it so that you never need to exit one map and load another.
Basically at all times I've got 9 regions (a 3x3 array) of the world map loaded. So for instance, if you move far enough left, it loads the new data into the rightmost column of regions and then treats the leftmost region as the center region, the center region as the right region, and the right region as the left region. (Sorry if this doesn't make a lot of sense, it's hard to explain concisely).
Basically, the horizontal and vertical centers are adjusted so that any column or any row can be 'considered' the current center - this way no data copying is ever needed - the new regions that need to be loaded are simply loaded into the region row or column that is 'farthest' away from the player.
Streaming Loads: I found out early on trying to fully load a new region, including the tilesets and such, would cause a momentary pause (just a split second, but still noticeable).
I thought of 'buffering' the load command and waiting til the player wasn't moving or went to a menu to load, but this still had the problem of a moment of sluggishness if the player tried to start moving while the load was occurring.
Instead I've decided to stream the load in over the course of a couple seconds. Once the player gets sufficiently close to the edge of a region (distance configureable), I start streaming the necessary tilesets into separate banks, and also start streaming the map file into separate banks. Then, after the load is complete I create the necessary region out of the map bank, and the tiles out of the tileset banks. Actually, this leads me into a question regarding my approach w/ the tiles themselves.
Some preliminary tests, as well as checking out these boards, verified just how much faster it is to deal with images than it is to deal with pixmaps. Well, for convenience I still wanted to utilize tilesets, but I wanted the speed of images.
My current approach involves loading the tileset image as a pixmap, and then using PixMapWindow to create pixmaps for each tile in the tileset. Then I create an image from the tilesized pixmap. I stash all of these individual tiles into an array whose size I calculate from the tileset image.
As mentioned I wanted each spot on the map to have any number of tiles. Also, I didn't want things like alpha level, layer level, etc, to be hardcoded into the tile, so each "map tile" contains not only a "tile" object that stores the actual image, but also stores information about how that tile should be drawn. The current approach would allow, for instance, a particular tile at X:40,Y:20 to be animated and set to 50% transparcency and not an obstacle, while the same tile image could be found elsewhere on the map unanimated (or animated at a different speed, whatever) at 0% transparency, and act as an obstacle.
The downside to this is that a fair amount of extra data is stored in this tile container class. In fact, large maps (1000x1000 or so) can get ridiculously large, and this is another reason why I've gone with dynamic loading.
Well, that's the basic design, although I've left out a lot of the intricacies of how it's all connected.
At 640x480, it only takes approx 335 tiles to draw a single layer on screen (assuming partial tiles are showing). In my early tests I was able to get 5000-6000 tiles while still getting 50+ fps, and was still getting 30 fps or so at 12000+ tiles. 5000 tiles = an average of 15 layers of tiles per each map location - which is obviously a ton. I imagine most spaces on the map I could get away with only 3 layers, possibly going up to 5 or 6 layers in a few spots. That's why I designed the engine so that a variable number of layers can exist per tile location - if I don't need 5 or 6 layers except in a few spots...why should the whole map have those extra layers?
Anyways - my early tests were conducted before all of the framework was set up. The framework introduces a bit more overhead, but at this point I'm not sure how much as I don't have the framework complete enough to do any accurate tests yet.
One thing I'm really unsure about is how to sort my tiles for proper drawing order. As I see it there's two approaches. The approach I'm planning on implementing goes like this:
The game looks at the currently loaded regions, and then creates an 1d array with the dimension being equal to the max number of layers needed in the current regions. So, if one particularly loaded tile spot has 15 layers, the array is 15 elements long.
The array itself is an array of lists. The lists are going to store these maptile objects (which contain the tile image and how it is drawn).
Anyhow, the game goes through each tile location needed for the screen(going row by row instead of column by column). At each tile location, it looks at each tile and adds it to the end of the list in the list array. So the pseudo code looks like
if tile.tilelayer = 2 then Array[2].ListAddlast(Tile).
So, after this is done, each list in the array should contain all the tiles in that layer, and by default they'll be Y sorted (ascending...since the tiles were added row by row, starting at the top of the screen).
Then I iterate through each list in the array and draw the tiles. This will (theoretically...I don't have it all coded yet) draw all of the tiles with the higher layer tiles overlapping lower level tiles, and the tiles will be Y sorted as well (so a player's avater will be drawn "over" a building if he is standing in front of (below) it).
The other approach I was considering was just straight iterating through each spot on the map and drawing all the tiles while adding them to an OpenGL depth buffer, and letting it take care of the sorting for me. I really don't know anything about OpenGL though, except that from what I've read you can't really properly use the depth buffer with partially transparent textures. If that's the case, then that idea is right out.
Anyhow, like I said, I'm really a novice to all this stuff and I think this design is a bit beyond my abilities but it's been fun designing and implementing it. I haven't hit any major roadblocks at this point - I'm just concerned about speed issues.
Has anyone had any experience using a depth buffer for a 2D game?
Actually, this still hasn't really explored all of the framework. Explaining all of the details would result in this excessively long post becoming RIDICULOUSLY long =).
I guess if anyone is really interested or anything you can contact me to further discuss this. As it stands though, If you notice anything particularly foolish about my approach, PLEASE let me know.
Thanks,
SoS
SculptureOfSoul@...
Anyways, I'm fairly new to real programming (used to use Clickteams Click & Create and Multimedia Fusion a few years back) and I'm not really sure if the approach I'm taking with my tile engine is particularly well crafted or efficient. Any suggestions or alterations that could help maximize the efficiency of design would be greatly appreciated!
I apologize in advance for the length of this - I figure it's just easier to explain the whole approach and how it's integrated than it is to take out bits and pieces that might not make much sense outside of the framework.
My tile engine design as of now...
A tile engine that:
Can theoretically have an unlimited number of layers (right now it's limited to 255 since I'm using a byte to represent it's layer number), and each tile can utilize from 1 to the max number of layers. (i.e. a spot on the map that only features empty grass might only need one layer, while the tile next to it might feature 4 layers...the grass, some rocks, a barrel, and a roof overhang)
Dynamically loads and unloads map regions and tilesets. If desired, I'd like it so that you never need to exit one map and load another.
Basically at all times I've got 9 regions (a 3x3 array) of the world map loaded. So for instance, if you move far enough left, it loads the new data into the rightmost column of regions and then treats the leftmost region as the center region, the center region as the right region, and the right region as the left region. (Sorry if this doesn't make a lot of sense, it's hard to explain concisely).
Basically, the horizontal and vertical centers are adjusted so that any column or any row can be 'considered' the current center - this way no data copying is ever needed - the new regions that need to be loaded are simply loaded into the region row or column that is 'farthest' away from the player.
Streaming Loads: I found out early on trying to fully load a new region, including the tilesets and such, would cause a momentary pause (just a split second, but still noticeable).
I thought of 'buffering' the load command and waiting til the player wasn't moving or went to a menu to load, but this still had the problem of a moment of sluggishness if the player tried to start moving while the load was occurring.
Instead I've decided to stream the load in over the course of a couple seconds. Once the player gets sufficiently close to the edge of a region (distance configureable), I start streaming the necessary tilesets into separate banks, and also start streaming the map file into separate banks. Then, after the load is complete I create the necessary region out of the map bank, and the tiles out of the tileset banks. Actually, this leads me into a question regarding my approach w/ the tiles themselves.
Some preliminary tests, as well as checking out these boards, verified just how much faster it is to deal with images than it is to deal with pixmaps. Well, for convenience I still wanted to utilize tilesets, but I wanted the speed of images.
My current approach involves loading the tileset image as a pixmap, and then using PixMapWindow to create pixmaps for each tile in the tileset. Then I create an image from the tilesized pixmap. I stash all of these individual tiles into an array whose size I calculate from the tileset image.
As mentioned I wanted each spot on the map to have any number of tiles. Also, I didn't want things like alpha level, layer level, etc, to be hardcoded into the tile, so each "map tile" contains not only a "tile" object that stores the actual image, but also stores information about how that tile should be drawn. The current approach would allow, for instance, a particular tile at X:40,Y:20 to be animated and set to 50% transparcency and not an obstacle, while the same tile image could be found elsewhere on the map unanimated (or animated at a different speed, whatever) at 0% transparency, and act as an obstacle.
The downside to this is that a fair amount of extra data is stored in this tile container class. In fact, large maps (1000x1000 or so) can get ridiculously large, and this is another reason why I've gone with dynamic loading.
Well, that's the basic design, although I've left out a lot of the intricacies of how it's all connected.
At 640x480, it only takes approx 335 tiles to draw a single layer on screen (assuming partial tiles are showing). In my early tests I was able to get 5000-6000 tiles while still getting 50+ fps, and was still getting 30 fps or so at 12000+ tiles. 5000 tiles = an average of 15 layers of tiles per each map location - which is obviously a ton. I imagine most spaces on the map I could get away with only 3 layers, possibly going up to 5 or 6 layers in a few spots. That's why I designed the engine so that a variable number of layers can exist per tile location - if I don't need 5 or 6 layers except in a few spots...why should the whole map have those extra layers?
Anyways - my early tests were conducted before all of the framework was set up. The framework introduces a bit more overhead, but at this point I'm not sure how much as I don't have the framework complete enough to do any accurate tests yet.
One thing I'm really unsure about is how to sort my tiles for proper drawing order. As I see it there's two approaches. The approach I'm planning on implementing goes like this:
The game looks at the currently loaded regions, and then creates an 1d array with the dimension being equal to the max number of layers needed in the current regions. So, if one particularly loaded tile spot has 15 layers, the array is 15 elements long.
The array itself is an array of lists. The lists are going to store these maptile objects (which contain the tile image and how it is drawn).
Anyhow, the game goes through each tile location needed for the screen(going row by row instead of column by column). At each tile location, it looks at each tile and adds it to the end of the list in the list array. So the pseudo code looks like
if tile.tilelayer = 2 then Array[2].ListAddlast(Tile).
So, after this is done, each list in the array should contain all the tiles in that layer, and by default they'll be Y sorted (ascending...since the tiles were added row by row, starting at the top of the screen).
Then I iterate through each list in the array and draw the tiles. This will (theoretically...I don't have it all coded yet) draw all of the tiles with the higher layer tiles overlapping lower level tiles, and the tiles will be Y sorted as well (so a player's avater will be drawn "over" a building if he is standing in front of (below) it).
The other approach I was considering was just straight iterating through each spot on the map and drawing all the tiles while adding them to an OpenGL depth buffer, and letting it take care of the sorting for me. I really don't know anything about OpenGL though, except that from what I've read you can't really properly use the depth buffer with partially transparent textures. If that's the case, then that idea is right out.
Anyhow, like I said, I'm really a novice to all this stuff and I think this design is a bit beyond my abilities but it's been fun designing and implementing it. I haven't hit any major roadblocks at this point - I'm just concerned about speed issues.
Has anyone had any experience using a depth buffer for a 2D game?
Actually, this still hasn't really explored all of the framework. Explaining all of the details would result in this excessively long post becoming RIDICULOUSLY long =).
I guess if anyone is really interested or anything you can contact me to further discuss this. As it stands though, If you notice anything particularly foolish about my approach, PLEASE let me know.
Thanks,
SoS
SculptureOfSoul@...