Ways to avoid casts

Started by
13 comments, last by mikeman 15 years, 3 months ago
In the RPG game I'm making, there is a base class Entity, from which all entities(players,npc,enemies,items) are derived from.Each entity is also associated with a Controller. In short, it all looks kind of like this:

class NPC:public Entity
{
   public:
       void Talk(){....} 
};

class NPCController:public Controller
{
  NPC* npc;
  public:
    void SetNPC(NPC* anpc)
    {
       npc=anpc;
    }
    virtual void Think()
    {
       npc->Talk()
    }
};

...
NPC* bob=new NPC();
NPCController* controller=new NPCController();
controller->SetNPC(bob);
...

Now all this is well, except the last snippet doesn't really show how I want the game/world to be populated by entities. In a nutshell, each new Entity class must be "registered" to a class factory, so the Editor can "see" it, and create it based on a string given by the user/designer(probably via a dropbox). Same for the various Controllers. So the creation of an Entity/Controller would be like:

void CreateEntity(string entityClass,string controllerClass)
{
  Entity* ent=entityFactory->Create(entityClass);
  Controller* controller=controllerFactory->Create(controllerClass)
}

CreateEntity("NPC","NPCController");

Now, the problem is, how to "connect" the entity and the controller since, in this point of code, we don't know the concrete types? The only thing I could think of was using casts inside Controller:

class Controller
{
public:
   virtual void SetEntity(Entity* entity)=0;
};

class NPCController:public Controller
{
  NPC* npc;
  public:
  virtual void SetEntity(Entity* entity)
  {
     NPC* o=dynamic_cast<NPC*>(entity);
    if (!o) error("Bad type") else npc=o;
  }
};

void CreateEntity(string entityClass,string controllerClass)
{
  Entity* ent=entityFactory->Create(entityClass);
  Controller* controller=controllerFactory->Create(controllerClass)
  controller->SetEntity(ent);
}

Now, is there a way I can avoid the cast(and keep of course the way the editor creates dynamically the entities)? I'm just not seeing it. Granted, I'm not taking any decisions based on the type of the object, but still it bothers me. Any ideas?
Advertisement
Why would the controller care about the type of entity it is controlling? Design an abstract interface for entities that encompasses sufficient functionality for the controllers to use.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Quote:Original post by swiftcoder
Why would the controller care about the type of entity it is controlling? Design an abstract interface for entities that encompasses sufficient functionality for the controllers to use.


Because different entities have different properties and methods. An item doesn't have a "talk" method, whereas an NPC does, for example.
Why would an item need a controller? They don't typically move or talk on their own.
Quote:Original post by mikeman

Because different entities have different properties and methods. An item doesn't have a "talk" method, whereas an NPC does, for example.


Frog is green and has two legs.
Basketball is orange.

Apparently, the only thing they have in common is color. So abstraction will join all objects which have color.

Of course, this is the caveat with OO-based rigid hierarchies. You need to define interactions up-front, and if the original abstractions are incorrect, it results in a huge mess.

But strangely, the solution is right in front of you.
struct Controllable {  virtual void Think() = 0;};class NPC : public Controllable, Entity {};struct Controller {  virtual void Update() = 0;};class NPCController : public Controller {  Controllable * c;  NPCController(Controllable * cEntity) : c(cEntity) { }  virtual void Update() { // non-virtual    c->Think();  }};


Controller controls things that can think. Regardless of whether they are entities or not.
Controllers are updated via Update(). Regardless of what type of controller they are, or what they control (or not).

In code:
std::vector<Controller *> controllers;for (Controller * c in controllers) {   c->Update();}


Quote:Why would an item need a controller? They don't typically move or talk on their own.


Because the item is Mexican Jumping Bean, which is controlled by Larva, which is an NPC.
Quote:Original post by mikeman
Quote:Original post by swiftcoder
Why would the controller care about the type of entity it is controlling? Design an abstract interface for entities that encompasses sufficient functionality for the controllers to use.
Because different entities have different properties and methods. An item doesn't have a "talk" method, whereas an NPC does, for example.
Then it wouldn't make sense to use the same controller for both. Factor your entities out into actors and items, for instance, where all actors are controllable, and items not.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Quote:
Frog is green and has two legs.
Basketball is orange.


Sure, a ColorController can control those abstractions that have color. But a Movement controller can only control those abstractions that have legs.

I don't think I'm understood correctly, it might be my fault. Entities are objects that just keep some state, or execute certain operations. It can be anything, an item, a weapon, an npc, a player. Now, a Controller is the "brain" of the entity. It translates input from the user or AI and instructs the entity it controls to perform some operations. For example, the entity could be an item like a switch that the user can operate on. Or an NPC that can "think" and do some things. Or a player avatar that takes input from the user. I'll give some examples:

class Switch:public Entity{  bool switched;  public:  Switch():switched(false);  void SwithOn()  {     switched=true;       }}class SwitchController:public Controller,IKeyboardListener{  void Think{...};  void OnKeyDown(key)  {    if (key==SPACE) switch->SwitchOn();  }};


class NPC:public Entity{  public:  void TalkTo(NPC* other)  {     ...  }}class NPCController:public Controller{  void Think  {     NPC* neighbour=npc->GetNeighbour();     if (neightbour->IsFriendly()) npc->TalkTo(neighbour);  };  };


class Player:public Entity{  public:  void Walk()  {     ....  }  void Shoot()  {  }}class PlayerController:public Controller,IKeyboardListener{  void Think{...};  void OnKeyDown(key)  {    if (key==SPACE) player->Fire();    if (key=='W') player->Walk();  }};


As you can see, there are various entity classes that can do various operations, and the "obey" to an attached controller. That doesn't mean that each entity class can work only with one controller. For example, I could create another variant of NPCController that "thinks" differently, and attach that to an NPC.

Now, the editor will work like this. In the left, there would be a combobox with all the entity classes, both built-ins and other that modders have created. In the right, there would be a combobox with all the controller classes. The designer will choose which entity and appropriate controller he wishes to attach, and that's it. For example, he could choose to create entity "NPC" and controller "EnemyNPC".Or "FriendlyNPC", or generally whatever the programmer/modder can think of. For example, a Switch that is controlled not by the KeyboardListener kind controller above, but a TimedSwitchController controller that automatically switches it on/off in intervals. I think it is understood that all entities can have a controller, but there are pairs that do not match. For example, an NPCController can't control a Switch. That's what the error("bad type") was in my first post, I'm just wondering if there's another way of doing this without casts.

Antheus also, your example didn't make sense to me, based on your code the Controller has access to Controllable interface so all it can do is call controllable->Think()? I don't understand what benefit this has. As I said, the controller just do what the name says: It makes the decisions and instructs the entity to perform certain operations.
I'm just going to make some amendments to your last example because it's the most complex of the three:

class Player : public IEntity, IWalker, IShooter{public:  void Walk()  {     ....  }  void Shoot()  {  }};class PlayerController : public IWalkController, IShootController, IKeyboardListener{  void Think{...};  void OnKeyDown(key)  {    if (key==SPACE) player->Fire();    if (key=='W') player->Walk();  }};


Now the PlayerController could never be given a switch or NPC because they don't have the same interface, this can even be enforced at compile-time rather than at run-time.

It would also be possible to have separate walk and shoot controllers allowing you to combine different abilities together, such as sharp-shooting with either a tendency to run away and hide or a tendency to get stuck in the thick of it.

[Edited by - dmatter on January 13, 2009 2:39:55 PM]
Since an entity and it's controller seem closely related, I'd leave each type of entity (or the factory) deal with creating the associated controller (when necessary)

Basically something like:

class NPC : public Entity{    void Talk(){....}    Entity()    {      controller = (NPCController*) controllerFactory->Create("NPCController");      controller->SetNPC(this);    }    Controller* GetController()    {      return controller;    }    NPCController* controller; // what other kind of controller can control a NPC anyway?};class NPCController : public Controller{  void SetNPC(NPC* _npc)  {     npc = _npc;  }  NPC* npc; // What other kind of entity can be controlled by a NPCController  anyway?};Entity* CreateEntity(string entityClass){   return entityFactory->Create(entityClass);}CreateEntity("NPC");
Thanks for the ammendments dmatter.

Quote:
this can even be enforced at compile-time rather than at run-time.


This exactly is the problem. *How* to be enforced at compile-time, since the class of the entity and controller created is decided at run-time by the level designer himself? Is it possible? Or I just have to make peace with the casts if I want all this functionality in C++(as a note, the problem would not exist at all in a dynamically typed language like,say, Python).

This topic is closed to new replies.

Advertisement