Artificial Intelligence Half-Life SDK: Retrospective

Original author: Alex J. Champandard
  • Transfer
image

At the time of its release in 1998, Half-life received a warm welcome for its game design, which was made possible thanks to artificial intelligence. This AI influence has led to that. what HL called one of the most important games in history .

And even twenty years later, having studied its code, you can learn a lot about creating simple but effective AI systems. All AI logic is hard-coded in C ++ and not too object-oriented, so it is much easier to understand than more recent engines (although expanding it is not so simple).

In this article, we will look at the open SDK for Half-Life 1, analyze various aspects of AI, such as the task scheduler system, its implementation, similar to state machines, and the sensor system. After reading the article, you will more deeply understand the principle of using these concepts and their implementation in games.

Cooperative AI and Monsters
Screenshot 1: Guard Barney is fighting one of the monsters

Download Half-Life SDK


Installing the Valve SDK for Half-Life is very simple (unlike the FEAR tools ) and if you want to develop mods, then it requires only the original game. Here is what you will need:

  1. Download version 2.3 of the Half-Life SDK, or just the source without resources, or a copy of the full SDK with models.
  2. Unzip the file to any directory, preferably in the folder with the game, if you want to develop using the SDK mod. This will take a few seconds, as a result you will have a bunch of catalogs with models and source code.

Half-Life SDK AI Files
Screenshot 2: C ++ game code in the Half-Life SDK version 2.3.

We deal with the code


The code base is not as well structured as in FEAR or even in Quake 3 . It has several subdirectories, but the files do not have very clear names, and the implementation of C ++ classes is scattered across several files, from the names of which almost nothing can be understood.

  • There are two folders in the full SDK that contain the code: Single-Player Sourceand Multiplayer Source. Both have a similar directory structure.
  • Most of the game logic is in a subdirectory /dll/that contains all the files needed to build hl.dll, which is also a mod framework. In addition, this directory contains AI code scattered across many files, with type names *monster*.[h,cpp], *ai*.[h,cpp]and other files
  • There are other directories in the source directory, for example engine, which contains header files that interact with the main executable (as basic entities). The directory commonalso contains similar low-level files used by the engine and game code.

If you study or modify AI, then most of the time you will devote to the catalog /dll/, because it contains the behavior of various actors in the game.

Scientist AI
Screenshot 3: cutscene from a game with a scientist.

Planner and Goal System


Files schedule.[h,cpp]contain a very simple goal-driven system. It consists of several levels of tasks that can be combined procedurally.

Tasks


Tasks are short atomized behaviors that have a specific purpose. For example, most actors Half-Life supports the following tasks: TASK_WALK_PATH, TASK_CROUCH, TASK_STAND, TASK_GUARD, TASK_STEP_FORWARD, TASK_DODGE_RIGHT, TASK_FIND_COVER_FROM_ENEMY, TASK_EAT, TASK_STOP_MOVING, TASK_TURN_LEFT, TASK_REMEMBER. They are defined as enumerations in the header file and are implemented as C ++ methods.

Conditions


Conditions are used to express the situation of an actor in the world. Since the logic rigidly defined, the terms can be expressed in very compact as bit fields, but in that case the conditions may not be more than 32. For example, the conditions are COND_NO_AMMO_LOADED, COND_SEE_HATE, COND_SEE_FEAR, COND_SEE_DISLIKE, COND_ENEMY_OCCLUDED, COND_ENEMY_TOOFAR, COND_HEAVY_DAMAGE, COND_CAN_MELEE_ATTACK2, COND_ENEMY_FACING_ME.

Plans


The plan consists of a series of tasks (with arbitrary parameters) and takes into account the bit field of the conditions to determine when the plan is not applicable. For ease of debugging, plan objects have names.

Goals


Goals are at a higher level and consist of plans. The logic of the goal can, if necessary, choose a plan based on the failed task and the current context. Examples of objectives of Life-of Half: GOAL_ATTACK_ENEMY, GOAL_MOVE, GOAL_TAKE_COVER, GOAL_MOVE_TARGETand GOAL_EAT.

The code used by Valve is extracted from the Quake engine, and is still fairly obvious, despite the fact that it was converted to C ++; files and struct have similar names.


Screenshot 4: paratroopers raised the alarm in a research center.

State machine


In practice, all these plans and tasks are connected together in a structure similar to a state machine. At the top level, to update the AI, a function is called in monsterstate.cpp:

void CBaseMonster :: RunAI ( void );

It, in turn, calls overloaded functions that are responsible for checking with the MaintainSchedule()applicability of the current plan and selecting new ones using GetSchedule(). They can be changed depending on the needs with the help of the generated classes, see, for example, barney.cppor scientist.cpp.

The lower level functions StartTask()and RunTask()implements the logic for each task identifiers defined in the design enum. They are implemented in classes also inherited from CBaseMonster. As a result, it looks a lot like a state machine implemented as a structure switch.

void CScientist :: RunTask( Task_t *pTask )
{
  switch ( pTask->iTask )
  {
  case TASK_RUN_PATH_SCARED:
    if ( MovementIsComplete() )
      TaskComplete();
    if ( RANDOM_LONG(0,31) < 8 )
      Scream();
    break;
  case TASK_MOVE_TO_TARGET_RANGE_SCARED:
    /* ... */
    break;
  case TASK_HEAL:
    if ( m_fSequenceFinished )
    {
      TaskComplete();
    }
    else
    {
      if ( TargetDistance() > 90 )
        TaskComplete();
      pev->ideal_yaw = UTIL_VecToYaw( /* ... */ );
      ChangeYaw( pev->yaw_speed );
    }
    break;
  default:
    CTalkMonster::RunTask( pTask );
    break;
  }
}

A more typical approach would be to implement each of these blocks casein their own class, but with the existing implementation it is much easier if necessary to use the logic of one object in another, albeit at the cost of modularity.

It is also interesting to note that AI stores two states: one ideal and one current. Thus, the game code is easier to create goals for the actors, and make them find the best ways to achieve them. This is an interesting combination of a state machine and a focused system.


Screenshot 5: game cutscene with a scientist.

Implementation of the sensor system


In the base monster.[h,cpp]there is a code that gives all actors vision, smell and hearing.

void CBaseMonster :: Look ( int iDistance );

The vision function checks various flags, such as SF_MONSTER_PRISONERand SF_MONSTER_WAIT_TILL_SEEN, in order to provide designers with the ability to control if necessary. The equation also takes into account parameters such as scope and viewing angle.

CSound* CBaseMonster :: PBestSound ( void );

The code for hearing and smell works in a similar way, only uses sound events. A list of objects that require the attention of monsters is stored, and the sensor system selects the best of them for focusing.

Summary and additional reading


In general, the source code behind this system, although simple, is very informative. If you want to choose an easy implementation of decision-making by artificial intelligence, then you should choose this approach. However, it may be worthwhile to implement each task in your own object: these days, commercial games usually use this solution.


Screenshot 6: Elemental squad behavior in Half-Life.

The AI ​​Half-Life code also contains other interesting ideas.

  1. The game code represents navigation points in the form of only a 3D vector and type of location! They are attached to the lower navigation system, but they can be used in the old-school system of "bread crumbs", which are followed by monsters.
  2. Half-Life surprised many with the behavior of units. However, in the game there is no top-level AI that controls these units, that is, all behavior appears spontaneously.

If you want to recreate something more than just a monster from Half-Life, it is best to study the bot framework. It will allow you to create AI bots for multiplayer games, which can be used in third-party Half-life mods. They can be found here:


Also popular now: