Authoring a Brush Entity: Difference between revisions
TomEdwards (talk | contribs) (partial rewrite) |
TomEdwards (talk | contribs) (phew) |
||
Line 1: | Line 1: | ||
''This tutorial assumes you have completed and understood [[Authoring a Logical Entity]] and, ideally, [[Authoring a Model Entity]].'' | ''This tutorial assumes you have completed and understood [[Authoring a Logical Entity]] and, ideally, [[Authoring a Model Entity]].'' | ||
Our last example dealt with [[Authoring a Model Entity|giving entities a model]]. Here we'll use world architecture ("[[brush]] | 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. | ||
== Declaration and DATADESC == | == Declaration and DATADESC == | ||
<span style="color:blue;">#include</span> <span style="color:brown;">"cbase.h"</span> | <span style="color:blue;">#include</span> <span style="color:brown;">"cbase.h"</span> | ||
<span style="color:blue;">#include</span> <span style="color:brown;">"triggers.h"</span> | |||
<span style="color:blue;">class</span> CMyBrushEntity : <span style="color:blue;">public</span> | <span style="color:blue;">class</span> CMyBrushEntity : <span style="color:blue;">public</span> CBaseTrigger | ||
{ | { | ||
<span style="color:blue;">public</span>: | <span style="color:blue;">public</span>: | ||
DECLARE_CLASS( CMyBrushEntity, | DECLARE_CLASS( CMyBrushEntity, CBaseTrigger ); | ||
DECLARE_DATADESC(); | DECLARE_DATADESC(); | ||
<span style="color:blue;">void</span> Spawn(); | <span style="color:blue;">void</span> Spawn(); | ||
<span style="color:blue;">void</span> BrushTouch( CBaseEntity *pOther ); | <span style="color:blue;">void</span> BrushTouch( CBaseEntity *pOther ); | ||
Line 23: | Line 23: | ||
BEGIN_DATADESC( CMyBrushEntity ) | BEGIN_DATADESC( CMyBrushEntity ) | ||
<span style="color:green;">// Declare this function as being | <span style="color:green;">// Declare this function as being the touch function</span> | ||
DEFINE_ENTITYFUNC( BrushTouch ), | DEFINE_ENTITYFUNC( BrushTouch ), | ||
END_DATADESC() | END_DATADESC() | ||
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 in <code> | 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 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. | <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 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. | ||
Line 44: | Line 44: | ||
SetSolid( SOLID_VPHYSICS ); | SetSolid( SOLID_VPHYSICS ); | ||
<span style="color:green;">// Use our brushmodel</span> | |||
SetModel( STRING( GetModelName() ) ); | |||
<span style="color:green;">// We push things out of our way</span> | <span style="color:green;">// We push things out of our way</span> | ||
SetMoveType( MOVETYPE_PUSH ); | SetMoveType( MOVETYPE_PUSH ); | ||
<span style="color:green;">// Create our physics hull information</span> | <span style="color:green;">// Create our physics hull information</span> | ||
VPhysicsInitShadow( <span style="color:blue;">false</span>, <span style="color:blue;">false</span> ); | |||
} | } | ||
<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. | |||
We'll be simulating collisions with [[VPhysics]]. Since this entity will never rotate you may think it more sensible to use <code>SOILD_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 SOLID_BSP either, as that only works for 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>. | |||
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 the, unlike our <code>void</code> functions, <code>[[STRING()]]</code> and <code>[[GetModelName()]]</code> return values. | |||
<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.) | |||
Finally, we | 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. | ||
== BrushTouch() == | == BrushTouch() == | ||
<span style="color:blue;">void</span> CMyBrushEntity::BrushTouch( CBaseEntity *pOther ) | |||
{ | { | ||
// Get the collision information | <span style="color:green;">// Get the collision information</span> | ||
const trace_t &tr = GetTouchTrace(); | <span style="color:blue;">const</span> trace_t &tr = GetTouchTrace(); | ||
// We want to move away from the impact point along our surface | <span style="color:green;">// We want to move away from the impact point along our surface</span> | ||
Vector vecPushDir = tr.plane.normal; | Vector vecPushDir = tr.plane.normal; | ||
vecPushDir.Negate(); | vecPushDir.Negate(); | ||
vecPushDir.z = 0.0f; | vecPushDir.z = 0.0f; | ||
// Move slowly in that direction | <span style="color:green;">// 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 );</span> | |||
<span style="color:green;">// Move slowly in that direction</span> | |||
LinearMove( GetAbsOrigin() + ( vecPushDir * 64.0f ), 32.0f ); | LinearMove( GetAbsOrigin() + ( vecPushDir * 64.0f ), 32.0f ); | ||
} | } | ||
This function uses a <code>[[trace_t]]</code>, 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 <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. | |||
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. | |||
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>. | |||
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. | |||
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 [[Wikipedia:Vector|check Wikipedia]] for further explanation. | |||
== FGD entry == | == FGD entry == | ||
Line 100: | Line 103: | ||
] | ] | ||
== 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</code>'s <code>normal</code> axes. Anyone know a solution? | |||
== See also == | == See also == |
Revision as of 14:18, 3 March 2008
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 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() { // 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 SOILD_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 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 the, 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 invertstr.plane
'snormal
axes. Anyone know a solution?