Efficient Way to Draw Grids in XNA

Posted by sm81095 on Game Development See other posts from Game Development or by sm81095
Published on 2013-06-27T15:48:12Z Indexed on 2013/06/27 16:29 UTC
Read the original article Hit count: 222

Filed under:
|
|
|

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.

© Game Development or respective owner

Related posts about XNA

Related posts about monogame