Game entities and their proprities

Started by
12 comments, last by evolutional 19 years, 2 months ago
Hi, I have some problems implementing an entity system for my game engine. What i want to do is to have a baseclass CEntity which holds information needed for all Entities like position, direction,... class CEntity { private: float fPos[3]; float fDir[3]; char* szClassName; public: CEntity(char* szClass) : szClassName(szClassName) {}; } From this baseclass more specialised entities are derived: class CPlayer : public Entity { private: float fHealth; int iMoney; } I create this entities by a factory class,so that i can call Entity* pE = Factory::Create("Player"); The Factory looks up the class name and creates an object of the desired class. This all works fine! The problem I am having is to set the different Members inside an object. For example I want to be able to do the following (mainly because of scripting): pE->SetProperty("Health", 1000.0f); To achive this i store a "member map" inside each factory, that holds the information that the class "CPlayer" has the property "Health" which is a float and starts at the offset 28 bytes. As soon as I insert virtual methods inside CEntity, all my offset-tables have to be shifted by 4 bytes because of the virtual function table created inside CEntity. So this aproach doesn't seem to be too well suited for what I want to do! (It looks kind of evil to me :) ) Here is the code I used to test this all:


#include <windows.h>
#include <string.h>
#include <iostream>
/*-------------------------------------------------------------------


-------------------------------------------------------------------*/
enum e_eptype {
	EPTYPE_FLOAT,
	EPTYPE_FLOAT3,
	EPTYPE_DWORD,
	EPTYPE_UNKNOWN = 0xffffffff
};


/*-------------------------------------------------------------------


-------------------------------------------------------------------*/
class EntityProperty {

private:
	e_eptype			m_Type;
	char				m_szPropertyName[32];
	int					m_iOffset;
	
public:
	EntityProperty();
	EntityProperty(const char* szName, const e_eptype type, const int iOffset);
	
	char*				GetName();
	e_eptype			GetType();
	int					GetOffset();
};

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
EntityProperty::EntityProperty() {
}

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
EntityProperty::EntityProperty(const char* szName, const e_eptype type, const int iOffset) {
	
	memset(m_szPropertyName, 0, 32);
	strcpy(m_szPropertyName, szName);

	m_Type				= type;
	m_iOffset			= iOffset;
}


/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
char* EntityProperty::GetName() {
	return m_szPropertyName;
}

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
e_eptype EntityProperty::GetType() {
	return m_Type;
}

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
int EntityProperty::GetOffset() {
	return m_iOffset;
}


/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
class Entity {

	friend class	EntityFactory;

private:
	float			m_fPos[3];
	float			m_fDir[3];

	char			m_szClass[32];

public:
    
	char*			GetClass();

	virtual void	Test() {};
};

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
char* Entity::GetClass() {

	return m_szClass;
}

#include <vector>

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
class EntityFactory {

private:
	static std::vector<EntityFactory*>*	m_pFactories;
	static EntityFactory*				GetFactory(const char* szClass);

	int									SetupProperties();
	virtual Entity*						CreateNewEntity() = 0;	
	char*								GetClass();


private:
	std::vector<EntityProperty*>		m_Properties;


protected:
	char								m_szClass[32];
	char*								m_szPropertyInfo;

public:
	EntityFactory(char* szEntityClass, char* szPropertyInfo);
	
	static Entity*						CreateEntity(const char* szClass);
	static int							GetEntityProperties(Entity* pEntity, EntityProperty* pProperties, int *iMax);

	static int							SetEntityProperty(Entity* pEntity, const char* szProperty, const float fValue);
	static int							SetEntityProperty(Entity* pEntity, const char* szProperty, const DWORD dwValue);
	static int							SetEntityProperty(Entity* pEntity, const char* szProperty, const float* f3Value);
};

std::vector<EntityFactory*>* EntityFactory::m_pFactories = 0;

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
EntityFactory::EntityFactory(char* szEntityClass, char* szPropertyInfo) {

	if (m_pFactories == 0) {
		m_pFactories = new (std::vector<EntityFactory*>);
	}
	
	strcpy(m_szClass, szEntityClass);

	m_szPropertyInfo = strdup(szPropertyInfo);
	
	SetupProperties();

	m_pFactories->push_back(this);
}

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
char* EntityFactory::GetClass() {

	return m_szClass;
}

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
int EntityFactory::SetEntityProperty(Entity* pEntity, const char* szProperty, const float fValue) {
	if (pEntity == 0)
		return 0;

	EntityFactory* pF = GetFactory(pEntity->GetClass());
	if (pF == 0)
		return 0;

	std::vector<EntityProperty*>::iterator i;

	for (i = pF->m_Properties.begin(); i < pF->m_Properties.end(); ++i) {
		
		EntityProperty* pP = *i;
		if (strcmp(szProperty, pP->GetName()) == 0) {
			char *pObj = ((char*) pEntity) + pP->GetOffset();
			if (pP->GetType() == EPTYPE_FLOAT) {
				*((float*)pObj) = fValue;
				return 0;
			}
		}

	}

	return -1;
}

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
int EntityFactory::SetEntityProperty(Entity* pEntity, const char* szProperty, const DWORD dwValue) {
	return -1;
}

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
int EntityFactory::SetEntityProperty(Entity* pEntity, const char* szProperty, const float* f3Value) {
	return -1;
}

/*-------------------------------------------------------------------
szPropertyInfo:
PropertyName;PropertyType;OffsetInsideObject;PropertyName;PropertyType;OffsetInsideObject;...
-------------------------------------------------------------------*/
int EntityFactory::SetupProperties() {
	
	char *szTmp = strdup(m_szPropertyInfo);
	char *szToken;
	char *szSeps = ";";
	int	iMember = 0;
   
	char*	szPos[3];

	szToken = strtok(szTmp, szSeps);

	while(szToken != NULL) {
		
		szPos[iMember++] = szToken;

		iMember %= 3;

		//All members have been read out of trokens
		if (iMember == 0) {
			
			e_eptype	type = EPTYPE_UNKNOWN;

			if (strcmp(szPos[1], "FLOAT") == 0) {
				type = EPTYPE_FLOAT;
			} else if (strcmp(szPos[1], "FLOAT3") == 0) {
				type = EPTYPE_FLOAT3;
			} else if (strcmp(szPos[1], "DWORD") == 0) {
				type = EPTYPE_DWORD;
			}

			int			iOffset = -1;
			iOffset = atoi(szPos[2]);
			
			if (type != EPTYPE_UNKNOWN && iOffset >= 0) {
				EntityProperty* pP = new EntityProperty(szPos[0], type, iOffset);
				m_Properties.push_back(pP);
			}
		}

		szToken = strtok(NULL, szSeps);
	}

	free(szTmp);

	return 1;
}

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
Entity* EntityFactory::CreateEntity(const char* szClass) {
		
	EntityFactory* pFactory = GetFactory(szClass);
	if (pFactory == 0)
		return 0;
	
	Entity* pE = pFactory->CreateNewEntity();
	
	strcpy(pE->m_szClass, pFactory->m_szClass);
	
	return pE;
}

/*-------------------------------------------------------------------
pEntity			pointer to entity to get properties from
pProperties		pointer to an array to receive properties
iMax			size of array at pProperties (receives number of properties written to pProperties

RETURN:			maximum number of properties
-------------------------------------------------------------------*/
int EntityFactory::GetEntityProperties(Entity* pEntity, 
									   EntityProperty* pProperties, 
									   int *iMax) 
{
	if (pEntity == 0)
		return 0;

	EntityFactory* pF = GetFactory(pEntity->GetClass());
	if (pF == 0)
		return 0;

	std::vector<EntityProperty*>::iterator i;
	int idx = 0;

	for (i = pF->m_Properties.begin(); i < pF->m_Properties.end() && idx < *iMax; ++i) {
		
		EntityProperty* pP = *i;
		pProperties[idx] = *pP;

		idx++;
	}
	*iMax = idx;

	return (int) pF->m_Properties.size();
}

/*-------------------------------------------------------------------
-------------------------------------------------------------------*/
EntityFactory* EntityFactory::GetFactory(const char* szClass) {
		
	if (m_pFactories == 0)
		return 0;

	std::vector<EntityFactory*>::iterator	i;

	for (i = m_pFactories->begin(); i < m_pFactories->end(); ++i) {
		EntityFactory* pFactory = *i;
		if (strcmp(pFactory->GetClass(), szClass) == 0) {
			return pFactory;
		}
	}

	return 0;
}



/*-------------------------------------------------------------------


-------------------------------------------------------------------*/
template <class EntityClass> 
class EntityFac : public EntityFactory {

public:
	EntityFac(char* szClassName, char* szPropertyInfo) : EntityFactory (szClassName, szPropertyInfo) {	};
	Entity*	CreateNewEntity() {
		Entity* pE = new EntityClass ();
		return pE;
	};
};

class Player : public Entity {
private:
	float fTest;
};

int main() {

	EntityFac<Player> efacPlayer("Player", "health;FLOAT;60");

	Entity * pE1 = EntityFactory::CreateEntity("Player");
	Entity * pE2 = EntityFactory::CreateEntity("Player1");

	EntityProperty	p[10];
	int				m = 10;
	int				i;

	EntityFactory::GetEntityProperties(pE1, p, &m);

	while (m--) {
		std::cout<<p[m].GetName() << " - " << p[m].GetType() << " - " << p[m].GetOffset() << "\n";
	}

	i = EntityFactory::SetEntityProperty(pE1, "health", 100.00333f);

	std::cin;

	return 0;
}


Anyone else wanted to do some similar bevor and has come to a useable solution for this? Thanks in advance Mathias
Advertisement
Well, in your instance you may want to have a "type" field or something like that, unless you want people assigning health values to ammo crates.

So check that your entity is of the right type, then you can typecast it by doing *((Player*)&entity) and change the health parameter.
You do know that you can use the offsetof(Type, member) macro? It saves quite a lot of time when dealing with stuff like this. What you'll want to do is something like this:

EntityProperty* pP = new EntityProperty("health", EPTYPE_FLOAT, offsetof(CPlayer, fHealth));				


Hope that helps :)

edit: I forgot to mention that the offsetof() macro only really works for public members. Unfortunately, this makes it less useful as it breaks the whole point of private vs. public accesses, but at least it takes into account virtual function tables.

[Edited by - FReY on February 10, 2005 5:45:58 AM]
do unto others... and then run like hell.

Hi,

> EntityProperty* pP = new EntityProperty("health", EPTYPE_FLOAT, offsetof(CPlayer, fHealth));

This is very much of what i have been looking for. I cant cast a pointer of CEntity to CPlayer, since I dont know the class at runtime without looking at the m_szClass member. And I dont want to end with a ugly big chunk of if-else-statements!

What I want to be able is the following:
Lets say in my World-Editor (not even started yet..) I want to be able
to do s.th like:

CEntity* pEntity = World::Intersect(SomeRay);

pEntity->EnumProperties();

Show a dialog that lets the user modify all members of the intersected Entity.


Is there another way to solve this problem? Or is this a common method to do this?

Hope you all understand what I am trying to too!

Thanks Again

Mathias
hi there,

you could implement a COM-like QueryInterface method:

class BaseClass{public:  virtual void* GetInterface(int IID)  {    if (IID == IID_BASECLASS) return this;    else return NULL;  }};class Player : public BaseClass{public:  void* GetInterface(int IID)  {    if (IID == IID_PLAYER) return this;    else return BaseClass::GetInterface(IID);  }};class BotPlayer : public Player{public:  void* GetInterface(int IID)  {    if (IID = IID_BOTPLAYER) return this;    else return Player::GetInterface(IID);  }};main(){  BaseClass* pObj = new Player();  BotPlayer* pBot = (BotPlayer*)pObj->GetInterface(IID_BOTPLAYER);  //will return NULL  Player* pPlayer = (Player*)pObj->GetInterface(IID_PLAYER);  //will point at player}
I gave this Code to Sauruman once. You're welcome to have a look at is if you will find it useful.

It's the WIP implementation of my Entity class. Each entity has a propertyset, a message handler and can be part of a heirarchy.

I'm currently working to add 'class' definitions definiable by XML to allow serialisation of entities to and from an XML document.

Let me know if this was useful or if you need anything explaining. I'm still working on the code, so I'm looking for suggestions to improve it if you have any. Like I said, feel free to use it.
Hello everyone

@FReY:

I didnt know the macro offsetof! This is realy great by using this I don't have to calculate the offset by myself and more important: I can add new nembers inside a class and the offset in the factory-definition are updated automaticaly!

@evolutional:

I have been reading through your code just right now, sadly I am in a hurry and call look at it in detail right now. But it seem like there is a major difference between our implementations:

In your code, every entity (even if it is from the same class) can have different properities. Also, your proprities are no class members defined by compile time, but more variables dynamicaly allocated and registerd at runtime. So you alway have to call s.th. like GetFloatProperty("Health") to get the health value of an object - even inside methods of your class. eg:

bool Player::AmIdead() {    return (this->GetFloatProperty("Health") < 0.0f ? true : false);}


In the method I am trying to get working I can simply do:

(inside class methods)bool Player::AmIdead() {    return (this->m_fHealth < 0.0f ? true : false);}and:(from outside the class)float f = (Player*) p->GetProperty("Health");


Or have I missed some parts of your code?

It is very interessting to read anyway, since I haven't thought on how to implement message handlers for my Entities!

I will make some more test with my code and the use of offsetof.
If u like, I will post future successes here to let u all benefit from it!

Greez

Mathias
I would have to say I prefer evolutional's system to yours. I guess I am just a sucker for homogeneous notation of that sort. In my opinion, it is a non-issue that he must treat properties as dynamic things that cannot be referenced statically in member functions ... because that is EXACTLY his intention, for the property to be completely dynamic. Now, it is easy to take "data-driven" too far, but I dont really see that happening in this case.
Quote:Original post by MathiasW
In your code, every entity (even if it is from the same class) can have different properities. Also, your proprities are no class members defined by compile time, but more variables dynamicaly allocated and registerd at runtime. So you alway have to call s.th. like GetFloatProperty("Health") to get the health value of an object - even inside methods of your class. eg:


Well, technically with my system you'll be able to use it in a similar way to yours. I've added the ability to bind class members to the property set so you can use them with the property accessors. Of course, you can also create properties at runtime too.

The goal of my system is, as TRE said, to create a data-driven entity class. My final aim is to have it so that I can define an entity class in XML which is created at runtime. Obviously, I'll need some hardcoded classes if only for speed, but it'll allow the user to derive from the base (hard) class and extend it with their own properties if needed. I'm going to be expanding my entity really soon to handle the XML parsing and create my entity factory/manager to handle the entity classes.

My entity classes will be defined using XML and creation of a new entity will be a matter of creating an 'instance'. A new instance will have all the attributes of the template class but can be extended further if needed (via script or code).

The final, 'crucial' aim for me is to allow each entity to have scripted methods (which will probably be called via the event system). Again, these methods are added to the concrete entity classes at runtime and should allow overides to be made.


Quote:It is very interessting to read anyway, since I haven't thought on how to implement message handlers for my Entities!


Glad I could have been of some use. My message system is far from perfect and is still WIP, but it's a decent starting point.

It's nice to see that I'm not the only one playing around with propertysets for entites though [grin]
Quote:Original post by evolutional
The final, 'crucial' aim for me is to allow each entity to have scripted methods (which will probably be called via the event system). Again, these methods are added to the concrete entity classes at runtime and should allow overides to be made.


Just out of curiosity, what are your current plans for how to implement this?

This topic is closed to new replies.

Advertisement