• Advertisement
Sign in to follow this  

Ways to avoid casts

This topic is 3295 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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?

Share this post


Link to post
Share on other sites
Advertisement
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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");

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
Quote:
Original post by mikeman
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?
It can be done, you have to associate the entity and the actor before you lose the type information; the consequence of that is you must have something that knows about both types. Unfortunately I'm far too busy to provide a good example of how to do this but I have knocked up some rather rubbishy pseudo code to demonstrate the concept:

struct actor
{
entity_ptr e;
controller_ptr c;
};

struct actor_assembler
{
virtual actor create() = 0;
};

struct player_assembler : actor_assembler
{
actor create()
{
actor a;
a.c = new player_controller(new player);
a.e = a.c->the_entity();
return a;
}
};

std::map<actor_desc, actor_assembler_ptr> actor_factory;
// ...

actor avatar = actor_factory[entity_desc("avatar", "fast_sharp_shooter")]->create();



In practice you might want a single assembler to be able to create different types of actors (in the traditional Factory Method way) which, of course, is doable too.

Share this post


Link to post
Share on other sites
Yes, I've thought of that too, it would simplify some other things too, but basically you need to write boilerplate code for each 'valid' combination of entity/controller, and the game will be rather complex, so there could be a lot of them. I will give it some more thought though. Thanks!

Share this post


Link to post
Share on other sites
I only skimmed the thread, but here's how I would implement a cast-less and typesafe map/entity editor. Im assuming you will be making the editor with a GUI.

//basic entity class
class Entity
{
virtual void draw() = 0;
virtual void editProperities() = 0; //you call this when the object is double-clicked, etc
};

//popup window used to edit entities
class PropertiesPopup : public popup_widget_from_whatever_gui_you_use
{
virtual void close() = 0; //might not need to be virtual
virtual void saveProperties() = 0; //saves changes made to object
};

//an example entity class, the duck
class Duck : public Entity
{
private:
bool is_quacking_;
OggFile quack_sound_;
int color_;
bool breathes_fire_;

virtual void draw() { //draw stuff here }
virtual void editProperties()
{
DuckPopup popup(this); //give the popup a handle to the current duck entity
RegisterPopupWithGUI(popup);
}
};

//a popup window for duck objects, with gui fields for each duck property
class DuckPopup : public PropertiesPopup
{
private:
Duck* duck_;
Checkbox is_quacking_;
FileDialog quack_sound_;
ColorWheel color_;
Checkbox breathes_fire_;
Button save_;
Button close_;

public:
DuckPopup(Duck* duck) : duck_(duck) { //do construction stuff here }

virtual void close() { //do closing stuff here }
virtual void saveProperties()
{
duck_->setIsQuacking( is_quacking_.value() )
duck_->setQuackSound( quack_sound_.value() )

//continue reading in all duck properties from widgets, and set them through the Duck*
}
};






The basic idea is this: all you need to do is get inside a virtual method, then you know exactly what types youre dealing with. From there, you can do whatever you want with entity-specific attributes.

Also, you will probably find that having a separate controller object needlessly complicates things. It should probably be replaced all together by a single virtual method in the entity class.

Share this post


Link to post
Share on other sites
Quote:
Original post by mikeman
Yes, I've thought of that too, it would simplify some other things too, but basically you need to write boilerplate code for each 'valid' combination of entity/controller, and the game will be rather complex, so there could be a lot of them. I will give it some more thought though. Thanks!
I think all the problems essentially come back to the fact that you want a factory for all types of entities and a factory for all types of controller; but it seems you have several types of each that pair up together (NPC entities and NPC controllers for example). Could you not have separate mangers for each pairing? Queue the pseudo-C++:

struct actor
{
entity_ptr e;
controller_ptr c;
};

class actor_factory
{
public:
virtual actor create(const std::string & entity_name,
const std::string & controller_name) = 0;
};

template <class E, class C>
class basic_actor_factory : public actor_factory
{
basic_entity_factory<E> entity_factory;
basic_controller_factory<C> controller_factory;

public:
actor create(const std::string & entity_name,
const std::string & controller_name)
{
actor a;
a.c = controller_factory.create(
controller_name,
entity_factory.create(entity_name));
a.e = a.c->the_entity();
return a;
}

template <class T>
void register_entity(const std::string & entity_name)
{
entity_factory.register_product(entity_name, basic_entity_creator<T>());
}

template <class T>
void register_controller(const std::string & controller_name)
{
controller_factory.register_product(controller_name, basic_controller_creator<T>());
}
};

typedef basic_actor_factory<npc_entity_base, npc_ctrl_base> npc_actor_factory;
typedef basic_actor_factory<player_entity_base, player_ctrl_base> player_actor_factory;

npc_actor_factory npc_factory;
npc_factory.register_entity <entities::red_guy> ("red_guy");
npc_factory.register_entity <entities::crazy_dude> ("crazy_dude");
npc_factory.register_entity <entities::amy> ("amy");

npc_factory.register_controller <controllers::timid> ("timid");
npc_factory.register_controller <controllers::brave> ("brave");
npc_factory.register_controller <controllers::suicidal> ("suicidal");


/*
same for player_factory
*/


std::map<std::string, actor_factory_ptr> actor_factories;

actor_factories["npc"] = &npc_factory;
actor_factories["player"] = &player_factory;

//....

actor timid_amy = actor_factories["npc"]->create("amy", "timid");


Edit: Forgot you can't register an entity or controller by string alone, fixed now. [rolleyes]

[Edited by - dmatter on January 13, 2009 6:36:02 PM]

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement