WPF-如何创建切片地图编辑器

时间:2018-07-29 22:37:48

标签: c# wpf performance canvas

我正在尝试构建这样的地图编辑器:在WPF中使用http://blog.rpgmakerweb.com/wp-content/uploads/2012/04/T2EditorLabelled.png

它应该如何工作:
通过在“图块列表”中选择特定的图块-B部分-可以在画布上绘制该图块-A部分。
最后,最终结果是在画布上绘制了完整的游戏关卡。

第一种方法:
在我的第一种方法中,通过创建一个新的图像控件并将其添加到画布(WPF画布控件)来绘制每个图块。

步骤:

  1. 从图块集中选择图块
  2. 在画布上捕获click事件
  3. 创建新图像,从图块中裁剪图块
  4. 将图像添加到画布上的正确位置(作为孩子)

这种方法很幼稚,它暗示了两个大问题:

  1. 所有像素都已经缓冲在包含所有图块的图块上,但是每次我在画布上绘制图块时,都会创建一个新图像,并且这样做,我必须将部分图块像素数据复制为新
  2. 的来源
  3. 画布上的控件过多:
      一张地图游戏的大小可以达到1000瓦片x 1000瓦片,WPF在100x100瓦片地图上开始表现出明显的性能下降。
      因此,为每个图块创建一个控制图像是不可行的解决方案。

第二种方法

第二种方法是考虑使用单个大WriteableBitmap作为画布背景。

与以前的方法一样,在图块集上选择了图块,而绘制事件是在画布上单击。

在这种情况下,虽然没有重新创建新图像,但是背景WriteableBitmap被相应地修改了。

由于所有绘制机制都是在WriteableBitmap上执行的,因此大大减少了控件的数量。

这种方法的主要问题是,如果我要创建一张包含1k x 1k瓦片和32x32瓦片的大型地图,则背景图像的大小将是天文数字。

我想知道在WPF中是否有一种方法可以很好地解决此问题。 您将如何解决这个发展问题?

1 个答案:

答案 0 :(得分:0)

您可以通过多种不同的方法来解决此问题,以提高性能。

就图像渲染而言,默认情况下WPF并不出色,因此您可以这样做;

  1. 使用GDI的BitBlt快速将图像呈现到可以托管的WinForms控件中。这样做的好处是GDI是软件,因此不需要图形卡或任何其他东西。 (WPF fast method to draw image in UI

  2. 您可以将D3DImage用作图像源。这意味着您可以将D3DImage用作要绘制到的画布。这样做将意味着您必须使用Direct3D将所有图块渲染到D3DImage图像源,因为它是硬件加速的,因此速度要快得多。 (https://www.codeproject.com/Articles/28526/Introduction-to-D3DImage

  3. 您也许可以通过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);
        }