Sign in to follow this  
MathiasW

Game entities and their proprities

Recommended Posts

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

Share this post


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

Share this post


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

Share this post


Link to post
Share on other sites

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

Share this post


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


Share this post


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

Share this post


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

Share this post


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

Share this post


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

Share this post


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

Share this post


Link to post
Share on other sites
Quote:
Original post by The Reindeer Effect
Just out of curiosity, what are your current plans for how to implement this?


I'm looking at two methods; one's going to be a simple command-style language that again will be loaded as a list of actions that can be 'triggered' by a user-definable event.

The other way I'm going to look at it is to use a real scripting language instead (probably GameMonkey Script or SpiderMonkey). Think of how javascript is added to HTML "onClick" events are declared, or some kind of .NET delegate. I'll have a section in the XML that allows the user to define their own events and provide a script method to call; each script method should have automatically pass a 'sender' and a 'trigger'.

Note that all scripting is in GameMonkey script for this pseudoexample.


<entityClass id="Door">
<entityEvent name="Open" call="door_OnOpened(this, trigger);" />
<entityAttrib name="isOpen" type="bool" value="false" />
</entityClass>


<entityInstance class="Door" id="NormalDoor">
<!-- When object tries to 'Open' the door, the normal script is called -->
</entityInstance>

<entityInstance class="Door" id="BigDoor">
<entityEvent overrides="Open" call="bigdoor_OnOpened(this, trigger);" />
</entityInstance>


//// Then some script...

door_OnOpened = function( this, triggerBy )
{
this.isOpen = true;
return true;
};


bigdoor_OnOpened = function( this, triggerBy )
{
if triggerBy.strength < someArbitraryValue
{
this.isOpen = true;
return true;
}

// else
gameMessage( "Not strong enough to open the door!" );
return false;
};








Here I'm likely to make scripting work like HTML/javascript eg: each 'call' will effectively be a mini-script that will have the 'this' and 'triggerBy' objects made available to the sandbox. Notice that here, in script, I can use the attributes as they normally appear. This could be done in real-time when checking for the attribute using GameMonkey script (because it's cool [wink])



Thinking in the dynamic/data-driven mindset, my native methods for events won't be normal method calls, they'll be events that need to be registered with the entity. Sure, these events will call native functions, but that's not the point. What I'm going to do to my current message/event system is allow an entity to keep a list of overrides for a given event. If an event is marked as overriden, a special (native) method will be called to pass the relevant parameters to the script and call the scripted function.

So, I could potentially hardcode a generic spaceship entity class but override the OnThink method with a scripted method to give it some unique kamikaze behaviour [wink].

Like I said, this is currently WIP and so have no idea about how effctive it'll be... But if it works it could really make things really cool in my games... And anyone else who'll want it too as I release most of my stuff as open-source.

Edit:

I forgot to add; each entity will have a native method to allow it access to the scripted functions. Something like Entity::CallMethod("name", this, that) - this then opens up the scripted overrides to the programmer who wants to work totally in script for things like AI, etc.

[Edited by - evolutional on February 10, 2005 2:37:56 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by evolutional
...


A while back I was implementing pretty much what you described in the first one. Sort of think ... Starcraft campaign editor. You deal with a whole bunch of predefined, hardcoded commands that you combine to create your logic with. I also looked into the scripting-combined-with-xml approach, but I really didnt like it as I started to work with both XML and a scripting language. It felt so natural to do one or the other, but for some reason I just never got to far into it. Maybe if I managed my scripts and data better it would have been nicer, but as it was I just sort of overloaded. I have been playing around with lisp alot lately, and I plan on using it for my next project. The cool thing is that it is so natural to merge process and data. I will be using pure scripts, not much need for XML since I can do it all the lisp way. Of course this sort of locks me into lisp, but if worst comes to worst I can just use lisp as my own version of xml and then have the rest written in C++/C#/whatever. Don't think I would do that, but its a possibility.

Although whatever gets the job done, I just wanted an excuse to use lisp more.

Share this post


Link to post
Share on other sites
Quote:

If u like, I will post future successes here to let u all benefit from it!


Yes! Let us know how it goes :)

Share this post


Link to post
Share on other sites
Quote:
Original post by The Reindeer Effect
A while back I was implementing pretty much what you described in the first one. Sort of think ... Starcraft campaign editor. You deal with a whole bunch of predefined, hardcoded commands that you combine to create your logic with.


I can see this as being painful as well. Mainly because of the logic-aspect of it. Whilst it works for XSL it could be too cumbersome for use in a game.


Quote:
I also looked into the scripting-combined-with-xml approach, but I really didnt like it as I started to work with both XML and a scripting language. It felt so natural to do one or the other, but for some reason I just never got to far into it. Maybe if I managed my scripts and data better it would have been nicer, but as it was I just sort of overloaded.


I'm using this method for another project, jsInvaders, and it's not too bad. However, it probably would be good to have both data and process mixed in with script; GameMonkey script is a lot like Lua so allows you to do just this. Either way, I'm likely to create versions of all three approaches and see which one works best for me - I'm going to hazard a guess that each one will be context-dependant on how suited it is to a task.

The first approach would probably be better for level editing as it'll allow no-brain scripting without ability to code as a requirement, however it'll be restricted to the actions the programmer puts into the game. The second approach would allow for decent editing ability and will give the user more freedom in creating their own actions, but the downside is that the user will need to be more capable at coding as it will no longer be drag/drop. Finally, the second approach could potentially be like 2 with more power, but the user will have to be competent at coding in order to see the benefit. It also raises questions about level editing tools being smart enough to deal with the scripts.


Quote:

Although whatever gets the job done, I just wanted an excuse to use lisp more.


[grin] - Exactly, I'm just looking for things to play around with. I've never used lisp and haven't really ever considered doing so, maybe I should take a look some time. Thanks for your comments :)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this