Chapter 5: Target System
In Chapter 4: Factor Monitoring System, we learned how NPCs can monitor their internal state and respond to changes in factors like health and ammunition. But for many behaviors, NPCs also need to be aware of the external world - particularly other characters and objects they might want to interact with. That’s where the Target System comes in.
What is the Target System?
Imagine you’re playing a game where enemy soldiers need to find and attack you. How do they know where you are? How do they keep track of you when you move? How do they decide whether to focus on you or another threat?
The Target System works like a radar for NPCs - it helps them find, identify, and track objects of interest in the game world. Just as a security guard might scan their surroundings for suspicious activity, our NPCs need to constantly evaluate potential targets in their environment.
With the Target System, NPCs can:
- Automatically find objects with specific tags (like “Player” or “Enemy”)
- Manually be assigned specific targets to track
- Keep track of multiple potential targets at once
- Choose the most relevant target based on distance, threat level, or other criteria
- Focus their behaviors (attacking, following, etc.) on appropriate targets
Key Components of the Target System
Let’s break down the main elements of the Target System:
1. Target Identification
First, our NPCs need a way to find potential targets in the game world:
// Automatically find targets with a specific tag
public void AssignTargetsAutomatically(string tag)
{
// Clear the current target list
targets.Clear();
// Find all game objects with the specified tag
SceneData sceneData = npc.uniObject.data?.packObject?.sceneData;
if (sceneData != null)
{
targets = sceneData.FindUniObjectsByTag(tag, true);
}
}
This code searches the entire game scene for objects with a specific tag and adds them to the NPC’s target list. It’s like giving the NPC a pair of binoculars and saying “find everyone wearing a red shirt.”
2. Target List Management
Once we’ve found potential targets, we need to keep track of them:
// List to store potential targets
List<UniObjectData> targets = new();
// Add a specific target to the list
public void AddTargetObject(AssemblyInstance target)
{
if (!targets.Contains(target.uniObject.data))
{
targets.Add(target.uniObject.data);
}
}
// Remove a target from the list
public void RemoveTargetObject(AssemblyInstance target)
{
if (targets.Contains(target.uniObject.data))
{
targets.Remove(target.uniObject.data);
}
}
These methods let us add and remove specific targets from the NPC’s target list. Think of this like a watchlist - the NPC keeps track of objects it should pay attention to.
3. Target Selection
With a list of potential targets, the NPC needs to decide which one to focus on:
// Get the closest target from the list
public UniObjectData GetClosestTarget()
{
if (targets.Count == 0) return null;
float minDistance = float.MaxValue;
UniObjectData closestTarget = null;
foreach (UniObjectData target in targets)
{
if (target == null || target.uniObject == null) continue;
// Calculate distance to this target
float distance = Vector3.Distance(
target.uniObject.transform.position,
transform.position
);
// If this is the closest target so far, remember it
if (distance < minDistance)
{
minDistance = distance;
closestTarget = target;
}
}
return closestTarget;
}
This method finds the closest target in the list. It’s like a guard scanning the room and focusing on the nearest suspicious person first.
Using the Target System in Practice
Let’s see how we can use the Target System to create an enemy that automatically targets players:
Setting Up Automatic Target Detection
void SetupEnemyTargeting()
{
// Get the NPC Controller component
NpcController enemyController = GetComponent<NpcController>();
// Enable automatic targeting
enemyController.npc.GetPropertyByName("automaticTargeting").Data.b = true;
// Set which tag to look for (in this case, "Player")
enemyController.npc.GetPropertyByName("targetsTag").Data.s = "Player";
// The system will now automatically find and track players
}
With this setup, the NPC will automatically find and track any objects tagged as “Player” in the game. It’s like telling a security camera to only pay attention to people wearing visitor badges.
Using Targets with the Behaviour System
Once our NPC has targets, we can use them in behaviors:
void CreateAttackBehavior()
{
// Get the NPC Controller
NpcController enemyController = GetComponent<NpcController>();
// Get a target (in this case, the closest one)
UniObjectData target = enemyController.GetRandomTarget();
if (target != null)
{
// Create a task to attack this target
Task attackTask = enemyController.manager.AddTask(2.0f);
// First go to the target
attackTask.AddBehaviour(new Behaviour(
BehaviourType.Go,
5,
TargetType.Character,
target
));
// Then attack the target
attackTask.AddBehaviour(new Behaviour(
BehaviourType.Attack,
3,
TargetType.Character,
target
));
}
}
This code creates a behavior sequence where the NPC first moves to the target, then attacks it. The Target System provides the target object, which the Behaviour System then uses to create appropriate behaviors.
How the Target System Works Internally
Let’s look at what happens inside the Target System when an NPC needs to find a target:
sequenceDiagram
participant GM as Game System
participant NC as NPC Controller
participant TS as Target System
participant SD as Scene Data
participant BM as Behaviour Manager
GM->>NC: Need a target for behavior
NC->>TS: GetRandomTarget()
alt Automatic Targeting Enabled
TS->>TS: Check if targets need refresh
TS->>SD: FindUniObjectsByTag("Player")
SD-->>TS: Return list of player objects
TS->>TS: Update targets list
end
TS->>TS: Find closest valid target
TS-->>NC: Return selected target
NC->>BM: Create behavior with target
This diagram shows the flow:
- The NPC Controller needs a target for a behavior
- It asks the Target System for a target
- If automatic targeting is enabled, the system refreshes its target list
- The system selects an appropriate target (usually the closest one)
- The NPC Controller uses this target to create a behavior
Now let’s look at the key code that makes this happen:
public UniObjectData GetRandomTarget()
{
// If automatic targeting is enabled, refresh the target list
if (npc.GetPropertyByName("automaticTargeting").Data.b)
{
string tag = npc.GetPropertyByName("targetsTag").Data.s;
AssignTargetsAutomatically(tag);
}
// Find the closest target in the list
if (targets.Count > 0)
{
float minDistance = float.MaxValue;
UniObjectData closestTarget = null;
for (int i = 0; i < targets.Count; i++)
{
// Skip invalid targets
if (targets[i] == null || targets[i].uniObject == null)
{
continue;
}
// Calculate distance to this target
float distance = Vector3.Distance(
targets[i].uniObject.transform.position,
transform.position
);
// If this is the closest target so far, remember it
if (distance < minDistance)
{
minDistance = distance;
closestTarget = targets[i];
}
}
return closestTarget;
}
// No valid targets found
return null;
}
This method is the heart of the Target System. Despite its name (GetRandomTarget), it actually returns the closest target, which is usually the most relevant one for NPCs to interact with.
Using Targets with Other Systems
The Target System is designed to work seamlessly with other AI systems:
Integration with the Behaviour System
The Behaviour System uses targets to create meaningful behaviors:
private void Database_NewTrigger(BehaviourType type)
{
// Skip if randomness check fails
if (Random.Range(0, 100) > settings.GetCurrentRandomness())
{
return;
}
switch (type)
{
case BehaviourType.Attack:
// Get a target using the Target System
UniObjectData target = GetRandomTarget();
if (target != null)
{
// Create a high-priority attack task
Task task = manager.AddTask(5);
// First go to the target
task.AddBehaviour(new Behaviour(
BehaviourType.Go,
Random.Range(2, 6),
TargetType.Character,
target
));
// Then attack the target
task.AddBehaviour(new Behaviour(
BehaviourType.Attack,
Random.Range(1, 2),
TargetType.Character,
target
));
}
break;
}
}
This code shows how the Factor Monitoring System can trigger an attack behavior, which then uses the Target System to find a target, and finally creates appropriate behaviors through the Behaviour System.
Integration with the Navigation System
The Navigation System uses targets to determine where the NPC should move:
private void UpdateFollowingTarget()
{
// Only update every 0.2 seconds to save performance
if (Time.time < nextTargetUpdate)
{
return;
}
nextTargetUpdate = Time.time + 0.2f;
// Get the current behavior
Behaviour behaviour = manager?.task?.behaviour;
// If we're going to a character target, update its position
if (behaviour != null &&
behaviour.type == BehaviourType.Go &&
behaviour.targetType == TargetType.Character &&
behaviour.data != null)
{
// Get the target data
UniObjectData data = (UniObjectData)behaviour.data;
// Update the navigation target to the current position
if (data != null && data.uniObject != null)
{
SetNavigationTarget(data.uniObject.transform.position);
}
}
}
This method updates the NPC’s navigation destination when following a moving target. It’s called regularly to ensure the NPC follows the target even as it moves.
Practical Example: Creating a Guard That Detects Intruders
Let’s put everything together to create a guard NPC that patrols normally but attacks when it detects intruders:
void CreatePatrollingGuard()
{
// Create the guard GameObject
GameObject guardObject = new GameObject("Guard");
// Add and set up the NPC Controller
NpcController controller = guardObject.AddComponent<NpcController>();
controller.Init(npcInstance, characterInstance);
// Enable automatic targeting of players
controller.npc.GetPropertyByName("automaticTargeting").Data.b = true;
controller.npc.GetPropertyByName("targetsTag").Data.s = "Player";
// Set up patrol points
Vector3 pointA = new Vector3(10, 0, 0);
Vector3 pointB = new Vector3(-10, 0, 0);
// Create patrol task (lower priority)
Task patrolTask = controller.manager.AddTask(1.0f);
// Add patrol behaviors
patrolTask.AddBehaviour(new Behaviour(
BehaviourType.Go, 10, TargetType.Point, pointA));
patrolTask.AddBehaviour(new Behaviour(
BehaviourType.Wait, 3));
patrolTask.AddBehaviour(new Behaviour(
BehaviourType.Go, 10, TargetType.Point, pointB));
patrolTask.AddBehaviour(new Behaviour(
BehaviourType.Wait, 3));
}
This code creates a guard that patrols between two points. Thanks to the automatic targeting, the guard will detect players tagged as “Player” and can react to them (for example, with the attack behavior we saw earlier).
Advanced Target Selection
While the closest target is often the most relevant, sometimes we want more sophisticated target selection. Here’s an example of how to choose targets based on threat level:
public UniObjectData GetHighestThreatTarget()
{
if (targets.Count == 0) return null;
float highestThreat = 0f;
UniObjectData mostThreateningTarget = null;
foreach (UniObjectData target in targets)
{
if (target == null || target.uniObject == null) continue;
// Calculate threat level (could be based on distance, damage potential, etc.)
float threatLevel = CalculateThreatLevel(target);
if (threatLevel > highestThreat)
{
highestThreat = threatLevel;
mostThreateningTarget = target;
}
}
return mostThreateningTarget;
}
private float CalculateThreatLevel(UniObjectData target)
{
// Simple threat calculation based on distance and damage potential
float distance = Vector3.Distance(
target.uniObject.transform.position,
transform.position
);
// The closer the target, the higher the threat
float distanceThreat = 100f / (distance + 1f);
// Could add more factors here, like target's weapon power
return distanceThreat;
}
This more advanced approach selects targets based on a calculated threat level rather than just distance. You could extend this to consider factors like target health, weapon type, or past behavior.
Target Visibility and Line of Sight
In many games, NPCs should only target what they can see. Here’s a simple line-of-sight check:
public bool CanSeeTarget(UniObjectData target)
{
if (target == null || target.uniObject == null) return false;
// Get the target's position
Vector3 targetPosition = target.uniObject.transform.position;
// Calculate direction to target
Vector3 directionToTarget = targetPosition - transform.position;
float distanceToTarget = directionToTarget.magnitude;
// Check if target is within view angle
float dotProduct = Vector3.Dot(
transform.forward,
directionToTarget.normalized
);
// If target is not in front of NPC (within 90 degrees), can't see it
if (dotProduct < 0.1f) return false;
// Check if there are obstacles blocking the view
if (Physics.Raycast(
transform.position,
directionToTarget,
out RaycastHit hit,
distanceToTarget))
{
// Something is blocking the view - is it the target?
if (hit.transform != target.uniObject.transform)
{
return false; // Line of sight blocked by obstacle
}
}
return true; // Can see the target
}
This method checks if the NPC can see a target by verifying:
- Is the target in front of the NPC? (using a dot product)
- Is there a clear line of sight to the target? (using a raycast)
You can add this to the target selection process to ensure NPCs only target what they can actually see.
Best Practices for Using the Target System
-
Tag Your Objects Appropriately: Make sure game objects are tagged correctly so NPCs can find them using automatic targeting.
-
Balance Target Updates: Updating target positions too frequently can impact performance. The example code updates every 0.2 seconds, which is usually sufficient.
-
Consider Line of Sight: In most games, NPCs shouldn’t be able to “see through walls.” Add visibility checks for more realistic behavior.
-
Choose Targeting Strategy Carefully: Different NPCs might need different targeting strategies - soldiers might target the closest threat, while healers might target the most injured ally.
-
Clear Invalid Targets: Regularly check for and remove invalid targets (e.g., destroyed objects) from the target list.
Conclusion
The Target System gives our NPCs the ability to identify, track, and select relevant objects in the game world. It’s like giving them eyes and a brain to decide what to focus on. This system is essential for creating NPCs that can interact meaningfully with the game world and with players.
By connecting the Target System with the Behaviour System, the NPC Personality System, and the Factor Monitoring System, we create NPCs that can make intelligent decisions about which targets to engage and how to interact with them.
In the next chapter, Navigation System, we’ll learn how NPCs can plan paths and move through the game world once they’ve decided where to go.
Generated by AI Codebase Knowledge Builder