Как эффективно отображать тайлы с помощью Graphics.Draw?

В настоящее время я делаю игру на основе плитки на С#, но каждый раз, когда я рисую плитки, она использует много ресурсов ЦП, и по мере того, как плитки становятся больше (если я делаю игру на весь экран), она потребляет еще больше. Это мой класс плитки:

 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;

    }
}

и вот как я рисую плитки:

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();

    }

Я хочу отметить, что карта имеет размер 20 x 20 тайлов и потребляет около 50% ресурсов процессора в полноэкранном режиме.


person Criss Ro    schedule 29.03.2016    source источник
comment
Код, который вы разместили, выглядит нормально, я не вижу, как его можно ускорить. Попробуйте интеллектуальное аннулирование и интеллектуальное рисование (т.е. рисуйте только недействительные части)   -  person Ivan Stoev    schedule 29.03.2016
comment
Как я могу проверить, недействительна ли часть?   -  person Criss Ro    schedule 29.03.2016
comment
Извините, но я не понимаю, как работает интеллектуальное аннулирование... я пробовал обрезать, и все в порядке, что я могу отобразить умную часть карты. Не могли бы вы привести пример того, как работает ClipRectangle.IntersectsWith(part.Rectangle)?   -  person Criss Ro    schedule 29.03.2016
comment
Конечно. Вы перемещаете эти плитки на экране? Или что-то выше? Или сменить имидж?   -  person Ivan Stoev    schedule 29.03.2016


Ответы (2)


Как я уже упоминал в комментариях, направление должно заключаться в том, чтобы меньше рисовать. Один из способов — аннулировать и закрашивать части холста для рисования только тогда, когда что-то, связанное с этой частью, изменяется. Сама Windows делает такую ​​оптимизацию для элементов управления/окон.

Вот пример. Посмотрите, как класс Gadget аннулирует свой прямоугольник при изменении какого-либо свойства. Затем во время рисования рисуются только те прямоугольники, которые пересекаются с e.ClipRectange. Это значительно сокращает количество операций рисования.

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);
        }
    }
}
person Ivan Stoev    schedule 29.03.2016
comment
Большое спасибо! Это именно то, что я искал! Большое спасибо! - person Criss Ro; 29.03.2016

Не уверен, как работает ваш класс resizer. я думаю, что есть проблема, когда вы каждый раз меняете размер каждого изображения.

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

я бы заменил строку выше, как это

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

в то же время обновите функцию рисования плитки, как показано ниже.

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));

}

надеюсь, это должно улучшить производительность. если он мерцает, вы можете использовать Double Buffer

person sm.abdullah    schedule 29.03.2016