Authoring a Brush Entity: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
m (Nesciuse moved page Authoring a Brush Entity/en to Authoring a Brush Entity without leaving a redirect: Move en subpage to basepage)
 
(25 intermediate revisions by 13 users not shown)
Line 1: Line 1:
[[Category:Programming]]
{{LanguageBar}}
Our last example dealt with [[Authoring a Model Entity|giving entities a model]]. Here we’ll use world architecture (or <i>brushes</i>) to represent our entity and how it collides and moves around the world. We’ll also look at the touch function, available to all entities. This will let us make the entity move when touched.
 
 


=Create a .CPP file for the new entity=
''This tutorial assumes you have completed and understood [[Authoring a Logical Entity]] and, ideally, [[Authoring a Model Entity]].''


[[Image:Add existing item3.gif|Add the source file to the server.dll project by right-clicking.]]
Our last example dealt with [[Authoring a Model Entity|giving entities a model]]. Here we'll use part of the world's [[BSP]] architecture (a "[[brush]]") to represent our entity and to decide how it collides and moves around the world. We'll also look at the touch function, available to all entities, which we will use to make the entity move when touched.


* Create a file named <code>sdk_brushentity.cpp</code>. 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 <code>C:\MyMod\src\dlls\sdk\sdk_brushentity.cpp</code>.
== Declaration and DATADESC ==
* Next, copy [[Brush Entity Code | 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 <code>hl</code> project in the Solution Explorer window and choose '''Add''', then '''Add Existing Item'''.


=Walking through the code=
<syntaxhighlight lang="cpp">
#include "cbase.h"
#include "triggers.h"


==Creating the Class Definition==
class CMyBrushEntity : public CBaseTrigger
<pre>
class CMyBrushEntity : public CBaseToggle
{
{
public:
public:
      DECLARE_CLASS( CMyBrushEntity, CBaseToggle );
DECLARE_CLASS( CMyBrushEntity, CBaseTrigger );
      DECLARE_DATADESC();
DECLARE_DATADESC();


      void Spawn( void );
void Spawn();
      bool CreateVPhysics( void );


      void BrushTouch( CBaseEntity *pOther );
void BrushTouch( CBaseEntity *pOther );
};
};
</pre>
We descend our new entity from the CBaseToggle class. This class has some basic functions to help us move our brush entity through the world.


==Defining the Data Description==
<pre>
LINK_ENTITY_TO_CLASS( my_brush_entity, CMyBrushEntity );
LINK_ENTITY_TO_CLASS( my_brush_entity, CMyBrushEntity );


// Start of our data description for the class
BEGIN_DATADESC( CMyBrushEntity )
BEGIN_DATADESC( CMyBrushEntity )
   
    // Declare this function as being a touch function
// Declare this function as being the touch function
    DEFINE_ENTITYFUNC( BrushTouch ),
DEFINE_ENTITYFUNC( BrushTouch ),


END_DATADESC()
END_DATADESC()
</pre>
</syntaxhighlight>
 
A brush entity can inherit from <code>[[CBaseEntity]]</code> if it really wants to, but in our example we'll take advantage of some code that's already written for brush entities in <code>[[CBaseTrigger]]</code>. To access it we need a second include: <code>triggers.h</code>.
 
<code>DEFINE_ENTITYFUNC</code> is a badly-named command to declare what function should be executed [[OnTouch]] - i.e. when the entity touches or is touched by another. Aside from our intended use, it's good for explode-on-contact ordnance, flying entities colliding with things, picking up health etc. (on foot or in a vehicle), [[trigger]]s, and so on.


Here we simply declare our touch function that we’ll use. See the [[Data Descriptions|Data Description Table Document]] for more information.
There is no constructor for this entity as there are no variables to initialise.


==Create the Spawn() function==
== Spawn() ==
<pre>
 
void CMyBrushEntity::Spawn( void )
<syntaxhighlight lang="cpp">
void CMyBrushEntity::Spawn()
{
{
      // We want to capture touches from other entities
BaseClass::Spawn();
      SetTouch( &CMyBrushEntity::BrushTouch );
 
// We want to capture touches from other entities
SetTouch( &CMyBrushEntity::BrushTouch );
 
// We should collide with physics
SetSolid( SOLID_VPHYSICS );
// Use our brushmodel
SetModel( STRING( GetModelName() ) );


      // We should collide with physics
// We push things out of our way
      SetSolid( SOLID_VPHYSICS );
SetMoveType( MOVETYPE_PUSH );
     
      // We push things out of our way
      SetMoveType( MOVETYPE_PUSH );
     
      // Use our brushmodel
      SetModel( STRING( GetModelName() ) );


      // Create our physics hull information
// Create our physics hull information
      CreateVPhysics();
VPhysicsInitShadow( false, false );
}
}
</pre>
</syntaxhighlight>


The first thing we do in this block is setup our touch function to point to <i>BrushTouch()</i> where we’ll do our movement code. Next we tell the entity to use <code>SOLID_VPHYSICS</code> so we’ll use our exact bounds to collide. Setting the entity to <code>MOVETYPE_PUSH</code> means that we’ll attempt to move entities out of our way, instead of just being blocked.
<code>[[SetTouch()]]</code> behaves like <code>[[SetThink()]]</code>. The default touch function is <code>[[StartTouch()]]</code> and we're only making one with a different name as a demonstration.


In this example we use the <code>SetModel()</code> with our model name from the editor. In this case it tells the entity to use its brush model, as defined in the map.
We'll be simulating collisions with [[VPhysics]]. Since this entity will never rotate you may think it more sensible to use <code>SOLID_BBOX</code>, like we did with the model entity, but bear in mind that the brush or brush''es'' tied to this entity could easily be forming a complex shape. We shouldn't use <code>SOLID_BSP</code> either, as that only works for non-interactive [[world]] brushes. Which leave two options: either <code>SOLID_VPHYSICS</code> or <code>SOLID_NONE</code>. Obviously, to achieve our goals, we want <code>SOLID_VPHYSICS</code>.


<pre>
A brush entity never has a pre-defined model. We always call <code>SetModel( STRING( GetModelName() ) )</code> to use whatever the map has given us. Note how we call three functions within each other here: One to return the model name, another to convert it to a string, and the third to act on the returned data. We are able to do this because these, unlike our <code>void</code> functions, <code>[[STRING()]]</code> and <code>[[GetModelName()]]</code> return values.
bool CMyBrushEntity::CreateVPhysics( void )
{
      // For collisions with physics objects
      VPhysicsInitShadow( false, false );


      return true;
<code>MOVETYPE_PUSH</code> is the behaviour you'll be used to from most brush entities; moving platforms, buttons, doors, and so on. It simply pushes whatever is in its way out of the way, stopping and potentially applying crush damage to obstructions when it can move no further. The only caveat for this behaviour is that it won't collide with world brushes, as it is assumed that the mapper has compensated for this by limiting the entity's freedom of movement. (Limiting our entity's movement won't be possible through any means, but you could easily make it so with some variables.)
}
</pre>


Finally, we call <code>CreateVPhysics()</code> to setup our collision shadow. This is what we’ll use to collide with physics objects in the world. Without this, the brush would pass through those objects.
Finally, we initialise our VPhysics collisions. <code>[[VPhysicsInitShadow()]]</code>, as opposed to <code>[[VPhysicsInitNormal()]]</code> and <code>[[VPhysicsInitStatic()]]</code>, creates a physics object that collides with other physics objects without being affected by them itself. It follows the entity rather than leading it. Hover your mouse over the function's name and you'll see the arguments it takes - <code>MOVETYPE_PUSH</code> overrides both, but keep them <code>false</code> anyway to avoid any unnecessary calculations.


==Create the BrushTouch() function==
== BrushTouch() ==


The entity has been told to notify us when its been touched, via the <code>BrushTouch()</code> function. When we receive this notification, we’ll cause the entity to move away from the entity that touched it. To do this, we’ll need information about the events surrounding the touch. This information is provided in the <code>trace_t</code> structure, returned by the <code>GetTouchTrace()</code> function. This returns the actual trace collision that generated the event.
<syntaxhighlight lang="cpp">
 
<pre>
void CMyBrushEntity::BrushTouch( CBaseEntity *pOther )
void CMyBrushEntity::BrushTouch( CBaseEntity *pOther )
{
{
    // Get the collision information
// Get the collision information
    const trace_t &tr = GetTouchTrace();
const trace_t &tr = GetTouchTrace();
 
// We want to move away from the impact point along our surface
Vector vecPushDir = tr.plane.normal;
vecPushDir.Negate();
vecPushDir.z = 0.0f;


    // We want to move away from the impact point along our surface
// Uncomment this line to print plane information to the console in developer mode
    Vector vecPushDir = tr.plane.normal;
//DevMsg ( "%s (%s) touch plane's normal: [%f %f]\n", GetClassname(), GetDebugName(),tr.plane.normal.x, tr.plane.normal.y );
    vecPushDir.Negate();
    vecPushDir.z = 0.0f;


    // Move slowly in that direction
// Move slowly in that direction
    LinearMove( GetAbsOrigin() + ( vecPushDir * 64.0f ), 32.0f );
LinearMove( GetAbsOrigin() + ( vecPushDir * 64.0f ), 32.0f );
}
}
</pre>
</syntaxhighlight>
 
This function uses a <code>[[trace_t]]</code>, or "[[UTIL_TraceLine|traceline]]". This is a special type of vector that is mainly used either to decide what something has hit, be that a bullet, an NPC's line of sight, or a debug command, or to work out where something that has hit an entity has come from. We are using it in the latter sense.


First we retrieve the normal of the surface that was hit. In our case, this will be one of the planes of the brush entity. We negate that value to point towards the direction of the impact, and then remove the Z component of the direction to keep us parallel to the floor.
A bit of optimisation: we declare <code>tr</code> as a <code>const</code> ("constant") because we will never need to change it. This reduces overhead slightly when compared to a normal, dynamic variable declaration.


Finally, we use the <code>LinearMove()</code> function to cause the brush to move to a location at a given speed. The <code>LinearMove()</code> function is implemented by <code>CBaseToggle</code> and takes care of behind-the-scenes maintenance in how the brush model moves.
With our traceline in place we extract the "normal" of the "[[plane]]" (i.e. face) on which our brush was hit. This returns the vector of the face struck without considering the angle it was struck at. For a cuboid brush this would give axes of either 0 or 1, since its sides are all at right angles. The easiest way of understanding this principle is by uncommenting the <code>DevMsg</code> command, enabling developer mode, and experimenting with brushes of different shapes yourself.


==Create The FGD Entry==
We then flip the vector around, so that it points away from the impact rather than toward, remove any z-axis (up/down) movement it might have (in order to stop it from flying into the sky where the player can't easily get at it), and call <code>[[LinearMove()]]</code>, a movement function we've inherited from <code>[[CBaseTrigger]]</code>.


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.
We ''multiply'' <code>vecPushDir</code> for <code>LinearMove()</code>. This is because its length is always 1, and multiplication by a number (<code>float</code> or <code>int</code>) does not affect any of a vector's axes.


The FGD entry allows you to assign the entity to a brush.
To sum up this final line of code: move 64 units away from your [[GetAbsOrigin()|current position]] in this direction, at 32 units per second. Vectors can be tricky to wrap your head around, so don't feel left behind if you don't understand them yet. Keep experimenting, and [[Wikipedia:Vector|check Wikipedia]] for further explanation.
 
== FGD entry ==
 
<syntaxhighlight>
@include "base.fgd"


<pre>
@SolidClass base(Targetname) = my_brush_entity : "Tutorial brush entity."
@SolidClass base(Targetname) = my_brush_entity : "Tutorial brush entity."
[
[
]
]
</pre>
</syntaxhighlight>


Be sure to add the line @include "base.fgd" at the top of the FGD file, which provides Hammer with some necessary functions.
== The working entity ==
 
You can't spawn brush entities from the console, so you'll have go into Hammer and tie a (preferably smallish) world brush to <code>my_brush_entity</code>. [[Brush entity|There's a guide to doing this here]]. Compile the map and you'll be able to start playing around.
 
;It won't stop at the edges of my map
:As discussed earlier, we are using a type of movement that assumes constraints. We don't have any, except in the z-axis.
;It will crush crates and boxes, but not NPCs
:The crushing is a function of the crates' and boxes' VPhysics simulation, and not related to anything we're doing. NPCs don't get crushed because, with a few exceptions, they aren't physically simulated.
;Sometimes it moves ''towards'' the impact, not away from it!
:There is a momentary delay in <code>BrushTouch()</code> being called that allows for the touching entity to begin bouncing away before a measurement is made. This inverts <code>tr.plane.normal</code>'s axes. Find the dot product of the direction vector, and the surface normal of the brush face. If it's positive, it's pointing away from the face. Negative, it's pointing into the face.
 
== See also ==
* [[Authoring a Brush Entity/Code|Tutorial code in full]]
* [[Your First Entity]]
 
[[Category:Programming]]
[[Category:Tutorials]]

Latest revision as of 04:48, 12 July 2024

English (en)Русский (ru)Translate (Translate)


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

Our last example dealt with giving entities a model. Here we'll use part of the world's BSP architecture (a "brush") to represent our entity and to decide how it collides and moves around the world. We'll also look at the touch function, available to all entities, which we will use to make the entity move when touched.

Declaration and DATADESC

#include "cbase.h"
#include "triggers.h"

class CMyBrushEntity : public CBaseTrigger
{
public:
	DECLARE_CLASS( CMyBrushEntity, CBaseTrigger );
	DECLARE_DATADESC();

	void Spawn();

	void BrushTouch( CBaseEntity *pOther );
};

LINK_ENTITY_TO_CLASS( my_brush_entity, CMyBrushEntity );

BEGIN_DATADESC( CMyBrushEntity )
	
	// Declare this function as being the touch function
	DEFINE_ENTITYFUNC( BrushTouch ),

END_DATADESC()

A brush entity can inherit from CBaseEntity if it really wants to, but in our example we'll take advantage of some code that's already written for brush entities in CBaseTrigger. To access it we need a second include: triggers.h.

DEFINE_ENTITYFUNC is a badly-named command to declare what function should be executed OnTouch - i.e. when the entity touches or is touched by another. Aside from our intended use, it's good for explode-on-contact ordnance, flying entities colliding with things, picking up health etc. (on foot or in a vehicle), triggers, and so on.

There is no constructor for this entity as there are no variables to initialise.

Spawn()

void CMyBrushEntity::Spawn()
{
	BaseClass::Spawn();

	// We want to capture touches from other entities
	SetTouch( &CMyBrushEntity::BrushTouch );

	// We should collide with physics
	SetSolid( SOLID_VPHYSICS );
	
	// Use our brushmodel
	SetModel( STRING( GetModelName() ) );

	// We push things out of our way
	SetMoveType( MOVETYPE_PUSH );

	// Create our physics hull information
	VPhysicsInitShadow( false, false );
}

SetTouch() behaves like SetThink(). The default touch function is StartTouch() and we're only making one with a different name as a demonstration.

We'll be simulating collisions with VPhysics. Since this entity will never rotate you may think it more sensible to use SOLID_BBOX, like we did with the model entity, but bear in mind that the brush or brushes tied to this entity could easily be forming a complex shape. We shouldn't use SOLID_BSP either, as that only works for non-interactive world brushes. Which leave two options: either SOLID_VPHYSICS or SOLID_NONE. Obviously, to achieve our goals, we want SOLID_VPHYSICS.

A brush entity never has a pre-defined model. We always call SetModel( STRING( GetModelName() ) ) to use whatever the map has given us. Note how we call three functions within each other here: One to return the model name, another to convert it to a string, and the third to act on the returned data. We are able to do this because these, unlike our void functions, STRING() and GetModelName() return values.

MOVETYPE_PUSH is the behaviour you'll be used to from most brush entities; moving platforms, buttons, doors, and so on. It simply pushes whatever is in its way out of the way, stopping and potentially applying crush damage to obstructions when it can move no further. The only caveat for this behaviour is that it won't collide with world brushes, as it is assumed that the mapper has compensated for this by limiting the entity's freedom of movement. (Limiting our entity's movement won't be possible through any means, but you could easily make it so with some variables.)

Finally, we initialise our VPhysics collisions. VPhysicsInitShadow(), as opposed to VPhysicsInitNormal() and VPhysicsInitStatic(), creates a physics object that collides with other physics objects without being affected by them itself. It follows the entity rather than leading it. Hover your mouse over the function's name and you'll see the arguments it takes - MOVETYPE_PUSH overrides both, but keep them false anyway to avoid any unnecessary calculations.

BrushTouch()

void CMyBrushEntity::BrushTouch( CBaseEntity *pOther )
{
	// Get the collision information
	const trace_t &tr = GetTouchTrace();

	// We want to move away from the impact point along our surface
	Vector	vecPushDir = tr.plane.normal;
	vecPushDir.Negate();
	vecPushDir.z = 0.0f;

	// Uncomment this line to print plane information to the console in developer mode
	//DevMsg ( "%s (%s) touch plane's normal: [%f %f]\n", GetClassname(), GetDebugName(),tr.plane.normal.x, tr.plane.normal.y );

	// Move slowly in that direction
	LinearMove( GetAbsOrigin() + ( vecPushDir * 64.0f ), 32.0f );
}

This function uses a trace_t, or "traceline". This is a special type of vector that is mainly used either to decide what something has hit, be that a bullet, an NPC's line of sight, or a debug command, or to work out where something that has hit an entity has come from. We are using it in the latter sense.

A bit of optimisation: we declare tr as a const ("constant") because we will never need to change it. This reduces overhead slightly when compared to a normal, dynamic variable declaration.

With our traceline in place we extract the "normal" of the "plane" (i.e. face) on which our brush was hit. This returns the vector of the face struck without considering the angle it was struck at. For a cuboid brush this would give axes of either 0 or 1, since its sides are all at right angles. The easiest way of understanding this principle is by uncommenting the DevMsg command, enabling developer mode, and experimenting with brushes of different shapes yourself.

We then flip the vector around, so that it points away from the impact rather than toward, remove any z-axis (up/down) movement it might have (in order to stop it from flying into the sky where the player can't easily get at it), and call LinearMove(), a movement function we've inherited from CBaseTrigger.

We multiply vecPushDir for LinearMove(). This is because its length is always 1, and multiplication by a number (float or int) does not affect any of a vector's axes.

To sum up this final line of code: move 64 units away from your current position in this direction, at 32 units per second. Vectors can be tricky to wrap your head around, so don't feel left behind if you don't understand them yet. Keep experimenting, and check Wikipedia for further explanation.

FGD entry

@include "base.fgd"

@SolidClass base(Targetname) = my_brush_entity : "Tutorial brush entity."
[
	
]

The working entity

You can't spawn brush entities from the console, so you'll have go into Hammer and tie a (preferably smallish) world brush to my_brush_entity. There's a guide to doing this here. Compile the map and you'll be able to start playing around.

It won't stop at the edges of my map
As discussed earlier, we are using a type of movement that assumes constraints. We don't have any, except in the z-axis.
It will crush crates and boxes, but not NPCs
The crushing is a function of the crates' and boxes' VPhysics simulation, and not related to anything we're doing. NPCs don't get crushed because, with a few exceptions, they aren't physically simulated.
Sometimes it moves towards the impact, not away from it!
There is a momentary delay in BrushTouch() being called that allows for the touching entity to begin bouncing away before a measurement is made. This inverts tr.plane.normal's axes. Find the dot product of the direction vector, and the surface normal of the brush face. If it's positive, it's pointing away from the face. Negative, it's pointing into the face.

See also