Authoring a Logical Entity: Difference between revisions
m (added russian otherlang) |
TomEdwards (talk | contribs) (made betterer, until the datadesc) |
||
Line 1: | Line 1: | ||
Logical entities are the simplest of entities because they have no position in the world, no visual component, and only exist to service [[input]] from other entities. <code>[[math_counter]]</code> for example stores a value that can be added to or subtracted from; other entities in the map can modify the data with inputs or receive information from it with an output. | |||
In this tutorial we'll create a logical entity that performs the simple task of storing a value and incrementing that value every time it receives the appropriate input. Once the counter has reached a value we'll define, the entity will fire an output. | |||
==Create the source file == | |||
[[Image:Addnewitem.png|center|Add a new .cpp file to Server/Source Files/]] | |||
< | Create a new .cpp file in <code>Sever/Source Files/</code> and call it '''sdk_logicalentity'''. Giving each entity its own .cpp reduces overhead, since it allows the compiler to separate your code into more discrete segments, and speeds compiling, since only .cpp files that have changed need to be re-compiled<!-- In VS2008 at least -->. | ||
class CMyLogicalEntity : public CLogicalEntity | |||
{ | == Includes == | ||
public: | |||
To get anywhere with our code we need to let the compiler know what "libraries" we are going to be calling on. Consider the commands we are going to be using to be books from these libraries. Insert this line of code: | |||
}; | |||
</pre> | <span style="color:blue;">#include</span> <span style="color:brown;">"cbase.h"</span> | ||
<code>cbase.h</code> provides Valve's basic set of commands for creating entities. Since we are creating their very simplest form, we only need the one base library. In more complex cases there can be many, many includes like this. | |||
The opening "c" means that this is a server-side library, and the closing ".h" means that it's a header file instead of a full .cpp - but don't worry about that for now. | |||
== Declaring the class == | |||
This section will be quite long, but once you're finished with it you'll have a grasp on the outline of all C++ code. | |||
In C++, instructions are handled by "objects". Think of them as the librarians of our ongoing ananlogy. | |||
Objects are created from templates like the one we are about to define: | |||
<span style="color:blue;">class</span> CMyLogicalEntity : <span style="color:blue;">public</span> CLogicalEntity | |||
{ | |||
<span style="color:blue;">public</span>: | |||
DECLARE_CLASS( CMyLogicalEntity, CLogicalEntity ); | |||
... | |||
}; | |||
This whole process is known as "declaring a class" (not just the <code>DECLARE_CLASS</code> command, despite its name!). Every version of your entity in the world is another "instance" of this template. The same is true of every entity: every [[Combine Soldier]] you've ever seen running around is an instance of the <code>CNPC_CombineS</code> C++ class. | |||
In the first line of code, we start by saying that we're declaring class with the <code>class</code> command. We then give it a name, <code>CMyLogicalEntity</code>, and then tell the compiler that it is "inheriting" from Valve's pre-existing <code>CLogicalEntity</code> class (that we can reach thanks to our include of <code>cbase.h</code> earlier).<!-- what does 'public' mean here? --> | |||
Inheriting means that we're working on a copy of the original class, overriding and extending it instead of starting entirely from scratch or changing the original class itself. It's almost exactly like copying a file on your desktop: you get the contents of the original, and can edit it separately. | |||
We then open a set of square parathenses. This means that all of the instructions inside are members of this class. Note how there isn't a line-ending semicolon here. We're still, technically, writing the same line of code. | |||
Next is "<code>public:</code>", which means that the commands coming up will need to interact with other .cpp files (We'll make some private commands in a moment). Each public command creates a little more overhead and increases the potential for nasty bugs, as well as adding another thing you have to think about when trying to interact with the class somewhere else. Only commands that really need to be public should be.<!-- this bit is crap --> | |||
=== The first command === | |||
Now - finally - we have the framework up and running and can start issuing commands. <code>DECLARE_CLASS</code> is a '[[macro]]' created by Valve to automate much of the book-keeping that needs to be done when declaring a new class. | |||
The round parentheses mean that we are passing "arguments", or "parameters", to it: to continue our analogy, we're saying (in this case) what book we want filing and where it belongs. If you're being observant you will already have noticed that we are sending it the same key information that's in the first line, separated, or "delimited", with a comma. | |||
This command means that when the class is initialised as your mod starts, somewhere else in the codebase a predefined class is performing lots of obscure and tedious legwork for you. The closing semicolon tells the compiler that this is the end of a command and a new one is about to start (line breaks and whitespace aren't taken into account - the compiler just sees a continuous stream of characters). | |||
=== Closing the declaration (ahead of time) === | |||
Don't type the ellipsis (<code>...</code>) - it's only there to show you where the rest of the declaration code will go. We're going to skip ahead for a moment instead and end the <code>class</code> command with <code>};</code>. It's simple enough: the closing bracket counters the opening one we typed earlier, and the semicolon performs the same function as it did after <code>DECLARE_CLASS</code>. | |||
=== Declaring a DATADESC, constructor and function === | |||
Now go back to where the ellipsis was and add these new lines: | |||
DECLARE_DATADESC(); | |||
<span style="color:green;">// Constructor</span> | |||
CMyLogicalEntity ( <span style="color:blue;">void</span> ) : m_nCounter( 0 ) {} | |||
<span style="color:green;">// Input function</span> | |||
<span style="color:blue;">void</span> InputTick( inputdata_t &inputData ); | |||
The first line is another macro, this time that automates the process of declaring a [[DATADESC]] table we'll be adding later. Thanks to the <code>//</code> the second is a commented line that is ignored by the compiler but helps people reading your code (including you, when you come back to it after a lengthy enough break) understand its meaning. | |||
The third is declaring the class "constructor": an instance of your class ''within'' your class that "[[Wikipedia:Bootstrapping (computing)|bootstraps]]" it into existence when triggered. | |||
I don't have a good idea of what's going on after the colon, someone please help. | |||
Lastly, we create our first "function". This is a collection of commands that are "called", or "executed", in one batch. In this case, our function will be called whenever an input is received and, if you remember back to our original goal, increment our stored value by 1. <code>void</code> means that the function won't return a value to the class elsewhere on the server that called it. That happens via a different route, and the remote class doing the calling wouldn't know what to do with a value anyway. | |||
We call this function <code>InputTick</code> and define the "parameters" it requires. In this case, one <code>inputdata_t</code> value named <code>&inputData</code>. This is information about the map entity that triggered the input that is automatically generated by the engine at run-time. | |||
=== Private declarations === | |||
Now we are declaring private objects. These won't be accessible anywhere outside this class unless we make them so in a public function (which we won't). | |||
<span style="color:blue;">private:</span> | |||
<span style="color:blue;">int</span> m_nThreshold; <span style="color:green;">// Count at which to fire our output</span> | |||
<span style="color:blue;">int</span> m_nCounter; <span style="color:green;">// Internal counter</span> | |||
COutputEvent m_OnThreshold; <span style="color:green;">// Output event when the counter reaches the threshold</span> | |||
The first two of these are "variables". They are buckets in the computer's physical memory that can be filled with a value, used in calculations, passed around, perhaps written to the disk, and emptied. They are "integer" or "int" variables which can be used to store whole numbers like 1, 2, 3, and so on. They can't store things like words of characters however, and can't store numbers with decimal points. C++ is very strict about these kinds of things! | |||
m_OnThreshhold is an instance of a class Valve have already written for you, COutputEvent. You'll use it in <code>InputTick</code> to pass a value through the I/O system when our conditions are met. | |||
A word about the names in this section. <code>m_</code> means that the objects are members of a class, while <code>n</code> means that the value is numeric. You don't ''have'' to follow these naming conventions, but it is strongly recommended. | |||
=== Check back === | |||
That's the declaration finished. Check back over what you're written and make sure it's the same as [[Logical Entity Code|the reference code]]. If it is, you're ready to move past that <code>};</code> and into the body of the entity's code. | |||
== | == Linking the class to an entity name == | ||
LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity ); | |||
This is another macro (you can tell from the capitals) that is giving the class an [[entity name]]. The programming code doesn't refer to the entity by the same name that the [[I/O]] system and [[Hammer]] does for reasons that you'll understand later. For the purposes of this tutorial you can choose whatever value you want for the first parameter, as this is the only time it will appear. | |||
==Declaring a data description for the entity== | ==Declaring a data description for the entity== | ||
Line 112: | Line 176: | ||
If your .FGD is otherwise empty, be sure to add the line <code>@include "base.fgd"</code>, 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.) | If your .FGD is otherwise empty, be sure to add the line <code>@include "base.fgd"</code>, 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.) | ||
== See Also == | |||
*[[Tutorial code in full|Logical Entity Code]] | |||
{{otherlang:en}} | {{otherlang:en}} |
Revision as of 14:31, 23 February 2008
Logical entities are the simplest of entities because they have no position in the world, no visual component, and only exist to service input from other entities. math_counter
for example stores a value that can be added to or subtracted from; other entities in the map can modify the data with inputs or receive information from it with an output.
In this tutorial we'll create a logical entity that performs the simple task of storing a value and incrementing that value every time it receives the appropriate input. Once the counter has reached a value we'll define, the entity will fire an output.
Create the source file
Create a new .cpp file in Sever/Source Files/
and call it sdk_logicalentity. Giving each entity its own .cpp reduces overhead, since it allows the compiler to separate your code into more discrete segments, and speeds compiling, since only .cpp files that have changed need to be re-compiled.
Includes
To get anywhere with our code we need to let the compiler know what "libraries" we are going to be calling on. Consider the commands we are going to be using to be books from these libraries. Insert this line of code:
#include "cbase.h"
cbase.h
provides Valve's basic set of commands for creating entities. Since we are creating their very simplest form, we only need the one base library. In more complex cases there can be many, many includes like this.
The opening "c" means that this is a server-side library, and the closing ".h" means that it's a header file instead of a full .cpp - but don't worry about that for now.
Declaring the class
This section will be quite long, but once you're finished with it you'll have a grasp on the outline of all C++ code.
In C++, instructions are handled by "objects". Think of them as the librarians of our ongoing ananlogy.
Objects are created from templates like the one we are about to define:
class CMyLogicalEntity : public CLogicalEntity { public: DECLARE_CLASS( CMyLogicalEntity, CLogicalEntity ); ... };
This whole process is known as "declaring a class" (not just the DECLARE_CLASS
command, despite its name!). Every version of your entity in the world is another "instance" of this template. The same is true of every entity: every Combine Soldier you've ever seen running around is an instance of the CNPC_CombineS
C++ class.
In the first line of code, we start by saying that we're declaring class with the class
command. We then give it a name, CMyLogicalEntity
, and then tell the compiler that it is "inheriting" from Valve's pre-existing CLogicalEntity
class (that we can reach thanks to our include of cbase.h
earlier).
Inheriting means that we're working on a copy of the original class, overriding and extending it instead of starting entirely from scratch or changing the original class itself. It's almost exactly like copying a file on your desktop: you get the contents of the original, and can edit it separately.
We then open a set of square parathenses. This means that all of the instructions inside are members of this class. Note how there isn't a line-ending semicolon here. We're still, technically, writing the same line of code.
Next is "public:
", which means that the commands coming up will need to interact with other .cpp files (We'll make some private commands in a moment). Each public command creates a little more overhead and increases the potential for nasty bugs, as well as adding another thing you have to think about when trying to interact with the class somewhere else. Only commands that really need to be public should be.
The first command
Now - finally - we have the framework up and running and can start issuing commands. DECLARE_CLASS
is a 'macro' created by Valve to automate much of the book-keeping that needs to be done when declaring a new class.
The round parentheses mean that we are passing "arguments", or "parameters", to it: to continue our analogy, we're saying (in this case) what book we want filing and where it belongs. If you're being observant you will already have noticed that we are sending it the same key information that's in the first line, separated, or "delimited", with a comma.
This command means that when the class is initialised as your mod starts, somewhere else in the codebase a predefined class is performing lots of obscure and tedious legwork for you. The closing semicolon tells the compiler that this is the end of a command and a new one is about to start (line breaks and whitespace aren't taken into account - the compiler just sees a continuous stream of characters).
Closing the declaration (ahead of time)
Don't type the ellipsis (...
) - it's only there to show you where the rest of the declaration code will go. We're going to skip ahead for a moment instead and end the class
command with };
. It's simple enough: the closing bracket counters the opening one we typed earlier, and the semicolon performs the same function as it did after DECLARE_CLASS
.
Declaring a DATADESC, constructor and function
Now go back to where the ellipsis was and add these new lines:
DECLARE_DATADESC(); // Constructor CMyLogicalEntity ( void ) : m_nCounter( 0 ) {} // Input function void InputTick( inputdata_t &inputData );
The first line is another macro, this time that automates the process of declaring a DATADESC table we'll be adding later. Thanks to the //
the second is a commented line that is ignored by the compiler but helps people reading your code (including you, when you come back to it after a lengthy enough break) understand its meaning.
The third is declaring the class "constructor": an instance of your class within your class that "bootstraps" it into existence when triggered.
I don't have a good idea of what's going on after the colon, someone please help.
Lastly, we create our first "function". This is a collection of commands that are "called", or "executed", in one batch. In this case, our function will be called whenever an input is received and, if you remember back to our original goal, increment our stored value by 1. void
means that the function won't return a value to the class elsewhere on the server that called it. That happens via a different route, and the remote class doing the calling wouldn't know what to do with a value anyway.
We call this function InputTick
and define the "parameters" it requires. In this case, one inputdata_t
value named &inputData
. This is information about the map entity that triggered the input that is automatically generated by the engine at run-time.
Private declarations
Now we are declaring private objects. These won't be accessible anywhere outside this class unless we make them so in a public function (which we won't).
private: int m_nThreshold; // Count at which to fire our output int m_nCounter; // Internal counter COutputEvent m_OnThreshold; // Output event when the counter reaches the threshold
The first two of these are "variables". They are buckets in the computer's physical memory that can be filled with a value, used in calculations, passed around, perhaps written to the disk, and emptied. They are "integer" or "int" variables which can be used to store whole numbers like 1, 2, 3, and so on. They can't store things like words of characters however, and can't store numbers with decimal points. C++ is very strict about these kinds of things!
m_OnThreshhold is an instance of a class Valve have already written for you, COutputEvent. You'll use it in InputTick
to pass a value through the I/O system when our conditions are met.
A word about the names in this section. m_
means that the objects are members of a class, while n
means that the value is numeric. You don't have to follow these naming conventions, but it is strongly recommended.
Check back
That's the declaration finished. Check back over what you're written and make sure it's the same as the reference code. If it is, you're ready to move past that };
and into the body of the entity's code.
Linking the class to an entity name
LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity );
This is another macro (you can tell from the capitals) that is giving the class an entity name. The programming code doesn't refer to the entity by the same name that the I/O system and Hammer does for reasons that you'll understand later. For the purposes of this tutorial you can choose whatever value you want for the first parameter, as this is the only time it will appear.
Declaring a data description for the entity
. . . public: DECLARE_CLASS( CMyLogicalEntity, CLogicalEntity); DECLARE_DATADESC(); . . . LINK_ENTITY_TO_CLASS( my_logical_entity, CMyLogicalEntity ); BEGIN_DATADESC( CMyLogicalEntity ) DEFINE_FIELD( m_nCounter, FIELD_INTEGER ), DEFINE_KEYFIELD( m_nThreshold, FIELD_INTEGER, "threshold" ), END_DATADESC()
The DECLARE_DATADESC
macro must be included to let the compiler know that we’ll be adding a data description table later in the class implementation. The data description holds various definitions for the data members and special functions for this class. In this case, m_nCounter
is defined for save/load functionality, and m_nThreshold
is defined to tell the game to use the value named "threshold"
to link this member variable to the entity keyvalue from Hammer. See the Data Descriptions Table Document for more information.
Creating the output event
COutputEvent m_OnThreshold; DEFINE_OUTPUT( m_OnThreshold, "OnThreshold" ),
This output event will be triggered when we meet the defined threshold. For more information on outputs, see Entity Inputs and Outputs.
Creating the input function
void InputTick( inputdata_t &inputData ); void CMyLogicalEntity::InputTick( inputdata_t &inputData ) { // Increment our counter m_nCounter++; // See if we've met or crossed our threshold value if ( m_nCounter >= m_nThreshold ) { // Fire an output event m_OnThreshold.FireOutput( inputData.pActivator, this ); // Reset our counter m_nCounter = 0; } }
This function simply increments a counter and fires an output when that counter reaches or exceeds a certain threshold value, as specified in the entity inside of Hammer. The function takes no parameters from Hammer.
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.
If you haven't created a custom .FGD file for your mod, you may want to do it at this point. To do this, create an empty file with a .FGD extension anywhere on your hard drive (putting it under your mod's folder is a good idea). Then paste the code below into it. Go into Hammer and choose Tools->Options and add the .FGD file in the Game Data files
section. The Game Configurations dialog is described in this document.
In this case we declare the "threshold" value we have linked to the m_nThreshold
data member, the input function Tick
and the OnThreshold
output function.
@PointClass base(Targetname) = my_logical_entity : "Tutorial logical entity." [ threshold(integer) : "Threshold" : 1 : "Threshold value." input Tick(void) : "Adds one tick to the entity's count." output OnThreshold(void) : "Threshold was hit." ]
If your .FGD is otherwise empty, be sure to add the line @include "base.fgd"
, 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.)