Skip to main content
GameDev.net gamedev.net
🔒 Locked

How to avoid enum types.

Started by Black Knight Nov 21, 2009 at 11:00 PM 15 replies 3.3k views
Original Post
Black Knight
Black Knight
Ok first let me explain the current system I have.I have a base Entity class in my game and other game objects derive from this class.Pretty standart stuff.The entity class has a pure virtual method called getType() every class that inherits from Entity implements this and returns an unsigned integer or enum.The values they can return are defined in an enum like this : namespace EntityTypes { enum types { BUILDING, OBJECT, UNIT, ITEM, SPAWNER, DUNGEON //etc. }; } So for example the Building class has a method like this : unsigned int getType()const {return EntityTypes::BUILDING;} In lots of places in my code I need to check the type of the entity and do stuff based on that.Also all classes that derive from entity can have different types too,but at the moment they dont have derive they just have a m_Type member.For example the Unit class return EntityTypes::UNIT and has a m_UnitType member which can be anything from : namespace UnitTypes { enum types { VILLAGER, SWORDSMAN, //change to sth else ARCHER, //change so sth else later TRAINER, GOBLIN, PLAYER, TRADER, ANIMAL, STABLEMASTER, UNIT_TYPE_COUNT }; } So is there anyway to remove this system or any alternatives? It works for now and I kinda need these values when I write the entities to disk.And when loading back I read the entity type and create the entity based on the type I read from file.
DrYap
DrYap
Hi Black Knight,

Enums can take unnecessary amounts of memory to store so they would be efficient for saving to the disk. As you only have a small number of options you could use defines or constant variables:

#define UNITTYPE_VILLAGER (unsigned char)0//--------------OR----------------const unsigned char UNITTYPE_VILLAGER = 0;

Using an unsigned char means you can have 256 different entities but would only take 1 byte to store.
visitor
visitor
The only place an enum takes space is inside the compiler.

EntityTypes::types t = EntityTypes::BUILDING;int t = EntityTypes::BUILDING;


Both t's are the same size (assuming enums are not using a larger underlying type than int).

------------

In general, I think there is not much point deriving everything from the same base class, so you can throw them all in one container, so you'll need to check the type of anything before you can use it (in completely different ways).
Scourage
Scourage
You could use a hash system to generate ids from string names instead of using enums. This is a bit more flexible, but at added complexity.

Cheers,

Bob

[size="3"]Halfway down the trail to Hell...
Stani R
Stani R
Generally you want to write your code in such a way that RTTI is not needed, but despite my best efforts I always end up with a bit of RTTI here and there. Component systems can help make things more generic but eventually you want to run a query like "list all enemy archers in fifty meters radius" and I haven't come up with a clean solution so far. Perhaps if the query is broadcast as an event with a callback for the relevant classes to register themselves?

Whether the actual RTTI is an enum (type safe, not extensible), a constant integer (not type safe, not extensible), or an interned string (not type safe, extensible) seems beside the point.
choffstein
choffstein
Can you give an example of somewhere in your code that you are needing to check the entity type? Perhaps we can come up with a way to refactor out the need for RTTI all together...
Black Knight
Black Knight
Ok here is a method from my game which basically handles left mouse button presses.I have similar situations for collision detection and other stuff.For example trees are not checked for collision but houses are and bridges have different collision.Anyway here is the code :

void PlayState::onLeftButtonDown(const POINT& cursor){	// send left button down to interace	m_Interface->onLeftButtonDown(cursor);	// check interaction with entities on map	if(!m_Interface->m_InterfaceInput && !m_Interface->isModalDialogOpen())	{		boost::shared_ptr<DARenderer> renderer = m_Engine->getRenderer();		boost::shared_ptr<STE::D3DCamera> camera = renderer->getCamera();		boost::shared_ptr<Map> map = m_World->getMap();		//update intersection ray		m_Engine->updateIntRay(camera->getPosition(),cursor);				boost::shared_ptr<Entity> entity;		boost::shared_ptr<Unit> player = m_World->getPlayer();				if(m_Interface->m_TargetEntity)		{			entity = m_Interface->m_TargetEntity;			if(entity->getType() == EntityTypes::UNIT)			{				boost::shared_ptr<Unit> unit = boost::static_pointer_cast<Unit>(entity);				if(unit->isHostile())				{					playerAttack(unit);				}				else if(m_Interface->isItemInHand())				{					InventoryItem item;					m_Interface->getItemInHand(item);					boost::shared_ptr<const Item> worldItem = m_World->getItem(item.m_Identifier);					playerGive(entity,worldItem);				}				else					playerInteractWithUnit(unit);			}			else if(entity->getType() == EntityTypes::BUILDING)			{				boost::shared_ptr<Building> building = boost::static_pointer_cast<Building>(entity);				if(building->getInteriorFileName().size())				{					m_World->passToMap(building->getInteriorFileName(),m_Engine->getRenderer());				}			}			else if(entity->getType() == EntityTypes::ITEM)			{				boost::shared_ptr<Item> item = boost::static_pointer_cast<Item>(entity);				// if item is taken reset the target entity and 				// simulate mousemove so that the tooltip is refreshed				if(playerTakeItem(item))				{					m_Interface->m_TargetEntity.reset();					m_Engine->m_CurrentToolTip.clear();				}								}			else if(entity->getType() == EntityTypes::OBJECT)			{				boost::shared_ptr<Object> object = boost::static_pointer_cast<Object>(entity);				float distanceToObject = STMath::Distance(entity->getPosition(),player->getPosition());				unsigned int objectType = object->getObjectType();				if(objectType == ObjectTypes::GOLD)				{					if(distanceToObject > 10.0f)					{						m_Interface->addMessage("Can't reach!");						return;					}					m_World->addGoldToPlayer(object->getValue());					map->deleteEntity(entity.get());					m_Interface->m_TargetEntity.reset();								m_Engine->m_CurrentToolTip.clear();				}				else if(objectType == ObjectTypes::CITYGATE || objectType == ObjectTypes::DUNGEONDOOR)				{					if(distanceToObject > 15.0f)					{						m_Interface->addMessage("Can't reach!");						return;					}					object->m_Open = !object->m_Open;				}			}		}	}}
j a
j a
Just a thought, but if you already have an class hierarchy set up, it would be simple to add an overridable OnMouseClick() function to the object. It may seem a little backwards, but conceptually you would then have the object telling the player what to do with it.
Black Knight
Black Knight
I think if I do what you suggest it will boil down something like this :


void PlayState::onLeftButtonDown(const POINT& cursor){	// send left button down to interace	m_Interface->onLeftButtonDown(cursor);	// check interaction with entities on map	if(!m_Interface->m_InterfaceInput && !m_Interface->isModalDialogOpen())	{		boost::shared_ptr<DARenderer> renderer = m_Engine->getRenderer();		boost::shared_ptr<STE::D3DCamera> camera = renderer->getCamera();		boost::shared_ptr<Map> map = m_World->getMap();		//update intersection ray		m_Engine->updateIntRay(camera->getPosition(),cursor);				boost::shared_ptr<Entity> entity;		boost::shared_ptr<Unit> player = m_World->getPlayer();				if(m_Interface->m_TargetEntity)		{			entity = m_Interface->m_TargetEntity;			entity->onMouseClick(player);					}	}}


But then I don't know if it makes sense for the entity class to know about mouse clicks,still it looks better than before I might give it a try.
thatguyfromthething
thatguyfromthething
It's not considered good programming to have a type method. It kind of breaks the whole idea of OOP. If you start to maintain things class by class you may as well make them separate classes in the first place.

There's a million things you could do but you mostly want to have an interface encompass functionality, not data. So you might have your entity pure virtual and then have a virtual serialize() method that knows how to write itself out so that your artificial ID is only ever written to the disk, not present in every single class instance.

And you don't have to edit anything but the class to change the class. Otherwise why go to the bother in the first place?

For C++ RTTI really sucks and doesn't buy you much. You could instead do something like have Entity and subclasses Animal and Object and have isAnimal() and isObject defined in Entity. Then in Animal you have subclasses Character, Civilian, and Enemy, then you have isEnemy, isCharacter, isCivilian() in Animal.

That is not a great design or anything, just there to illustrate you don't ever really need RTTI, and since it causes a lot of trouble and is not very effective it's best avoided.
This is my thread. There are many threads like it, but this one is mine.
j a
j a
Yea, OnMouseClick() might be a poor name. OnInteract() might be more conceptually what you are looking for.

C++ rtti is kinda obnoxious, and you can end up with giant case/switch statements which are pretty ugly. But it is a tool, much like many others. Sometimes you gotta try them out to see how they work and what you end up with. Then you can go back and refactor and find things that are more to your liking.

Just so long as you keep moving forward, its all good.
Red Ant
Red Ant
Quote:
Original post by DrYap
Hi Black Knight,

Enums can take unnecessary amounts of memory to store so they would be efficient for saving to the disk. As you only have a small number of options you could use defines or constant variables:

*** Source Snippet Removed ***
Using an unsigned char means you can have 256 different entities but would only take 1 byte to store.



I really doubt whether this

const char VALUE_ONE = 1;const char VALUE_TWO = 2;const char VALUE_THREE = 3;


saves you any space at all over this

enum Values{    VALUE_ONE = 1,    VALUE_TWO = 2,    VALUE_THREE = 3};


since the compiler will normally try to align your variables on machine word boundaries, so each char will effectively take up as much space as a machine word would anyway.
Steadtler
Steadtler
Quote:

In lots of places in my code I need to check the type of the entity and do stuff based on that


Here's your mistake. Branching on types should be avoided as much as possible. Use plain polymorphism, or maybe a visitor pattern. Behavior specific to a class should be encapsulated in that class!
Black Knight
Black Knight
Yea I get the point,instead of doing something like this :

if(entityType == EntityTypes::PLAYER){ doSomething1();}else if(entityType == EntityTypes::GOBLIN){ doSomething2();}else if(entityType == EntityTypes::ANIMAL){ doSomething3();}


doSomething should be a virtual method of entity and the code will become this :

entity->doSomething();


So everything that happens in doSomething1,doSomething2,doSomething3 should go to the appropriate class.
gameplayprogammer
gameplayprogammer
very interesting thread, is there anywhere online where there articles on high level c++ design online?
theOcelot
theOcelot
Quote:
Original post by gameplayprogammer
very interesting thread, is there anywhere online where there articles on high level c++ design online?


Why yes, there is.
http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf
http://www.objectmentor.com/resources/publishedArticles.html

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.