Thinking: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
(corrections)
(redone: properly explained think contexts, made headings task-oriented)
Line 1: Line 1:
{{toc-right}}
'''Think functions''' allow entities to schedule code to be run in the future. By constantly rescheduling, automated "think loops" can be created that make the entity run code autonomously.


An entity's '''Think()''' function is the root gateway for processing without external [[input]]. Some entities (such as <code>[[CAI_BaseNPC]]</code>) think automatically when they are spawned, but others [[#SetNextThink()|need prompting]] first.
== Scheduling ==


This article describes all of the functions that play a part in thinking.
<code>SetNextThink()</code> is used to configure when an entity should next think. Its accepts a [[float]] value.


== Creating a think function ==
<source lang=cpp>
void CMyEntity::Spawn()
{
BaseClass::Spawn();
SetNextThink( gpGlobals->curtime ); // Think now
}


A new think function:  
void CMyEntity::Think()
{
BaseClass::Think(); // Always do this if you override Think()


* Must be <code>void</code>
Msg( "I think, therefore I am.\n" );
* Must be added to the entity's [[DATADESC]] with <code>DEFINE_THINKFUNC</code>
SetNextThink( gpGlobals->curtime + 1 ); // Think again in 1 second
* Should probably make a call to <code>SetNextThink()</code>
}
</source>


Otherwise it is like any other function.
Notice the use of <code>[[gpGlobals]]->curtime</code> to make the value passed relative to the time of execution.


{{note|Overrides of <code>Think()</code> itself should include a call to <code>BaseClass::Think()</code>, or you may find that <code>SetThink()</code> breaks.}}
{{tip|<code>SetNextThink(0)</code> or <code>SetNextThink(null)</code> will cancel any future thinks.}}


== SetNextThink() ==
== New think functions ==


* Defines when the entity next automatically thinks.
An entity can have any number of additional think functions. To register a new one:
* Accepts a [[float|floating point]] value.
* New values override old.


<span style="color:blue;">void</span> CMyEntity::Think()
# Ensure the function is <code>void</code>.
{
# Add it to the entity's [[DATADESC]] with <code>DEFINE_THINKFUNC()</code>.
Msg( <span style="color:brown;">"I think, therefore I am.\n"</span> );
# Call <code>SetThink()</code> and pass a pointer to the function (see example below).
SetNextThink( gpGlobals->curtime + 1 ); <span style="color:green;">// Think again in 1 second</span>
 
BaseClass::Think();
<source lang=cpp>
}
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);
}


This code causes the entity to print a message to the console once per second. Note the use of <code>[[gpGlobals]]->curtime</code> to make the NextThink time relative to now.
void CMyEntity::MyThink()
{
Msg( "I think, therefore I am.\n" );
SetNextThink( gpGlobals->curtime + 1 );
}
</source>


{{tip|<code>SetNextThink(0)</code> or <code>SetNextThink(null)</code> will pause automatic thinking.}}
Splitting your think code into different functions makes it easy to switch an entity between modes of operation.


{{tip|Use [[#ClientThink()|ClientThink()]] to have an entity think exactly once per screen frame.}}
{{tip|<code>SetThink()</code> can be called from within a think function, too. The next call will be to the new function.}}


== SetThink() ==
== Using contexts ==


* Changes the active automatic think function
It is possible to schedule any number of think functions side-by-side with "think contexts". To create a new context:
* Accepts a function [[pointer]]: add <code>&</code> in front and omit the parentheses.
* New values override old.


<span style="color:blue;">void</span> CMyEntity::Think()
# Call <code>RegisterThinkContext([[string]] ContextName)</code>
{
# Call <code>SetContextThink([[void]]* Function, float NextThinkTime, string ContextName)</code>
Msg( <span style="color:brown;">"I think, therefore I am.\n"</span> );
# For subsequent thinks, call <code>SetNextThink(float NextThinkTime, string ContextName)</code>
SetThink( &CMyEntity::Think2 ); <span style="color:green;">// Think with this function next</span>
SetNextThink( gpGlobals->curtime + 1 );
BaseClass::Think();
}
<span style="color:blue;">void</span> CMyEntity::Think2()
{
Msg( <span style="color:brown;">"Variety is the spice of life.\n"</span> );
SetThink( &CMyEntity::Think ); <span style="color:green;">// Think with this function next</span>
SetNextThink( gpGlobals->curtime + 1 );
}


This code switches thinking between two functions. A real-world application is to change an entity between various life stages: consider [http://forums.gamedesign.net/viewtopic.php?t=3702 a buildable gun turret]. One think function would run while it waits to be unpackaged, another while it is being built, another while it is active, and a fourth when it dies. Creating think functions for each discrete stage increases code stability and aids debugging.
<source lang=cpp>
BEGIN_DATADESC( CMyEntity )
DEFINE_THINKFUNC( ContextThink ),
END_DATADESC()


== SetContextThink() ==
void CMyEntity::Spawn()
{
SetNextThink( gpGlobals->curtime ); // Default think loop - no context
RegisterThinkContext( "TestContext" );
SetContextThink( &TestEnt432::ContextThink, gpGlobals->curtime, "TestContext" );
}


{{todo|Confirm all of this.}}
void TestEnt432::Think()
{
BaseClass::Think();


Fires a think function once, at a defined time. The third parameter is a "context", a [[string]] value that seems to be used to group multiple thinkfuncs under a given heading.
Msg( "Think\n" );
SetNextThink( gpGlobals->curtime + .1 );
}


<code>[[CAI_ActBusyQueueGoal]]</code> provides good examples, such as:
void TestEnt432::ContextThink()
{
Msg( "Context think\n" );
SetNextThink(gpGlobals->curtime + .2, "TestContext" );
}
</source>


<span style="color:blue;">void</span> CAI_ActBusyQueueGoal::MoveQueueUp()
This creates two simulataneous think loops, both writing to the console at different rates.
{
<span style="color:green;">// Find the node the NPC has arrived at, and tell the guy behind him to move forward</span>
<span style="color:blue;">if</span> ( GetNextThink( QUEUE_MOVEUP_THINK_CONTEXT ) < gpGlobals->curtime )
{
<span style="color:blue;">float</span> flTime = gpGlobals->curtime + RandomFloat( 0.3, 0.5 );
SetContextThink( &CAI_ActBusyQueueGoal::MoveQueueUpThink, flTime, QUEUE_MOVEUP_THINK_CONTEXT );
}
}


== GetLastThink() ==
{{tip|Creating a new context is a great way to delay function calls to the future without upsetting existing think loops.}}


A utility function that returns the time of the entity’s last think as a float.
== Utilities ==


<span style="color:blue;">float</span> dt = gpGlobals->curtime - GetLastThink();
These should be self-explanatory:
SetAltitude( m_flAltitude + m_flBarnaclePullSpeed * dt ); <span style="color:green;">// Change tongue altitude</span>


[[Image:Barnacle.jpg|right|50px|npc_barnacle]]
<source lang=cpp>
float GetLastThink()
float GetNextThink()
int GetLastThinkTick()
int GetNextThinkTick()
</source>


This real-world think code for [[npc_barnacle]] modulates the speed of tongue movement, even if the frequency of thinking changes. <code>dt</code> is short for “delta time”.
[[File:Barnacle.jpg|right|50px|link=npc_barnacle]]


{{note|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 modulation.}}
The <code>GetLast</code> 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:


There are also:
<source lang=cpp>
float dt = gpGlobals->curtime - GetLastThink(); // dt is "delta time"
SetAltitude( m_flAltitude + m_flBarnaclePullSpeed * dt ); // Change tongue altitude
</source>


*<code><span style="color:blue;">float</span> GetNextThink()</code>
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.
*<code><span style="color:blue;">int</span> GetNextThinkTick()</code>
*<code><span style="color:blue;">int</span> GetLastThinkTick()</code>


== ClientThink() ==
== ClientThink() ==


Thinking can also occur on the client, but its effects are limited. Additionally, only one think function is supported for each entity.
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()
Line 110: Line 133:
* Visual effects / particles
* Visual effects / particles
* VGUI screen interaction
* VGUI screen interaction
* Modifying player speed (calculated on the client as well as server to avoid lag)
* Modifying player speed (done on the client as well as server to avoid [[prediction]] errors)
* Striders’ legs snapping ropes (disabled by default)
* Striders’ legs snapping ropes (disabled by default)


=== SetNextClientThink() ===
<code>SetNextClientThink()</code> is used to schedule <code>ClientThink()</code>. There are two special values it accepts:
 
Used to re-fire <code>ClientThink()</code>. In addition to normal float values, it accepts:


;CLIENT_THINK_ALWAYS
; CLIENT_THINK_ALWAYS
:Think on the client once every frame. Use with caution!
: Think on the client once every frame. Use with caution!
;CLIENT_THINK_NEVER
; CLIENT_THINK_NEVER
:Pause all automated client thinking.
: Pause all automated client thinking.


[[Category:Programming]]
[[Category:Programming]]
[[Category:AI]]
[[Category:AI]]
[[Category:Functions]]
[[Category:Functions]]

Revision as of 04:37, 17 September 2009

Think functions allow entities to schedule code to be run in the future. By constantly rescheduling, automated "think loops" can be created that make the entity run code autonomously.

Scheduling

SetNextThink() is used to configure when an entity should next think. Its 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.

Tip.pngTip:SetNextThink(0) or SetNextThink(null) will cancel any future thinks.

New think functions

An entity can have any number of additional think functions. To register a new one:

  1. Ensure the function is void.
  2. Add it to the entity's DATADESC with DEFINE_THINKFUNC().
  3. Call SetThink() and pass a pointer to the function (see example below).
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.

Tip.pngTip: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:

  1. Call RegisterThinkContext(string ContextName)
  2. Call SetContextThink(void* Function, float NextThinkTime, string ContextName)
  3. 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( &TestEnt432::ContextThink, gpGlobals->curtime, "TestContext" );
}

void TestEnt432::Think()
{
	BaseClass::Think();

	Msg( "Think\n" );
	SetNextThink( gpGlobals->curtime + .1 );
}

void TestEnt432::ContextThink()
{
	Msg( "Context think\n" );
	SetNextThink(gpGlobals->curtime + .2, "TestContext" );
}

This creates two simulataneous think loops, both writing to the console at different rates.

Tip.pngTip: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:

float	GetLastThink()
float	GetNextThink()
int	GetLastThinkTick()
int	GetNextThinkTick()
Barnacle.jpg

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!
CLIENT_THINK_NEVER
Pause all automated client thinking.