Difference between revisions of "Authoring a Model Entity"

From Valve Developer Community
Jump to: navigation, search
(Create The FGD Entry)
(Create The FGD Entry)
Line 195: Line 195:
 
</pre>
 
</pre>
  
Be sure to add the line @include "base.fgd" at the top of the FGD file, which provides Hammer with some necessary functions.
+
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>.)

Revision as of 14:03, 4 December 2005


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.

Create a .CPP file for the new entity

Add the source file to the server.dll project by right-clicking.

  • Create a file named sdk_modelentity.cpp. The file should go under the dlls folder under your source code folder. For example, if you installed the source code into C:\MyMod\src, then you would create a file called C:\MyMod\src\dlls\sdk_modelentity.cpp</
  • Next, copy this code and paste it into this new file.
  • Last, add this file to your server.dll project. If you opened the game_sdk.sln 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

Creating The Class Definition

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;
};

We descend our new entity from the CBaseAnimating class. This allows us to use models and animate. Also new to this entity is the Spawn() and Precache() function.

Defining The Data Description

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()

Much like our logical entity, we must declare the variables used by the entity so that the engine knows their intention.

It’s important to note that the MoveThink() function must be declared as an entity think function in the entity’s data description table using the DEFINE_THINKFUNC macro. See the Data Description Table for more information.

Creating The Precache() Function

#define ENTITY_MODEL     "models/gibs/airboat_broken_engine.mdl"

void CMyModelEntity::Precache( void )
{
     PrecacheModel( ENTITY_MODEL );
}

The Precache() function is where all asset precaching must be done. For more information on this subject, see Precaching Assets. Here we also define the model we’ll use to represent our entity in the world.

In this example, we call PrecacheModel() 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.

Creating The Spawn() Function

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;
}

The Spawn() 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 Spawn() 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 Activate() function of the entity. Activate() 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.

In this example, we first call the Precache() function to be sure all of our assets are precached properly. After that, we use the SetModel() function to set our entity’s model to the one we defined previously.

Next, we set the solidity of the entity via the SetSolid() function. There are multiple possible solid types, defined as:

SOLID_NOT 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 the vcollide object for the entity to test collisions.

For this example, we’ll make the entity use a bounding box. The UTIL_SetSize() function allows us to set the size of that bounding box. Here we set it to a 40x40x40 cube.

Creating The MoveThink() Function

Entities have the ability to update internal state and make decisions via a think 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.

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 are 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 to face towards this direction of travel.

The call to SetNextThink() 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).

Create the InputToggle() function

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.

void CMyModelEntity::InputToggle( inputdata_t &inputData )
{
       // Toggle our active state
       if ( !m_bActive )
       {
              // Start thinking
              SetThink( 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;
       }
}

To start the entity thinking, we use the SetThink() function in conjunction with the SetNextThink() function. This tells the entity to use our MoveThink() 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 SetThink() function to choose between them. Entities can even have multiple think functions running at the same time using the SetContextThink() covered in another document.

We also set the entity’s movement type to MOVETYPE_FLY. This allows the entity to move along a direction without gravity.

In the second portion of this function we stop the entity from moving. The think function is set to NULL to stop all thinking. Its movement type is also set to MOVETYPE_NONE to keep it from moving.

Create The FGD Entry

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 Format document for more information about FGD files.

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

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

Be sure to add the line @include "base.fgd" 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 halflife2.fgd instead of base.fgd.)