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).
Edited by cr88192, 07 February 2013 - 04:00 AM.