Authoring a Model Entity: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
(betterised)
Line 1: Line 1:
After having created a logical entity in the previous example, we will now create an entity that can move, collide with other objects, and that has a visual component (in this case, a model). In this example we will create an entity that is displayed using a model, and make that entity randomly move around the world.
''This tutorial assumes you have completed and understood [[Authoring a Logical Entity]].''


=Create a .CPP file for the new entity=
In this tutorial we'll create an entity that can move, collide with other objects, and that has a visual component (in this case, a model). We will make the entity randomly move around the world.
[[Image:Add existing item2.gif|Add the source file to the server.dll project by right-clicking.]]


* Create a file named <code>sdk_modelentity.cpp</code>. The file should go under the dlls folder under your source code folder. For example, if you installed the source code into <code>C:\MyMod\src</code>, then you would create a file called <code>C:\MyMod\src\dlls\sdk_modelentity.cpp</code>
Create <code>Server/Source Files/sdk_modelentity.cpp</code> and we'll begin.
* Next, copy [[Model Entity | this code]] and paste it into this new file.
* Last, add this file to your server.dll project. If you opened the <code>game_sdk.sln</code> solution, then you can right-click on the hl project in the Solution Explorer window and choose '''Add''', then '''Add Existing Item'''.


=Walking Through The code=
== Include and Declare ==


==Creating The Class Definition==
You should understand this code now:


<pre>
<span style="color:blue;">#include</span> <span style="color:brown;">"cbase.h"</span>
class CMyModelEntity : public CBaseAnimating
{
<span style="color:blue;">class</span> CMyModelEntity : <span style="color:blue;">public</span> CBaseAnimating
public:
{
    DECLARE_CLASS( CMyModelEntity, CBaseAnimating );
<span style="color:blue;">public</span>:
    DECLARE_DATADESC();
DECLARE_CLASS( CMyModelEntity, CBaseAnimating );
DECLARE_DATADESC();
<span style="color:blue;">void</span> Spawn( <span style="color:blue;">void</span> );
<span style="color:blue;">void</span> Precache( <span style="color:blue;">void</span> );
<span style="color:blue;">void</span> MoveThink( <span style="color:blue;">void</span> );
<span style="color:green;">// Input function</span>
<span style="color:blue;">void</span> InputToggle( inputdata_t &inputData );
<span style="color:blue;">private</span>:
<span style="color:blue;">bool</span> m_bActive;
<span style="color:blue;">float</span> m_flNextChangeTime;
};


    void Spawn( void );
Notice how we are now inheriting from <code>[[CBaseAnimating]]</code>, and how we have some new functions. The private variables are a boolean (true/false) and a floating point (decimalised number).
    void Precache( void );
    void MoveThink( void );


    // Input function
== Entity name and Datadesc ==
    void InputToggle( inputdata_t &inputData );


private:
LINK_ENTITY_TO_CLASS( my_model_entity, CMyModelEntity );
    bool        m_bActive;
    float        m_flNextChangeTime;
<span style="color:green;">// Start of our data description for the class</span>
};
BEGIN_DATADESC( CMyModelEntity )
</pre>
<span style="color:green;">// Save/restore our active state</span>
DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flNextChangeTime, FIELD_TIME ),
// Links our input name from Hammer to our input member function</span>
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
<span style="color:green;">// Declare our think function</span>
DEFINE_THINKFUNC( MoveThink ),
END_DATADESC()


We descend our new entity from the <code>CBaseAnimating</code> class. This allows us to use models and animate. Also new to this entity is the <code>Spawn()</code> and <code>Precache()</code> function.
The biggest change here from our logical entity is <code>DEFINE_THINKFUNC</code>. [[Think]] functions are special cases in Source, and we need to identify them.


==Defining The Data Description==
== Defining the model ==


<pre>
<span style="color:green;">// Name of our entity's model</span>
LINK_ENTITY_TO_CLASS( my_model_entity, CMyModelEntity );
<span style="color:blue;">#define</span> ENTITY_MODEL <span style="color:brown;">"models/gibs/airboat_broken_engine.mdl"</span>


// Start of our data description for the class
This hard-codes the [[model]] that our entity will present to the world. This is a static piece of information, not a variable. It cannot change once the code has been compiled. The path to the .mdl file is relative to the game directory (e.g. <code>/hl2/</code>).
BEGIN_DATADESC( CMyModelEntity )
   
    // Save/restore our active state
    DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
    DEFINE_FIELD( m_flNextChangeTime, FIELD_TIME ),


    // Links our input name from Hammer to our input member function
== Precache() ==
    DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),


    // Declare our think function
Now we come to our first function. <code>[[Precache()]]</code> should be called when an entity spawns, and ensures that loading of whatever is listed in it occurs before the player spawns (i.e. sees the world and gains control). See [[Precaching Assets]] for more information.
    DEFINE_THINKFUNC( MoveThink ),


END_DATADESC()
Precaching gets a special name because there are other types of "[[asynchronous]]" loading that can occur ''after'' player spawn.
</pre>


Much like our logical entity, we must declare the variables used by the entity so that the engine knows their intention.
<span style="color:green;">//-----------------------------------------------------------------------------
// Purpose: Precache assets used by the entity
//-----------------------------------------------------------------------------</span>
<span style="color:blue;">void</span> CMyModelEntity::Precache( <span style="color:blue;">void</span> )
{
PrecacheModel( ENTITY_MODEL );
}


It’s important to note that the <code>MoveThink()</code> function must be declared as an entity think function in the entity’s data description table using the <code>DEFINE_THINKFUNC</code> macro. See the [[Data Descriptions|Data Description Table]] for more information.
For this entity there is only one resource that needs precaching. More complex entities might also precache sounds, particle effects, and so on. No matter how much loading needs to be done though, this is generally a very simple function.


==Creating The Precache() Function==
== Spawn() ==
<pre>
#define ENTITY_MODEL    "models/gibs/airboat_broken_engine.mdl"


void CMyModelEntity::Precache( void )
You may have noticed the absence of a constructor from the class declaration. That is because we're now using Valve's <code>[[Spawn()]]</code> function instead. Like a constructor it gets called every time an instance is made, but it does some Source-specific things as well.
{
    PrecacheModel( ENTITY_MODEL );
}
</pre>


The <code>Precache()</code> function is where all asset precaching must be done. For more information on this subject, see [[Precaching Assets |Precaching Assets]]. Here we also define the model we’ll use to represent our entity in the world.
<span style="color:green;">//-----------------------------------------------------------------------------
// Purpose: Sets up the entity's initial state
//-----------------------------------------------------------------------------</span>
<span style="color:blue;">void</span> CMyModelEntity::Spawn( <span style="color:blue;">void</span> )
{
Precache();
SetModel( ENTITY_MODEL );
SetSolid( SOLID_BBOX );
UTIL_SetSize( <span style="color:blue;">this</span>, -Vector(20,20,20), Vector(20,20,20) );
m_bActive = <span style="color:blue;">false</span>;
}


In this example, we call <code>PrecacheModel()</code> to precache our model. Without this step the entity’s model would not appear in the world and the engine would complain of a missed precache.
We call <code>Precache()</code> first of course, and then call several <code>CBaseAnimating</code> functions.  


==Creating The Spawn() Function==
<code>[[SetSolid()]]</code> defines the shape of our entity [[ai_moveprobe_debug|when considering movement]]. It ''doesn't'' define [[hitbox]]es or affect physics collisions. These are all different things! We have several options to choose from:
<pre>
void CMyModelEntity::Spawn( void )
{
      Precache();


      SetModel( ENTITY_MODEL );
;<code>SOLID_NONE</code>
      SetSolid( SOLID_BBOX );
:Not solid.
      UTIL_SetSize( this, -Vector(20,20,20), Vector(20,20,20) );
;<code>SOLID_BSP</code>
:Uses the BSP tree to determine solidity (used for brush models)
;<code>SOLID_BBOX</code>
:Uses an axis-aligned bounding box.
;<code>SOLID_CUSTOM</code>
:Entity defines its own functions for testing collisions.
;<code>SOLID_VPHYSICS</code>
:Uses a .mdl's embedded [[collision model]] to test accurate physics collisions.


      m_bActive = false;
These are engine-level options that mod authors cannot change or add to. We will choose <code>SOLID_BBOX</code>, a generic box shape that we then mould to the approximate size of our model with <code>UTIL_SetSize</code>. This is cheap and simple, which is what we're after for this tutorial.
}
</pre>


The <code>Spawn()</code> function is called after the entity is first created. This function can be thought of as the game’s constructor method for the entity. In this function the entity can setup its initial state, including what model to use for itself, its method of movement and its solidity. It’s important to note that the <code>Spawn()</code> function is called immediately after the allocation of the entity and that if this has occurred at the beginning of a map, there is no guarantee that other entities have been spawned yet. Therefore, any code which requires the entity to search or otherwise link itself to other named entities must do so in the <code>Activate()</code> function of the entity. <code>Activate()</code> is called when all entities have been spawned and had their Spawn() function called. Searching for entities before the Activate() function will rely on the spawning order of the entities and is unreliable.
Lastly, we use <code>m_bActive</code> to set the entity to default to inactivity.


In this example, we first call the <code>Precache()</code> function to be sure all of our assets are precached properly. After that, we use the <code>SetModel()</code> function to set our entity’s model to the one we defined previously.
{{warning|It is important to note that <code>Spawn()</code> is called ''immediately'' after the creation of the entity. If this has occurred at the beginning of a map there is no guarantee that other entities have been spawned yet. Therefore, any code which requires the entity to search or otherwise link itself to other entities is unreliable. Use the <code>[[Activate()]]</code> function instead, which is always called after all spawning has completed.}}


Next, we set the solidity of the entity via the <code>SetSolid()</code> function. There are multiple possible solid types, defined as:
== MoveThink() ==


{|
A [[Think]] function allows an entity to make decisions without being prompted by an external source. If you think back to our logical entity, it only ever did anything when it received an input; this is clearly no good for something that is meant to move around of its own accord. A think function, if present, is usually the core of the entity's programming.
| <code>SOLID_NONE</code> || Not solid.
|-
| <code>SOLID_BSP</code> || Uses the BSP tree to determine solidity (used for brush models)
|-
| <code>SOLID_BBOX</code> || Uses an axis-aligned bounding box.
|-
| <code>SOLID_CUSTOM</code> || Entity defines its own functions for testing collisions.
|-
| <code>SOLID_VPHYSICS</code> || Uses the vcollide object for the entity to test collisions.
|}


For this example, we’ll make the entity use a bounding box. The <code>UTIL_SetSize()</code> function allows us to set the size of that bounding box. Here we set it to a 40x40x40 cube.
Here we create a think function that will be called up to 20 times a second. This may sound like a lot, but modern processors are very fast and this entity is very simple. You'll be able to have an awful lot of <code>CMyModelEntity</code>s in a map without running into any CPU issues!


==Creating The MoveThink() Function==
<span style="color:green;">//-----------------------------------------------------------------------------
// Purpose: Think function to randomly move the entity
//-----------------------------------------------------------------------------</span>
<span style="color:blue;">void</span> CMyModelEntity::MoveThink( <span style="color:blue;">void</span> )
{
<span style="color:green;">// See if we should change direction again</span>
<span style="color:blue;">if</span> ( m_flNextChangeTime < gpGlobals->curtime )
{
<span style="color:green;">// Randomly take a new direction and speed</span>
Vector vecNewVelocity = RandomVector( -64.0f, 64.0f );
SetAbsVelocity( vecNewVelocity );
<span style="color:green;">// Randomly change it again within one to three seconds</span>
m_flNextChangeTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
}
<span style="color:green;">// Snap our facing to where we're heading</span>
Vector velFacing = GetAbsVelocity();
QAngle angFacing;
VectorAngles( velFacing, angFacing );
  SetAbsAngles( angFacing );
<span style="color:green;">// Think every 20Hz</span>
SetNextThink( gpGlobals->curtime + 0.05f );
}


Entities have the ability to update internal state and make decisions via a <i>think</i> function, which will be called at a rate specified by the entity. Here we will create a think function that we will have called up to 20 times a second. We will use this think function to randomly update our movement and direction in the world.
While a lot of code is packed into this function, its outcome is fairly simple. Once a random time interval has elapsed, the entity will choose a new, random direction and speed to travel at. It will also update its angles so that the model visibly faces towards the new direction. This occurs in three dimensions.


<pre>
Some help:
void CMyModelEntity::MoveThink( void )
{
    // See if we should change direction again
    if ( m_flNextChangeTime < gpGlobals->curtime )
    {
            // Randomly take a new direction and speed
            Vector vecNewVelocity = RandomVector( -64.0f, 64.0f );
            SetAbsVelocity( vecNewVelocity );


            // Randomly change it again within one to three seconds
*<code>[[gpGlobals]]->curtime</code> returns the time at which the code is being executed as a floating point value.
            m_flNextChangeTime = gpGlobals->curtime + random->RandomFloat(1.0f,3.0f);
*<code>[[Wikipedia:Vector (spatial)|Vector]]</code>s are used for movement, since they contain information about both facing and speed. <code>[[SetAbsVelocity]]</code> 'Sets' the 'Absolute' 'Velocity' with one.
    }
*<code>[[QAngle|qangle]]</code> is simply an angle - a vector minus data about velocity. It's used to set facing.
*<code>[[VectorAngles]]</code> converts a vector (<code>velFacing</code>) to an angle (<code>angFacing</code>). Remember that C++ is very strict about data types: you need utility functions to convert between any two.


    // Snap our facing to where we are heading
Having done all this we call <code>[[SetNextThink()]]</code>. This tells the entity when next to run its think function. Here it is set to think again in 0.05 seconds (1/20th), but that number can vary between entities. It’s important to note that failure to use SetNextThink() will cause the entity to stop thinking (which is sometimes desired).
    Vector velFacing = GetAbsVelocity();
    QAngle angFacing;
    VectorAngles( velFacing, angFacing );
    SetAbsAngles( angFacing );


    // Think every 20Hz
== InputToggle() ==
    SetNextThink( gpGlobals->curtime + 0.05f );
}
</pre>


While a lot of code is packed into this function, its outcome is fairly simple: once a random time interval has elapsed, the entity will choose a new, random direction and speed to travel at. It will also update its angles to face towards this direction of travel.
Now we come to our last function. This is an input that will toggle movement on and off.


The call to <code>SetNextThink()</code> is important in this function, because it tells the entity when next to think. Here it is set to think again 1/20th of a second in the future. Most entities will only need to think at a rate of 1/10th of a second, depending on their behaviors. It’s important to note that failure to update the next think time of the entity will cause it to stop thinking (which is sometimes desired).
<span style="color:green;">//-----------------------------------------------------------------------------
// Purpose: Toggle the movement of the entity
//-----------------------------------------------------------------------------</span>
<span style="color:blue;">void</span> CMyModelEntity::InputToggle( inputdata_t &inputData )
{
<span style="color:green;">// Toggle our active state</span>
<span style="color:blue;">if</span> ( !m_bActive )
{
<span style="color:green;">// Start thinking</span>
SetThink( &CMyModelEntity::MoveThink );
SetNextThink( gpGlobals->curtime + 0.05f );
<span style="color:green;">// Start flying</span>
SetMoveType( MOVETYPE_FLY );
<span style="color:green;">// Set our next time for changing our speed and direction</span>
m_flNextChangeTime = gpGlobals->curtime;
m_bActive = <span style="color:blue;">true</span>;
}
<span style="color:blue;">else</span>
{
<span style="color:green;">// Stop thinking</span>
SetThink( NULL );
<span style="color:green;">// Stop moving</span>
SetAbsVelocity( vec3_origin );
  SetMoveType( MOVETYPE_NONE );
m_bActive = <span style="color:blue;">false</span>;
}
}


==Create the InputToggle() function==
This is all very straightforward. We use an <code>if</code> statement to check whether or not <code>m_bActive</code> is true. The exclamation mark means "not": "if <code>m_bActive</code> is not true, do this". Later on, we use the <code>else</code> command to specify what we want to do in any other case - which with a boolean value can only be if <code>m_bActive</code> ''is'' true.


For this entity, we’ll use an input to toggle its movement on and off. To do so, we declare the input function like any other.
When activating the entity we start its think loop by telling Source what think function to use (the default is <code>[[Think()]]</code>, but we don't have that here) - note the addition of an & and the omission of any parentheses in the argument. We then tell Source that the entity will move by flying, although this would actually be better-described as "floating" since there is no true simulation of flight in Source (perhaps you could make one?). And, of course, we set <code>m_bActive</code> to true. It isn't going to change itself!


<pre>
Under the <code>else</code> command we stop the entity from moving. The think function is set to <code>NULL</code> to stop all thinking, <code>[[AbsVelocity]]</code> is set to the entity's  [[origin]], a vector with no movement, the movement type is set to <code>[[MOVETYPE_NONE]]</code> to prevent any kind of movement that might be imposed externally, and lastly <code>m_bActive</code> is made false.
void CMyModelEntity::InputToggle( inputdata_t &inputData )
{
      // Toggle our active state
      if ( !m_bActive )
      {
              // Start thinking
              SetThink( MoveThink );
              //SetThink( &CMyModelEntity::MoveThink ); // (nb. Use this code in Visual Studio 2005)


              SetNextThink( gpGlobals->curtime + 0.05f );
== FGD entry ==
           
              // Start flying
              SetMoveType( MOVETYPE_FLY );
              // Set our next time for changing our speed and direction
              m_flNextChangeTime = gpGlobals->curtime;
              m_bActive = true;
      }
      else
      {
              // Stop thinking
              SetThink( NULL );
           
              // Stop moving
              SetAbsVelocity( vec3_origin );
              SetMoveType( MOVETYPE_NONE );
           
              m_bActive = false;
      }
}
</pre>


To start the entity thinking, we use the <code>SetThink()</code> function in conjunction with the <code>SetNextThink()</code> function. This tells the entity to use our <code>MoveThink()</code> function and to call it 1/20th of a second in the future. It’s important to note that an entity can have any number of think functions and use the <code>SetThink()</code> function to choose between them. Entities can even have multiple think functions running at the same time using the <code>SetContextThink()</code> covered in another document.
The FGD entry for this entity displays the model in Hammer and allows you to name it and send the input "Toggle".


We also set the entity’s movement type to <code>MOVETYPE_FLY</code>. This allows the entity to move along a direction without gravity.
@PointClass base(Targetname) studio("models/gibs/airboat_broken_engine.mdl")= my_model_entity :  "Tutorial model entity."
[
input Toggle(void) : "Toggle movement."
]


In the second portion of this function we stop the entity from moving. The think function is set to <code>NULL</code> to stop all thinking. Its movement type is also set to <code>MOVETYPE_NONE</code> to keep it from moving.
== The working entity ==
<br><br>
'''''Just a note, in Visual Studios 2005 the code for setThink gives an Error "SetThink( MoveThink )" and must be changed to:


<pre>
[[Image:My model entity.jpg|thumb|my_model_entity in-game.]]
SetThink( &CMyModelEntity::MoveThink );
</pre>


'''''once that is done your code should compile without this error, so all you need to do is trigger or toggle the input on so that the object will move. (Do make sure the object isn't touching any surface or it will still not move)'''''
Play with your entity. You can use the console command <code>ent_fire my_model_entity toggle</code> to get it moving without map I/O. You'll notice a few things:


==Create The FGD Entry==
;If I spawn it flush against the ground, it will rotate but not move
:The bounding box goes inside the BSP, perhaps? Create it in the air and there won't be any problems.
;It won't collide with any physics objects (but it will with me)
:The model we're using has a collision mesh, but perhaps we haven't loaded it?
;It will dodge around me every time I get in its way
:Perhaps <code>CBaseAnimating</code> thinks every time its path is blocked?


To use the entity within Hammer, we’ll need to create an entry in our FGD file. Hammer will use this data to interpret the various keyvalues and functions the entity is exposing. See the [[FGD|FGD Format document]] for more information about FGD files.
== See also ==


The FGD entry for this entity simply displays the model in hammer and allows you to send the input "Toggle" to it.
*[[Model Entity|Tutorial code in full]]
 
<pre>
@PointClass base(Targetname) studio("models/gibs/airboat_broken_engine.mdl")= my_model_entity : "Tutorial model entity."
[   
      input Toggle(void) : "Toggle movement."
]
</pre>
 
Be sure to add the line <code>@include "base.fgd"</code> at the top of the FGD file, which provides Hammer with some necessary functions. (That is appropriate for a total conversion. For a mod based on existing content, include the appropriate FGD instead; eg, for an [[HL2]] mod, include <code>halflife2.fgd</code> instead of <code>base.fgd</code>.)


{{otherlang:en}}
{{otherlang:en}}

Revision as of 10:07, 24 February 2008

This tutorial assumes you have completed and understood Authoring a Logical Entity.

In this tutorial we'll create an entity that can move, collide with other objects, and that has a visual component (in this case, a model). We will make the entity randomly move around the world.

Create Server/Source Files/sdk_modelentity.cpp and we'll begin.

Include and Declare

You should understand this code now:

#include "cbase.h"

class CMyModelEntity : public CBaseAnimating
{
public:
	DECLARE_CLASS( CMyModelEntity, CBaseAnimating );
	DECLARE_DATADESC();

	void Spawn( void );
	void Precache( void );

	void MoveThink( void );

	// Input function
	void InputToggle( inputdata_t &inputData );

private:

	bool	m_bActive;
	float	m_flNextChangeTime;
};

Notice how we are now inheriting from CBaseAnimating, and how we have some new functions. The private variables are a boolean (true/false) and a floating point (decimalised number).

Entity name and Datadesc

LINK_ENTITY_TO_CLASS( my_model_entity, CMyModelEntity );

// Start of our data description for the class
BEGIN_DATADESC( CMyModelEntity )
	
	// Save/restore our active state
	DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flNextChangeTime, FIELD_TIME ),

	// Links our input name from Hammer to our input member function
	DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),

	// Declare our think function
	DEFINE_THINKFUNC( MoveThink ),

END_DATADESC()

The biggest change here from our logical entity is DEFINE_THINKFUNC. Think functions are special cases in Source, and we need to identify them.

Defining the model

// Name of our entity's model
#define	ENTITY_MODEL	"models/gibs/airboat_broken_engine.mdl"

This hard-codes the model that our entity will present to the world. This is a static piece of information, not a variable. It cannot change once the code has been compiled. The path to the .mdl file is relative to the game directory (e.g. /hl2/).

Precache()

Now we come to our first function. Precache() should be called when an entity spawns, and ensures that loading of whatever is listed in it occurs before the player spawns (i.e. sees the world and gains control). See Precaching Assets for more information.

Precaching gets a special name because there are other types of "asynchronous" loading that can occur after player spawn.

//-----------------------------------------------------------------------------
// Purpose: Precache assets used by the entity
//-----------------------------------------------------------------------------
void CMyModelEntity::Precache( void )
{
	PrecacheModel( ENTITY_MODEL );
}

For this entity there is only one resource that needs precaching. More complex entities might also precache sounds, particle effects, and so on. No matter how much loading needs to be done though, this is generally a very simple function.

Spawn()

You may have noticed the absence of a constructor from the class declaration. That is because we're now using Valve's Spawn() function instead. Like a constructor it gets called every time an instance is made, but it does some Source-specific things as well.

//-----------------------------------------------------------------------------
// Purpose: Sets up the entity's initial state
//-----------------------------------------------------------------------------
void CMyModelEntity::Spawn( void )
{
	Precache();

	SetModel( ENTITY_MODEL );
	SetSolid( SOLID_BBOX );
	UTIL_SetSize( this, -Vector(20,20,20), Vector(20,20,20) );

	m_bActive = false;
}

We call Precache() first of course, and then call several CBaseAnimating functions.

SetSolid() defines the shape of our entity when considering movement. It doesn't define hitboxes or affect physics collisions. These are all different things! We have several options to choose from:

SOLID_NONE
Not solid.
SOLID_BSP
Uses the BSP tree to determine solidity (used for brush models)
SOLID_BBOX
Uses an axis-aligned bounding box.
SOLID_CUSTOM
Entity defines its own functions for testing collisions.
SOLID_VPHYSICS
Uses a .mdl's embedded collision model to test accurate physics collisions.

These are engine-level options that mod authors cannot change or add to. We will choose SOLID_BBOX, a generic box shape that we then mould to the approximate size of our model with UTIL_SetSize. This is cheap and simple, which is what we're after for this tutorial.

Lastly, we use m_bActive to set the entity to default to inactivity.

Warning.pngWarning:It is important to note that Spawn() is called immediately after the creation of the entity. If this has occurred at the beginning of a map there is no guarantee that other entities have been spawned yet. Therefore, any code which requires the entity to search or otherwise link itself to other entities is unreliable. Use the Activate() function instead, which is always called after all spawning has completed.

MoveThink()

A Think function allows an entity to make decisions without being prompted by an external source. If you think back to our logical entity, it only ever did anything when it received an input; this is clearly no good for something that is meant to move around of its own accord. A think function, if present, is usually the core of the entity's programming.

Here we create a think function that will be called up to 20 times a second. This may sound like a lot, but modern processors are very fast and this entity is very simple. You'll be able to have an awful lot of CMyModelEntitys in a map without running into any CPU issues!

//-----------------------------------------------------------------------------
// Purpose: Think function to randomly move the entity
//-----------------------------------------------------------------------------
void CMyModelEntity::MoveThink( void )
{
	// See if we should change direction again
	if ( m_flNextChangeTime < gpGlobals->curtime )
	{
		// Randomly take a new direction and speed
		Vector vecNewVelocity = RandomVector( -64.0f, 64.0f );
		SetAbsVelocity( vecNewVelocity );

		// Randomly change it again within one to three seconds
		m_flNextChangeTime = gpGlobals->curtime + random->RandomFloat( 1.0f, 3.0f );
	}

	// Snap our facing to where we're heading
	Vector velFacing = GetAbsVelocity();
	QAngle angFacing;
	VectorAngles( velFacing, angFacing );
 	SetAbsAngles( angFacing );

	// Think every 20Hz
	SetNextThink( gpGlobals->curtime + 0.05f );
}

While a lot of code is packed into this function, its outcome is fairly simple. Once a random time interval has elapsed, the entity will choose a new, random direction and speed to travel at. It will also update its angles so that the model visibly faces towards the new direction. This occurs in three dimensions.

Some help:

  • gpGlobals->curtime returns the time at which the code is being executed as a floating point value.
  • Vectors are used for movement, since they contain information about both facing and speed. SetAbsVelocity 'Sets' the 'Absolute' 'Velocity' with one.
  • qangle is simply an angle - a vector minus data about velocity. It's used to set facing.
  • VectorAngles converts a vector (velFacing) to an angle (angFacing). Remember that C++ is very strict about data types: you need utility functions to convert between any two.

Having done all this we call SetNextThink(). This tells the entity when next to run its think function. Here it is set to think again in 0.05 seconds (1/20th), but that number can vary between entities. It’s important to note that failure to use SetNextThink() will cause the entity to stop thinking (which is sometimes desired).

InputToggle()

Now we come to our last function. This is an input that will toggle movement on and off.

//-----------------------------------------------------------------------------
// Purpose: Toggle the movement of the entity
//-----------------------------------------------------------------------------
void CMyModelEntity::InputToggle( inputdata_t &inputData )
{
	// Toggle our active state
	if ( !m_bActive )
	{
		// Start thinking
		SetThink( &CMyModelEntity::MoveThink );

		SetNextThink( gpGlobals->curtime + 0.05f );
		
		// Start flying
		SetMoveType( MOVETYPE_FLY );

		// Set our next time for changing our speed and direction
		m_flNextChangeTime = gpGlobals->curtime;
		m_bActive = true;
	}
	else
	{
		// Stop thinking
		SetThink( NULL );
		
		// Stop moving
		SetAbsVelocity( vec3_origin );
 		SetMoveType( MOVETYPE_NONE );
		
		m_bActive = false;
	}
}

This is all very straightforward. We use an if statement to check whether or not m_bActive is true. The exclamation mark means "not": "if m_bActive is not true, do this". Later on, we use the else command to specify what we want to do in any other case - which with a boolean value can only be if m_bActive is true.

When activating the entity we start its think loop by telling Source what think function to use (the default is Think(), but we don't have that here) - note the addition of an & and the omission of any parentheses in the argument. We then tell Source that the entity will move by flying, although this would actually be better-described as "floating" since there is no true simulation of flight in Source (perhaps you could make one?). And, of course, we set m_bActive to true. It isn't going to change itself!

Under the else command we stop the entity from moving. The think function is set to NULL to stop all thinking, AbsVelocity is set to the entity's origin, a vector with no movement, the movement type is set to MOVETYPE_NONE to prevent any kind of movement that might be imposed externally, and lastly m_bActive is made false.

FGD entry

The FGD entry for this entity displays the model in Hammer and allows you to name it and send the input "Toggle".

@PointClass base(Targetname) studio("models/gibs/airboat_broken_engine.mdl")= my_model_entity :  "Tutorial model entity."
[
	input Toggle(void) : "Toggle movement."
]

The working entity

my_model_entity in-game.

Play with your entity. You can use the console command ent_fire my_model_entity toggle to get it moving without map I/O. You'll notice a few things:

If I spawn it flush against the ground, it will rotate but not move
The bounding box goes inside the BSP, perhaps? Create it in the air and there won't be any problems.
It won't collide with any physics objects (but it will with me)
The model we're using has a collision mesh, but perhaps we haven't loaded it?
It will dodge around me every time I get in its way
Perhaps CBaseAnimating thinks every time its path is blocked?

See also

Template:Otherlang:en Template:Otherlang:en:ru