Skin Deep: Enemy Brains


Skin Deep is currently discounted in the Steam Summer Sale 2025! Nab it for yourself, nab it for a friend, help spread the word 🙏🙏🙏


Here’s a short writeup on how enemy behaviors work in Skin Deep.

Goals

The goal was to make humanoid “space pirate” enemies that were fun to encounter and mess with.

The general vibe of Skin Deep’s enemies is inspired by:
The air pirates of Porco Rosso (1992)


The air pirates of TaleSpin (1990)

As you may guess from the images above: Skin Deep’s enemies are intended to be rascals, rapscallions, goobers, lunkheads. They present a threat but are also lovably dim.

However, they still needed to react to player actions in a way that was consistent, understandable, and predictable. As it turns out, even if they’re dolts, making them fun dolts takes a lot of work.

Background

We used idTech4 to make Skin Deep. idTech4 was used to make the game Doom 3 (2004), and as such, included core enemy functionality like animation states, behavior states, pathfinding, etc.

However, we needed to expand upon this to handle stealth, suspicion, investigation, seeing and smelling disturbances, non-combat idle states, and more.


Interest Points

The main component behind Skin Deep’s enemy sensory/investigation logic is what we called the “Interest Point” system. Whenever you see these little question marks in the game, it’s an Interest Point:

This approach was originally suggested by my friend Alex Belzer, who led me to this writeup about Mark of the Ninja’s implemention. It’s a really great writeup and was essential to our development. (also, Mark of the Ninja is really really really great)

The way it works in Skin Deep is:

That’s a very broad overview. We liked how flexible, elegant, and effective this approach was.

Example Case

Here’s some examples:

Nitty Gritty

More details of the Interest Point system:

• Ad-hoc Teamwork

We had a basic system for coordinating multiple enemies who noticed an Interest Point.

The original motivation was: it was pretty awkward when multiple enemies noticed the same Interest Point and all clumped up together at the same spot. It looks weird.

So, we prevent that from happening. In the case of 2+ enemies noticing the same Interest Point, we:

This creates an improvised “teamwork-esque” setup where one enemy investigates while others keep watch from afar.

• Ownership

Interest Points have a concept of who currently “owns” them. This is to prevent the awkward situation where the same Interest Point gets investigated repeatedly by multiple enemies.

• DuplicateRadius

Interest Points have what we call a “duplicate radius” – this makes enemies ignore an Interest Point if a duplicate Interest Point happens nearby.

Example: if a series of explosions rapidly happen in the same spot, the enemy will only notice the first explosion that happens. This prevents a lot of unnecessary thrashing on what the enemy is paying attention to and makes the enemy behavior more readable.

• Switch cooldown

Enemies have a 1 second cooldown between switching to a new Interest Point. The purpose of this is to prevent the enemy from rapidly switching their attention, as the rapid switching felt awkward and made the enemy behavior difficult to read.

• Smells

Secret: did you know a smell is just a sound? (in this video game. not real life)

We treat smells as a variant of a sound Interest Point. Like a sound, smells are perceived without needing line of sight, and can permeate walls. This approach was inspired by how Half-life 1 handled smells.

The only difference is that smells are allowed to penetrate doors. (Smell interest points penetrate closed doors; Audio interest points are blocked by closed doors).

• Priority

Interest Points have a “priority” number that ranges from 0 (low) to 10 (high).

This allowed us to make the enemy prioritize certain Interest Points over others – for example, a gunshot sound is treated more urgently than a teapot falling to the ground.

• Interest Point definition

The Interest Points were all defined in a text file. Here’s the definition for the toilet flush:

entityDef interest_toiletflush
{
    "inherit"                   "interest_base"
    "editor_visibility"         "hidden"
    "priority"                  "0"
    "type"                      "noise"
    "noticeRadius"              "512"
    "duplicateRadius"           "128"
    "lifetime"                  "1000"
    "displayname"               "#str_def_interestpoints_100136"
}

Some other details about how enemy behaviors work:

Vision Box

In the first implementation, vision was based on a big cone:

This had some issues. Namely, it was challenging to tune. Whenever you adjusted the cone angle, it would affect both the narrow end at the large end. This made it difficult to find values that worked for both ends of the cone.

Instead of a vision cone, we opted to use a vision “rectangle”:

This gave us better results. Notice the tapered angles near the enemy – this was to make it easier for the player to sneak up on the enemy.

An issue came up, however. The vision rectangle is attached to the enemy’s head. If the enemy looks up or down, the orange vision rectangle likewise gets angled up or down:

The issue was the player could sometimes be directly in front of an enemy who was looking upward or downward, and not get noticed. This made the enemy eyeballs feel broken, and happened due to the player sometimes being just slightly outside of the vision rectangle, depending on the level layout.

The solution ended up being to add a vision half-circle that was always oriented forward:

This ended up solving the issue – the enemy now had a way to perceive a player who was right in front of them.

Credit where it’s due: parts of this vision approach was borrowed from this GDC 2014 Martin Walsh talk about perception and awareness in Splinter Cell: Blacklist.

Combat blind spot

We do a vision tweak between the enemy idle state and combat state.

When the enemy is in the idle state, the vision blind-spot angle is relatively forgiving, allowing you to sneak up to the side of the enemy without being caught. However, in the combat state, we shrink the blind-spot:

The reason for this is to incentivize the “non-alert” state in the spaceship, and to make it more difficult to escape enemies once the combat state does start.

IdleTasks

IdleTask is a system that was similar to Interest Point – this is a system that gives the enemy a specific objective to do. Example: when the enemy is injured and is in an idle state, we give them an IdleTask to visit a health station to heal themselves.

In hindsight, this probably should’ve instead been integrated into the existing Interest Point system. I think I kept separate it in order to simplify the Interest Point system (it’s been a while and I don’t quite remember all the reasons), but that had some trade-offs.


Et al

Enemy behaviors took a lot of time, iteration, playtesting, and energy, and was an ongoing process throughout most of Skin Deep’s development. Suzanne, Sanjay, Eric, Tynan, and myself put a lot of work into wrangling it together, and I’m proud of what we ended up with.

Further reading:


To view all posts in this series, click here.

This page is based on the Journal theme by Damien Caselli.