首页 > 解决方案 > WPF - 如何创建平铺地图编辑器

问题描述

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

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

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

脚步:

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

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

  1. 所有像素已经在包含所有图块的图块集上缓冲,但是每次我在画布上绘制图块时,都会创建一个新图像,因此我有义务复制部分图块集像素数据作为源新的
  2. 画布上的控件太多:
    地图游戏可以达到 1000 x 1000 块的大小,WPF 在 100x100 块地图上的性能开始明显下降。
    所以为每个图块创建一个控制图像是一个不可行的解决方案。

第二种方法

我的第二种方法考虑使用单个大WriteableBitmap画布背景。

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

在这种情况下,虽然没有从头创建新图像,但背景WriteableBitmap会相应修改。

因此,由于所有绘制机制都是在WriteableBitmap.

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

我想知道是否有办法在 WPF 中开发一个很好的解决这个问题的方法。您将如何解决这个开发问题?

标签: c#wpfperformancecanvas

解决方案


有多种不同的方法可以解决此问题以提高性能。

在图像的渲染方面,默认情况下 WPF 并不出色,所以你可以;

  1. 使用 GDI 的 BitBlt 将图像快速渲染到可以托管的 WinForms 控件。这样做的好处是 GDI 是软件,因此不需要显卡或任何东西。(在 UI 中绘制图像的 WPF 快速方法

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

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

推荐阅读