AI Programming Overview

From Valve Developer Community
Jump to: navigation, search

NPCs follow a fairly simple (and real-world logical) process for making decisions. The easiest way to understand it is to examine the basic outline first, and then dig further into the necessary exceptions afterwards.

Each time an NPC thinks, it does the following:

  1. Perform Sensing
    The NPC generates a list of entities that it can see, and another list of NPC sounds that it can hear. The NPC ignores entities and sounds that it doesn't care about, and hence doesn't place them into the lists.
  2. Generate a list of Conditions
    Conditions are key pieces of information that the NPC will be using to make a decision. They are extracted from the sensed lists of entities and sounds, and from the state of the world and the NPC.
    Some possible conditions are:
    • I can see an enemy
    • I have taken some damage
    • My weapon's clip is empty
  3. Choose an appropriate State
    The State is a high-level assessment of the NPC's desired behaviour, determined by the conditions. For example:
    • NPCs with a visible enemy will consider themselves in Combat.
    • NPCs who have no enemies left after killing one will drop back to Alert.
    • NPCs with a health of 0 will move (somewhat briefly!) to Dead.
  4. Select a new Schedule if appropriate
    The Schedule is the NPC's current goal, which breaks down into Tasks (below) for the NPC to actually perform. They are chosen based on both the current state and the current conditions.
    Some examples:
    • I'm taking cover to reload my gun
    • I'm chasing after my enemy
    • I'm moving to a position where I have line-of-sight to my enemy
    NPCs will choose a new schedule for one of two reasons:
    • They finish performing the last part of the current schedule.
    • They generate a condition that their current schedule has specified as an Interrupt.
    If neither of these are true, the NPC will continue running its current schedule.
  5. Perform the current Task
    The Task is a sub-component of the current schedule that describes a discrete action. Tasks must be performed, one by one, for the schedule to be completed. For example:
    • Schedule: I'm taking cover to reload my gun
      1. Find a position to take cover at
      2. Generate a path to that position
      3. Run the path
      4. Reload my gun
    Many tasks take some time to perform (like the "Run the path" task in the above example), so the NPC will keep performing that task each time it thinks until the task is completed. Then, it'll move onto then next task in the current schedule, or pick a new schedule if there are none left.

Code Overview

For us to understand how to code our own custom NPC's we must understand the programming logic that drives Source's AI. Most of this logic is driven by the code in CAI_BaseNPC, in order to start making a custom NPC, this is the best place to subclass from.


Each time the NPC thinks with NPCThink() a core method is executed RunAI().


RunAI() gathers the NPC's current Conditions according to its environment and determines the best State it should be in, any kind of logic can influence the NPC's conditions and state including the things it sees or the sounds it hears.


The NPC will then determine the best Schedule to run for the given conditions and state, it will maintain any current schedules or interrupt them and run more appropriate schedules to those given conditions and state.

The following links describe states, conditions, schedules and tasks in more detail:

For convenience a stripped down version of the RunAI() method is shown below in order to give you an idea of how an NPC's core logic works.

void CAI_BaseNPC::RunAI( void )
{
	g_AIRunTimer.Start();

	GatherConditions();

	TryRestoreHull();

	g_AIPrescheduleThinkTimer.Start();

	PrescheduleThink();

	g_AIPrescheduleThinkTimer.End();

	MaintainSchedule();

	PostscheduleThink();

	ClearTransientConditions();

	g_AIRunTimer.End();
}


Outside this NPC core logic, there are two event handling methods.


HandleAnimEvent() handles script events(defined in scriptevent.h), NPC events(npcevent.h), and animation events(defined in eventlist.h, and DECLARE_ANIMEVENT). An animation event is fired when the game play animation sequence with an event option.

If a sequence is defined in .qc as:

$sequence fire01 "Fire01" fps 30 snap activity ACT_VM_PRIMARYATTACK 1 { event AE_MUZZLEFLASH 0 "SHOTGUN MUZZLE" } node 2 

It will fire an animevent AE_MUZZLEFLASH with an option "SHOTGUN MUZZLE" when played.



HandleInteraction() handles specific interactions between different types of characters. HandleInteraction() in CAI_BaseNPC shows how this BaseNPC reacts to barnacle grabbing. If you want your NPC to interact with another NPC, Use DECARE_INTERACTION() macro to define one, then call its target entity's DispatchInteraction() to make it react to the interaction.