Как можно ускорить Graphics.DrawImage С# WinForms

Задача написать игру на Winforms. Я пишу 2д рогалик, использую тайловую графику и заполняю форму в методе OnPaint. Проблема в том что даже на небольших размерах поля типа 30х30 поле заполняется тайлами медленно. Буквально видно как цикл бежит по массиву и мостит поле тайлами (слева направо, сверху вниз). Играть из-за этого не возможно. Подскажите пожалуйста как можно ускорить отрисовку поля.

protected override void OnPaint(PaintEventArgs e)
        {            
            var map = game.mapData.mapInSight;
            var g = CreateGraphics();
            for (int i = 0; i < map.GetLength(0); i++)
                for (int j = 0; j < map.GetLength(1); j++)
                    g.DrawImage(bitmaps[map[i, j].ToString() + ".png"], new Point(i * game.ElementSize, j * game.ElementSize));

        }

Здесь map - прямоугольный массив Enum-ов, тайлы вытаскиваю из словаря bitmaps. Понимаю, что после первой отрисовки можно рисовать не все клетки поля, но я хочу сделать "камеру" т.е. поле показывается на экране не целиком и "область видимости" сдвигается вместе с движением игрока, поэтому мне надо перерисовывать поле целиком на каждом ходе.


Ответы (1 шт):

Автор решения: Alexander Petrov

Во-первых, нужно включить двойную буфферизацию.
Во-вторых, использовать свойство e.Graphics вместо вызова CreateGraphics().
В-третьих, лучше использовать метод DrawImageUnscaled.
В-четвёртых, ключом в словаре делаем enum вместо строки.
В-пятых, меняем вложенность циклов: ось Y первая, ось X - внутри. Так лучше используется кэш процессора.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace WinForm
{
    public partial class Form1 : Form
    {
        Dictionary<Tile, Bitmap> bitmaps;
        Tile[,] map = new Tile[40, 30];
        Random random = new Random();

        int size = 32; // размер SystemIcons
        int offsetX;

        public Form1()
        {
            //InitializeComponent();

            //DoubleBuffered = true;
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true);

            WindowState = FormWindowState.Maximized;

            bitmaps = new Dictionary<Tile, Bitmap> {
                { Tile.Application, SystemIcons.Application.ToBitmap() },
                { Tile.Asterisk, SystemIcons.Asterisk.ToBitmap() },
                { Tile.Error, SystemIcons.Error.ToBitmap() },
                { Tile.Exclamation, SystemIcons.Exclamation.ToBitmap() },
                { Tile.Information, SystemIcons.Information.ToBitmap() },
                { Tile.Question, SystemIcons.Question.ToBitmap() },
                { Tile.Shield, SystemIcons.Shield.ToBitmap() },
                { Tile.Warning, SystemIcons.Warning.ToBitmap() }
            };

            for (int j = 0; j < map.GetLength(1); j++)
                for (int i = 0; i < map.GetLength(0); i++)
                    map[i, j] = (Tile)random.Next(8);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            for (int j = 0; j < map.GetLength(1); j++)
                for (int i = 0; i < map.GetLength(0); i++)
                    e.Graphics.DrawImageUnscaled(bitmaps[map[i, j]], new Point(i * size + offsetX, j * size));
        }
        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Left)
            {
                for (int i = 0; i < size; i++)
                {
                    offsetX--;
                    Invalidate();
                }
            }
            else if (e.KeyCode == Keys.Right)
            {
                for (int i = 0; i < size; i++)
                {
                    offsetX++;
                    Refresh();
                }
            }
        }
    }

    enum Tile
    {
        Application, Asterisk, Error, Exclamation, Information, Question, Shield, Warning
    }
}

Копируем, запускаем. Клавишами влево-вправо двигаем тайлы туда-сюда.

Обратите внимание на разницу в поведении Refresh() и Invalidate(). Первый метод гарантирует перерисовку на каждой итерации (будет плавная отрисовка), второй (без дополнительного вызова Update()) - будет сам выбирать момент перерисовки (отрисовка скачками).


Но гораздо эффективнее будет отрисовать всю карту со статичными элементами целиком в одно большое изображение и двигать его. А поверх этого большого изображения рисовать мелкие предметы (персонажа и т. п.)

→ Ссылка