2

I'm writing a game and it's going to use a component-based entity system. The first two components that I'm implementing are EntityRepresentation and EntityState. The representation holds the animations, and the state decides how the entity should react to game events (examples of EntityStates: standing, jumping, attacking, dead, falling).

The EntityRepresnetation decides what animation to draw on the screen depending on the Entity's EntityState. If the Entity is in "jumping" state, the corresponding animation will be played. (See the EntityRepresentation::changeAnimation() function.)

Here's roughly how I wrote the class...

class EntityRepresentation
{
  public:
    void draw(float x, float y, long currentTime, DrawingContext& dc) {
        // ...
    }

    void changeAnimation(const EntityState::Id& stateId) {
        currentAnimation = animationMap_[stateId];
    }

  private:
    map<EntityState::Id, const Animation*> animationMap_;
    const Animation* currentAnimation_;
};

There's something that I really don't like with my current approach... >:( The EntityState::Id part. Currently, the EntityRepresentation maps each animation it holds to a specific EntityState::Id. The ID is unique to every class that derives from EntityState (unique to the class, not the instance). These IDs are basically strings that have to be written by hand in the class' constructor, which means they are highly prone to name-collisions — more so since I plan to make the game scriptable (Python).

Can someone give me some advice as to how I can alleviate this problem I have with the IDs. I've seen a lot of implementations of the component-based system in games that also use IDs, but I still don't like them. It just rubs me the wrong way.

Maybe you can suggest a design change, such that the EntityRepresentation doesn't have to know the type of the EntityState, all while still preserving encapsulation.

Ivan Aksamentov - Drop
  • 12,860
  • 3
  • 34
  • 61
Paul Manta
  • 30,618
  • 31
  • 128
  • 208
  • "depending on the Entity's state" -- is this related to EntityState, or e.g. to a state in a wider sense? – Luc Danton Jun 16 '11 at 12:35
  • @Luc On the EntityState class. EntityState is the base class for all states. EntityState has a field of type EntityState::Id (which is just a string), that is (should be) unique to every type of EntityState. – Paul Manta Jun 16 '11 at 12:40

3 Answers3

1

Assuming that the IDs associated with each EntityState are not needed at compile time, one trick I've used is to use pointers as an ID. This works particularly well if the EntityState is a singleton, in which case you could simply use the address of the singleton instance as the ID. Otherwise statically allocating a small chunk of memory for each ID works fine.

class EntityState {
public:
    EntityState(void *id) : mId(id) { }
private:
    void *mId;
};

class EntityStateJump : public EntityState {
public:
    EntityStateJump() : EntityState(getId()) { }
private:
    void *getId() {
        static void *id = new int;
        return id;
    }
};

Edit: Minor change to avoid static initialization order issues.

zennehoy
  • 6,405
  • 28
  • 55
  • I've though up of both of these solutions. Out of the two, I'd go with singletons, which begin to look more and more as a viable option, especially since they're rather easy to implement in Python (the scripting language I'm going to use). I don't really understand how the `EntityStateJump` class would generate unique IDs for every class type, though. – Paul Manta Jun 16 '11 at 13:31
  • What do you mean with "class type"? I was assuming that you would have one and only one ID for every class derived from EntityState, where every instance of that class would have the same ID. – zennehoy Jun 17 '11 at 09:47
  • @zennnehoy Yes, that's what I meant. – Paul Manta Jun 17 '11 at 10:00
  • Regarding singletons: if your various EntityState classes do not contain any Entity-specific information (members), I would strongly recommend making them singletons. By using the instance pointer as the ID, this also makes it trivial to retrieve the EntityState given the ID (since the ID is the instance). Plus you don't need to allocate additional memory for the ID. – zennehoy Jun 17 '11 at 10:01
1

I suggest to implement the design using State pattern

class EntityState
{
public:
    EntityState(const Animation* animation);
    static const EntityState* getStandingState();
    static const EntityState* getJumpingState();
    static const EntityState* getAttackingState();

    const Animation& getAnimation() const;

protected:
    const Animation* pAnymation_;
};

class EntityRepresentation
{
public:
    void draw(float x, float y, long currentTime, DrawingContext& dc) {
        // ...
        const Animation& animation(state_->getAnimation());
    }

    void changeState(const EntityState* newState) {
        state_ = newState;
    }

private:
    const EntityState* state_;
};

void EventHandler(EntityRepresentation& entity)
{
    entity.changeState(EntityState::getJumpingState());
}
  • Thank you for the answer! :) This is what I was talking about when I made that remark about "preserving encapsulation". I don't want the state to hold data that should otherwise be of interest to the representation only. – Paul Manta Jun 16 '11 at 13:27
1

Use typeid(), that's what it's for. You could use a const type_info* as the key type.

Alternatively, you know, you could just use actual inheritance, like, calling a virtual function to get the current animation, which would be much smarter.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Using `typeid` is an option that I keep in the back of my head, but am trying to avoid since it usually meant for debugging purposes. I'm not discarding it, though. **|** I don't really understand how inheritance would work in my case though. Could you please add some more details? – Paul Manta Jun 16 '11 at 13:29