Criss Ro
Criss Ro

Reputation: 55

How to render tiles efficiently using Graphics.Draw?

I am currently making a tile-based game in c#, but every time I draw the tiles it uses a lot of CPU, and as the tiles get bigger(if i make the game full screen) it consumes even more. This is my Tile class:

 public class Tiles
{
    //PRIVATE :

    //variabiles
    private int XPosition, YPosition;
    private Image Texture;
    private bool Colidable;
    private int SizeW = 32;
    private int SizeH = 32;
    private Resizer resizer = new Resizer();
    //methods




    //PUBLIC :

    //variabiles


    //methods

    //CONSTRUCTOR
    public Tiles(int _x,int _y,Image _i, int _sW = 32, int _sH = 32, bool _c = false)
    {
        XPosition = _x;//set position X
        YPosition = _y;//set position Y
        SizeW = _sW;
        SizeH = _sH;
        Texture = resizer.ResizeImage(_i, SizeW, SizeH) ;// set texture

        Colidable = _c;//set if the tile is colidable,default : false
        resizer = null;
    }

    //DRAW METHOD
    //gets graphics object to draw on, adn draws at the position of the tile
    public void Draw(Graphics _g)
    {
        _g.DrawImage(this.Texture, this.XPosition, this.YPosition);
    }

    //GET PRIVATE MEBERS
    //returns if the tile is colidable
    public bool getColidable()
    {

        return this.Colidable;

    }
}

and this is how I draw the tiles:

private void DrawMap(Graphics _g)
    {
        //CALLS THE DRAW METHOD OF EACH TILE
       for (int i = 0; i < MAP_WIDTH; i++)
        {
            for (int j = 0; j < MAP_HEIGHT; j++)
            {
                Tile[i, j].Draw(_g);
            }

        }

    }
    bool TilesUpdate = false;


    private void _Window_Paint(object sender, PaintEventArgs e)
    {
        e.Graphics.Clear(Color.Black);

        if (isGameRunning)
        {

            DrawMap(e.Graphics);
        }
        else
        {
            FullRezolutionBtn.Draw(e.Graphics);
            BigRezolutionBtn.Draw(e.Graphics);
            NormalRezolutionBtn.Draw(e.Graphics);
        }

    }


    private void Update_Tick(object sender, EventArgs e)
    {
        Invalidate();

    }

I want to mention that the map is 20 x 20 tiles and it's cosuming around 50% of the cpu when it's fullscreen.

Upvotes: 3

Views: 1620

Answers (2)

Ivan Stoev
Ivan Stoev

Reputation: 205599

As I mentioned in the comments, the direction should be to do less painting. One way is to invalidate and paint portions of the drawing canvas only when something related to that portion changes. Windows itself does such optimization for controls/windows.

Here is an example. Look how Gadget class invalidates it's rectangle when some property changes. Then during the paint, only rectangles that intersect with e.ClipRectange are drawn. This highly reduces the number of the drawing operations.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace Samples
{
    class Gadget
    {
        public readonly Control Canvas;

        public Gadget(Control canvas) { Canvas = canvas; }

        private Rectangle bounds;
        public Rectangle Bounds
        {
            get { return bounds; }
            set
            {
                if (bounds == value) return;
                // NOTE: Invalidate both old and new rectangle
                Invalidate();
                bounds = value;
                Invalidate();
            }
        }

        private Color color;
        public Color Color
        {
            get { return color; }
            set
            {
                if (color == value) return;
                color = value;
                Invalidate();
            }
        }

        public void Invalidate()
        {
            Canvas.Invalidate(bounds);
        }

        public void Draw(Graphics g)
        {
            using (var brush = new SolidBrush(color))
                g.FillRectangle(brush, bounds);
        }
    }


    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            var form = new Form { WindowState = FormWindowState.Maximized };
            int rows = 9, cols = 9;
            var gadgets = new Gadget[rows, cols];
            var rg = new Random();
            Color[] colors = { Color.Yellow, Color.Blue, Color.Red, Color.Green, Color.Magenta };
            int size = 64;
            var canvas = form;
            for (int r = 0, y = 8; r < rows; r++, y += size)
                for (int c = 0, x = 8; c < cols; c++, x += size)
                    gadgets[r, c] = new Gadget(canvas) { Color = colors[rg.Next(colors.Length)], Bounds = new Rectangle(x, y, size, size) };
            int paintCount = 0, drawCount = 0;
            canvas.Paint += (sender, e) =>
            {
                paintCount++;
                for (int r = 0; r < rows; r++)
                {
                    for (int c = 0; c < cols; c++)
                    {
                        if (e.ClipRectangle.IntersectsWith(gadgets[r, c].Bounds))
                        {
                            gadgets[r, c].Draw(e.Graphics);
                            drawCount++;
                        }
                    }
                }
                form.Text = $"Paint:{paintCount} Draw:{drawCount} of {(long)paintCount * rows * cols}";
            };
            var timer = new Timer { Interval = 100 };
            timer.Tick += (sender, e) =>
            {
                gadgets[rg.Next(rows), rg.Next(cols)].Color = colors[rg.Next(colors.Length)];
            };
            timer.Start();


            Application.Run(form);
        }
    }
}

Upvotes: 2

sm.abdullah
sm.abdullah

Reputation: 1802

Not sure how your resizer class works. i think there is problem when you re-size every image every time.

 Texture = resizer.ResizeImage(_i, SizeW, SizeH) ;// set texture

i would replace above line like this

 Texture = _i;// set texture but do not resize image now

at the same time update the Draw Function of Tile like below.

public void Draw(Graphics _g)
{
    //now specify the location and size of the image.
   _g.DrawImage(Texture , new Rectangle(this.XPosition, this.YPosition, SizeW, SizeH));

}

hopefully it should improve the performance. if it flicker then you can use Double Buffer

Upvotes: 0

Related Questions