An entity class in a game - contents

Started by
8 comments, last by Helixirr 11 years, 2 months ago

Hi!

While desperately trying to create a game engine to fuel up a game idea of mine, I have encountered several problems in design of the game engine. What kind of class hierarchy might there be? I'm sure I'm not alone with this problem, no matter the object-oriented language.

I'm not thinking about covering the whole class hierarchy here. I'm talking about an Entity-class. Pretty self-explanatory. The question is, what kind of data (member variables) should this class contain? Direction and position vector? 3D model object? Entity name? Although this is game dependent, what should be the general structure for the Entity-class? unsure.png

Advertisement

This is largely your own decision based on your own taste. But one example is:

CActor inherits from CEntity.

CEntity provides a child/parent system (scene graph) so that objects can be children of other objects.

CActor is a CEntity and has a COrientation class which describes the position, scale, and rotation of the object. It also has local and world matrices and an actor ID.

And a boolean describing whether or not it inherits its transform.

Sometimes CActor and CEntity are merged into one class, in which case that class would do all of the above.

In any case, this is the normal extent of the class(es). Everything in the scene inherits from these, including cameras, 3D models, etc.

L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

Remember to program to interfaces, not directly to entity classes.

For your IGameEntity object, I'd recommend something like the following:

Vector3 Position { get; } // Note that this can return an "out of world" coordinate. A good constant for that is all values set to Float Max.
Vector3 ForwardVector { get; }
Vector3 UpVector { get; }
...
void SetPosition(...)
void SetRotation(...)
void SetForward(...)
...
void AddToWorld()
void RemoveFromWorld()
bool InWorld()
bool InInventory()
bool AddFlags(...)
bool RemoveFlags(...)
...
void SetModel(...)
void SetGeometry(...)
void SetMaterial(...)
and so on, depending on what you need your objects to do.

I'm not thinking about covering the whole class hierarchy here. I'm talking about an Entity-class. Pretty self-explanatory. The question is, what kind of data (member variables) should this class contain?

Actually it's not that self explanatory, because entity is just another word for 'thing' --"what should I put in my thing?".
Assuming every object in your world is going to inherit from this class, then first just start making your other classes (withou inheritance) and then find the union of their members -- these are the members that every thing has.

Also, there's not much point in having a common base class for every thing unless you have some algorithms that need to operate on every thing, without knowing what kind of things they are.
E.g. Some engines assume every thing is drawable, and have a common algorithm like -- for each thing, draw thing -- which they achieve with a virtual void Draw member function in Entity.

Personally, I don't use an Entity class because I couldn't find any common abstraction for all of my things.

I'm not thinking about covering the whole class hierarchy here. I'm talking about an Entity-class. Pretty self-explanatory. The question is, what kind of data (member variables) should this class contain?

Actually it's not that self explanatory, because entity is just another word for 'thing' --"what should I put in my thing?".
Assuming every object in your world is going to inherit from this class, then first just start making your other classes (withou inheritance) and then find the union of their members -- these are the members that every thing has.

Also, there's not much point in having a common base class for every thing unless you have some algorithms that need to operate on every thing, without knowing what kind of things they are.
E.g. Some engines assume every thing is drawable, and have a common algorithm like -- for each thing, draw thing -- which they achieve with a virtual void Draw member function in Entity.

Personally, I don't use an Entity class because I couldn't find any common abstraction for all of my things.

Good points! smile.png Abstraction isn't always the solution. It is a good idea to keep classes in their own units so that they wouldn't be too connected to each other, otherwisely there's a risk that everything's going to break when one class breaks. wacko.png

Remember to program to interfaces, not directly to entity classes.

For your IGameEntity object, I'd recommend something like the following:

Vector3 Position { get; } // Note that this can return an "out of world" coordinate. A good constant for that is all values set to Float Max.
Vector3 ForwardVector { get; }
Vector3 UpVector { get; }
...
void SetPosition(...)
void SetRotation(...)
void SetForward(...)
...
void AddToWorld()
void RemoveFromWorld()
bool InWorld()
bool InInventory()
bool AddFlags(...)
bool RemoveFlags(...)
...
void SetModel(...)
void SetGeometry(...)
void SetMaterial(...)
and so on, depending on what you need your objects to do.

The list looks promising. smile.png I have one question, though, which might be a little off topic, but what are vectors forward, position and up supposed to represent? I can understand position vector, but what kind of movement or placement are the other two supposed to represent? Is there something wrong with using vectors direction, position and velocity? huh.png

Here's a rough structure for my Entity-class (in C++):


/// ------------------------------------
/// @class	Entity
/// @brief	Base class for all entities.
/// ------------------------------------
class Entity{
public:

	/// Constructors & destructors:
	virtual ~Entity(void);

	/// Member data:
	Entity::Type type;
	ZD::Vector3<float> vector_forward, vector_position;

	/// Member functions:
	inline bool is_visible(void) const{
		return _m_bVisible;
	}
	virtual void show(void) const = 0;
	Entity& set_visible(bool const& visible);
	Entity& set_visible(bool&& visible);

	/// Member functions (overloaded operators):
	Entity& operator=(Entity const& entity) = default;
	Entity& operator=(Entity&& entity) = default;

protected:
	/// Constructors & destructors:
	Entity(void);
	Entity(Entity const& entity) = default;
	Entity(Entity&& entity) = default;
	/// Member data:
	bool _m_bVisible;
};

Would be this suitable base class? mellow.png

There are many ways of forming a class hierarchy for an entity system, and a lot of it is based on your own preference. For example, I personally prefer a component based (an old, but still useful presentation from Scott Bilas who worked at Gas Powered Games at the time: http://scottbilas.com/files/2002/gdc_san_jose/game_objects_slides.pdf) system, searching the net will find you a few resources too. Though it is generally less talked about than graphics.

Would be this suitable base class?

My initial thought on looking at the code is, unfortunately, ''no'', the way you've structured it I would make the copy constructor and assignment operators private otherwise anything derived from it have to override them (and they are not virtual, so anything using them via the base class would break). This may have been a typo but, I would say, you would never need to copy an object like this anyway.

If you can think of many ways where you would like to do this, then my preference would be for a ''virtual Entity* Clone()'' function to be exposed (but only if you'd deem it a necessity) rather than copy ctor and assignment operators.

I'm also unsure why you have a virtual 'show' function as well as set_visible functions. They seem, at first glance, to be the same thing. However, I would do somethinng more like:


void isVisible() const { return m_isVisible; }
void show() { m_isVisible = true; }
void hide() { m_isVisible = false; }

I would also expect there to be an update() function somewhere. One suggestion I would give, is that I see a lot of games where the update function takes a time delta alone. eg:


virtual void update( float timeDelta );

I would recommend that you pass in,rather, a structure instead: similar to:


struct UpdateArgs
{
    float timeDelta;
};
...
virtual void update( const UpdateArgs &updateArgs );

Which allows you to extend the arguments passed to an object without having to revisit every class. For example, you could add a ISystem* or other things that are globally necessary to it.

And whilst virtual functions are awesome, think carefully before you make a function virtual. They can be a large performance loss on some machines, and even can be on the PC if much of your architecture relies on them. This is not an anti-virtual post though, I'm not advocating to avoid them completely just that you put thought into what you make virtual smile.png

I would also expect a serialization method in the entity class (whether it's a base class, or the owner of a component system).

Hopefully thats of some use,

n!

maybe people will agree, or maybe not, but an important question may need to be addressed:
will there be a single type of "entity" shared between both the client and server? (or, will there be client/server split in the first place?);
or, will there be a separate set of client and server entities?


this may seem odd, but has a basis in this:
properties which are useful, say, for something which may be seen or rendered, may not necessarily be the same as those related to something more closely connected to the game logic.

usually though, a common set of general properties can be derived though:
what type of entity is it? (such as a "classname");
where is it? (origin);
where it is going? (velocity);
where is it facing? (angles or rotation);
how big is it? (bounding box / AABB: mins/maxs);
does it have a 3D model? (modelname, animation and/or frame-number, ...);
is it solid or subject to physics? (movetype, solidtype, ...);
...


sub-classes, such as "Actor", may make sense for the game logic (providing methods for character behavior), but have only a weak relationship on the client (say, we have a skeletal model, which may be useful for plenty of things which aren't "actors", and maybe even actor-type entities which may use a static 3D model, or a sprite, ...).

and, sometimes, straightforward inheritance may not make much sense for a lot of the behavior anyways.

as mentioned by someone else, interfaces may be a better solution, or maybe have a fairly generic "Entity" class, and maybe an "Actor" class, which mostly remembers really general fields/... needed by pretty much all entities of a given catagory, and leave most other things to interfaces.


eg (adjust to language of choice):
public interface IEntityAction
{
public function use(other:Entity):void;
public function touch(other:Entity):void;
public function blocked(other:Entity):void;
public function pain(other:Entity, damage:float):void;
public function die(other:Entity, damage:float):void;
public function prethink():void;
}

public interface IActor extends IEntityAction
{
public function stand():void;
public function walk():void;
public function run():void;
public function missile():void;
public function melee():void;
}

another relevant event is "think", but think-events would probably be better handled with a dedicated interface, or (assuming the language supports it) some sort of callback function.

setThinkDelay(function() { do-something... }, 0.25);
//do-something 0.25 seconds from now.


as for how I did it... well, it gets messy (actually, some of the worst code in the project IMO. I wouldn't recommend doing it exactly this way...).

I have a client/server split, with an entity being mostly a server side concept (the client side sees entities more like "a 3D model or similar placed somewhere in space" or "something which a sound-effect or other effect is emitted from").


the actual main server code is written in C, so has an "Entity" mostly as a C struct, containing mostly general purpose fields (classname, origin, solidtype/movetype, mins/maxs, ...), and a few spots to plug in "entity-type specific data" and similar.

a lot of the general entity logic (such as core AI behaviors), is also currently written in C (but would probably be better as script code, ...).


for parts written in script-code, there is an actual "EntityBase" class, as well as an "ActorBase" class (which basically defines a few abstract methods for other Actor-type entities to implement).
actually, the these class instances are independent of the C Entity objects, just when something is spawned, it is rigged up something like (if the relevant class is found):
the Entity struct is created (and initialized with the contents of the "SpawnEntity", which is basically a list of key/value pairs);
a new instance of the classname is created (passing the Entity-struct to the constructor), with the top-level EntityBase-class constructor basically just shoving it into an instance variable called "self" (so, "self" is the C-struct, but "this" is the current object);
the new object-instance reference is shoved into the Entity struct (so C code can call its methods, ...);
many C-side handlers may redirect method-calls into the script-code as-needed (typically passing self, again, as an argument), otherwise they fall back to trying to use C function pointers (in some other cases, the C function-pointer is simply set to the handler to redirect the call into the script method).

there are "Item"'s, but they are their own thing (and only really exist as entities when dropped).


usually, individual classes are created for specific spawnable entities, and usually use a different naming convention.

IOW, example:
EntityBase
* ActorBase
** monster_generic2
*** monster_army
*** monster_enemyhead
*** passive_sheep
*** passive_pig
...

"monster_*", "passive_*", "func_*" ... basically giving general groups of "things that can be spawned in world" (whereas, trying to spawn an "ActorBase" or "EntityBase" wont really work). currently, they also either need to be in the toplevel package, or spawned with a classname something like "mypackage/foo_whatever", or the spawn-entity needs to supply a "classpkg" property in addition to "classname".

"monster_generic2" is a very bland soldier-type enemy (the first one written in script code), which a few other enemies are derived from (at least, the non-C ones, which are largely just copy/pasted code, from the original C-land "monster_generic", with "monster_generic2" mostly just being a script-port of the original monster logic).

if I were doing it again, with the current form of my script-VM available, I would probably write the server end mostly in script code (rather than the current ugly mix of largely C and some script-code).

Good points! :) Abstraction isn't always the solution. It is a good idea to keep classes in their own units so that they wouldn't be too connected to each other, otherwisely there's a risk that everything's going to break when one class breaks. :wacko:

Again, if you are using SOLID development principles, you will program to interfaces. Dependency inversion -- or programming to interfaces -- is an extremely beneficial part of well-written code.


The list looks promising. :) I have one question, though, which might be a little off topic, but what are vectors forward, position and up supposed to represent? I can understand position vector, but what kind of movement or placement are the other two supposed to represent? Is there something wrong with using vectors direction, position and velocity? :huh:

A Vector or Point is actually a 4-entry array: X, Y, Z, W. For a Point, W=1. For a Vector, W=0. Otherwise the two are interchangeable.

Position is a point in space.
Velocity is a vector.

But you suggest that "direction" is a velocity? It does not work mathematically. You can represent 3D orientation with a matrix, or with a quaternion, or with two vectors. Quaternion is the most compact, requiring four values. Two vectors is the next most compact; I prefer it because you can easily perform other operations on it; dot products for forward-facing tests are the most obvious. If you need the third component you can use a cross product of the forward and up.

Good points! smile.png Abstraction isn't always the solution. It is a good idea to keep classes in their own units so that they wouldn't be too connected to each other, otherwisely there's a risk that everything's going to break when one class breaks. wacko.png

Again, if you are using SOLID development principles, you will program to interfaces. Dependency inversion -- or programming to interfaces -- is an extremely beneficial part of well-written code.

>
The list looks promising. smile.png I have one question, though, which might be a little off topic, but what are vectors forward, position and up supposed to represent? I can understand position vector, but what kind of movement or placement are the other two supposed to represent? Is there something wrong with using vectors direction, position and velocity? huh.png

A Vector or Point is actually a 4-entry array: X, Y, Z, W. For a Point, W=1. For a Vector, W=0. Otherwise the two are interchangeable.

Position is a point in space.
Velocity is a vector.

But you suggest that "direction" is a velocity? It does not work mathematically. You can represent 3D orientation with a matrix, or with a quaternion, or with two vectors. Quaternion is the most compact, requiring four values. Two vectors is the next most compact; I prefer it because you can easily perform other operations on it; dot products for forward-facing tests are the most obvious. If you need the third component you can use a cross product of the forward and up.

I may have implicitly suggested direction to be the same as velocity, but I didn't mean that, I simply forgot to mention about velocity separately. rolleyes.gif

You can represent 3D orientation with a matrix, or with a quaternion, or with two vectors. Quaternion is the most compact, requiring four values. Two vectors is the next most compact; I prefer it because you can easily perform other operations on it; dot products for forward-facing tests are the most obvious. If you need the third component you can use a cross product of the forward and up.

This part of your post was very helpful for me, because it hasn't been clear to me if I should use quaternions or vectors to represent 3D orientation. I guess quarternions would be alright. Have to think about this, though. unsure.png

maybe people will agree, or maybe not, but an important question may need to be addressed:
will there be a single type of "entity" shared between both the client and server? (or, will there be client/server split in the first place?);
or, will there be a separate set of client and server entities?


this may seem odd, but has a basis in this:
properties which are useful, say, for something which may be seen or rendered, may not necessarily be the same as those related to something more closely connected to the game logic.

usually though, a common set of general properties can be derived though:
what type of entity is it? (such as a "classname");
where is it? (origin);
where it is going? (velocity);
where is it facing? (angles or rotation);
how big is it? (bounding box / AABB: mins/maxs);
does it have a 3D model? (modelname, animation and/or frame-number, ...);
is it solid or subject to physics? (movetype, solidtype, ...);
...


sub-classes, such as "Actor", may make sense for the game logic (providing methods for character behavior), but have only a weak relationship on the client (say, we have a skeletal model, which may be useful for plenty of things which aren't "actors", and maybe even actor-type entities which may use a static 3D model, or a sprite, ...).

and, sometimes, straightforward inheritance may not make much sense for a lot of the behavior anyways.

as mentioned by someone else, interfaces may be a better solution, or maybe have a fairly generic "Entity" class, and maybe an "Actor" class, which mostly remembers really general fields/... needed by pretty much all entities of a given catagory, and leave most other things to interfaces.


eg (adjust to language of choice):
public interface IEntityAction
{
public function use(other:Entity):void;
public function touch(other:Entity):void;
public function blocked(other:Entity):void;
public function pain(other:Entity, damage:float):void;
public function die(other:Entity, damage:float):void;
public function prethink():void;
}

public interface IActor extends IEntityAction
{
public function stand():void;
public function walk():void;
public function run():void;
public function missile():void;
public function melee():void;
}

another relevant event is "think", but think-events would probably be better handled with a dedicated interface, or (assuming the language supports it) some sort of callback function.

setThinkDelay(function() { do-something... }, 0.25);
//do-something 0.25 seconds from now.


as for how I did it... well, it gets messy (actually, some of the worst code in the project IMO. I wouldn't recommend doing it exactly this way...).

I have a client/server split, with an entity being mostly a server side concept (the client side sees entities more like "a 3D model or similar placed somewhere in space" or "something which a sound-effect or other effect is emitted from").


the actual main server code is written in C, so has an "Entity" mostly as a C struct, containing mostly general purpose fields (classname, origin, solidtype/movetype, mins/maxs, ...), and a few spots to plug in "entity-type specific data" and similar.

a lot of the general entity logic (such as core AI behaviors), is also currently written in C (but would probably be better as script code, ...).


for parts written in script-code, there is an actual "EntityBase" class, as well as an "ActorBase" class (which basically defines a few abstract methods for other Actor-type entities to implement).
actually, the these class instances are independent of the C Entity objects, just when something is spawned, it is rigged up something like (if the relevant class is found):
the Entity struct is created (and initialized with the contents of the "SpawnEntity", which is basically a list of key/value pairs);
a new instance of the classname is created (passing the Entity-struct to the constructor), with the top-level EntityBase-class constructor basically just shoving it into an instance variable called "self" (so, "self" is the C-struct, but "this" is the current object);
the new object-instance reference is shoved into the Entity struct (so C code can call its methods, ...);
many C-side handlers may redirect method-calls into the script-code as-needed (typically passing self, again, as an argument), otherwise they fall back to trying to use C function pointers (in some other cases, the C function-pointer is simply set to the handler to redirect the call into the script method).

there are "Item"'s, but they are their own thing (and only really exist as entities when dropped).


usually, individual classes are created for specific spawnable entities, and usually use a different naming convention.

IOW, example:
EntityBase
* ActorBase
** monster_generic2
*** monster_army
*** monster_enemyhead
*** passive_sheep
*** passive_pig
...

"monster_*", "passive_*", "func_*" ... basically giving general groups of "things that can be spawned in world" (whereas, trying to spawn an "ActorBase" or "EntityBase" wont really work). currently, they also either need to be in the toplevel package, or spawned with a classname something like "mypackage/foo_whatever", or the spawn-entity needs to supply a "classpkg" property in addition to "classname".

"monster_generic2" is a very bland soldier-type enemy (the first one written in script code), which a few other enemies are derived from (at least, the non-C ones, which are largely just copy/pasted code, from the original C-land "monster_generic", with "monster_generic2" mostly just being a script-port of the original monster logic).

if I were doing it again, with the current form of my script-VM available, I would probably write the server end mostly in script code (rather than the current ugly mix of largely C and some script-code).

This post is so long I can barely do justice for it, but the effort you put into this is excellent. I like the question-like approach to the problem: "Can it do this?" "Does it contain this?" etc. smile.png

There are many ways of forming a class hierarchy for an entity system, and a lot of it is based on your own preference. For example, I personally prefer a component based (an old, but still useful presentation from Scott Bilas who worked at Gas Powered Games at the time: http://scottbilas.com/files/2002/gdc_san_jose/game_objects_slides.pdf) system, searching the net will find you a few resources too. Though it is generally less talked about than graphics.

Would be this suitable base class?

My initial thought on looking at the code is, unfortunately, ''no'', the way you've structured it I would make the copy constructor and assignment operators private otherwise anything derived from it have to override them (and they are not virtual, so anything using them via the base class would break). This may have been a typo but, I would say, you would never need to copy an object like this anyway.

If you can think of many ways where you would like to do this, then my preference would be for a ''virtual Entity* Clone()'' function to be exposed (but only if you'd deem it a necessity) rather than copy ctor and assignment operators.

I'm also unsure why you have a virtual 'show' function as well as set_visible functions. They seem, at first glance, to be the same thing. However, I would do somethinng more like:


void isVisible() const { return m_isVisible; }
void show() { m_isVisible = true; }
void hide() { m_isVisible = false; }

I would also expect there to be an update() function somewhere. One suggestion I would give, is that I see a lot of games where the update function takes a time delta alone. eg:


virtual void update( float timeDelta );

I would recommend that you pass in,rather, a structure instead: similar to:


struct UpdateArgs
{
    float timeDelta;
};
...
virtual void update( const UpdateArgs &updateArgs );

Which allows you to extend the arguments passed to an object without having to revisit every class. For example, you could add a ISystem* or other things that are globally necessary to it.

And whilst virtual functions are awesome, think carefully before you make a function virtual. They can be a large performance loss on some machines, and even can be on the PC if much of your architecture relies on them. This is not an anti-virtual post though, I'm not advocating to avoid them completely just that you put thought into what you make virtual smile.png

I would also expect a serialization method in the entity class (whether it's a base class, or the owner of a component system).

Hopefully thats of some use,

n!

Sorry for double posting, but I also had to cover this one separately.

I admit using "show" along with "set_visible" member functions. "show" should be replaced by "update". That way it would be more clear.

Time delta solution looks very handy, have to put that into consideration.

This topic is closed to new replies.

Advertisement