Abstract
Problem: How should developers approach designing enemy AI in games β balancing complexity, fairness, performance, and player experience?
Approach: Tim Cain draws on his AI graduate training and decades of game development (including Arcanum) to outline four pillars of good enemy AI design, with practical implementation techniques for each.
Findings: Great enemy AI rests on four traits: challenging, scalable, fair, and computable. Smart architectural choices β like attack plans, last-known-position tracking, heartbeat systems, and randomness scaling β let developers create enemies that feel intelligent without burning CPU or frustrating players.
Key insight: The appearance of fairness matters more than actual fairness β enemies can cheat computationally as long as the player never notices, and enemies that seem to follow logical clues feel far more satisfying than ones that omnisciently track the player.
The Four Pillars of Enemy AI
Tim identifies four essential traits every enemy AI should have:
- Challenging β enemies should pose genuine danger
- Scalable β the same AI code should work across levels
- Fair β enemies shouldn't obviously cheat
- Computable β calculations must fit within your time and memory budget
Challenging AI
Enemies should have some chance to kill the player, especially if the player ignores them. Even "trash mobs" should deal enough damage to eventually kill an inattentive player β otherwise they're just loot piΓ±atas.
Enemies shouldn't be overly predictable. Some predictability is good β it makes the player feel smart and in control β but if enemies always react identically regardless of player level or abilities, the AI feels too simple. Predictability should be something the player earns through abilities and knowledge, not something handed to them by default.
Combat should require skill to win, whether that's player skill (action RPGs) or character skill (pure RPGs).
Scalable AI
Good AI is written once and handles progression through versatility, not rewrites. Tim warns against writing separate AI for each player level ("this is the AI at level 1, this is level 5, this is level 10").
Scaling Through Attacks
Design AI code that's flexible enough to incorporate new attacks automatically. A new attack might trigger when the player has low health, damaged armor, or isn't defending. The AI framework should accommodate these conditions without rewriting.
Scaling Through Items
Giving enemies new items can change their behavior organically:
- An enemy with a bow understands range and seeks cover
- An enemy with heavy armor knows it can fight aggressively in melee
- An enemy with a shield starts blocking attacks
If the AI is versatile enough, new items create new behaviors without new code.
Scaling Through Situational Routines
AI routines can exist in the code but only trigger situationally:
- A single wolf won't attack, but three wolves rush the player
- Enemies use cover when cover exists
- Enemies lead players into environmental traps
- Enemies form group attacks when companions are present
The Level Scaling Trap
Players don't hate level scaling per se β they hate badly done level scaling. Bandits wearing 20,000-gold armor and wielding +10 flaming swords breaks immersion. Instead, swap to different creature types that logically have better equipment and abilities.
Fair AI
Fairness is about perception, not reality. Players judge whether the AI seems to be cheating.
What Feels Unfair
- Enemies that always know the player's exact location
- Enemies that immediately use the perfect counter-attack on first encounter
- Enemies that react to information they shouldn't have (like knowing the player is fire-immune before ever attacking them)
What Feels Fair
- Enemies running to where a gunshot originated (not where the player currently is)
- Enemies tracing grenade arcs back to the throw position
- Enemies failing to notice an invisible player opening a door (makes the player feel clever)
- Fire elementals continuing to use fire attacks against a fire-immune player (dumb feels fair; omniscient feels unfair)
Last-Known-Position Architecture
Tim's preferred approach: enemies always operate off the player's last known position, not their actual position. When the player attacks, makes noise, or is within line-of-sight range, the last known position updates. This single architectural choice:
- Makes enemies feel like they're following clues
- Simplifies calculations (no constant position queries)
- Creates emergent behaviors (shoot-and-move tactics work naturally)
Acceptable Cheating
Sometimes calculating something properly is expensive, so the enemy can just "know" it β but should wait to act on it until there's a plausible reason. If there's only one piece of cover in a barren area and the NPC gets shot, it's reasonable for them to "figure out" where the player is immediately.
Computable AI
Every enemy gets a limited budget of bytes and milliseconds per tick. Techniques to stay within budget:
Cooldowns and Dirty Flags
After a complex calculation, either put it on cooldown (don't recalculate for 200-500ms) or mark it as valid until an underlying factor changes and sets a "dirty" flag. Example: an enemy thinks it knows where the player is β it uses that position until it arrives and sees the player isn't there, then marks the location dirty.
Time Slicing
Give each enemy a sliver of processing time per tick. If an enemy needs more time, it takes additional update cycles. This makes the enemy appear to hesitate β playing fidget animations while "thinking." In some games, this accidental delay actually looks great.
Distance-Based Heartbeats (Arcanum)
In Arcanum, every enemy had a heartbeat proportional to their distance from the player:
- Close enemies: rapid heartbeats, frequent AI updates
- Distant enemies: slower heartbeats, fewer updates
- Enemies in other sectors: heartbeat suspended entirely
This was essential for 1990s CPU constraints but remains a smart optimization.
Pre-Baked Attack Plans
Enemies can use predetermined attack sequences: shoot arrows β seek cover β defend β switch to melee when out of ammo. These are predictable, but that predictability can make the player feel smart when they exploit it (e.g., using illusions to waste an archer's arrows).
Skip Sensory Simulation
Don't simulate enemies actually looking around or listening. Instead, let them query the player's position instantly but use it through the last-known-position system. The abstraction is invisible to the player.
Randomness as a Scaling Tool
Adding randomness to enemy actions serves double duty β it makes enemies feel natural and controls difficulty:
- Low-level enemies run near the player, not directly at them. They shoot near the player, not exactly at them.
- As enemies level up, the randomness range tightens: they run closer and shoot more accurately.
This creates a natural difficulty curve without changing the underlying AI logic.