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:
- Whenever anything makes a noise, or makes a smell, or exists, it can create an “Interest Point”. We usually represent it with a question mark icon in the world.
- Enemies will perceive these Interest Points, and behave accordingly.
That’s a very broad overview. We liked how flexible, elegant, and effective this approach was.
Example Case
Here’s some examples:
You throw a teapot near an enemy:
- The teapot hits the ground. When it hits the ground, it makes a “noise” Interest Point that lasts for 1 second.
- The teapot creates a “visual” Interest Point on itself that lasts forever.
- The enemy hears the noise Interest Point. The enemy walks toward the teapot to investigate.
You jump in a trash chute and become smelly:
- Every 1 second, a “smelly” Interest Point is spawned at the player’s location. These Interest Points each last for 8 seconds.
- Nearby enemies perceive the smelly Interest Points and walk toward them to investigate.
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:
- Have the closest enemy be the “Investigator”:
- This enemy approaches the Interest Point.
- Everyone else takes the role of “Overwatch”:
- They also approach the Interest Point. However, they immediately stop moving when they reach any kind of visual line-of-sight with the Interest Point.
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:
- Source code to Skin Deep. Take a look at our enemy code.
- Brook Miles’ writeup on Mark of the Ninja. This is largely what Skin Deep was based on.
- Jeff Orkin’s talk about how enemies work in F.E.A.R. We didn’t take this specific approach, but it’s really great stuff.
To view all posts in this series, click here.