5

我正在开发一个包含许多不同实体的游戏环境。每一个都有一些常见的功能(绘制、更新等),但有时游戏必须根据敌人的类型对它们进行不同的处理。到目前为止,我已经在他们的实例类中编码了敌人的“类型”。所以,我们有这样的情况:

class MotionObject { ... };

class Entity : public MotionObject { ... };

class Coin : public Entity { ... };

class TextSign : public Entity { ... };

class ShapeEnemy : public Entity { ... };

class Attractor : public ShapeEnemy { ... };

class Bumper : public ShapeEnemy { ... };

所以 Coin、TextSign、Attractor 和 Bumper 类是游戏中实例化的实体类型。为不同类型的实体设置不同的类感觉不错,但我无法摆脱这样一种感觉,即如果我只有一个实体类,可能会避免一些麻烦,其中包含一个 switch 语句,该语句根据其“实体类型”控制其行为,存储在变量中的东西。玩家根据它们的类型以不同的方式与这些实体交互,我每次都使用 dynamic_cast 和一个空测试来确定应用了哪些行为。需要明确的是,这些是针对我无法在每个实体上调用简单 Update() 的行为;玩家将以特定的方式做出响应,或者他们将进行实体间的交互,这一切都基于实体类型。我的代码如下所示:

void Game::Update(float deltaT) {

    for (int i =0; i < DrawChildren.size(); i++) {
        //each entity has its Update called
        DrawChildren[i].Update(deltaT);

        //What follows is code that the Game class needs to run based on the entity type.
        Coin * coin = dynamic_cast<Coin*>(DrawChildren[i]);
        if (coin != nullptr) {
            ...
            continue; //so no other type's code executes, in case I have inherited types.
        }

        TextSign * textSign = dynamic_cast<TextSign*>(DrawChildren[i]);
        if (textSign != nullptr) {
            ...
            continue; //so no other type's code executes, in case I have inherited types.
        }

        Attractor * attractor = dynamic_cast<Attractor*>(DrawChildren[i]);
        if (attractor != nullptr) {
            ...
            continue; //so no other type's code executes, in case I have inherited types.
        }

        Bumper * bumper = dynamic_cast<Bumper*>(DrawChildren[i]);
        if (bumper != nullptr) {
            ...
            continue; //so no other type's code executes, in case I have inherited types.
        }

    }

    ...

}

有没有更简单的方法来做到这一点?

4

4 回答 4

1

(这个问题最近似乎引起了一些关注,所以我将提供我自己开始使用的解决方案)。

我切换到Entity-Component System,它已经很棒了。最初很难弄清楚要制作组件的粒度级别,但在做了几个月后,我“得到”了它,无法想象回到类继承。

对于我的 ECS,我使用的是entityx。这有点令人沮丧,因为它依赖于 C++11,我最终想在 Windows 上构建,但在我发现的所有 ECS 引擎中,它的语法对我来说似乎是最自然的。

我当前的组件类型是:

ParentComponent
Transformable
TranslateBounds
Drawable
Sprite
SpriteAnimation //gif-style
Velocity
Tags //a list of identifying strings
Animations
于 2014-02-12T15:40:20.343 回答
1

在我看来,这个问题通常最好使用接口来解决。如果对象是可绘制的,则实现 IDrawable。如果它是可移动的,请实现 IMovable。如果可以更新,请实现 IUpdatable。

当您创建对象时,他们可以添加它们 (IOC),或者他们可以将自己添加到各种列表中,以确保允许它们在适当的时间移动/更新/绘制/等。

该策略简单、灵活,代码可读性强。抵制使用 switch 语句或脆弱的类层次结构的冲动。

是的,我知道这是 C++ 而不是 C#,因此在构建基础架构方面需要做更多的工作。您可以使用抽象类、没有数据成员的多重继承和 dynamic_cast 来获得类似的效果。老派的一些成员更喜欢使用位标志和静态转换来避免运行时开销。

于 2014-02-12T14:13:13.177 回答
1

一种替代方法是拥有一个“浅层”类层次结构和独立的“行为”抽象类,您可以使用它们来装饰您的主要层次结构。

所以你可以拥有:

实体

-> 角色 -> NPC ->....

-> 对象 -> 容器 -> .... 可渲染

活动

可杀

可配...

您将使用多重继承来装饰类。

当特性在不同的“子树”之间共享时,具有单一层次结构会迫使您将特性向上移动,这意味着您将特性添加到可能不使用它们的类中,从而使整个设计复杂化;否则,您最终不得不重复代码(DRY 原则?)。在这两种情况下,这都变得更难维护和扩展。

于 2014-02-12T13:31:38.183 回答
0

选择子类或不子类是一个微妙的选择。只有少量的实体特定代码吗?考虑一个普通的旧通用实体类。这真的取决于你在其他地方做什么,以及哪种模式最适合你。

在您的情况下,如果您需要一个类识别另一个多态类,您可以选择一个

virtual EntityTypeEnum GetType();

方法返回一个枚举,说明它是什么。这比尝试多次进行类型转换并且switches love enums 更干净一些。但它本质上是相同的模式。

于 2013-09-20T00:17:33.477 回答