我正在尝试构建这样的地图编辑器:在WPF中使用http://blog.rpgmakerweb.com/wp-content/uploads/2012/04/T2EditorLabelled.png。
它应该如何工作:
通过在“图块列表”中选择特定的图块-B部分-可以在画布上绘制该图块-A部分。
最后,最终结果是在画布上绘制了完整的游戏关卡。
第一种方法:
在我的第一种方法中,通过创建一个新的图像控件并将其添加到画布(WPF画布控件)来绘制每个图块。
步骤:
这种方法很幼稚,它暗示了两个大问题:
第二种方法:
第二种方法是考虑使用单个大WriteableBitmap
作为画布背景。
与以前的方法一样,在图块集上选择了图块,而绘制事件是在画布上单击。
在这种情况下,虽然没有重新创建新图像,但是背景WriteableBitmap
被相应地修改了。
由于所有绘制机制都是在WriteableBitmap
上执行的,因此大大减少了控件的数量。
这种方法的主要问题是,如果我要创建一张包含1k x 1k瓦片和32x32瓦片的大型地图,则背景图像的大小将是天文数字。
我想知道在WPF中是否有一种方法可以很好地解决此问题。 您将如何解决这个发展问题?
答案 0 :(得分:0)
您可以通过多种不同的方法来解决此问题,以提高性能。
就图像渲染而言,默认情况下WPF并不出色,因此您可以这样做;
使用GDI的BitBlt快速将图像呈现到可以托管的WinForms控件中。这样做的好处是GDI是软件,因此不需要图形卡或任何其他东西。 (WPF fast method to draw image in UI)
您可以将D3DImage用作图像源。这意味着您可以将D3DImage用作要绘制到的画布。这样做将意味着您必须使用Direct3D将所有图块渲染到D3DImage图像源,因为它是硬件加速的,因此速度要快得多。 (https://www.codeproject.com/Articles/28526/Introduction-to-D3DImage)
您也许可以通过WinForms控件托管XNA,并使用它进行呈现,我对此没有经验,因此我可以测试任何性能。 (WPF vs XNA to render thousands of sprites)
就渲染而言,我个人会使用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);
}