【问题标题】:How to draw pictures in the given overlay order? (layers...)如何以给定的叠加顺序绘制图片? (层...)
【发布时间】:2021-03-21 12:39:00
【问题描述】:

我正在制作一个在基本级别上运行良好的 2D 游戏引擎。现在我想对其进行微调并为引擎添加一些额外的(但必要的)功能。不幸的是,我坚持使用我的“层”系统。

您可以将其想象为 Unity 中的相同内容,或 Photoshop 中的图层...我想确定精灵(图像)的显示顺序。我发现的大多数“解决方案”都说“在另一个图像之前绘制一个图像,这样它就会显示在顶部......”是的,我知道这是如何工作的,但这不是最好的方式,因为游戏可以包含数千个精灵并且渲染顺序也可以由用户动态改变。所以我需要应用某种“图层”系统,用户可以在实例化 Sprite 类的对象期间设置顺序,或者稍后在需要时通过更改相关属性值来更改图层顺序(类似于“layerOrder = 2;” )。我想,我在说什么非常直截了当。

是否有任何合适且具有成本效益的方法来做到这一点?这应该具有成本效益,因为 Renderer() 函数在一秒钟内运行多次,同时将大量图像绘制到屏幕上。如果这个“函数”是 C# 本身的一部分,我会很高兴,但看起来不是!

这里是我简化的 Renderer() 方法:

    private void Renderer(object sender, PaintEventArgs e)                                                              
    {
        Graphics g = e.Graphics;
        g.Clear(BkgColor);                                                                                              

        foreach (Sprite s in RegisterSprite.ToList())
        {
            g.DrawImage(s.SpriteBmp, s.Position.X, s.Position.Y, s.Size.X * s.Scale.X, s.Size.Y * s.Scale.Y;
        }
    }

我的 Sprite 类没有什么特别的......构造函数从给定目录加载图像并设置用户通过构造函数传递的属性,如位置、名称等......“RegisterSprite”只是一个包含所有内容的列表的精灵。当用户创建精灵的对象时,精灵就会被添加到列表中。(如墙壁或敌人......) Sprite 类会自动将新精灵注册到列表中,仅此而已!

请尝试提供一些可以集成到我的引擎中的想法或解决方案。或者让我知道是否有内置的方法可以做到这一点。很难想象没有任何方法可以决定哪个位图/图像绘制在其他图像之上...谢谢!

【问题讨论】:

  • 如果您需要 z 顺序,则需要 z 顺序,这意味着您需要将其包含在系统中。然后其余的将通过简单地按顺序自行工作。唯一可能昂贵的操作是更改精灵的 z 值。一种选择是使用相对 z 值,另一种选择是将它们作为绝对值,如实际 z 坐标。后者需要处理碰撞,前者是重新排序,在您的示例中重新排序RegisterSprite..
  • 这听起来很有趣!很明显的方式,但由于我的引擎是纯 2D 的,这将很难实现一个新的维度。老实说,我不知道该怎么做,因为我从不编写任何 3D 代码(仅使用 Unity)。我也有 AABB 碰撞检测 - 正如你所提到的 - 我需要在其中实现新的 z 坐标,这听起来有问题。无论如何我会考虑如何做到这一切。目前我无法想象如何在 'DrawImage()' 方法中添加一个额外的轴,因为它只接受两个轴在 2D 屏幕上绘制。感谢您的想法!
  • 好吧,我仍然会为所有其他帐户保留 2D,但如果您需要以某种顺序堆叠它们,则需要将订单信息存储在某个地方。 (或从列表中的顺序暗示​​,但这不适用于任何类型的更改,包括插入......)。给定 sprite 类中的 z 值,一个小的 LINQ 会即时重新排序列表,并且由于几乎所有项目都已经处于正确的顺序,因此重新排序应该非常快。 - 但是你需要在你的场景中决定一些“意义”。它是关于什么类型的场景?太空歌剧?
  • 其实我的意思是“GameEngine”就像一个真正的通用游戏引擎,适用于所有类型的 2D 游戏......我有一个简单的自上而下的演示游戏,但没什么特别的......无论如何你的解决方案是这样做的方法!很简单,我向 Sprite.cs 添加了一个变量,例如“int layerOrder”,并在创建实例时设置它。然后,在渲染器开始运行之前,我根据变量对整个列表进行排序......所以我将得到一个有组织的列表,其中第一个元素是最低/最深的元素。这样,渲染器的 foreach 将首先绘制我想要的内容并自动存储“层”。谢谢!
  • 如果您只想将您的想法作为评论,我将接受它作为解决方案!如果你不想要,我会根据你的想法回答我的问题,这样有类似问题的人就会看到如何去做。

标签: c# graphics bitmap overlay drawimage


【解决方案1】:

好的.. TaW 在问题的评论部分发布了我的问题的解决方案,但我会在这里自行回答,因为 TaW 看起来不再写答案了。

创建一个“层系统”更简单,正如我在写问题时所想的那样。我在我的 Sprite 类中添加了一个整数变量(layerOrder),所以我可以为每个精灵(图像)设置我需要的顺序。正如我所提到的,我有一个列表(RegisterSprite),当我创建它们(实例化对象)时,我会在其中存储每个精灵。然后我写了一个小方法,可以根据精灵的 layerOrder 值对列表进行排序。这样,列表中的第一个精灵将是最低值,最后一个是最高值。我使用 foreach 循环将列表中的每个图像绘制到屏幕上,当然,这将从第一个元素开始。因此,layerOrder 较低的 sprite 将首先被绘制,在具有较高值的​​那些 sprite 之下。在渲染器(使用 foreach)部分之前调用排序方法很重要。

如果需要在“运行时”更改精灵layerOrder,只需在更改值后调用排序算法或创建一个方法来更改layerOrder,它也会自动重新排序列表。如果需要优化算法(堆优化?)或编写另一个更新列表(更改相关元素)以便快速重新排序的算法......

这是我的排序方法和渲染器的简化版本:

    public List<Sprite> SortSpriteList(List<Sprite> list)
    {
        for (int i = 0; i < list.Count - 1; i++)
        {
            int lowest = i;
            int j = i + 1;

            for (; j < list.Count; j++)
            {
                if (list[j].Layer < list[lowest].Layer)
                {
                    lowest = j;
                }
            }

            if (lowest != i)
            {
                Sprite copy = list[i];
                list[i] = list[lowest];
                list[lowest] = copy;
            }
        }

        return list;
    }

private void Renderer(object sender, PaintEventArgs e)                                                              
{
    Graphics g = e.Graphics;
    g.Clear(BkgColor);                                                                                              

    foreach (Sprite s in RegisterSprite.ToList())
    {
        g.DrawImage(s.SpriteBmp, s.Position.X, s.Position.Y, s.Size.X * s.Scale.X, s.Size.Y * s.Scale.Y;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-10
    相关资源
    最近更新 更多