It’s pretty awesome that someone’s managed to port Descent, one of my personal top five games, to the Unreal Engine for release later this year. I’ve always been curious to know how the seemingly advanced game AIs worked, and whether they they truly learned from the player. The AI would have to learn from scratch during each gaming session, or the parameters would form part of the level access codes (the first Descent for PSX didn’t save to memory card).
Fortunately Parallax Software open sourced Descent sometime before 1999, and it’s possible to examine the internals of how the game worked.
The AI source is huge, at about 4,700 lines of C++, including comments. Obviously a lot of code to sift through, but we can still get a conceptual view of how it works.
There are four source files that are central to the game’s AI, all of them calling functions from various other files handling the physics, objects and game environment:
* aistruct.h: Sets up constants and variables for the AI. There are a load of buffers here for defining behaviour.
* aipath.c: Controls the movement of the AIs, and the distance they should travel between objects in the environment.
* ai.h: Seems to define other variables external to the robot behaviour. More to do with interaction with the game environment.
* ai.c: The main AI source file.
The AI in Descent is quite sophisticated, but still quite rule-based. Examples of this can be found within ai.c:
The CURRENT_STATE and GOAL_STATE parameters are also fixed, it turns out, and the AIs perform a transition between the two (AIS) when a given trigger event happens:
1. Receive trigger event
2. Read current state on trigger event
3. Perform sequence of actions to reach goal state
The trigger AI Events (AIE) could be any of the following:
• AIE_FIRE: Local object (i.e. player or AI) fired.
• AIE_HITT: Nearby object was hit.
• AIE_COLL: Player object collided with the AI.
• AIE_HURT: Some other physical interaction with the AI.
For each possible event, an AI in its current state will be resting, firing, recoiling, aiming or searching for the player.
A transition table for a given event will define the sequence of actions the AI will follow from the initial state, which is predefined. The current state is buffered and recalled later in various functions that govern the AI behaviour. For example:
if (aip->GOAL_STATE != AIS_FLIN)
aip->GOAL_STATE = AIS_LOCK;
else if (aip->CURRENT_STATE == AIS_FLIN)
aip->GOAL_STATE = AIS_LOCK;
if ((aip->behavior != AIB_FOLLOW_PATH) && (aip->behavior != AIB_RUN_FROM))
do_firing_stuff(obj, player_visibility, &vec_to_player);
One of the things I often did when playing Descent was play around with the AI itself, testing it. One thing I noticed was the AI would sometimes follow the player until both objects exceeded a given distance from each other. This is because there is also code that defines a robot’s field of view, along with the maximum distance it should follow the player and whether there’s another object between the two points.
So there it is. While Descent’s AIs appear quite impressive, it’s actually an extremely well-designed rule-based system, and its behaviour seems to be adjusted by a set of variables the player sets as the game progresses.