Where is Poppy
Your First Custom Entity
You can find the accompanying PDF here.
The Original Tutorial can be read here.
You can Download the Project Source Code and Releases through Github.
This is Part 2
Due to the monstrous size of this tutorial and the styling applied I had to split it into two parts because of Moddb's 100000 Character Limit.
You can Read the First Part here
You can read the complete tutorial over at Sourcemodding.
You can Read the First Part here
You can read the complete tutorial over at Sourcemodding.
Animation
You might wonder why we want to include animation with something that is supposed to be an inanimate static object? There may be situations for instance where we use this loader for a plant or a bush which could include a sway animation to mimic movement in the wind.
Let’s add to our header file an integer to hold the id for what animation our currently loaded model should be playing. I add it to the end of our other unsigned short for ease of use and reuse of code.
Next let’s add to the KeyValue Function so that it reads in the correct value for the animation sequence.
The whole function should now look like this:
We must then set the sequence based on what the user input through hammer. We do that in the Spawn function near the end. We must add the following:
Another small change we should make while we are in the Spawn function is to change this:
To:
This makes sure that the correct sequence Collision box will be used if the user selects to use that collision mode.
We then need to add the following to our Animate function so that the animation can play.
For those unfamiliar with the above line it is known as a ternary operation, basically an inline if else statement. Its written like this simply as an arguably easier alternative which is a little faster to write.
The equivalent as an if else would be:
Basically we are just making sure that the frames are incrementing with each update and if we reach the max of 255 reset to zero and start incrementing again.
pev->frame controls the individual frames in a sequence.
We must change our FGD from:
To:
We use the following to set a sequence that we can visualize in the editor, for some reason this does not work for actually setting the value we would expect to see in game and that’s why we have a separate animate key. I believe that the word “sequence” is reserved much like the keys “mins” and “maxs”. I could not for the life of me get them to work.
And finally we use the following to set what sequence will actually be used in game
Save the FGD, compile the code, restart Hammer and you should see 2 new entries in the properties of our entity.
The Editor Entry changes it for the editor and the Game Entry changes it in game so be sure to set the in game entry correctly if you intend to use animations.
Try the editor version and watch as your model plays the different animations you switch to. I highlight this option as an easy way to preview what sequence you play and its totally optional, rip it out if you don’t need it.
If you load tree.mdl into JHLMV note that you have two animations to choose from. A sequence number is given to be used as an ID/Index, this is the number you provide in hammer to select a Sequence.
This current implementation does not provide for people who do not want to play animations. So let’s prepare a Flag and a condition in the code to cater for this.
Change the following to our header:
To:
And add the following after that:
I explain why I skip 3 below.
Next in our CPP change the following in our Spawn Function:
To:
Then in our Animate function change:
To:
Again this is cheaper to check a Boolean in the update loop as opposed to checking the state of the Flag which involves calling further functions.
Add the following to our flags section in the FGD
Note: that I skip the number 3 here and in the header which seems to be a feature/bug/limit in how flags work or are sent between the FGD and the game or between the Map and the game. Flags are stored as a power of 2. So the series to set them goes like this 1, 2, 4, 8, 16, 32, 64, 128, 256 etc.
All the spawn flags together should now look like this:
Save the FGD, Compile the code, restart hammer and check out our new flag in the entities properties flags tab.
Test your changes with the flag enabled and observe the inanimate model within our seen.
Let’s also add a speed variable to our entity to control how fast the model’s sequence plays back in game.
Add the following float to our header
Then add it to our KeyValue Function to read in its data. We use atof() not atoi()
Let’s add it to our animate function where it will be used to set the animation speed. Note: 1.0 = normal speed, Greater than 1.0 = faster animation, we will use a negative number to change the direction of the animation, which involves a little extra coding.
Let’s change:
To:
Basically what I am doing here is checking if we are animating, then I am checking our speed variable for what was input in the map, If it’s a positive number, we increment using the speed as the increment value If it’s a negative number, we decrement using the speed as the decrement value effectively reversing the animation.
1.0 = normal forward animation speed, default
>1.0 = faster than normal speed, dependant on what you input
0.0 = No animation
0.0 = slower animation speed
<= -1 = reversed animation and a lower negative value gives faster reverse playback
You could argue against my animate flag here (and use the animation speed condition instead) since a value of 0.0 for the animation speed means the model wont animate either but I wanted to also show you that flag id’s were powers of 2.
Add our latest addition to the FGD:
Restart hammer, check the new property and test out both positive and negative values.
Let’s add to our header file an integer to hold the id for what animation our currently loaded model should be playing. I add it to the end of our other unsigned short for ease of use and reuse of code.
unsigned short m_iCollisionMode, m_iSequence;
Next let’s add to the KeyValue Function so that it reads in the correct value for the animation sequence.
if (FStrEq(pkvd->szKeyName, "animate")) { m_iSequence = atoi(pkvd->szValue); pkvd->fHandled = TRUE; }
The whole function should now look like this:
void CStaticMesh::KeyValue(KeyValueData *pkvd) { if (FStrEq(pkvd->szKeyName, "animate")) { m_iSequence = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "collisionmode")) { m_iCollisionMode = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "bbmins")) { UTIL_StringToVector(mins, pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "bbmaxs")) { UTIL_StringToVector(maxs, pkvd->szValue); pkvd->fHandled = TRUE; } else CBaseEntity::KeyValue(pkvd); }
We must then set the sequence based on what the user input through hammer. We do that in the Spawn function near the end. We must add the following:
pev->sequence = m_iSequence;
Another small change we should make while we are in the Spawn function is to change this:
ExtractBbox(0, mins, maxs);
To:
ExtractBbox(m_iSequence, mins, maxs);
This makes sure that the correct sequence Collision box will be used if the user selects to use that collision mode.
We then need to add the following to our Animate function so that the animation can play.
pev->frame > 255 ? pev->frame = 0 : pev->frame++;
For those unfamiliar with the above line it is known as a ternary operation, basically an inline if else statement. Its written like this simply as an arguably easier alternative which is a little faster to write.
The equivalent as an if else would be:
if (pev->frame > 255){ pev->frame = 0; } else{ pev->frame++ }
Basically we are just making sure that the frames are incrementing with each update and if we reach the max of 255 reset to zero and start incrementing again.
pev->frame controls the individual frames in a sequence.
We must change our FGD from:
@PointClass color(0 255 0) size(-20 -20 -20, 20 20 20) studio() = wip_StaticMesh : "wip_StaticMesh" [ model(studio) : "Model" bbmins(string) : "Collision Volume Mins" : "-16 -16 -16" bbmaxs(string) : "Collision Volume Maxs" : "16 16 16" spawnflags(flags) = [ 1: "Solid?" : 1 2: "Debug Bounding Box?" : 0 ] collisionmode(choices) : "Collision Mode" : 2 = [ 0: "None" 1: "Manual Inputs" 2: "Sequence Based" ] ]
To:
@PointClass color(0 255 0) size(-20 -20 -20, 20 20 20) studio() = wip_StaticMesh : "wip_StaticMesh" [ sequence(integer) : "Animation Sequence (Editor)" : 0 : "Sequence to display in Jackhammer. This does not affect gameplay." animate(integer) : "Animation Sequence (Game)" : 0 : "Setting an in game Animation Sequence for the selected model" model(studio) : "Model" bbmins(string) : "Collision Volume Mins" : "-16 -16 -16" bbmaxs(string) : "Collision Volume Maxs" : "16 16 16" spawnflags(flags) = [ 1: "Solid?" : 1 2: "Debug Bounding Box?" : 0 ] collisionmode(choices) : "Collision Mode" : 2 = [ 0: "None" 1: "Manual Inputs" 2: "Sequence Based" ] ]
We use the following to set a sequence that we can visualize in the editor, for some reason this does not work for actually setting the value we would expect to see in game and that’s why we have a separate animate key. I believe that the word “sequence” is reserved much like the keys “mins” and “maxs”. I could not for the life of me get them to work.
sequence(integer) : "Animation Sequence (editor)" : 0 : "Sequence to display in Jackhammer. This does not affect gameplay."
And finally we use the following to set what sequence will actually be used in game
animate(integer) : "Animation Sequence (Game)" : 0 : "Setting an in game Animation Sequence for the selected model"
Save the FGD, compile the code, restart Hammer and you should see 2 new entries in the properties of our entity.
The Editor Entry changes it for the editor and the Game Entry changes it in game so be sure to set the in game entry correctly if you intend to use animations.
Try the editor version and watch as your model plays the different animations you switch to. I highlight this option as an easy way to preview what sequence you play and its totally optional, rip it out if you don’t need it.
If you load tree.mdl into JHLMV note that you have two animations to choose from. A sequence number is given to be used as an ID/Index, this is the number you provide in hammer to select a Sequence.
This current implementation does not provide for people who do not want to play animations. So let’s prepare a Flag and a condition in the code to cater for this.
Change the following to our header:
bool m_bDebugBB = false;
To:
bool m_bDebugBB = false, m_bAnimate = false;
And add the following after that:
I explain why I skip 3 below.
#define WIP_ANIMATE 4
Next in our CPP change the following in our Spawn Function:
pev->sequence = m_iSequence;
To:
// Check if the User wants to animate if (FBitSet(pev->spawnflags, WIP_ANIMATE)) { m_bAnimate = true; pev->sequence = m_iSequence; }
Then in our Animate function change:
pev->frame > 255 ? pev->frame = 0 : pev->frame++;
To:
if (m_bAnimate){ pev->frame > 255 ? pev->frame = 0 : pev->frame++; }
Again this is cheaper to check a Boolean in the update loop as opposed to checking the state of the Flag which involves calling further functions.
Add the following to our flags section in the FGD
4: "Animate?" : 1
Note: that I skip the number 3 here and in the header which seems to be a feature/bug/limit in how flags work or are sent between the FGD and the game or between the Map and the game. Flags are stored as a power of 2. So the series to set them goes like this 1, 2, 4, 8, 16, 32, 64, 128, 256 etc.
All the spawn flags together should now look like this:
spawnflags(flags) = [ 1: "Solid?" : 1 2: "Debug Bounding Box?" : 0 4: "Animate?" : 1 ]
Save the FGD, Compile the code, restart hammer and check out our new flag in the entities properties flags tab.
Test your changes with the flag enabled and observe the inanimate model within our seen.
Let’s also add a speed variable to our entity to control how fast the model’s sequence plays back in game.
Add the following float to our header
float m_flAnimationSpeed = 0.0f;
Then add it to our KeyValue Function to read in its data. We use atof() not atoi()
if (FStrEq(pkvd->szKeyName, "animationspeed")) { m_flAnimationSpeed = atof(pkvd->szValue); pkvd->fHandled = TRUE; }
Let’s add it to our animate function where it will be used to set the animation speed. Note: 1.0 = normal speed, Greater than 1.0 = faster animation, we will use a negative number to change the direction of the animation, which involves a little extra coding.
Let’s change:
if (m_bAnimate){ pev->frame > 255 ? pev->frame = 0 : pev->frame++ }
To:
if (m_bAnimate){ if (m_flAnimationSpeed >= 0.0){ pev->frame > 255 ? pev->frame = 0 : pev->frame += m_flAnimationSpeed; } else{ pev->frame < 0 ? pev->frame = 255 : pev->frame += m_flAnimationSpeed; } }
Basically what I am doing here is checking if we are animating, then I am checking our speed variable for what was input in the map, If it’s a positive number, we increment using the speed as the increment value If it’s a negative number, we decrement using the speed as the decrement value effectively reversing the animation.
1.0 = normal forward animation speed, default
>1.0 = faster than normal speed, dependant on what you input
0.0 = No animation
0.0 = slower animation speed
<= -1 = reversed animation and a lower negative value gives faster reverse playback
You could argue against my animate flag here (and use the animation speed condition instead) since a value of 0.0 for the animation speed means the model wont animate either but I wanted to also show you that flag id’s were powers of 2.
Add our latest addition to the FGD:
animationspeed(string) : "Animation Speed" : "1.0"
Restart hammer, check the new property and test out both positive and negative values.
Scaling our Mesh
The last major addition I want to bring to our entity is the ability to scale our mesh.
This does not work out of the box for meshes in GoldSrc as the support was only built in for sprites. However, we can make a small change to our client project which would enable pev->scale for meshes.
Locate in the hl_cdll project the file StudioModelRenderer.cpp.
In the function:
Towards the bottom lets add the following:
Basically this checks for a change in the models scale. The scale setting by default is 0 since it was unused prior to this. If its zero technically we shouldn’t see it so let’s not modify it if its zero.
A scale of 1 would also mean no change and the model would be its original scale baked into the mdl file.
Anything between those numbers require that the rotation matrix be modified by multiplying each value in the matrix by the scale input by the user through the hammer level.
Let’s tie it all together by returning to wip_static_mesh.h
Add the following float:
Then we must also consider that we have to modify the collision boxes. We scale them by the same amount we scale the visible mesh.
Change the following:
To:
We then modify KeyValue to include a check for the scale.
Add the following:
Finally let’s add to the FGD
Again the word “scale” seems to be reserved and won’t pass values to the game engine so we use “scale” for the editor and “modelscale” for ingame scale changes.
Save the FGD, Compile the Code, make scale changes in Hammer and load the game to see a scaled Mesh. If you enable the bounding box visualizer and set the collision mode to sequence you can see the collision box scale perfectly with your model.
Here is a xen tree scaled by 0.2:
This does not work out of the box for meshes in GoldSrc as the support was only built in for sprites. However, we can make a small change to our client project which would enable pev->scale for meshes.
Locate in the hl_cdll project the file StudioModelRenderer.cpp.
In the function:
CStudioModelRenderer::StudioSetUpTransform (int trivial_accept)
Towards the bottom lets add the following:
if (m_pCurrentEntity->curstate.scale != 0 && m_pCurrentEntity->curstate.scale != 1.0) { for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { (*m_protationmatrix)[i][j] *= m_pCurrentEntity->curstate.scale; } } }
Basically this checks for a change in the models scale. The scale setting by default is 0 since it was unused prior to this. If its zero technically we shouldn’t see it so let’s not modify it if its zero.
A scale of 1 would also mean no change and the model would be its original scale baked into the mdl file.
Anything between those numbers require that the rotation matrix be modified by multiplying each value in the matrix by the scale input by the user through the hammer level.
Let’s tie it all together by returning to wip_static_mesh.h
Add the following float:
float m_fModelScale = 1.0f;
Then we must also consider that we have to modify the collision boxes. We scale them by the same amount we scale the visible mesh.
Change the following:
if (FBitSet(pev->spawnflags, WIP_IS_SOLID)) { pev->solid = SOLID_BBOX; if (m_iCollisionMode == 1) { UTIL_SetSize(pev, mins, maxs); } else if (m_iCollisionMode == 2) { ExtractBbox(m_iSequence, mins, maxs); UTIL_SetSize(pev, mins, maxs); } if (FBitSet(pev->spawnflags, WIP_DEBUG_BB)){ m_bDebugBB = true; } }
To:
if (FBitSet(pev->spawnflags, WIP_IS_SOLID)) { pev->solid = SOLID_BBOX; if (m_iCollisionMode == 1) { mins = mins * m_fModelScale; maxs = maxs * m_fModelScale; UTIL_SetSize(pev, mins, maxs); } else if (m_iCollisionMode == 2) { ExtractBbox(m_iSequence, mins, maxs); mins = mins * m_fModelScale; maxs = maxs * m_fModelScale; UTIL_SetSize(pev, mins, maxs); } if (FBitSet(pev->spawnflags, WIP_DEBUG_BB)){ m_bDebugBB = true; } } pev->scale = m_fModelScale;
We then modify KeyValue to include a check for the scale.
Add the following:
else if (FStrEq(pkvd->szKeyName, "modelscale")) { m_fModelScale = atof(pkvd->szValue); pkvd->fHandled = TRUE; }
Finally let’s add to the FGD
modelscale(string) : "Model Scale (Game)" : "1.0" : "Set the Model Scale (0.0 - 1.0)" scale(string) : "Model Scale (Editor)" : "1.0" : "Set the Model Scale (0.0 - 1.0)"
Again the word “scale” seems to be reserved and won’t pass values to the game engine so we use “scale” for the editor and “modelscale” for ingame scale changes.
Save the FGD, Compile the Code, make scale changes in Hammer and load the game to see a scaled Mesh. If you enable the bounding box visualizer and set the collision mode to sequence you can see the collision box scale perfectly with your model.
Here is a xen tree scaled by 0.2:
Error Prevention
I noticed during development of this entity two scenarios one of which could be hard to detect and debug.
They both relate to loading the mesh itself.
If the model you are trying to load does not exist, the game will throw a precaching error and tell you which model is missing.
That’s useful enough in this case because you know what model is missing so you can simply locate and fix the issue.
The other issue causes a crash with no error and occurs if you create an instance of our entity but do not apply a model file to it.
To avoid this crash, I have made a small null.mdl model which says no model loaded and I use the defaults parameter in the FGD to set this model.
That way when you set the model in hammer you will see this null.mdl instead of a solid box.
I placed this custom model in my Mods model folder.
Its looks like this:
Without our custom null.mdl set it would look like this:
And would cause a crash.
To avoid the crash let’s make a small change to the following code:
Change:
To:
So now even if the user removes the defaults from the entity to force the error we will always load the null.mdl (provided it hasn’t been removed) to visualize that a model is missing.
They both relate to loading the mesh itself.
If the model you are trying to load does not exist, the game will throw a precaching error and tell you which model is missing.
That’s useful enough in this case because you know what model is missing so you can simply locate and fix the issue.
The other issue causes a crash with no error and occurs if you create an instance of our entity but do not apply a model file to it.
To avoid this crash, I have made a small null.mdl model which says no model loaded and I use the defaults parameter in the FGD to set this model.
That way when you set the model in hammer you will see this null.mdl instead of a solid box.
model(studio) : "Model" : "models/null.mdl" : "Set a Mesh to load into the Game"
I placed this custom model in my Mods model folder.
Its looks like this:
Without our custom null.mdl set it would look like this:
And would cause a crash.
To avoid the crash let’s make a small change to the following code:
Change:
PRECACHE_MODEL((char *)STRING(pev->model)); SET_MODEL(ENT(pev), STRING(pev->model));
To:
if (pev->model != 0){ PRECACHE_MODEL((char *)STRING(pev->model)); SET_MODEL(ENT(pev), STRING(pev->model)); } else{ ALERT(at_console, "[wip_staticMesh] Error, Model Failed to load!\n"); ALERT(at_console, "[wip_staticMesh] Setting model/null.mdl in its place!\n"); PRECACHE_MODEL("models/null.mdl"); SET_MODEL(ENT(pev), "models/null.mdl"); }
So now even if the user removes the defaults from the entity to force the error we will always load the null.mdl (provided it hasn’t been removed) to visualize that a model is missing.
Final Reference Code
I have also made some changes to the header file to account for the possibility that some variable might be used and haven’t yet been set to a value. To do this is simply assign each to a default value.
In case you hadn’t noticed FGDs support a default value as well as the option to include a short description of what the function does. For Example
I cleaned up the FGD to include descriptions which can be shown in the help section of the Entity in Hammer.
Click on Help in our object properties. The help tips in hammer for our entity looks like this:
Header
/****************************** Where is Poppy 29.8.2016 wip_static_mesh.h Static Mesh Loader for the mod Where is Poppy *******************************/ #ifndef WIP_STATIC_MESH_H #define WIP_STATIC_MESH_H #include "extdll.h" // Required for KeyValueData #include "util.h" // Required Consts & Macros #include "cbase.h" // Required for CPointEntity class CStaticMesh : public CBaseAnimating { private: void Spawn(void); void EXPORT Animate(void); void KeyValue(KeyValueData *pkvd); Vector mins = { 0, 0, 0 }, maxs = { 0, 0, 0 }; unsigned short m_iCollisionMode = 0, m_iSequence = 0; bool m_bDebugBB = false, m_bAnimate = false; float m_flAnimationSpeed = 1.0f, m_fModelScale = 1.0f; #define WIP_IS_SOLID 1 #define WIP_DEBUG_BB 2 #define WIP_ANIMATE 4 }; #endif
Source CPP
/****************************** Where is Poppy February, 2017 wip_static_mesh.cpp Static Mesh Loader for the mod Where is Poppy *******************************/ #include "wip_static_mesh.h" // Need to Link our class to the name (wip_StaticMesh) that hammer will read from the FGD. // This will be linked directly to the level as well so that the engine can link to it. LINK_ENTITY_TO_CLASS(wip_StaticMesh, CStaticMesh); /////////////////////////////// // Spawn(void) // // The Spawn function handles the creation and intialization of our entitty // It is the second function to run in this Class //////////////////////////////// void CStaticMesh::Spawn(void) { // Precache and Load the model if (pev->model != 0){ PRECACHE_MODEL((char *)STRING(pev->model)); SET_MODEL(ENT(pev), STRING(pev->model)); } // If the Model doesnt exist, print an error and set a default null.mdl as the model else{ ALERT(at_console, "[wip_staticMesh] Error, Model Failed to load!\n"); ALERT(at_console, "[wip_staticMesh] Setting model/null.mdl in its place!\n"); PRECACHE_MODEL("models/null.mdl"); SET_MODEL(ENT(pev), "models/null.mdl"); } // Check if the Solid Flag is set and if so be sure to set it // solid with appropriate Collisions // If not we also do not set a Collision Box because for a static mesh // there is no reason to do so.. if (FBitSet(pev->spawnflags, WIP_IS_SOLID)) { // Set Model solid pev->solid = SOLID_BBOX; // Check the collision mode 0 = None, 1 = Manual, 2 = Sequence based if (m_iCollisionMode == 1) { // Scale the collision box mins = mins * m_fModelScale; maxs = maxs * m_fModelScale; // Set Collision box Size UTIL_SetSize(pev, mins, maxs); } else if (m_iCollisionMode == 2) { // Grab Bounding box size from current sequence ExtractBbox(m_iSequence, mins, maxs); // Sacle the collision box mins = mins * m_fModelScale; maxs = maxs * m_fModelScale; // Set Collision box Size UTIL_SetSize(pev, mins, maxs); } // Check if the bounding box Visualizer flag is set if (FBitSet(pev->spawnflags, WIP_DEBUG_BB)){ m_bDebugBB = true; // If so set this value to true, we using it in Animate() } } // Set the visible meshes scale pev->scale = m_fModelScale; // Check if the User wants to animate if (FBitSet(pev->spawnflags, WIP_ANIMATE)) { m_bAnimate = true; // Used in Animate() pev->sequence = m_iSequence; // Set the animation based on what the user set in the level } // Set our update/think function to Animate() SetThink(&CStaticMesh::Animate); // Set when in the future to update next pev->nextthink = gpGlobals->time + 0.01; } /////////////////////////////// // KeyValue(KeyValueData *pkvd) // // The KeyValue function imports values set by our level editor in our map // These Keys are created in our FGD // We set local variables to the values that the map returns when requested // It is the first function to run in this Class //////////////////////////////// void CStaticMesh::KeyValue(KeyValueData *pkvd) { // Grab the speed our animation plays at // 0.0 here also stops the animation // A netagive value plays the animation in reverse // A higher value speeds up the animation if (FStrEq(pkvd->szKeyName, "animationspeed")) { m_flAnimationSpeed = atof(pkvd->szValue); pkvd->fHandled = TRUE; } // In-Game version of editor only variable "sequence" // Set an integer to what sequence you want this model to play ingame else if (FStrEq(pkvd->szKeyName, "animate")) { m_iSequence = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } // Set a mode for Collision // 0 = No Collision // 1 = Manual Mins & Maxs // 2 = Sequence Based Collision else if (FStrEq(pkvd->szKeyName, "collisionmode")) { m_iCollisionMode = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } // Minimum Bounding box position else if (FStrEq(pkvd->szKeyName, "bbmins")) { UTIL_StringToVector(mins, pkvd->szValue); pkvd->fHandled = TRUE; } // Maximum Bounding box position else if (FStrEq(pkvd->szKeyName, "bbmaxs")) { UTIL_StringToVector(maxs, pkvd->szValue); pkvd->fHandled = TRUE; } // Set the scale of our model and collision boxes // In-Game version of the editor only "scale" keyword else if (FStrEq(pkvd->szKeyName, "modelscale")) { m_fModelScale = atof(pkvd->szValue); pkvd->fHandled = TRUE; } // defaults else CBaseEntity::KeyValue(pkvd); } /////////////////////////////// // Animate(void) // // The Animate function is basically the Update function of this Entitiy // You add thinks here that you want to change on a frame by frame basis // Things like animations // Position changes // Interactive code //////////////////////////////// void CStaticMesh::Animate(void) { // Set when in the future to next run the animate function pev->nextthink = gpGlobals->time + 0.01; // If animation is allowed if (m_bAnimate) { if (m_flAnimationSpeed >= 0.0) { // Ternary condition to update the models normal animation + any extra speed the user adds pev->frame > 255 ? pev->frame = 0 : pev->frame += m_flAnimationSpeed; } else { // Ternary condition to update the models reverse animation + any extra speed the user adds pev->frame < 0 ? pev->frame = 255 : pev->frame += m_flAnimationSpeed; } } // Visualize the Collision Volume around the model if (m_bDebugBB){ UTIL_RenderBBox(pev->origin, pev->mins, pev->maxs, 1, 0, 255, 0); } }
FGD
// Key(type) : Name : Default Value : Description of function model(studio) : "Model" : "path/to/model" : "Select a Model to Load" sequence(integer) : "Animation Sequence (editor)" : 0 : "Sequence to display in Jackhammer. This does not affect gameplay."
I cleaned up the FGD to include descriptions which can be shown in the help section of the Entity in Hammer.
// Where is Poppy Forge Game Data // Cathal McNally // sourcemodding@gmail.com // www.sourcemodding.com // 29.8.2016 // wip_StaticMesh @PointClass color(0 255 0) size(-20 -20 -20, 20 20 20) studio() = wip_StaticMesh : "wip_StaticMesh" [ animate(integer) : "Animation Sequence (Game)" : 0 : "Setting an in game Animation Sequence for the selected model" sequence(integer) : "Animation Sequence (Editor)" : 0 : "Sequence to display in Jackhammer. This does not affect gameplay." animationspeed(string) : "Animation Speed" : "1.0" : "Set the Speed of your animation. 1.0 = normal, 0.0 - 1.0 is slower, greater than 1 is faster and less or equal to -1 reverses the animation" modelscale(string) : "Model Scale (Game)" : "1.0" : "Set the Model Scale (0.0 - 1.0)" scale(string) : "Model Scale (Editor)" : "1.0" : "Set the Model Scale (0.0 - 1.0)" model(studio) : "Model" : "models/null.mdl" : "Set a Mesh to load into the Game" bbmins(string) : "Collision Volume Mins" : "-16 -16 -16" : "Set the Minuimum Collision position for our Manually set Bounding Volume" bbmaxs(string) : "Collision Volume Maxs" : "16 16 16" : "Set the Maximum Collision position for our Manually set Bounding Volume" spawnflags(flags) = [ 1: "Solid?" : 1 : "Enable Collisions?" 2: "Debug Bounding Box?" : 0 : "Show a visual representation of the bounding box?" 4: "Animate?" : 1 : "Animate the Model?" ] collisionmode(choices) : "Collision Mode" : 2 = [ 0: "None" : "No Collisions" 1: "Manual Inputs" : "Enter Manual Min and Max values for a Custom Bounding Volume?" 2: "Sequence Based" : "Take the Bounding volume from the selected Animation Sequence?" ] ]
Click on Help in our object properties. The help tips in hammer for our entity looks like this:
Further Reading
Custom Model Entity
Automatically set entity collision box by model
Custom AABB collision boxes for an entity
A very technical explanation regarding the engine
Automatically set entity collision box by model
Custom AABB collision boxes for an entity
A very technical explanation regarding the engine
Special Thanks
Sam Vanheer aka Solokiller, for his insight into engine features and his ever eager nature to help me.
Elias Ringhauge aka eliasr, for his tutorial on the collision system on GoldSrc and for taking the time to help me understand it better, especially the visualizer for the Collision box.
I hope this tutorial helps you get to grips with coding your own entities in GoldSrc. If you find any issues or if you know of anything this document should include please feel free to send a mail onto me concerning it.
The support thread for this tutorial can be found over on the forums.
You can download the Project Source Code, Assets and Release through Github
Elias Ringhauge aka eliasr, for his tutorial on the collision system on GoldSrc and for taking the time to help me understand it better, especially the visualizer for the Collision box.
I hope this tutorial helps you get to grips with coding your own entities in GoldSrc. If you find any issues or if you know of anything this document should include please feel free to send a mail onto me concerning it.
The support thread for this tutorial can be found over on the forums.
You can download the Project Source Code, Assets and Release through Github
Thanks for the thanks, but also for your contribution.
I'm in the middle of making a new teleporter, and your tutorial will be a great help to me.
Update on "A very technical explanation regarding the engine"
Use Archive.ph
The forum post has been deleted, most likely due to the issue described here: Reddit.com
Thank you. I suspect it was removed for that reason as well. such a shame but thankfull for the archived snapshot :)