c# - WPF - 如何创建平铺地图编辑器
问题描述
我正在尝试构建一个像这样的地图编辑器:http: //blog.rpgmakerweb.com/wp-content/uploads/2012/04/T2EditorLabelled.png在 WPF 中。
它应该如何工作:
通过在“图块列表” - B 部分选择特定图块 - 可以在画布上绘制该图块 - 部分 A。
最终结果是在画布上绘制完整的游戏关卡。
第一种方法:
在我的第一种方法中,通过创建一个新的图像控件并将其添加到画布(WPF 画布控件)来绘制每个图块。
脚步:
- 从图块集中选择图块
- 捕捉画布上的点击事件
- 创建一个新图像,从图块集中裁剪图块
- 在画布上的正确位置添加图像(作为孩子)
这种方法非常幼稚,它暗示了两个大问题:
- 所有像素已经在包含所有图块的图块集上缓冲,但是每次我在画布上绘制图块时,都会创建一个新图像,因此我有义务复制部分图块集像素数据作为源新的
- 画布上的控件太多:
地图游戏可以达到 1000 x 1000 块的大小,WPF 在 100x100 块地图上的性能开始明显下降。
所以为每个图块创建一个控制图像是一个不可行的解决方案。
第二种方法:
我的第二种方法考虑使用单个大WriteableBitmap
画布背景。
与前面的方法一样,在图块集上选择图块,绘制事件是在画布上单击。
在这种情况下,虽然没有从头创建新图像,但背景WriteableBitmap
会相应修改。
因此,由于所有绘制机制都是在WriteableBitmap
.
这种方法的主要问题是,如果我想创建一个 1k x 1k 瓦片和 32x32 瓦片的大地图,背景图像的大小将是天文数字。
我想知道是否有办法在 WPF 中开发一个很好的解决这个问题的方法。您将如何解决这个开发问题?
解决方案
有多种不同的方法可以解决此问题以提高性能。
在图像的渲染方面,默认情况下 WPF 并不出色,所以你可以;
使用 GDI 的 BitBlt 将图像快速渲染到可以托管的 WinForms 控件。这样做的好处是 GDI 是软件,因此不需要显卡或任何东西。(在 UI 中绘制图像的 WPF 快速方法)
您可以使用 D3DImage 作为图像源。这意味着您可以使用 D3DImage 作为要绘制的画布。这样做意味着您必须使用 Direct3D 将所有图块渲染到 D3DImage 图像源,这会更快,因为它是硬件加速的。(https://www.codeproject.com/Articles/28526/Introduction-to-D3DImage)
您可以通过 WinForms 控件托管 XNA 并使用它进行渲染,我没有这方面的经验,所以我测试了任何性能。(WPF 与 XNA 渲染数千个精灵)
就个人而言,对于渲染,我会使用 GDI 方法,因为它是基于软件的,相对容易设置,并且我有过使用它的经验,并且看到了它的性能。
此外,在将图块渲染到控件时,您可以使用滚动条位置和控件大小来确定实际可见的地图区域。从中,您可以简单地选择那几个图块并只渲染它们,从而节省大量时间。
此外,当您自己管理它时,您可以简单地将不同的精灵加载到内存中,然后使用相同的内存将其绘制到缓冲区图像的不同位置。这将减少您提到的内存问题。
下面是我的 GDI 方法示例代码,我渲染了 2500 个 32x32 像素的精灵(所有这些精灵都是相同的绿色,但是您可以将此内存设置为实际的精灵 -src
内存)。精灵被 bitblit 到缓冲区图像(srcb
内存),然后 bitblit 到窗口,在您的情况下,您可以将缓冲区图像 bitblit 到 winforms 画布或其他东西。有了这个,我在基本模型 Surface Pro 3 上获得了 30 到 40 fps 的速度。这对于关卡编辑器的渲染来说应该足够了。请注意,这段代码非常粗略,只是粗略地概述了过程,几乎可以肯定它可以改进。
//
// GDI DLL IMPORT
//
[DllImport("gdi32.dll", SetLastError = true)]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteDC(IntPtr hDC);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern bool BitBlt(IntPtr hDC, int x, int y, int width, int height, IntPtr hDCSource, int sourceX, int sourceY, uint type);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern bool FillRgn(IntPtr hdc, IntPtr hrgn, IntPtr hbr);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr CreateSolidBrush(uint crColor);
[DllImport("gdi32.dll", ExactSpelling = true)]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
public const uint SRCCOPY = 0x00CC0020; // dest = source
public const uint SRCPAINT = 0x00EE0086; // dest = source OR dest
public const uint SRCAND = 0x008800C6; // dest = source AND dest
public const uint SRCINVERT = 0x00660046; // dest = source XOR dest
public const uint SRCERASE = 0x00440328; // dest = source AND (NOT dest )
public const uint NOTSRCCOPY = 0x00330008; // dest = (NOT source)
public const uint NOTSRCERASE = 0x001100A6; // dest = (NOT src) AND (NOT dest)
public const uint MERGECOPY = 0x00C000CA; // dest = (source AND pattern)
public const uint MERGEPAINT = 0x00BB0226; // dest = (NOT source) OR dest
public const uint PATCOPY = 0x00F00021; // dest = pattern
public const uint PATPAINT = 0x00FB0A09; // dest = DPSnoo
public const uint PATINVERT = 0x005A0049; // dest = pattern XOR dest
public const uint DSTINVERT = 0x00550009; // dest = (NOT dest)
public const uint BLACKNESS = 0x00000042; // dest = BLACK
public const uint WHITENESS = 0x00FF0062; // dest = WHITE
//
// END DLL IMPORT
//
//GDI Graphics
private Graphics g;
//Colors
private const int BACKGROUND_COLOR = 0xffffff;
private const int GRAPH_COLOR_ONE = 0x00FF00;
//Pointers
IntPtr hdc;
IntPtr srcb;
IntPtr dchb;
IntPtr origb;
IntPtr src;
IntPtr dch;
IntPtr orig;
//Brushes
IntPtr brush_one;
IntPtr brush_back;
public Form1()
{
InitializeComponent();
//Create a graphics engine from the window
g = Graphics.FromHwnd(this.Handle);
//Get the handle of the Window's graphics and then create a compatible source handle
hdc = g.GetHdc();
srcb = CreateCompatibleDC(hdc);
src = CreateCompatibleDC(hdc);
//Get the handle of a new compatible bitmap object and map it using the source handle to produce a handle to the actual source
dchb = CreateCompatibleBitmap(hdc, ClientRectangle.Width, ClientRectangle.Height);
origb = SelectObject(srcb, dchb);
//Get the handle of a new compatible bitmap object and map it using the source handle to produce a handle to the actual source
dch = CreateCompatibleBitmap(hdc, 32, 32);
orig = SelectObject(src, dch);
//Create the burshes
brush_one = CreateSolidBrush(GRAPH_COLOR_ONE);
brush_back = CreateSolidBrush(BACKGROUND_COLOR);
//Create Image
FillRectangle(brush_one, src, 0, 0, 32, 32);
//Fill Background
FillRectangle(brush_back, hdc, 0, 0, ClientRectangle.Width, ClientRectangle.Height);
this.Show();
Render();
}
private void Render()
{
Stopwatch s = new Stopwatch();
s.Start();
int frames = 0;
while(frames <= 30)
{
frames++;
FillRectangle(brush_back, srcb, 0, 0, ClientRectangle.Width, ClientRectangle.Height);
for (int i = 0; i < 50; i++)
for (int j = 0; j < 50; j++)
BlitBitmap(i * 5, j * 5, 32, 32, srcb, src);
BlitBitmap(0, 0, ClientRectangle.Width, ClientRectangle.Height, hdc, srcb);
}
s.Stop();
float fps = (float)frames / ((float)s.ElapsedMilliseconds / 1000.0f);
MessageBox.Show(Math.Round(fps, 2).ToString(), "FPS");
}
private void FillRectangle(IntPtr b, IntPtr hdc, int x, int y, int w, int h)
{
//Create the region
IntPtr r = CreateRectRgn(x, y, x + w, y + h);
//Fill the region using the specified brush
FillRgn(hdc, r, b);
//Delete the region object
DeleteObject(r);
}
private void BlitBitmap(int x, int y, int w, int h, IntPtr to, IntPtr from)
{
//Blit the bits of the actual source object to the window, using its handle
BitBlt(to, x, y, w, h, from, 0, 0, SRCCOPY);
}
推荐阅读
- c# - 有没有办法只用 C# 构建一个 DataTemplate
- javascript - React Native - 使用正文打开邮件客户端
- javascript - 如何在动画速度期间禁用按钮?
- django - Django显示相关计数
- javascript - 在 JSONforms 中填充字段并创建动态表单
- javascript - 为 html 网页使用 nodejs 模块
- cuda - 尝试使用 FFT 卷积时不支持 cuDNN 状态
- php - 无法从与 laravel 的关系中提取数据
- java - Java版的python列表
- javascript - 管理员、教师和学生,如果学生未注销并且您打开一个新选项卡尝试访问管理员,您将绕过登录