Efficient Way to Draw Grids in XNA
- by sm81095
So I am working on a game right now, using Monogame as my framework, and it has come time to render my world. My world is made up of a grid (think Terraria but top-down instead of from the side), and it has multiple layers of grids in a single world. Knowing how inefficient it is to call SpriteBatch.Draw() a lot of times, I tried to implement a system where the tile would only be drawn if it wasn't hidden by the layers above it. The problem is, I'm getting worse performance by checking if it's hidden than when I just let everything draw even if it's not visible. So my question is: how to I efficiently check if a tile is hidden to cut down on the draw() calls?
Here is my draw code for a single layer, drawing floors, and then the tiles (which act like walls):
public void Draw(GameTime gameTime)
{
int drawAmt = 0;
int width = Tile.TILE_DIM;
int startX = (int)_parent.XOffset;
int startY = (int)_parent.YOffset;
//Gets the starting tiles and the dimensions to draw tiles, so only onscreen tiles are drawn, allowing for the drawing of large worlds
int tileDrawWidth = ((CIGame.Instance.Graphics.PreferredBackBufferWidth / width) + 4);
int tileDrawHeight = ((CIGame.Instance.Graphics.PreferredBackBufferHeight / width) + 4);
int tileStartX = (int)MathHelper.Clamp((-startX / width) - 2, 0, this.Width);
int tileStartY = (int)MathHelper.Clamp((-startY / width) - 2, 0, this.Height);
#region Draw Floors and Tiles
CIGame.Instance.GraphicsDevice.SetRenderTarget(_worldTarget);
CIGame.Instance.GraphicsDevice.Clear(Color.Black);
CIGame.Instance.SpriteBatch.Begin();
//Draw floors
for (int x = tileStartX; x < (int)MathHelper.Clamp(tileStartX + tileDrawWidth, 0, this.Width); x++)
{
for (int y = tileStartY; y < (int)MathHelper.Clamp(tileStartY + tileDrawHeight, 0, this.Height); y++)
{
//Check if this tile is hidden by layer above it
bool visible = true;
for (int i = this.LayerNumber; i <= _parent.ActiveLayer; i++)
{
if (this.LayerNumber != (_parent.Layers - 1) && (_parent.GetTileAt(x, y, i + 1).Opacity >= 1.0f || _parent.GetFloorAt(x, y, i + 1).Opacity >= 1.0f))
{
visible = false;
break;
}
}
//Only draw if visible under the tile above it
if (visible && this.GetTileAt(x, y).Opacity < 1.0f)
{
Texture2D tex = WorldTextureManager.GetFloorTexture((Floor)_floors[x, y]);
Rectangle source = WorldTextureManager.GetSourceForIndex(((Floor)_floors[x, y]).GetTextureIndexFromSurroundings(x, y, this), tex);
Rectangle draw = new Rectangle(startX + x * width, startY + y * width, width, width);
CIGame.Instance.SpriteBatch.Draw(tex, draw, source, Color.White * ((Floor)_floors[x, y]).Opacity);
drawAmt++;
}
}
}
//Draw tiles
for (int x = tileStartX; x < (int)MathHelper.Clamp(tileStartX + tileDrawWidth, 0, this.Width); x++)
{
for (int y = tileStartY; y < (int)MathHelper.Clamp(tileStartY + tileDrawHeight, 0, this.Height); y++)
{
//Check if this tile is hidden by layers above it
bool visible = true;
for (int i = this.LayerNumber; i <= _parent.ActiveLayer; i++)
{
if (this.LayerNumber != (_parent.Layers - 1) && (_parent.GetTileAt(x, y, i + 1).Opacity >= 1.0f || _parent.GetFloorAt(x, y, i + 1).Opacity >= 1.0f))
{
visible = false;
break;
}
}
if (visible)
{
Texture2D tex = WorldTextureManager.GetTileTexture((Tile)_tiles[x, y]);
Rectangle source = WorldTextureManager.GetSourceForIndex(((Tile)_tiles[x, y]).GetTextureIndexFromSurroundings(x, y, this), tex);
Rectangle draw = new Rectangle(startX + x * width, startY + y * width, width, width);
CIGame.Instance.SpriteBatch.Draw(tex, draw, source, Color.White * ((Tile)_tiles[x, y]).Opacity);
drawAmt++;
}
}
}
CIGame.Instance.SpriteBatch.End();
Console.WriteLine(drawAmt);
CIGame.Instance.GraphicsDevice.SetRenderTarget(null); //TODO: Change to new rendertarget instead of null
#endregion
}
So I was wondering if this is an efficient way, but I'm going about it wrongly, or if there is a different, more efficient way to check if the tiles are hidden.
EDIT: For example of how much it affects performance: using a world with three layers, allowing everything to draw no matter what gives me 60FPS, but checking if its visible with all of the layers above it gives me only 20FPS, while checking only the layer immediately above it gives me a fluctuating FPS between 30 and 40FPS.