1

几天来,我一直在尝试实施此操作时遇到麻烦。关于我正在尝试做的事情,我已经广泛搜索了类似的问题,但我没有遇到直接帮助我解决问题的问题。

基本上,我在UserControl课堂上将瓷砖渲染到网格上。这是我正在开发的基于 Tile Engine 的世界编辑器。这是一个开放世界文档的屏幕截图和一些刷过的图块。

在此处输入图像描述

最初,我打算Bitmap在我的控件中使用一个作为世界预览画布的控件。例如,使用画笔工具时,当您移动鼠标并按住左键时,它会将光标下方最近的图块设置为画笔的图块,并将其绘制在位layer图上。控件的OnPaint方法被覆盖到layer相对于绘制事件的剪切矩形绘制位图的位置。

这种方法的问题是,在处理大世界时,位图会非常大。我需要这个应用程序在世界大小上具有通用性,而且很明显,每次将大型位图渲染到控件上时都会出现性能问题。

目前,我在控件的覆盖OnPaint事件中直接将图块绘制到控件上。这很棒,因为它不需要大量内存。例如,每个 tile 的(1000, 1000)世界(20, 20)(总画布大小为(20000, 20000))在整个应用程序的大约 18mb 内存中运行。虽然不是内存密集型,但它是相当密集的处理器,因为每次控件无效时,它都会遍历视口中的每个图块。这会产生非常烦人的闪烁。

我想要完成的是一种在内存使用和性能方面处于中间状态的方法。本质上是双重缓冲世界,以便在重绘控件时不会闪烁(表单调整大小、焦点和模糊、滚动等)。以 Photoshop 为例——当它溢出容器视口时,它如何渲染打开的文档?

OnPaint作为参考,这是我使用上述直接绘制方法的控件的覆盖。

getRenderBounds返回一个相对于PaintEventArgs.ClipRectangle用于渲染可见图块的矩形,而不是循环遍历世界中的所有图块并检查它是否可见。

protected override void OnPaint(PaintEventArgs e)
{
    WorldSettings settings = worldSettings();

    Rectangle bounds = getRenderBounds(e.ClipRectangle),
        drawLocation = new Rectangle(Point.Empty, settings.TileSize);

    e.Graphics.InterpolationMode = 
        System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
    e.Graphics.SmoothingMode = 
        System.Drawing.Drawing2D.SmoothingMode.None;
    e.Graphics.PixelOffsetMode = 
        System.Drawing.Drawing2D.PixelOffsetMode.None;
    e.Graphics.CompositingQuality = 
        System.Drawing.Drawing2D.CompositingQuality.HighSpeed;

    for (int x = bounds.X; x < bounds.Width; x++)
    {
        for (int y = bounds.Y; y < bounds.Height; y++)
        {
            if (!inWorld(x, y))
                continue;

            Tile tile = getTile(x, y);

            if (tile == null)
                continue;

            drawLocation.X = x * settings.TileSize.Width;
            drawLocation.Y = y * settings.TileSize.Height;

            e.Graphics.DrawImage(img, 
                drawLocation, 
                tileRectangle, 
                GraphicsUnit.Pixel);
        }
    }
}

如果您需要我的代码中的更多上下文,请发表评论。

4

1 回答 1

1

诀窍是根本不使用大位图。您只需要一个覆盖可见区域的位图。然后你画出任何可见的东西。

为此,您需要将数据与位图分开维护。这可以是一个简单的数组或一个数组/列表,其中包含一个简单的类,其中包含每个块的信息,例如世界位置。

当您的块在可见区域内时,您可以绘制它。您可能需要也可能不需要遍历整个数组,但这并不是真正的问题(您也可以在单独的线程上计算可见数组)。您还可以通过创建区域索引使函数更智能,这样您就不会迭代所有块。

要将新块添加到数组中,请计算其画布位置到世界坐标,添加它然后再次渲染数组(或绘制块的区域)。

这也是系统绘制具有可滚动区域的控件的方式。

启用双缓冲将保持清晰和无闪烁。

在这种情况下,我还将使用带有单独滚动条的面板并计算滚动条的相对位置。

于 2012-12-06T02:05:42.920 回答