In the previous chapter, we covered weapons. Weapons are a special type of entity, but an entity nonetheless.
In this chapter, we will look at entities more generally, and see how they work. Later, we'll create two point entities (trigger_timer and trigger_conprint - prints to the console) and a brush entity (a "counting impulse" button - the more the player presses E, the more it'll progress).
edict_t vs. entity classes
Let's make this clear. The engine doesn't know the difference between a
. Instead, the engine only knows one form of entity. And that is
It's basically an entity dictionary, containing data such as whether the entity's memory is free, its "serial number", its link to a BSP leaf, if it's a brush entity how many leaves it has, private entity data (to store member variables specific to the C++ classes of entities), and an entvars_t variable.
Generally, you won't use
, there's an
"pev" stands for "Pointer to Entity Variables", and it is essentially a set of variables that is common to all entities.
is defined in
, and constants that can be used with variables from entvars_t are in
Some of the most important variables from
classname - entity's classname
targetname - entity's name
target - target entity's name
origin - entity's current position
velocity - entity's current velocity
movedir - entity's current movement direction
angles - entity's current angles
avelocity - angular velocity
model - path to the model
nextthink - next time the entity will call its think callback
movetype - movement type of the entity, which can be:
MOVETYPE_NONE - entity won't move
MOVETYPE_WALK - entity will use walk physics (players only)
MOVETYPE_STEP - entity will use walk physics for monsters
MOVETYPE_FLY - entity not be affected by gravity
MOVETYPE_TOSS - affected by gravity
MOVETYPE_PUSH - entity can get pushed
MOVETYPE_NOCLIP - not affected by gravity, passes through everything
MOVETYPE_FLYMISSILE - FLY but with extra size so it can hit monsters
MOVETYPE_BOUNCE - affected by gravity, bounces from the surface depending on pev->friction
MOVETYPE_BOUNCEMISSILE - combination of FLYMISSILE and BOUNCE
MOVETYPE_FOLLOW - follows pev->aiment
MOVETYPE_PUSHSTEP - BSP model that doesn't move on its own, but can collide with other entities
solid - solidity mode of the entity, which can be:
SOLID_NOT - non-solid, everything passes through it
SOLID_TRIGGER - calls Touch, otherwise non-solid
SOLID_BBOX - uses its axis-aligned bounding box for collisions (this collision box cannot rotate, keep in mind), calls Touch when an entity touches one of its edges, can call Blocked
SOLID_SLIDEBOX - can only call Touch when on the ground, calls it when an entity touches one of its edges
SOLID_BSP - uses BSP cliphulls of the brush model for collisions, Touch on edge and can call Blocked
rendermode - render mode of this entity, can be:
kRenderNormal - Normal
kRenderTransColor - Color
kRenderTransTexture - Texture
kRenderGlow - Glow
kRenderTransAlpha - Solid
kRenderTransAdd - Additive
renderamt - render amount of this entity (typically wrongly named in FGDs as "FX Amount")
rendercolor - render colour
renderfx - render effect (look at
const.h, it's got many of them)
spawnflags - flags set in the map editor (in the Flags tab)
flags - temporary entity flags
Certain utility functions will accept
as a parameter, so you'll need to convert your
or any entity class you're using into
. While there's no direct conversion, you can use the
utility function to 'convert' an
. For example:
We'll discuss utility functions later on.
You may have noticed several variables of the
type. This is not
Beginners get very confused by
, since it's essentially an
, so I'll briefly explain it here.
The engine has a giant
is in fact just an offset into that giant string.
You can imagine it this way:
H e l l o w o r l d \0 f u n c _ w a l l \0
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
If we wanted to allocate a new string there via
, like so:
string_t something = ALLOC_STRING( "something" );
...it'd return 22. If we had a
variable with a value of 11,
would return 11. That is essentially what string_t is. Just an offset into the internal string.
The mother of all entities.
As mentioned previously, every entity in the HL SDK inherits from
. We won't cover all of its methods and members, but we will go through the most important ones.
Called for every entity in the map by the engine.
Entities override this so they can load model, sound and other files that they may use.
Files can be precached with the following macros:
- precaches studio models
- precaches audio files
- precaches any file, you can even precache a whole .bsp if you wanted to
Handles map keyvalues and assigns values from them to member variables. The engine calls this multiple times until it covers all keyvalues of every entity.Parameters:
pkvd - the data of one keyvalue
This is how keyvalues are typically handled:
if (FStrEq(pkvd->szKeyName, "myKeyvalue"))
m_myVariable = atoi(pkvd->szValue);
pkvd->fHandled = TRUE;
to all possible keyvalues this entity may have, and then convert the
string into a value of the type we need.
If no match is found, then go back up to the KeyValue method of our superclass.
are the key methods for transferring custom data fields across savefiles. If an entity has its own members, but doesn't save/restore, then it is most likely to break while loading a savefile, or across level transitions.
A save/restore is fully set up when an entity class has defined and implemented a save-restore table, as well as the save-restore methods.
Inside the entity class, you override the
methods as well as declaring the save-restore table (
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData;
Outside of the entity class, you define the save-restore table like this:
Note: Valve generally placed this near either the class declaration or the
TYPEDESCRIPTION CPendulum::m_SaveData =
DEFINE_FIELD( CPendulum, m_accel, FIELD_FLOAT ),
DEFINE_FIELD( CPendulum, m_distance, FIELD_FLOAT ),
DEFINE_FIELD( CPendulum, m_time, FIELD_TIME ),
DEFINE_FIELD( CPendulum, m_damp, FIELD_FLOAT ),
DEFINE_FIELD( CPendulum, m_maxSpeed, FIELD_FLOAT ),
DEFINE_FIELD( CPendulum, m_dampSpeed, FIELD_FLOAT ),
DEFINE_FIELD( CPendulum, m_center, FIELD_VECTOR ),
DEFINE_FIELD( CPendulum, m_start, FIELD_VECTOR ),
IMPLEMENT_SAVERESTORE( CPendulum, CBaseEntity );
LINK_ENTITY_TO_CLASS line, but it can be placed anywhere.
requires 3 parameters: the class being saved/restored, the attribute being saved/restored and its type. The following table contains all available types of variables you can save and restore. The types with an asterisk (
) are the ones you will see and likely use very often in the HL SDK :
|FIELD_FLOAT*||A floating point value (|
75.5) that is not related to time (use
FIELD_TIME for that purpose)
|FIELD_STRING*||A string ID (|
string_t - often the result of
|FIELD_ENTITY||An entity offset (|
|FIELD_CLASSPTR*||An entity class pointer (like |
|FIELD_EHANDLE*||An entity handle (|
|FIELD_EVARS||An entity variables pointer (|
|FIELD_EDICT||An entity dictionary pointer (|
|FIELD_VECTOR*||A vector (|
vec3_t, array of 3
|FIELD_POSITION_VECTOR||A world coordinate (fixed up across level transitions "automagically")|
|FIELD_POINTER||Arbitrary data pointer (scheduled to be removed by Valve but seems that isn't the case)|
|FIELD_INTEGER*||An integer (|
|FIELD_FUNCTION||A class function pointer (like |
|FIELD_BOOLEAN*||A "boolean" value (see the warning below as to why boolean is between quotes)|
|FIELD_SHORT||A 2 byte integer value.|
|FIELD_CHARACTER||A single byte value.|
|FIELD_TIME*||Same as |
FIELD_FLOAT but for variables related to time (like weapons reload time), usually a floating point value that has a relation to the game time (
|FIELD_MODELNAME||An engine string that is a model's name (requires precaching).|
|FIELD_SOUNDNAME||Same as previous one but for sounds.|
The last line,
requires 2 parameters: the class being saved and its parent. It tells the save/restore code to take care of your entity variables as well as the parent's ones. This is handy because it avoids having to redefine every variable from the parent(s).
A way of storing read-only flags for entities. Entities can override this to return certain constants that will make the entity usable by the player, or held down continuously, transferable between levels etc.
The constants can be:
- unknown and unused
- the entity will transfer across level transitions
- calls Spawn right after Restore, i.e. after loading a savefile
- don't write into the savefile
- can be used by the player
- can be held by the player (such as levers and valves)
- can be toggled by the player
- receives +/- from the player, only used by func_tracktrain
- entity can be used as a master (multisource has this cap)
- entity always goes across transitions
Called on every entity after the server is activated. It is used, for example, by CFuncTrain/func_train to teleport itself to the first path_corner as soon as the server starts. It is rarely used.
These methods are actually callbacks.
void (CBaseEntity ::*m_pfnThink)(void);
void (CBaseEntity ::*m_pfnTouch)( CBaseEntity *pOther );
void (CBaseEntity ::*m_pfnUse)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void (CBaseEntity ::*m_pfnBlocked)( CBaseEntity *pOther );
This means that each can be changed at any time.
Called every time period, or every tick, this method is often used by entities to perform real-time or otherwise periodic logic, e.g. check if a certain entity is within radius every 0.5 seconds.
To set a think callback, use the
To set the next time an entity will "think", set the
pev->nextthink = gpGlobals->time + 0.5; // think every 0.5 seconds
Called every time the entity is touched by another entity. Entities such as trigger_once absolutely rely on this to work.
To set a touch callback, use the
pOther - entity that touched this entity
Called either by players when USE-ing an entity, or by other entities that have the ability to trigger other entities. For example, when a func_button triggers a func_door, it is calling the func_door's Use method.
To set the use callback, use the
pActivator - entity that started the trigger sequence
pCaller - entity that actually fired this entity
useType - type of usage: USE_TOGGLE, USE_ON, USE_OFF and USE_SET
value - the value sent from pCaller, typically used only in func_tracktrain
Called every time an entity blocks this entity.
This is essentially how doors and trains damage and gib things. They apply damage to any entity that blocked them.Parameters:
pOther - entity that blocked this entity
These methods are useful in certain situations.
Allocates a new entity of any classname and spawns it at given coordinates. Returns a CBaseEntity pointer to the newly spawned entity.Parameters:
szName - the classname itself, e.g. env_sprite, monster_zombie etc.
vecOrigin - the position at which it'll spawn
vecAngles - the angles it'll have after spawning
pentOwner - owner of the newly created entity (NULL by default)
Retrieves private data from an existing entity. It is used to 'convert'
or entity indices into
pent - entity dictionary/edict to convert from (
pev - entity variables to convert from (
eoffset - entity index to retrieve from (
If you wonder how the engine actually calls these functions, it is done through the Dispatch functions:
It locates their addresses when it loads
, and then calls them for each
in its internal array of entities.
Other base classes and important entity classes
- worldspawn entity
- base item class
- generic delay entity, can be spawned when an entity tries to trigger another entity with a delay
CBaseAnimating - entity that supports studio model animations, provides utilities for submodels, setting controllers etc.
CBasePlayerItem - generic player item
CBasePlayerWeapon - generic player weapon
CBaseToggle - generic toggleable entity, implements linear movement for trains, opening and closing mechanism for doors etc.
CBaseMonster - base monster class
CBasePlayer - player class
On the next page, you'll learn how to create a very basic entity that will print a mapper-defined message to the console.Note: The next page isn't actually written yet.