In C++, should different game entities have different classes? Or should it be in one class which contains all behaviours?

心已入冬 提交于 2019-12-23 13:08:12

问题


I am working on a game environment which will feature a number of different entities. Each of this will have a few common functions (Draw, Update, etc) but sometimes the game must treat them differently based on their enemy type. So far, I have encoded the "type" of enemy in their instances' class. So, we have a situation like:

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 { ... };

So the classes Coin, TextSign, Attractor and Bumper are the types of entities instantiated in the game. Having different classes for different types of entities feels right, but I can't shake the feeling that some cumbersomeness might be avoided if I just had one entity class, which contained a switch statement that controlled its behaviour based on its "entity type", something stored in a variable. The player interacts with these entities in different ways depending on their type, and I use dynamic_cast and a null test each time to figure out which behaviour is applied. To be clear, these are for behaviours where I can't call a simple Update() on each entity; the player will respond in a specific way, or their will be inter-entity interaction, all based on the entities type. My code looks like this:

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.
        }

    }

    ...

}

Is there a less cumbersome way to do this?


回答1:


One alternative approach is to have a "shallow" class hierarchy and independent "behaviour" abstract classes that you can use to decorate your main hierarchy.

So you can have:

Entity

-> Character -> NPC ->....

-> Object -> Container -> .... Renderable

Moveable

Killable

Equippable...

You'll use multiple inheritance to decorate the classes.

Having a single hierarchy forces you to move features up the tree when they are shared between different "subtrees", which means you add features to classes that might not use them, complicating the whole design; or otherwise, you end up having to repeat code (DRY principle?). In both cases, this becomes harder to maintain and expand.




回答2:


In my opinion this problem is usually best resolved using interfaces. If an object is drawable, implement IDrawable. If it's movable, implement IMovable. If it can be updated, implement IUpdatable.

As you create objects, they can add them (IOC) or they can add themselves to various lists that ensure they will be allowed to move/update/draw/etc at the appropriate times.

The strategy is simple, flexible and leads to highly readable code. Resist the urge to use either switch statements or fragile class hierarchies.

Yes, I know this is C++ and not C# so you will need a bit more work in constructing the infrastructure. You can use abstract classes, multiple inheritance with no data members and dynamic_cast to get a similar effect. Some members of the old school prefer to use bit flags and static cast to avoid runtime overhead.




回答3:


The choice of to subclass or not to subclass is a nuanced one. Is there only a small amount of entity specific code? Consider a plain old general entity class. It really depends on what you're doing elsewhere, and which pattern fits most comfortably.

In your case, if you need to have one class identify another polymorphic class, you could go for a

virtual EntityTypeEnum GetType();

method which returns an enum saying what it is. It's a little bit cleaner than attempting to typecast it many times and switches love enums. But it's essentially the same pattern.




回答4:


(This question seemed to get some attention recently, so I'll provide the solution I started using myself).

I switched to an Entity-Component System, and it has been GREAT. It was difficult initially to figure out what level of granularity to make Components, but after doing it for a few months I "got" it and can't imagine going back to class inheritance.

For my ECS, I'm using entityx. It's been slightly frustrating because it depends on C++11 and I want to build on Windows eventually, but of all the ECS engines I found, it had the syntax that seemed the most natural to me.

My current Component types are:

ParentComponent
Transformable
TranslateBounds
Drawable
Sprite
SpriteAnimation //gif-style
Velocity
Tags //a list of identifying strings
Animations


来源:https://stackoverflow.com/questions/18901507/in-c-should-different-game-entities-have-different-classes-or-should-it-be-i

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!