Как можно ускорить 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 шт):
Во-первых, нужно включить двойную буфферизацию.
Во-вторых, использовать свойство 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()) - будет сам выбирать момент перерисовки (отрисовка скачками).
Но гораздо эффективнее будет отрисовать всю карту со статичными элементами целиком в одно большое изображение и двигать его. А поверх этого большого изображения рисовать мелкие предметы (персонажа и т. п.)