NPC - Non-player character
It's one of the most important aspects of a game. It makes the world feel alive. Getting NPCs right is key to a game's success. But how do you make one?
An NPC consists of many parts, such as visuals, movement, and personality. The brain, however, carries the most weight — what makes the NPC go there, choose this, or say that. Is it all just conditions like in Undertale? Does the NPC have a schedule that drives it? What about the relationships between NPCs and the player?
These questions have many different answers and solutions. I wanted my game to feel dynamic and alive — no hardcoded schedule that would make the NPCs feel rigid and "locked." I did some research and found something called a Utility AI system.
Unlike schedule systems or pure conditions, it doesn't control or make the NPCs do anything. It's just a decision-making layer.

The way it works is quite simple. You have a series of utilities, under which are many values — I call them consideration values. Each is a value from 0 to 1 representing a "state" of the NPC: hunger, fatigue, time of day, essentially anything that can be normalized to a 0→1 range. These values then get added, multiplied, divided, or subtracted to compute a single value for the utility. The utility with the highest value gets chosen. That sends a signal to the NPC carrying the ID of the action to perform, along with some arguments.
This system is easily expandable. For my game, I have a structure like this:
Agent - the controller that chooses the utility and state.
State - the state of the NPC, such as combat, idle, or flee.
Utility - the action to perform. Utilities can be stacked.
Aggregation - just a wrapper for all values. You have a choice of which mathematical function you want to use to compute the final value.
Consideration value - a simple function that "normalizes" a certain value to a range from 0 to 1, with the addition of a curve to modify the value even further.
Using this system comes with great upsides, like independent behaviors and autonomous responses. On the other hand, it has downsides.
In some cases, for example, we want locked behavior so key NPCs for story progression won't wander off. Or during a mission, an NPC can't just do whatever it wants. And most importantly, what about NPCs that are off-screen and far away? We can't just keep them running all the time — that's an extreme performance killer.
In order to keep NPCs locked, we need two things:
a) restricted versions of the system to make story progression always available, and
b) a schedule system as a backbone for irregular cases.
The Utility AI System will choose on its own whether it wants to use the schedule or not — but it's there. We also need a schedule system for the next thing:
NPC ghosts — a minimal version of the NPC that's alive at all times. It holds all the states and stats of the NPC, without simulating the Utility AI System. We use the same schedule system to move it around instead of the full utility system. When the player gets close enough, the NPC spawns with the full system and overrides the ghost. The ghost keeps on living and simulating, though.
This way performance stays great, with an almost unnoticeable difference.