Chapter 4: Factor Monitoring System
In Chapter 3: NPC Personality System, we learned how to give our NPCs unique personalities that influence their decision-making. But even the most well-defined personality needs to respond to changing conditions. Just as you might change your behavior when you’re hungry or tired, our NPCs need to react to their changing state - and that’s where the Factor Monitoring System comes in.
Introducing the Factor Monitoring System
Imagine you’re playing a game with an enemy soldier NPC. When the NPC is at full health, they might aggressively attack you. But what should happen when their health drops low? Or when they run out of ammunition? A realistic character would change their behavior - perhaps retreating to find health or taking cover to reload.
The Factor Monitoring System acts like the NPC’s senses and survival instincts. It constantly monitors important statistics (factors) and triggers appropriate behaviors when certain conditions are met. It’s similar to how your body knows when you’re thirsty, hungry, or tired, and urges you to drink, eat, or rest.
Key Components of the Factor Monitoring System
Let’s break down the main elements of this system:
1. Factors: The Important Statistics
Factors are the vital statistics that an NPC needs to monitor:
public enum Factor
{
Health, // Physical wellbeing
Stamina, // Energy for actions
Strength, // Physical power
Power, // Special abilities energy
Ammo, // Ammunition remaining
Hit // When taking damage
}
Each factor represents something important to the NPC’s functioning and survival. Just as you might monitor your phone’s battery level or your car’s fuel gauge, NPCs monitor these factors to make good decisions.
2. Events: The Significant Changes
The system detects different types of events that can happen to each factor:
public enum EventType
{
Low, // Factor is below minimum threshold
High, // Factor is above maximum threshold
Drop, // Factor is decreasing significantly
Raise, // Factor is increasing significantly
DoubleDrop, // Factor is decreasing very rapidly
DoubleRise // Factor is increasing very rapidly
}
These events represent significant changes or states that the NPC should respond to. For example, “Health-Low” means health has fallen below a critical threshold, while “Ammo-Drop” means ammunition is being used up quickly.
3. Thresholds: The Trigger Points
For each factor, we define thresholds that determine when events are triggered:
// Threshold storage (Factor, min, max)
public Dictionary<Factor, (float, float)> thresholds = new()
{
{Factor.Health, (0.5f, 0.8f) },
{Factor.Stamina, (0.4f, 0.7f) },
{Factor.Strength, (0.3f, 0.6f) },
{Factor.Power, (0.4f, 0.7f) },
{Factor.Ammo, (3, 10) }
};
In this example, Health below 50% triggers a “Low” event, while Health above 80% is considered “High”. These thresholds act like warning lights on your car’s dashboard - they alert the NPC when something needs attention.
4. Event-Behavior Connections: The Response Plan
Finally, we connect specific events to appropriate behaviors:
public Dictionary<(Factor, EventType), BehaviourType> events = new()
{
{ (Factor.Health, EventType.Drop), BehaviourType.Attack },
{ (Factor.Health, EventType.DoubleDrop), BehaviourType.Escape },
{ (Factor.Health, EventType.Low), BehaviourType.Escape },
{ (Factor.Stamina, EventType.Low), BehaviourType.Escape },
{ (Factor.Ammo, EventType.Low), BehaviourType.Escape },
// More connections...
};
This dictionary maps factor events to appropriate behaviors. For example, when health drops very quickly (DoubleDrop), the NPC will try to escape. When ammunition is low, the NPC will also try to escape. These connections ensure the NPC responds appropriately to changing conditions.
How to Use the Factor Monitoring System
Let’s implement a simple enemy NPC that responds to changing health and ammunition:
Step 1: Set Up the Factor Monitoring System
First, we need to initialize the system for our NPC:
// Create the NPC controller
NpcController enemyController = GetComponent<NpcController>();
// Initialize the Factor Monitoring System
// (This happens automatically when the NpcController is initialized)
enemyController.Init(npcInstance, characterInstance);
// The database within the controller now monitors factors
MicroDatabase database = enemyController.database;
The NpcController automatically sets up a MicroDatabase that will monitor the NPC’s factors. The database will track changes and trigger appropriate behaviors when thresholds are crossed.
Step 2: Trigger Events When Factors Change
Next, we need to inform the system when factors change:
// When the NPC takes damage
public void OnDamaged(float damageAmount)
{
// Create a data value representing the damage
UniData damageData = new UniData(-damageAmount);
// Inform the Factor Monitoring System
enemyController.ExternalEvent((int)Factor.Health, damageData);
// The system will automatically check thresholds and trigger behaviors
}
When the NPC takes damage, we tell the Factor Monitoring System that the Health factor has changed. The system will check if this crosses any thresholds and trigger appropriate behaviors.
Step 3: Adjust for Ammunition Use
Similarly, we can track ammunition usage:
// When the NPC fires a weapon
public void OnWeaponFired(int ammoUsed)
{
// Create a data value representing the ammo used
UniData ammoData = new UniData(-ammoUsed);
// Inform the Factor Monitoring System
enemyController.ExternalEvent((int)Factor.Ammo, ammoData);
// System will check if ammo is low and trigger behaviors if needed
}
This tells the system that the Ammo factor has decreased. If ammunition drops too low, the system will trigger an escape behavior.
How the Factor Monitoring System Works Internally
Let’s look at what happens inside the system when a factor changes:
sequenceDiagram
participant Game as Game Event
participant NC as NPC Controller
participant DB as MicroDatabase
participant TH as Threshold Checker
participant BM as Behaviour Manager
Game->>NC: OnDamaged(-10)
NC->>DB: ExternalEvent(Health, -10)
DB->>DB: Record factor data
DB->>TH: CheckThresholdData(Health)
alt Health below threshold
TH->>DB: RaiseEvent(Health, Low)
DB->>BM: Trigger(Escape)
else Health dropping rapidly
TH->>DB: RaiseEvent(Health, DoubleDrop)
DB->>BM: Trigger(Escape)
end
This diagram shows:
- A game event (like taking damage) triggers the process
- The NPC Controller sends the event to the MicroDatabase
- The database records the data and checks thresholds
- If thresholds are crossed, the system raises appropriate events
- These events trigger behaviors in the Behaviour Manager
Let’s look at the key method that checks if thresholds are crossed:
void CheckThresholdData(Factor factor)
{
float changePerMinute = changePerMinData[factor];
float value = data[factor].Data.f;
float maxValue = maxData[factor].Data.f;
// Rapid decrease detection
if (changePerMinute <= changePerMinThresholds[factor].Item1 * 2 * maxValue)
{
RaiseEvent(factor, EventType.DoubleDrop);
}
else if (changePerMinute <= changePerMinThresholds[factor].Item1 * maxValue)
{
RaiseEvent(factor, EventType.Drop);
}
// Low value detection
if (value <= thresholds[factor].Item1 * maxValue)
{
RaiseEvent(factor, EventType.Low);
}
// More checks...
}
This method examines both the current value and the rate of change for a factor. It can detect not just when a value is low, but also when it’s dropping quickly - allowing NPCs to anticipate problems and react proactively.
Practical Example: A Smart Enemy NPC
Let’s see a complete example of an enemy NPC that uses the Factor Monitoring System to behave realistically:
Setting Up the NPC
void CreateSmartEnemy()
{
// Create and set up the NPC as we've learned
GameObject enemyObject = new GameObject("Smart Enemy");
NpcController controller = enemyObject.AddComponent<NpcController>();
controller.Init(npcInstance, characterInstance);
// Customize factor thresholds for this enemy
MicroDatabase database = controller.database;
// This enemy will escape at 30% health instead of the default 50%
database.thresholds[Factor.Health] = (0.3f, 0.8f);
// This enemy will consider 5 bullets low ammo instead of default 3
database.thresholds[Factor.Ammo] = (5, 15);
}
Here we’re creating an enemy NPC and customizing its factor thresholds. This particular enemy will fight until its health drops to 30% (more aggressive than the default 50%), and it considers 5 bullets to be low ammunition.
Reacting to Factor Changes
With the Factor Monitoring System set up, the NPC will automatically react to changing conditions:
- When health is high (>80%): The NPC aggressively attacks
- When health drops below 30%: The “Health-Low” event triggers an escape behavior
- When health drops very rapidly: The “Health-DoubleDrop” event also triggers an escape behavior
- When ammo drops below 5 bullets: The “Ammo-Low” event triggers an escape behavior
The NPC will make these decisions automatically based on the Factor Monitoring System, without requiring explicit code for each situation.
Integration with Other Systems
The Factor Monitoring System works closely with other AI systems:
Connection to the Behaviour System
When the Factor Monitoring System detects important events, it creates appropriate behaviors:
private void Database_NewTrigger(BehaviourType type)
{
// Various checks and randomness...
switch (type)
{
case BehaviourType.Escape:
if (manager.task?.behaviour?.type == type)
{
return; // Already escaping
}
// Find a safe position to escape to
(bool isOk, Vector3 position) = GetRandomPosition();
if (isOk)
{
// Create a high-priority escape task (priority 3)
manager.AddTask(3).AddBehaviour(new Behaviour(
BehaviourType.Escape,
Random.Range(2, 6),
TargetType.Point,
position
));
}
break;
// Other behavior types...
}
}
This method is called when the Factor Monitoring System triggers a behavior. It creates appropriate tasks in the Behaviour System, which then executes them through the NPC Controller.
Connection to the NPC Personality System
Factor changes can also influence an NPC’s personality:
void RaiseEvent(Factor factor, EventType eventType)
{
// Trigger appropriate behavior...
// If health is dropping, increase aggressiveness
if (factor == Factor.Health && eventType == EventType.Drop)
{
float changePerMinute = changePerMinData[factor];
settings.aggresiveness += changePerMinute;
Debug.Log("Aggresiveness: " + settings.GetCurrentAggresiveness());
}
}
This creates a “cornered animal” effect - as the NPC loses health, it becomes more aggressive, reflecting how real creatures often fight harder when wounded.
Creating Custom Factor Reactions
You can customize how NPCs react to factor changes by modifying the events dictionary:
// Create a cautious NPC that takes cover when ammo is low
void CreateCautiousNPC()
{
// Setup NPC...
// Modify factor reactions
MicroDatabase database = controller.database;
// When ammo is low, take cover instead of escaping
database.events[(Factor.Ammo, EventType.Low)] = BehaviourType.TakeCover;
// When health is low but not critical, heal instead of escaping
database.events[(Factor.Health, EventType.Low)] = BehaviourType.UseHealthItem;
// Only escape on critical health
database.events[(Factor.Health, EventType.DoubleDrop)] = BehaviourType.Escape;
}
This customizes how the NPC responds to different factor events, creating a more nuanced behavior pattern.
Best Practices for Using the Factor Monitoring System
-
Adjust Thresholds Based on Difficulty: For easier enemies, set health thresholds higher so they retreat sooner. For harder enemies, set them lower so they fight longer.
-
Consider Multiple Factors: Don’t just monitor health - use stamina, ammo, and other factors to create more realistic NPCs that respond to many conditions.
-
Customize for Different NPC Types: A soldier might prioritize ammo, while a monster might care more about health. Adjust thresholds and reactions accordingly.
-
Add Randomness: Combine the Factor Monitoring System with the NPC Personality System randomness trait to create less predictable reactions.
-
Use Rate of Change: Don’t just respond to absolute values - use the rate of change (Drop/Raise events) to create proactive NPCs that anticipate problems.
Conclusion
The Factor Monitoring System creates realistic, dynamic NPCs by connecting their internal state to appropriate behaviors. Just as real creatures adapt their behavior based on hunger, injury, or fatigue, our NPCs can now respond intelligently to changing conditions in the game world.
This system works hand-in-hand with the Behaviour System, NPC Controller, and NPC Personality System to create NPCs that seem alive and responsive. By monitoring important factors and triggering appropriate reactions, we create characters that players will find both challenging and believable.
In the next chapter, Target System, we’ll learn how NPCs identify and evaluate potential targets in the game world, allowing them to make informed decisions about who to attack, follow, or avoid.
Generated by AI Codebase Knowledge Builder