Make an NPC move somewhere

From Valve Developer Community
Jump to: navigation, search
Icon-broom.png
This article or section needs to be cleaned up to conform to a higher standard of quality.
For help, see the VDC Editing Help and Wikipedia cleanup process. Also, remember to check for any notes left by the tagger at this article's talk page.

Move and navigation types

NPCs mostly rely on goals that are either defined by their navigator or singularly called by subroutines or in code.

An NPC goal is a target that acts either under the form of a target entity or Vector position. There are several types of possibilities to set up a goal for an NPC. In order to set up a goal, there are several conditions that need to be taken into account.

Since most NPC classes in Half-Life 2 are considered humanoid (such as CAI_BaseActor, CAI_BaseHumanoid, and CAI_BaseNPC), their navigation type are set up by default to ground. Thus, in order to make this operation successful in the first place is to ensure that correct NPC flags and functions are well called in the default 'Spawn' function.

Usually, a base ground moving actor has the following setup set in it's spawn method.

SetHullType( HULL_HUMAN );

SetHullSizeNormal();

SetSolid( SOLID_BBOX );

AddSolidFlags( FSOLID_NOT_STANDABLE );

SetNavType( NAV_GROUND );

SetMoveType( MOVETYPE_STEP );


The last two lines of this code represent the main base of a mover. NAV_GROUND is the primary setup that tells the navigator which kind of navigation this NPC should be using. Every humanoid entity in Half-Life 2 use this. Flying NPCs such as Manhacks or Scanners use the common NAV_FLY which is used to specify a flyin NPC. If you are willing to make a ground mover, this statement 'NAV_GROUND' is important.

In second place comes the movetype which determines how this NPC should be affected by physical systems or gravity. Several other choices can be found in it's header file such as MOVETYPE_NONE, MOVETYPE_WALK, MOVETYPE_FLY, MOVETYPE_FLYGRAVITY; Several info regarding their use can be found next to their definitions.

Once the second function is called 'SetMoveType( MOVETYPE_STEP )', this NPC should be able to move on the ground using default ground node systems such as info_node.

Making the NPC move to a position

To begin with, the first thing that would be required is a goal and a good way to set up a predefined goal is by tasks. usually, tasks are grouped by schedules which are mainly used to execute a serie of those in a defined order. Make sure you have read introduction on how to make a simple NPC and understand the purpose of conditions / tasks / schedules.

Start out by declaring a new task in your designed task enum. e.g.

enum 
{
   TASK_MY_CUSTOM_NPC_TASK = BaseClass::NEXT_TASK,

   TASK_MY_CUSTOM_NPC_GET_PATH_TO_CUSTOM_POSITION,

   NEXT_TASK,
};


Ensure to write a custom schedule in order to call the main task.


enum
{
   SCHED_MY_FIRST_NPC_SCHEDULE = BaseClass::NEXT_SCHEDULE,

   SCHED_MY_FIRST_NPC_MOVE_TO_POSITION,

   NEXT_SCHEDULE,
};


Once this is done, you will be required to define the task that will make the npc move to a custom location. Before continuing, it is important that you add this macro definition to your class so that it will allow custom schedules and tasks.

Add this macro to you class.

DEFINE_CUSTOM_AI;


After this, in your .cpp file, you will be required to define the schedule providers by this:

Add this to your .cpp file


AI_BEGIN_CUSTOM_NPC( npc_myfisrtnpc, CNPC_MyFirstNPC )
AI_END_CUSTOM_NPC()


Example:

// npc_combine.h
class CNPC_Combine : public CAI_BaseActor
{

public:
  // Code written by Valve
  DECLARE_CLASS( CNPC_Combine, CAI_BaseActor );
  DECLARE_DATADESC();
  DEFINE_CUSTOM_AI;


};

// npc_combine.cpp

BEGIN_DATADESC()
END_DATADESC()

LINK_ENTITY_TO_CLASS( npc_combine, CNPC_Combine );

AI_BEGIN_CUSTOM_NPC( npc_combine, CNPC_Combine )

   DECLARE_CONDITION( 'custom condition from enum' );
   DECLARE_TASK( 'custom task from enum' );

AI_END_CUSTOM_NPC()


Make sure you add your newly defined tasks in the AI_BEGIN_CUSTOM_NPC macro as follows

AI_BEGIN_CUSTOM_NPC( npc_myfisrtnpc, CNPC_MyFirstNPC )

   DECLARE_TASK( TASK_MY_CUSTOM_NPC_GET_PATH_TO_CUSTOM_POSITION );

   DEFINE_SCHEDULE
	(
	 SCHED_MY_FIRST_NPC_MOVE_TO_POSITION,

	"	Tasks"
	"		 TASK_STOP_MOVING			0"
	"		 TASK_MY_CUSTOM_NPC_GET_PATH_TO_CUSTOM_POSITION	0"
	"		 TASK_RUN_PATH				0"
	"		 TASK_WAIT_FOR_MOVEMENT			0"					
	""
	"	Interrupts"
	"		COND_NEW_ENEMY"
	"		COND_SEE_ENEMY"
	)

AI_END_CUSTOM_NPC()


Explanation:

DEFINE_SCHEDULE // --> Indicates a new defined schedule
(
	SCHED_MY_FIRST_NPC_MOVE_TO_POSITION, //--> Name of defined schedule followed by a coma. 

	"	Tasks"
	"		 TASK_STOP_MOVING  0"   //--> NPC will stop moving.
	"		 TASK_MY_CUSTOM_NPC_GET_PATH_TO_CUSTOM_POSITION	0"  //--> Task used to setup a new goal.
	"		 TASK_RUN_PATH				0" //--> NPC should run to the path until reached .
	"		 TASK_WAIT_FOR_MOVEMENT			0" //--> Wait for the movement.				
	""
	"	Interrupts"
	"		COND_NEW_ENEMY" //--> If this NPC sees a 'New' enemy, the schedule will be interrupted.
	"		COND_SEE_ENEMY" //--> If this NPC sees an enemy, this schedule will be interrupted.
)


As seen above, this serie of tasks list a schedule that should make an NPC move to another location in the most basic way. Several other more advance techniques are employed but this tutorial is mainly to familiarize with the use of navigation support.

In order to be able to move to another location, the task needs to be defined. Locate ( or create if not already present ) the function 'void StartTask( const Task_t *pTask )' and add a new case in the switch statement.

In ( void CNPC_MyFirstNPC::StartTask( const Task_t *pTask ) ):

Add the following:

case TASK_MY_CUSTOM_NPC_GET_PATH_TO_CUSTOM_POSITION :
{
   Vector vGoalPos, vForward;
   AngleVectors( GetLocalOrigin(), &vForward );

   vGoalPos = GetAbsOrigin() + ( vForward * 128 );

   if ( GetNavigator()->SetGoal( vGoalPos ) == true )
   {
      TaskComplete();
   }
   else
   {
      TaskFail( FAIL_NO_GOAL );
   }
}
break;


This segment of code should simply let an NPC move 128 units in front of him, assuming that there are no walls or obstacles in front of him. 'TaskComplete()' signals that this task has been successfully completed and should move on to the next task which would be 'TASK_RUN_PATH' in this case. However, if the main task cannot be completed,this will cause the schedule to fail and in this case, there should be the need to assign , if desired, a schedule that should be launched if the current schedule has failed to complete. In order to call a fail schedule, simply add this definition to your main task table:

DEFINE_SCHEDULE
(
	SCHED_MY_FIRST_NPC_MOVE_TO_POSITION,

        "	 Tasks"
        "             TASK_SET_FAIL_SCHEDULE   SCHEDULE:SCHED_IDLE_STAND" // --> Specify fail schedule. 'SCHED_IDLE_STAND'
        "             TASK_STOP_MOVING         0"
...


As the schedule build is completed, you will be required to call the schedule. As explained in 'Introduction to AI programming', there should be no problem in calling a schedule in the 'SelectSchedule()' function.


Launching a schedule manually

This is a simply copy and paste section of code that should easily allow programmers to test a sequence without having to pass by conditions or SelectSchedule().

CON_COMMAND( debug_myfirstnpc_schedule , "Schedule command used to debug schedules." )
{
   CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
   if ( !pPlayer )
      return;
    
   Vector vStart, vForward;
   vStart = pPlayer->EyeOrigin();

   vForward = pPlayer->GetAutoAimVectors( 0 );
   VectorNormalize( vForward );
   
   trace_t tr;
   UTIL_TraceLine( vStart, vStart + ( vForward * MAX_TRACE_LENGTH ), MASK_ALL, pPlayer, COLLISION_GROUP_NONE, &tr );

   if ( tr.m_pEnt )
   {
      CNPC_MyFirstNPC *pNPC = dynamic_cast< CNPC_MyFirstNPC* >( tr.m_pEnt );
      if ( pNPC )
      {
         // Summon desired schedule.
         pNPC->Debug_SetSchedule();
      }
      
   }
}


In your class header, add this function in your NPC class:


// npc_myfirstnpc.h
void Debug_SetSchedule();

// npc_myfirstnpc.cpp
void CNPC_MyFirstNPC::Debug_SetSchedule()
{
   SetSchedule( SCHED_MY_FIRST_NPC_MOVE_TO_POSITION );
}


Recompile and bind a key in the console to the following con command: e.g. bind n "debug_myfirstnpc_schedule"

Spawn your new NPC using the following method: npc_create "NPC Class name". You will need to aim at the ground so that the NPC will spawn where the local player is aiming.

Enter this code in the console. "npc_create npc_myfirstnpc"

Press 'N' while looking at your NPC and according to this tutorial, it should move 128 units in front of him and stop. It will resume to SCHED_IDLE_STAND if no additional idle schedule is specified.

There is still a great deal to explain in terms of NPC navigation and how to understand the full potential of the navigation system. This tutorial is only and should be considered as an introduction on how to make an NPC move from it's current position to a defined location.