Thinking: Difference between revisions
(Eu traduzi tudo, mas pode ter alguns erros... eu nao sou muito bom em ver ce tem) |
mNo edit summary |
||
(2 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
{{ | {{LanguageBar}} | ||
[[File:Entity init.png|right|130px|The Source engine entity initialization process.]] | [[File:Entity init.png|right|130px|The Source engine entity initialization process.]] | ||
'''Think functions''' | '''Think functions''' allow entities to schedule code to be run in the future. By constantly rescheduling thinks, automated loops can be created that make an entity autonomous. | ||
<source lang=cpp> | == Scheduling == | ||
<code>SetNextThink()</code> is used to configure when an entity should next think. It accepts a [[float]] value. | |||
<source lang=cpp style="background:initial"> | |||
void CMyEntity::Spawn() | void CMyEntity::Spawn() | ||
{ | { | ||
BaseClass::Spawn(); | BaseClass::Spawn(); | ||
SetNextThink( gpGlobals->curtime ); // | SetNextThink( gpGlobals->curtime ); // Think now | ||
} | } | ||
void CMyEntity::Think() | void CMyEntity::Think() | ||
{ | { | ||
BaseClass::Think(); // | BaseClass::Think(); // Always do this if you override Think() | ||
Msg( " | Msg( "I think, therefore I am.\n" ); | ||
SetNextThink( gpGlobals->curtime + 1 ); // | SetNextThink( gpGlobals->curtime + 1 ); // Think again in 1 second | ||
} | } | ||
</source> | </source> | ||
Notice the use of <tt>gpGlobals->curtime</tt> to make the value passed relative to the time of execution. | |||
{{tip|<code>SetNextThink(-1)</code> | {{tip|<code>SetNextThink(-1)</code> will cancel any future thinks. This is better than <tt>SetNextThink(NULL)</tt>, because <tt>TICK_NEVER_THINK</tt> is -1.}} | ||
== | == New Think Functions == | ||
An entity can have any number of additional think functions. To register a new one: | |||
# | # Ensure the function is <tt>void</tt>. | ||
# | # Add it to the entity's [[DATADESC]] with <tt>DEFINE_THINKFUNC()</tt>. | ||
# | # Call <code>SetThink()</code> and pass a pointer to the function (see example below). | ||
# | # Ensure <code>DECLARE_DATADESC();</code> is in your class. | ||
<source lang=cpp> | <source lang=cpp> | ||
BEGIN_DATADESC( CMyEntity ) | BEGIN_DATADESC( CMyEntity ) | ||
DEFINE_THINKFUNC( MyThink ), // | DEFINE_THINKFUNC( MyThink ), // Register new think function | ||
END_DATADESC() | END_DATADESC() | ||
Line 41: | Line 41: | ||
{ | { | ||
BaseClass::Spawn(); | BaseClass::Spawn(); | ||
SetThink( &CMyEntity::MyThink ); // | SetThink( &CMyEntity::MyThink ); // Pass a function pointer | ||
SetNextThink(gpGlobals->curtime); | SetNextThink(gpGlobals->curtime); | ||
} | } | ||
Line 47: | Line 47: | ||
void CMyEntity::MyThink() | void CMyEntity::MyThink() | ||
{ | { | ||
Msg( " | Msg( "I think, therefore I am.\n" ); | ||
SetNextThink( gpGlobals->curtime + 1 ); | SetNextThink( gpGlobals->curtime + 1 ); | ||
} | } | ||
</source> | </source> | ||
Splitting your think code into different functions makes it easy to switch an entity between modes of operation. | |||
{{tip|<code>SetThink()</code> | {{tip|<code>SetThink()</code> can be called from within a think function, too. The next call will be to the new function.}} | ||
== | == Using Contexts == | ||
It is possible to schedule any number of think functions side-by-side with "think contexts." To create a new context: | |||
# | # Call <code>RegisterThinkContext([[string]] ContextName)</code> | ||
# | # Call <code>SetContextThink([[void]]* Function, float NextThinkTime, string ContextName)</code> | ||
# For subsequent thinks, call <code>SetNextThink(float NextThinkTime, string | # For subsequent thinks, call <code>SetNextThink(float NextThinkTime, string ContextName)</code> | ||
<source lang=cpp> | <source lang=cpp> | ||
Line 68: | Line 68: | ||
void CMyEntity::Spawn() | void CMyEntity::Spawn() | ||
{ | { | ||
SetNextThink( gpGlobals->curtime ); // | SetNextThink( gpGlobals->curtime ); // Default think loop - no context | ||
RegisterThinkContext( "TestContext" ); | RegisterThinkContext( "TestContext" ); | ||
SetContextThink( &CMyEntity::ContextThink, gpGlobals->curtime, " | SetContextThink( &CMyEntity::ContextThink, gpGlobals->curtime, "TestContext" ); | ||
} | } | ||
Line 89: | Line 89: | ||
</source> | </source> | ||
This creates two simultaneous think loops, both writing to the console at different rates. | |||
{{tip| | {{tip|Creating a new context is a great way to delay function calls to the future without upsetting existing think loops.}} | ||
== Utilities == | |||
These should be self-explanatory: | |||
<source lang=cpp> | <source lang=cpp> | ||
float GetLastThink() | float GetLastThink() | ||
float GetNextThink() | float GetNextThink() | ||
int GetLastThinkTick() | int GetLastThinkTick() | ||
int GetNextThinkTick() | int GetNextThinkTick() | ||
</source> | </source> | ||
[[File:Barnacle.jpg|right|50px|link=npc_barnacle]] | [[File:Barnacle.jpg|right|50px|link=npc_barnacle]] | ||
The <code>GetLast</code> functions are useful for controlling the rate at which something occurs. | |||
This think code from {{ent|npc_barnacle}} modulates the speed of tongue movement, even if the frequency of thinking changes: | |||
<source lang=cpp> | <source lang=cpp> | ||
float dt = gpGlobals->curtime - GetLastThink(); // dt is "delta time" | |||
float dt = gpGlobals->curtime - GetLastThink(); // dt | SetAltitude( m_flAltitude + m_flBarnaclePullSpeed * dt ); // Change tongue altitude | ||
SetAltitude( m_flAltitude + m_flBarnaclePullSpeed * dt ); // | |||
</source> | </source> | ||
For its non-[[skeletal]] animation to be smooth, this code would need to be executed every frame. This is exactly what happens, until the barnacle is no longer in the player's [[PVS]] and the rate is slowed down—thus requiring the above code. | |||
==<tt>ClientThink()</tt>== | ==<tt>ClientThink()</tt>== | ||
Thinking can also occur on the client, but its effects are limited. Only one think function is supported for each entity. | |||
<span style="color:blue;">void</span> C_MyEntity::ClientThink() | <span style="color:blue;">void</span> C_MyEntity::ClientThink() | ||
{ | { | ||
Msg( <span style="color:brown;">" | Msg( <span style="color:brown;">"Don't put anything [[expensive]] in this function!\n"</span> ); | ||
SetNextClientThink( CLIENT_THINK_ALWAYS ); <span style="color:green;">/ | SetNextClientThink( CLIENT_THINK_ALWAYS ); <span style="color:green;">// Think every frame</span> | ||
} | } | ||
Some examples of client-side thinking are: | |||
* Visual effects / particles | |||
* VGUI screen interaction | |||
* Modifying player speed (done on the client as well as server to avoid [[prediction]] errors) | |||
* Striders’ legs snapping ropes (disabled by default) | |||
<code>SetNextClientThink()</code> is used to schedule <code>ClientThink()</code>. There are two special values it accepts: | |||
< | ; <tt>CLIENT_THINK_ALWAYS</tt>: Think on the client once every frame. Use with caution! {{tip|Use <code>gpGlobals->frametime</code> to regulate speed.}} | ||
; <tt>CLIENT_THINK_NEVER</tt>: Pause all automated client thinking. | |||
[[Category:AI]] | |||
[[Category:Functions]] | |||
[[Category:Programming]] |
Latest revision as of 00:54, 24 August 2024
Think functions allow entities to schedule code to be run in the future. By constantly rescheduling thinks, automated loops can be created that make an entity autonomous.
Scheduling
SetNextThink()
is used to configure when an entity should next think. It accepts a float value.
void CMyEntity::Spawn()
{
BaseClass::Spawn();
SetNextThink( gpGlobals->curtime ); // Think now
}
void CMyEntity::Think()
{
BaseClass::Think(); // Always do this if you override Think()
Msg( "I think, therefore I am.\n" );
SetNextThink( gpGlobals->curtime + 1 ); // Think again in 1 second
}
Notice the use of gpGlobals->curtime to make the value passed relative to the time of execution.

SetNextThink(-1)
will cancel any future thinks. This is better than SetNextThink(NULL), because TICK_NEVER_THINK is -1.New Think Functions
An entity can have any number of additional think functions. To register a new one:
- Ensure the function is void.
- Add it to the entity's DATADESC with DEFINE_THINKFUNC().
- Call
SetThink()
and pass a pointer to the function (see example below). - Ensure
DECLARE_DATADESC();
is in your class.
BEGIN_DATADESC( CMyEntity )
DEFINE_THINKFUNC( MyThink ), // Register new think function
END_DATADESC()
void CMyEntity::Spawn()
{
BaseClass::Spawn();
SetThink( &CMyEntity::MyThink ); // Pass a function pointer
SetNextThink(gpGlobals->curtime);
}
void CMyEntity::MyThink()
{
Msg( "I think, therefore I am.\n" );
SetNextThink( gpGlobals->curtime + 1 );
}
Splitting your think code into different functions makes it easy to switch an entity between modes of operation.

SetThink()
can be called from within a think function, too. The next call will be to the new function.Using Contexts
It is possible to schedule any number of think functions side-by-side with "think contexts." To create a new context:
- Call
RegisterThinkContext(string ContextName)
- Call
SetContextThink(void* Function, float NextThinkTime, string ContextName)
- For subsequent thinks, call
SetNextThink(float NextThinkTime, string ContextName)
BEGIN_DATADESC( CMyEntity )
DEFINE_THINKFUNC( ContextThink ),
END_DATADESC()
void CMyEntity::Spawn()
{
SetNextThink( gpGlobals->curtime ); // Default think loop - no context
RegisterThinkContext( "TestContext" );
SetContextThink( &CMyEntity::ContextThink, gpGlobals->curtime, "TestContext" );
}
void CMyEntity::Think()
{
BaseClass::Think();
Msg( "Think\n" );
SetNextThink( gpGlobals->curtime + .1 );
}
void CMyEntity::ContextThink()
{
Msg( "Context think\n" );
SetNextThink(gpGlobals->curtime + .2, "TestContext" );
}
This creates two simultaneous think loops, both writing to the console at different rates.

Utilities
These should be self-explanatory:
float GetLastThink()
float GetNextThink()
int GetLastThinkTick()
int GetNextThinkTick()
The GetLast
functions are useful for controlling the rate at which something occurs.
This think code from npc_barnacle modulates the speed of tongue movement, even if the frequency of thinking changes:
float dt = gpGlobals->curtime - GetLastThink(); // dt is "delta time"
SetAltitude( m_flAltitude + m_flBarnaclePullSpeed * dt ); // Change tongue altitude
For its non-skeletal animation to be smooth, this code would need to be executed every frame. This is exactly what happens, until the barnacle is no longer in the player's PVS and the rate is slowed down—thus requiring the above code.
ClientThink()
Thinking can also occur on the client, but its effects are limited. Only one think function is supported for each entity.
void C_MyEntity::ClientThink() { Msg( "Don't put anything expensive in this function!\n" ); SetNextClientThink( CLIENT_THINK_ALWAYS ); // Think every frame }
Some examples of client-side thinking are:
- Visual effects / particles
- VGUI screen interaction
- Modifying player speed (done on the client as well as server to avoid prediction errors)
- Striders’ legs snapping ropes (disabled by default)
SetNextClientThink()
is used to schedule ClientThink()
. There are two special values it accepts:
- CLIENT_THINK_ALWAYS
- Think on the client once every frame. Use with caution!
Tip:Use
gpGlobals->frametime
to regulate speed. - CLIENT_THINK_NEVER
- Pause all automated client thinking.