-
|
|
Tile Engine Optimization for single big maps
|
I have been working very hard on coding a tile engine that meets my needs. I am making a 2D RPG and I'm doing really well. The problem is, I want the entire world in one tile map. I've written and rewritten what can be considered "standard" tile engines. The editor I'm using is Mappy, which I'm pretty happy with, because it handles the big worlds just fine. Once I reach the phase where I want to load the huge map into the tile engine I write, scrolling is really slow and laggy, while smaller maps that will NOT suit my game do work just fine. I am beginning to think that I'm going to have to implement something more advanced to do this properly. I'm not necessarily asking for final answers, I just want to know if anyone could point me in the right direction? If you want me to post some of my code to understand my question better, just ask.
Edit: I just set up a Rectangle based on the screen so that it would save memory by only drawing tiles intersected/within this rectangle. It did significantly speed it up but it is far from smooth. I also forgot to mention that for my current tile engine I'm using the new XNA playback library for Mappy. Information still appreciated.
|
|
-
|
|
Re: Tile Engine Optimization for single big maps
|
I have a few ideas to speed things up:
If you're using SpriteBatch if you want full control over drawing I'd recommend writing your own graphics system with pixel shaders and geometry which could reduce the number of draw calculations needed for frame if you're careful.
Consolidating all of your tile-sets into a single large texture would improve performance since the graphics card wouldn't need to change textures as required, and you could use the content pipeline to combine all of your tiles into a single texture.
The third option would be to render the current scene into a render target, then only redraw the areas of the map that have changed over the top of it (e.g. where your character currently is), and combine that into the render target for the next frame. For scrolling you could then draw the render target at the previous position of the camera, and then only draw the parts of the screen that have been revealed because of the scrolling.
Is this answering your question? What you're asking isn't too clear. Do you advice on improving scrolling and drawing speed or how to combine everything into a single texture?
Graphics Programmer,Sandswept Studios.http://www.sandswept.net
|
|
-
|
|
Re: Tile Engine Optimization for single big maps
|
I'm not sure if this will help as I haven't really tested this with anything other than my own small-ish maps (8k pixels x 1k pixels) and I'm a relative beginner.
What I do is, at 'build' time, I divide the map into 512 x 1024 buckets (a horizontal array), and sort my List<Sprite> from left to right. Then in each bucket, I store the lowest and highest index of sprites in that bucket.
For example, if there are 30 sprites in one bucket, their indexes ranging from 103 to 133, I store bucket[n].Lowest = 103, bucket[n].Highest = 133.
Then at draw time, I find camera.X / bucket.Width, and screenWidth / bucket.Width, to get the lowest and highest visible bucket, then take the bucket[lowestVisible].Lowest and bucket[highestVisible].Highest indexes, and call a draw loop on that range of sprites.
Works well enough as far as I can tell, but it's obviously designed for horizontal maps - if I were to try to implement this on a map that scrolled in both X and Y, I would need to use a 2D Array of buckets, and the algorithm would be slightly more complicated.
I really have no idea if this is a good way to go about it, I'm just solving problems in my own clunky way, but it may help you?
|
|
-
|
|
Re: Tile Engine Optimization for single big maps
|
Thanks for the replies. I apologize for taking a while to respond. faultymoose- Thanks for the great idea, unfortunately, I am scrolling in both X and Y.
zanders-
1. I will probably attempt my own graphics system instead of SpriteBatch, now that you have suggested that. I will probably do this in conjunction with another idea I have that I will write about below.
2. I already have them in a single file, because that is required to use the mappy playback library.
3. Although the RenderTarget idea is the simplest, I tried implementing it (with the help of the documentation), and I get the following runtime error:
System.ArgumentException was unhandled
Message="Texture width or height is larger than the device supports. Parameter name: width"
Source="Microsoft.Xna.Framework" ...
Which obviously means my map is too big. FYI, it is 1740x1134 in tiles and the tiles are 32x32 pixels.
My idea that I came up with on my own that I mentioned earlier is to basically have a map object draw several tile objects. I based this on what lots have said about entity management. Basically, they recommend that when you have a big game world and are drawing lots of sprites, when one gets killed or destroyed, instead of creating new entities whenever new sprites need to be spawned, just use the same object only with the properties of the new sprite instead. I shouldn't have to say more about that. My point is, I could do that, only instead of doing that with sprites, I'm doing that with tiles. I'm only occupying memory with whatever tiles are currently on and/or just a little beyond the screen. This sounds a lot like only drawing the tiles that are supposed to be on the screen, only the difference is, only the ones nearby are in memory. I would like opinions other than mine on this though because there's also the fact that you're almost always reaccessing the file, pulling new tiles into memory to replace other tiles. Would the file access thing be too much? Would this be content pipeline friendly? If there are enough doubts about this, I'll just go with the replace SpriteBatch idea.
|
|
-
|
|
Re: Tile Engine Optimization for single big maps
|
UberElvis:
Which obviously means my map is too big. FYI, it is 1740x1134 in tiles and the tiles are 32x32 pixels.
That's a big map, but if you only have 40*20 tiles or so visible at once, then it is obviously not the drawing code that you need to optimize. Make sure you are only drawing the tiles that are visible, and don't loop over the humongous map unnecessarily.
You can look into loading seamless worlds (Dungeon Siege, GTA) if there are other performance problems with the big map.
The game I'm working on in my spare time: http://clorky.spaces.live.com
|
|
-
|
|
Re: Tile Engine Optimization for single big maps
|
Well, I think at this point, it'll be best if I just use small maps with portals, and then someday in the future I can tackle something more advanced to get big maps working.
|
|
-
-
- (1334)
-
premium membership
-
Posts
1,096
|
Re: Tile Engine Optimization for single big maps
|
If the tiles are separate textures you can see an improvement by converting to sprite sheets or sorting the draw calls by texture. You can also take big chunks of tiles and pre-draw them into a large texture of the whole room/scene. Then you're just drawing a few large textures instead of a lot of small ones. A lot of the sorting/grouping of the tiles can be done in the Content Pipeline assuming your maps don't change dynamically. If you need the tiles to be sorted different ways for different applications, I would keep separate lists and have the ContentReader sort them all out so you aren't doing that kind of work per frame.
|
|
-
-
- (966)
-
premium membership
-
Posts
265
|
Re: Tile Engine Optimization for single big maps
|
It would help if we could see your map drawing code. I was able to load and draw much larger maps using 64x64 tiles while still maintaining around 1000fps. This of course was single layer at the time.
So the questions are:
1. How many layers are you drawing of tiles?
2. How is your map format represented? Is a straight array, or a list, or what?
3. What does the draw code look like currently"?
4. How are tile sets loaded into memory, individual textures or as a sprite sheet?
5. Are you drawing items/mobiles as well and if so how are these stored and then selected for rendering?
-Zenroth
|
|
-
-
- (2811)
-
premium membership
-
Posts
1,362
|
Re: Tile Engine Optimization for single big maps
|
You need to determine where you issue lies... in drawing the tiles or in the proccessing end of things. There are different performance monitoring tools available that will help for this or sometimes it can be as simple as commenting out an area of your code that you feel is taking a long time and seeing if it actually does.
If it's drawing, try something (as previously suggested) like converting many tiles into one texture (one draw call instead of many). I did that here:
http://www.krissteele.net/blogdetails.aspx?id=146
If you're doing lots of SpriteBatch.Begin and SpriteBatch.End calls, those can quickly kill your draw performance too. I cut my game down to one begin and one end per draw loop and that make a significant improvement.
It can also make a big difference if you load all your textures upfront as opposed to loading when needed. In the init for your level, load everything you need.
If drawing is not the issue, you can also do things like sort your tiles and use techiques like Sweep and Prune... this tutorial is good for setting up something like that:
http://www.ziggyware.com/readarticle.php?article_id=128
Don't be afraid to post some code here and maybe one of us will see a problem with how you are doing things too.
|
|
-
|
|
Re: Tile Engine Optimization for single big maps
|
Moose kind of already went over this but you didn't seem to understand as you said it wouldn't fit your model when it really truly does. Only draw stuff that is in view.
Most Tile Engines draw only the visible tiles onto a graphical plane, and then scoot the graphic plane around to do the scrolling, updating the graphic plane as you reach the edge of each tile. this is pretty fast however if you have the camera moving around a lot you will need to add more padding in your graphic plane for scrolling over multiple tiles. Between graphic plane refreshes.
A method I like is very similar to moose's. You take your huge map, and split it into smaller submaps. and draw the visible submaps onto a graphic plane, and scroll that around. When your view get to the edge of a submap then you refresh the graphic plane and reposition the camera on it. This might be slightly slower on the refreshes of the graphic plane but probably not noticeably however they happen less often, AND this added freedom of the margins allow you to do some pretty dramatic camera scrolling aswell as zooming in/out without having to refresh the plane so much.
I'm assuming you are using a huge graphical plane to draw your huge map, and then viewing it at different offsets to scroll it... this is very wasteful of memory. Draw only the visible stuff.
I would suggest that your submaps be approximately as wide and tall as the screen's view is wide when in the normal view (whatever that pixel ratio happens to be). This way only 4 submaps would be visible at any time on the normal view. textures this large are usually no trouble to mess with and don't slow your computer down.
|
|
-
|
|
Re: Tile Engine Optimization for single big maps
|
Thanks for all the replies everyone. I will get to the rest of you, but because I have to head to school soon, it won't be immediately. But I will start by answering some of Centurion's questions.
Centurion Games:It would help if we could see your map drawing code. I was able to load and draw much larger maps using 64x64 tiles while still maintaining around 1000fps. This of course was single layer at the time.
So the questions are:
1. How many layers are you drawing of tiles?
2. How is your map format represented? Is a straight array, or a list, or what?
3. What does the draw code look like currently"?
4. How are tile sets loaded into memory, individual textures or as a sprite sheet?
5. Are you drawing items/mobiles as well and if so how are these stored and then selected for rendering?
-Zenroth
1. I am currently drawing only one layer.
2. It was either a one or a two dimensional array, I'll have to check my project and get back to you on that when I have more time.
3. Again, I'm heading to school soon, so I will post it as soon as I get the chance.
4. I have the tile textures in a single sprite sheet (which I know will answer a lot of other people's questions)
5. I plan on drawing other stuff in the future, but I want to get this problem out of the way before I do anything like that.
Thanks again! Sorry about the delay in getting more information posted. I will get that done later today when I have more time.
|
|
-
|
|
Re: Tile Engine Optimization for single big maps
|
Okay I shall now post my draw code. This is the Draw method in the Map2D class. If anyone needs to see more code, just ask. The Draw method in Game1 does nothing but call this:
| void Draw(GameTime gameTime, SpriteBatch spriteBatch, Vector2 position, Color color, Rectangle screen) |
| { |
| |
| |
| this.Position = position; |
| |
| for (int i = 0; i < this.MapWidth; i++) |
| { |
| for (int j = 0; j < this.MapHeight; j++) |
| { |
| position.X = this.Position.X + (i * this.TileWidth); |
| position.Y = this.Position.Y + (j * this.TileHeight); |
| |
| if (screen.Intersects(new Rectangle(i * this.TileWidth,j * this.TileHeight,this.tiles[i][j].Width,this.tiles[i][j].Height))) |
| { |
| spriteBatch.Draw(this.TileStripTexture, position, |
| this.GetTileSourceRectangle(this.Tiles[i][j].Id), color); |
| } |
| } |
| } |
|
|
-
-
- (966)
-
premium membership
-
Posts
265
|
Re: Tile Engine Optimization for single big maps
|
This is exactly why your FPS is terrible.
With a map size of 1740x1134 in tiles you are performing 1,973,160 bounding rectangle collision checks per render as a culling mechanism!
What you want to do is abandon that entire line of thought, and instead compute how many tiles you need to draw vertically and horizontally given your current offset in the world to cover just the screen. This way at you run no collision/culling checks and only draw lets see 900 tiles to the screen at 32x32 for 720p. I would also personally recommend increasing your tile size resolution to something else other than 32x32. I've found that 64x64 and even 128x128 can work well at 720p. Actually the current Action Adventure Rpg game that we are working on is using 256x256 tiles, but that does get a bit massive to manage well. (Very nice quality though)
-Zenroth
|
|
-
|
|
Re: Tile Engine Optimization for single big maps
|
where screenvectors[] is an array of vectors starting at the top left and ending at the bottom right.
| for (int a = 0; a < 450; a++) |
| { |
| |
| rectangle[a].X = (int)screenvectors[a].X; |
| rectangle[a].Y = (int)screenvectors[a].Y; |
| |
| |
| if (surface[var.xpos, var.ypos] == "grass") |
| { |
| spriteBatch.Draw(sprite("grass"), rectangle[a],null, Color.White,0.0f,new Vector2(0,0),0,f); |
| } |
| |
| b++; |
|
| if(b==25){b=0;c++;} |
| |
| |
|
|
|