Game Career Guide is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.

Get the latest Education e-news
  • The AI Of DOOM (1993)

    - Tommy Thompson

  • Defining Actions

    To execute the logic, any actor such as an enemy is spawned in with a thinker object attached to them. Each thinker is a struct written in the C programming language that has inside it a pointer to a function of game logic the enemy must execute, alongside a range of additional variables that help it take shape. This function helps each monster to determine the particular behaviour it should exhibit, based on its current state.

    But the actual definition of each enemy in DOOM is actually achieved outside of the codebase. A text file from the project is imported and compiled into an 'info' file, which contains within it all of the enemy's specifications as well as the definitions for all states. The information from the text file is compiled into a struct that carries the following elements:

    - Their internal ID number in the game.
    - The amount of health an imp starts with.
    - Their movement speed.
    - Probability of pain state interrupts
    - Their radius and height
    - The specific properties of the NPC.

    In the case of imps, they are solid objects you can't walk through (MF_SOLID), they can be shot (MF_SHOOTABLE) and damaged and their death counts towards the total number of enemies in the level (MF_COUNTKILL).

    As mentioned previously, monsters do not always go into the pain state when hurt. Instead, it is influenced by the pain state interrupt probability which in the codebase is referred to as pain chance. Every time an enemy is hurt by a weapon, there is a probability check to decide whether the enemy will go into the PAIN state, thereby interrupting its movement and any attacks. The number ranges from 0 to 256 and is configured for each enemy type. So in the case of the imp and shotgun guy, these are 200 and 170 respectively. Meaning that the imp, anytime it is hurt has a 79.3% chance of being put in the PAIN state, while the shotgun guy only has a 67.6% chance. But the trick is that each individual hit from a weapon counts towards this. So while a rocket counts as only one hit, while the shotgun technically has seven separate attacks courtesy of its spread. This is why the minigun is such a useful weapon against the likes of the Cacodemon and Arachnotron, because each monster has a pain chance of 128 (or 50%) but each consecutive bullet fired is another opportunity to trigger the pain state (or, as it was referred to in the original DOOM manual, making them "do the chaingun cha-cha"). This has a huge impact on weapon selection during gameplay - to a point that it's so intrinsic to the gunplay of DOOM that it was reimplemented in the 2016 reboot. Some enemies have incredibly low pain chances - with the Cyberdemon and Archvile both in single digits (5.5% and 3.1%), while others like the lost souls have a 100% pain chance. Also technically the barrels also have a pain chance, but it's set to 0 - which y'know, makes sense because they're barrels.


    On top of all of the information about the behaviour of the enemy itself, there are also individual action definitions for each state. These are custom definitions for each of the states in the state machine. So we can see that the imp uses the attack action for both the melee and ranged FSM states. Meanwhile the shotgun guy doesn't have a state defined for melee attacks.

    Now as for the actual actions themselves, these are actually a lot more complicated. Each state is compiled down into a definition for each individual action frame that is executed in that state. So if we consider the attack for the imp, it blows up into even more information. This includes:

    - The family of sprites that are read from when rendering this behaviour and the frames to be used.
    - The number of in-game tics that frame executes for.
    - The in-game action in the source code that it should execute on that action frame.
    - The next action frame it should then execute.


    So in the case of the imp attacking (see above) it will...
    - Render the first two sprites for 8 frames each, and run the code that ensures the imp turns to face the player.
    - Then draw the third frame for 6 frames and run the special TroopAttack function, which is designed specifically for the Imp.
    - Once it has finished the third action frame, it will transition back to the main 'see' state for the imp.

    And when you look at the code for TroopAttack, it highlights why there isn't a separate state for melee versus ranged attacks, given the Imp will check if it's in melee range and then commit to that, or it will simply launch a missle at the player. These rules are in place for things such as enemy characters attacking the player, dying or being gibbed after taking damage and animated sprites in the environment. The fun part, is returning back to the RAISE state mentioned earlier, any enemy that can be resurrected by the archvile has a set of RAISE action definitions, which have the exact same set of sprite frames used for dying, but it plays them in reverse.

    One last thing is that for each of these action definitions, we need a separate frame of animation, but that hides a further complexity. Given enemies in DOOM can be rendered from 8 different angles relative to the player's position. So when one of these sprite families is being used, it always pulls the appropriate set of sprites for this character based on their angle relative to the player. So in the case of the Imp: there are 21 unique sprite indices. Meaning you have 168 individual sprites to cover all 8 angles. However, one optimisation to this, is that some characters - such as the imp - are symmetrical. So instead of having 8 sets of sprites, there are only 5 and if a sprite set for that orientation is missing, it simply takes the mirror equivalent. Sadly some characters such as the Cyberdemon, are not symmetrical and so they still had to build each individual sprite.

    From Fabian Sanglard's 'Game Engine Black Book: DOOM'From Fabian Sanglard's 'Game Engine Black Book: DOOM'


comments powered by Disqus