Welcome to the Tactical AI Hiding System!
This asset is a powerful, flexible, and performance-oriented framework for creating intelligent stealth and cover-based AI. It's built on a simple philosophy: You are in control.
Instead of a black box that takes over your characters, this system acts as an intelligent service. Your AI's brain (like a Behavior Tree or State Machine) asks the question, "Where is the best place to hide right now?", and this system provides the answer.
Core Features
- Data-Driven AI Personality: Define complex behaviors not in code, but in ScriptableObjects. Create cautious snipers, aggressive flankers, and sneaky assassins simply by mixing and matching components.
- High Performance by Design: The asynchronous, multithreaded core uses Unity's Job System and Burst Compiler to run complex calculations on background threads, leaving your main thread free for smooth gameplay.
- Extensible & Customizable: Don't like how the AI evaluates cover? Write your own custom Scorer. Need the AI to recognize a unique type of cover in your game? Write your own custom Provider.
- Environment-Aware: Use optional helper components to tag your scene with tactical information. Designate cover nodes, danger zones (
Hazards), strategic areas (TacticalZones), and even soft cover like smoke (VolumetricCover). - Dynamic Behavior: Use the
SearchProfileAPI to change an AI's "mood" or tactics on the fly in response to game events like taking damage or receiving a squad command.
Key Concepts at a Glance
EnemyHider: The main MonoBehaviour component you add to your AI. It's the central brain.HidingSettings: A ScriptableObject that acts as the AI's "Rulebook" or "Personality," defining how it thinks.- Providers: ScriptableObjects that find potential hiding spots (e.g., "look behind obstacles").
- Scorers: ScriptableObjects that rate those spots (e.g., "spots far from the player are better").
Ready to begin? Let's get your first AI hiding in under 10 minutes.
➡️ Next: Getting Started Guide
Getting Started: Your First Hiding AI
This guide will walk you through the entire process of setting up an AI that can intelligently hide from a player. We will cover the complete setup, from components to the final trigger script.
Step 1: Scene Preparation
Before we begin, ensure your scene is ready:
- NavMesh: Your scene must have a baked NavMesh so the AI can navigate.
- Layers: Create a new Layer called
Obstacles(or similar). Place all your static, cover-providing geometry (walls, crates, etc.) on this layer. We will use this to tell the system what blocks line of sight.
Step 2: Create the AI's "Personality" Asset
The AI's behavior is stored in a HidingSettings ScriptableObject.
- In your Project window, right-click and go to Create > HidingSystem > Hiding Settings.
- Name it
StandardAI_Settings. - Select the new asset. In the Inspector, set the Occluder Mask to your
Obstacleslayer. This is critical for visibility checks.
Step 3: Configure the EnemyHider Component
- Select your AI character GameObject. It must already have a
NavMeshAgentcomponent. - Click Add Component and add the
EnemyHider. - Drag your
StandardAI_Settingsasset into the Settings slot on theEnemyHider. - Drag the character's
NavMeshAgentcomponent into the Agent slot.
Step 4: Define the Hiding Rules (Providers & Scorers)
Now, let's give our AI some basic rules. Select your StandardAI_Settings asset again.
- Add Providers (Where to look for cover):
- Under
Providers, click+and addBehindObstacleProviderSO. - Click
+again and addCurrentPositionProviderSO. This is important, as it allows the AI to consider "staying put" if it's already in a good spot.
- Under
- Add Scorers (How to decide which spot is best):
- Under
Scorers, click+. Add aVisibilityScorerSOand set its Weight to2.0. This makes being hidden very important. - Add a
PathSafetyScorerSOand set its Weight to1.5. This tells the AI to avoid running through open areas. - Add a
DistanceScorerSOwith a Weight of1.0. This will make the AI prefer spots that are a reasonable distance away.
- Under
Step 5: Triggering the AI with a Simple Controller
The EnemyHider needs to be told when to hide. Create a new script, AIController.cs, and add it to your AI character.
using UnityEngine;
using MaharajaStudio.HidingSystem; // The Hiding System namespace
[RequireComponent(typeof(EnemyHider))]
public class AIController : MonoBehaviour
{
// Assign your Player GameObject to this in the Inspector
public Transform playerTarget;
private EnemyHider hider;
void Start()
{
hider = GetComponent();
// Tell the hider who the enemy is
hider.SetSinglePlayerTarget(playerTarget);
}
void Update()
{
// Check if we are exposed AND not already searching or moving to cover
if (hider.IsThreatened && !hider.IsSearching && !hider.IsMoving)
{
Debug.Log("I'm exposed! Finding a place to hide.");
hider.RetreatToHide();
}
}
}
Assign your Player object to the Player Target field on the AIController.
You're Done!
Press Play! Move your player into the AI's line of sight. You should see the "I'm exposed!" debug log, and the AI will use your rules to find the best spot behind an obstacle and move there.
➡️ Next: Learn the underlying principles in Core Concepts
How-To Guides (The Cookbook)
This section provides practical, goal-oriented recipes for creating common AI behaviors.
Recipe: The Cautious Marksman
Goal: An AI that tries to stay as far away from the player as possible and heavily prioritizes safety.
Ingredients:
- A
HidingSettingsasset namedMarksman_Settings. - Providers:
BehindObstacleProviderSO,CurrentPositionProviderSO. - Scorers:
DistanceScorerSO,VisibilityScorerSO,PathSafetyScorerSO.
Method:
- In your
Marksman_Settingsasset, add the providers listed above. - Add the scorers with the following weights:
DistanceScorerSO:Weight: 3.0(This is the highest priority. The AI desperately wants to be far away).VisibilityScorerSO:Weight: 2.0(Being unseen is very important).PathSafetyScorerSO:Weight: 2.0(The path to the safe spot must also be safe).
Result: When this AI hides, it will consistently choose valid cover points near the maximum edge of its search radius.
Recipe: The Aggressive Flanker
Goal: An AI that prefers to hide in locations that create a crossfire angle with its allies, getting into an advantageous flanking position.
Ingredients:
- A
HidingSettingsasset namedFlanker_Settings. - Providers:
NavMeshRingProviderSO,BehindObstacleProviderSO. - Scorers:
CrossfireScorerSO,VisibilityScorerSO,ProximityPenaltyScorerSO.
Method:
- Configure the
Flanker_Settingsasset. - Set up the scorer weights:
CrossfireScorerSO:Weight: 3.0(The absolute main priority is getting a good angle).VisibilityScorerSO:Weight: 1.5(The spot should still be hidden).ProximityPenaltyScorerSO:Weight: 1.0(Ensure it doesn't try to flank from a position that is suicidally close).
Result: This AI will ignore spots directly behind it, instead favoring cover that puts it at a 90-degree angle to its allies, relative to the player's position. Make sure to populate the allies list on the EnemyHider for this to work
Core Concepts: How the System Thinks
Understanding the core architecture will empower you to create truly unique and intelligent AI. The entire system is built on a simple but powerful pipeline.
The Generate -> Score -> Select Pipeline
Every time you call RetreatToHide(), the system performs three main steps:
- GENERATE: Providers scan the world and generate a list of potential hiding spots (
CandidateInfo). They don't know if a spot is "good" or "bad"; they only know how to find possibilities. - SCORE: Each candidate spot is then shown to a list of Scorers. Each scorer evaluates the spot based on one specific criterion (e.g., "How visible is it?") and gives it a score.
- SELECT: The system multiplies each score by its user-defined Weight and adds them all up. The candidate with the highest total score is the winner.
This pipeline is what allows for such modular and data-driven behavior.
graph TD
A[Start Search: RetreatToHide()] --> B{HidingContext Assembled};
B --> C[1. GENERATE Candidates];
C --> D(BehindObstacleProvider);
C --> E(CoverNodeProvider);
C --> F(...);
D & E & F --> G[Master List of Candidates];
G --> H[2. SCORE Each Candidate];
H -- Candidate 1 --> I{For each Scorer...};
I -- VisibilityScorer --> J(Score: 0.0 * Weight: 2.0);
I -- DistanceScorer --> K(Score: 0.8 * Weight: 1.0);
I -- PathSafetyScorer --> L(Score: 1.0 * Weight: 1.5);
J & K & L --> M[Total Score for Candidate 1 = 2.3];
H -- Candidate 2... --> N[...];
M & N --> O[3. SELECT Candidate with Highest Score];
O --> P[Return Best HidingSpot];
AI Personality: HidingSettings vs. SearchProfile
You have two ways to define an AI's behavior:
HidingSettingsSO(The Rulebook): This is the AI's default "personality." It's a static asset that defines the complete set of providers and scorers the AI uses. You might have aSniperSettingsasset that prefers high ground and long distances, and aGruntSettingsasset that prefers staying close to allies.SearchProfile(The "Mood"): This is a temporary, code-driven override for a single search. It allows you to dynamically change the AI's priorities. For example, when an AI's health is low, you can use aSearchProfileto temporarily crank up the weight of theDistanceScorerandPathSafetyScorer, making it act more desperately and cautiously without changing its base personality asset.
The Facade Pattern: Keeping Your Code Clean
The EnemyHider component acts as a Facade. It provides a simple, clean API (RetreatToHide(), IsThreatened, etc.) that hides the complex machinery underneath. Your AI controller only ever needs to talk to the EnemyHider, keeping your game logic decoupled and easy to manage.
➡️ Next: See practical examples in the How-To Guides (Cookbook)
EnemyHider Class API Documentation
The EnemyHider is the central component of the AI Hiding System. Attach it to your AI agent to enable intelligent cover-finding and retreat behaviors. This documentation covers all public properties, methods, and events you can use to control and query the AI's state.
1. Core Properties (Status & State)
These properties provide real-time information about the AI's current state. They are read-only and are perfect for driving animations, behavior trees, or UI debugging.
IsSearching
Returns true if the AI is currently in the process of finding a hiding spot.
- Use Case: Prevent the AI from performing other actions (like attacking or reloading) while it's busy thinking about where to hide. It's an essential flag for state management in a Behavior Tree or State Machine.
- How to Use:
EnemyHider hider = GetComponent<EnemyHider>(); void Update() { // Example: Don't allow the AI to shoot while it's looking for cover. if (hider.IsSearching) { myWeaponController.HoldFire(); } }
IsInCover
Returns true if the agent has successfully reached its destination and is stationary.
- Use Case: Use this to trigger "in cover" behaviors, such as crouching, peeking, or starting a timer before re-engaging. It confirms that the AI is not only safe but also in position.
- How to Use:
EnemyHider hider = GetComponent<EnemyHider>(); void Update() { // If the AI is in cover, make it crouch. if (hider.IsInCover) { animator.SetBool("IsCrouching", true); } else { animator.SetBool("IsCrouching", false); } }
IsThreatened
A convenient alias for IsNeedToHide(). Returns true if the agent is currently visible to any player.
- Use Case: This is the primary trigger for deciding when to start a search. It's a lightweight check that can be called every frame in an
Updateloop or as a condition in a Behavior Tree to initiate a retreat. - How to Use:
EnemyHider hider = GetComponent<EnemyHider>(); void Update() { // If the AI is threatened and not already moving to cover, tell it to hide. if (hider.IsThreatened && !hider.IsMoving) { hider.RetreatToHide(); } }
IsMoving
Returns true if the NavMeshAgent is actively following a path to a destination.
- Use Case: Useful for controlling animations (e.g., switching between idle/run) and for preventing the AI from starting a new search while it's already on its way to cover.
- How to Use:
EnemyHider hider = GetComponent<EnemyHider>(); void Update() { // Control the "IsRunning" parameter in the animator. animator.SetBool("IsRunning", hider.IsMoving); }
HasValidHidingSpot
Returns true if the agent has a valid CurrentHidingSpot, even if it hasn't reached it yet.
- Use Case: Differentiate between an AI that has no plan (
HasValidHidingSpotis false) and an AI that has a plan but is still executing it (HasValidHidingSpotis true, butIsInCoveris false). - How to Use:
// In a Behavior Tree, this could be a condition: // "Does the AI have a valid destination?" if (hider.HasValidHidingSpot) { // Proceed with movement task. } else { // Trigger a search task. }
CurrentHidingSpot
The HidingSpot struct the agent is currently moving to or located at. It's HidingSpot.Invalid if no spot has been found.
- Use Case: Access detailed information about the AI's destination, such as its position, the direction of cover (
coverNormal), and whether it can peek left or right. - How to Use:
if (hider.IsInCover && hider.CurrentHidingSpot.canPeekRight) { // The AI is in cover and can peek right, so trigger a peek-and-shoot action. myCombatLogic.PerformPeek(hider.CurrentHidingSpot.peekRightPosition); }
LastKnownPlayerPosition
The position where the AI last had a line of sight to a player. This acts as the AI's short-term memory.
- Use Case: This is crucial for tactical decisions. When the AI gets into cover, it should aim or throw grenades towards this position, not the player's new, unseen location.
- How to Use:
// When the AI decides to throw a grenade from cover: if (hider.IsInCover && hider.LastKnownPlayerPosition.HasValue) { Vector3 target = hider.LastKnownPlayerPosition.Value; myGrenadeController.ThrowAt(target); }
TimeInCover
Returns the number of seconds the agent has been stationary in its current valid cover spot.
- Use Case: Make the AI's behavior more dynamic. For example, an aggressive AI might decide to pop out of cover after 2 seconds, while a cautious one might wait for 5 seconds.
- How to Use:
// A cautious AI might only re-engage after being in cover for a while. if (hider.IsInCover && hider.TimeInCover > 5.0f) { // It's been 5 seconds, time to look for an opportunity to attack. myCombatLogic.SeekAttackOpportunity(); }
2. Core Methods (Actions)
These methods instruct the EnemyHider to perform an action, such as starting a search or stopping its current behavior.
RetreatToHide()
Initiates a search for the best hiding spot using the default settings. This is the primary and most common way to make the AI find cover.
- Use Case: This is your go-to method. Call it when the AI is exposed and needs to find safety.
- How to Use:
EnemyHider hider = GetComponent<EnemyHider>(); void OnTookDamage() { // When the AI takes damage, it should immediately try to find cover. hider.RetreatToHide(); }
RetreatToHide(SearchProfile profile)
Initiates a search using a temporary SearchProfile to override the default settings for a single search.
- Use Case: This is powerful for creating dynamic, context-aware AI. An AI might use a "cautious" profile (preferring distant cover) when its health is low, or an "aggressive" profile (preferring ambush spots) when it has a shotgun.
- How to Use:
// Create a profile for an aggressive flank. var flankProfile = new SearchProfile { // Create a dictionary to override scorer weights. ScorerWeightOverrides = new Dictionary<HideSpotScorerSO, float> { { crossfireScorerAsset, 5.0f }, // Heavily prioritize crossfire angles { distanceScorerAsset, 0.5f } // Care less about being far away } }; // Tell the AI to find a flanking position using this custom profile. hider.RetreatToHide(flankProfile);
RetreatToHideInArea(Vector3 areaCenter, float areaRadius)
A convenient wrapper that tells the AI to find cover, but only within a specific circular area in the world.
- Use Case: Perfect for mission objectives or squad tactics. For example, "Defend this control point" means the AI should find cover inside the control point's radius, not run across the map.
- How to Use:
Vector3 controlPointCenter = new Vector3(10, 0, 50); float controlPointRadius = 15f; // The AI must find cover within the designated objective area. hider.RetreatToHideInArea(controlPointCenter, controlPointRadius);
StopAndClear()
Immediately cancels any ongoing search, stops all movement, and clears the current hiding spot.
- Use Case: Use this to interrupt the AI's current action. For example, if a player gets too close, you might want the AI to stop retreating and switch to close-quarters combat, regardless of its previous plan.
- How to Use:
float distanceToPlayer = Vector3.Distance(transform.position, player.position); if (distanceToPlayer < 5.0f) { // The player is too close! Stop running and fight back. hider.StopAndClear(); myCombatLogic.EngageInMelee(); }
CancelSearch()
Cancels an ongoing asynchronous search. It does not stop the agent if it's already moving.
- Use Case: This is a more subtle interruption than
StopAndClear(). Use it if you want to abort the thinking process (which can take a few frames) without necessarily stopping the AI's current movement. - How to Use:
// The AI started a search, but the player suddenly died. void OnPlayerDied() { // Cancel the search since the threat is gone. The AI might continue to its last spot. hider.CancelSearch(); }
3. Query Methods (Checks & Information)
These methods allow you to ask questions and get information from the system without changing the AI's state. They are ideal for decision-making logic.
IsNeedToHide()
Performs a lightweight line-of-sight check to see if the agent is currently visible to any player.
- Use Case: This is the most common query. Use it in
Updateor a Behavior Tree to decide if a retreat is necessary. It's much cheaper than running a fullRetreatToHide()every frame. - How to Use:
// A classic "tick" for a Behavior Tree node. public NodeStatus CheckThreatStatus() { if (hider.IsNeedToHide()) { return NodeStatus.FAILURE; // We are exposed, the "Stay Safe" check fails. } return NodeStatus.SUCCESS; // We are hidden, the check passes. }
CanFindHidingSpot(out HidingSpot potentialSpot)
Performs a quick, synchronous search to see if any valid hiding spot exists from the current position. It does not make the AI move.
- Use Case: A crucial "look before you leap" check. Before deciding to retreat, you can use this to see if there's anywhere to retreat to. If not, the AI might choose to stand and fight instead of running into a dead end.
- How to Use:
// Before committing to a retreat, check if it's even possible. if (hider.IsThreatened) { if (hider.CanFindHidingSpot(out HidingSpot spot)) { // A safe spot exists, so let's go! hider.RetreatToHide(); } else { // There's nowhere to hide. Stand and fight! myCombatLogic.HoldPositionAndFight(); } }
GetTopNHidingSpotsSync / GetTopNHidingSpotsAsync
Performs a full search and returns a list of the best hiding spots found, sorted by score. The Sync version blocks the main thread for a frame, while the Async version is non-blocking.
- Use Case: Advanced usage for squad tactics or complex behaviors. A squad leader could call this to find the top 3 spots and then assign each squad member to one of them, ensuring they don't all run to the same piece of cover.
- How to Use (Async):
private async void AssignSquadPositions() { // Find the 3 best cover spots near the squad leader. List<HidingSpot> bestSpots = await squadLeader.hider.GetTopNHidingSpotsAsync(3); if (bestSpots.Count >= 3) { squadMember1.hider.MoveToSpot(bestSpots[0]); squadMember2.hider.MoveToSpot(bestSpots[1]); squadMember3.hider.MoveToSpot(bestSpots[2]); } }
ReEvaluateCurrentSpot()
Checks if the agent's CurrentHidingSpot is still safe from all players. This is much cheaper than a full new search.
- Use Case: Periodically check if the AI's cover has been flanked. If a player moves and gets a new angle, this method will return
false, signaling that the AI needs to find a new, better spot. - How to Use:
// Run this check every couple of seconds while in cover. IEnumerator CheckCoverValidity() { while(true) { yield return new WaitForSeconds(2.0f); if (hider.IsInCover && !hider.ReEvaluateCurrentSpot()) { // Our cover is blown! Find a new spot. hider.RetreatToHide(); } } }
4. Configuration Methods
These methods are used to set up and change the EnemyHider's behavior and context at runtime.
SetPlayers(IReadOnlyList<Transform> playerList) / SetAllies(IReadOnlyList<Transform> allyList)
Updates the list of player targets and allies that the AI should be aware of.
- Use Case: This is fundamental for setup. Your game manager or AI spawner should call this to tell each AI who its enemies and friends are. It should be updated if players or allies enter or leave the AI's awareness range.
- How to Use:
EnemyHider myAI = GetMyAI(); List<Transform> allPlayers = FindAllPlayerTransforms(); List<Transform> nearbyAllies = FindNearbyAllies(); myAI.SetPlayers(allPlayers); myAI.SetAllies(nearbyAllies);
UpdateHidingSettings(HidingSettingsSO newSettings)
Swaps the currently used HidingSettingsSO asset at runtime.
- Use Case: Allows for profound changes in AI behavior. You could have different settings assets for different enemy types (e.g., "GruntSettings", "SniperSettings") or for different game states (e.g., "StealthSettings", "CombatSettings").
- How to Use:
public HidingSettingsSO cautiousSettings; public HidingSettingsSO aggressiveSettings; void OnHealthLow() { // When health is low, switch to the cautious behavior set. hider.UpdateHidingSettings(cautiousSettings); }
SetCurrentEyeHight(float eyeHeight)
Updates the AI's eye height, which is used for all visibility calculations.
- Use Case: Make the AI's visibility checks more accurate based on its stance. If the AI is crouching, its eye height should be lower, which might make shorter objects valid cover.
- How to Use:
void Crouch() { animator.SetBool("IsCrouching", true); hider.SetCurrentEyeHight(0.8f); // Set eye height for crouching. } void StandUp() { animator.SetBool("IsCrouching", false); hider.SetCurrentEyeHight(1.7f); // Reset to default standing height. }
ForgetPlayerLocation()
Manually clears the AI's memory of the LastKnownPlayerPosition.
- Use Case: Useful in stealth scenarios. If the player successfully hides for a certain amount of time, you can call this to make the AI "give up" the search and return to its patrol, making it seem less omniscient.
- How to Use:
// Called by a stealth manager after the player has been hidden for 10 seconds. public void OnPlayerLost() { aiHider.ForgetPlayerLocation(); aiHider.ReturnToPatrol(); }
Advanced API & Architecture Documentation
This guide covers the core architectural components of the AI Hiding System. Mastering these concepts will allow you to extend the system, define custom AI personalities, and deeply integrate it into your game's unique mechanics.
The system's core philosophy is a simple three-step process:
- GENERATE: Potential hiding spots (
CandidateInfo) are generated by Providers. - SCORE: Each candidate is evaluated and scored by multiple Scorers.
- SELECT: The candidate with the highest total score is chosen as the best
HidingSpot.
1. Creating Custom HideSpotProviderSO (Generation)
What is it?
A HideSpotProviderSO is a ScriptableObject whose sole purpose is to generate a list of potential hiding spots. Providers are the "eyes" of the system, finding candidate locations in the world based on a specific strategy (e.g., looking behind obstacles, sampling the NavMesh, finding tagged objects).
Why is it useful?
Your game might have unique types of cover that the default providers don't know about.
- Want AI to hide in tall grass? Create a
TallGrassProvider. - Want AI to use teleporter exits as escape points? Create a
TeleporterProvider. - Want AI to hide near health packs? Create a
HealthPackProvider.
How to use it:
- Create the C# Script: Create a new script that inherits from
HideSpotProviderSO. - Implement
GenerateCandidates: This is the only method you need to override. Your logic should addCandidateInfostructs to theresultslist. - Create the Asset: In the Unity Editor, right-click in your project folder and create an instance of your new provider asset via the
CreateAssetMenuattribute. - Assign the Asset: Drag your new provider asset into the
Providersarray in yourHidingSettingsSO.
Example: A SafeZoneProvider
Let's imagine you have designer-placed "Safe Zones" in your level. This provider will generate candidate spots inside those zones.
Step 1 & 2: C# Script (SafeZoneProviderSO.cs)
using UnityEngine;
using MaharajaStudio.HidingSystem;
using MaharajaStudio.HidingSystem.Providers;
using System.Collections.Generic;
// Assume you have a simple component to tag your safe zones
public class SafeZoneVolume : MonoBehaviour { }
[CreateAssetMenu(menuName = "HidingSystem/Providers/Custom/Safe Zone Provider")]
public class SafeZoneProviderSO : HideSpotProviderSO
{
public override void GenerateCandidates(in HidingContext ctx, List<CandidateInfo> results)
{
// Find all active safe zones in the scene.
// Note: A real implementation should use a static registry for better performance.
var safeZones = Object.FindObjectsByType<SafeZoneVolume>(FindObjectsSortMode.None);
foreach (var zone in safeZones)
{
Bounds bounds = zone.GetComponent<Collider>().bounds;
int samples = ctx.lodSettings.candidatesPerProvider;
for (int i = 0; i < samples; i++)
{
// Generate a random point within the zone's bounds
Vector3 randomPoint = new Vector3(
Random.Range(bounds.min.x, bounds.max.x),
Random.Range(bounds.min.y, bounds.max.y),
Random.Range(bounds.min.z, bounds.max.z)
);
// Add the point as a candidate. We don't know the cover normal.
results.Add(new CandidateInfo { position = randomPoint, coverNormal = Vector3.zero });
}
}
}
}
Step 3 & 4: In the Editor
- Create the
SafeZoneProviderSOasset in your project. - Drag this asset into the
Providerslist on yourHidingSettingsSO. Now, whenever the AI searches for cover, it will include points from your safe zones.
2. Creating Custom HideSpotScorerSO (Scoring)
What is it?
A HideSpotScorerSO is a ScriptableObject that evaluates a single CandidateInfo and returns a score. This is the "brain" of the AI. The score typically ranges from 0.0 (bad) to 1.0 (good). A score of float.NegativeInfinity will instantly disqualify the spot.
Why is it useful?
Scorers define an AI's personality and priorities. By creating custom scorers and adjusting their weights in the HidingSettingsSO, you can create vastly different behaviors.
- Cautious AI: Give a high weight to
DistanceScorerSO. - Aggressive AI: Give a high weight to
AmbushScorerSOand a customProximityToEnemyScorer. - Stealth AI: Give a high weight to
DarknessScorerSOor a customInBushesScorer.
How to use it:
- Create the C# Script: Create a new script that inherits from
HideSpotScorerSO. - Implement
Score: Override theScoremethod. This method receives theHidingContext(for world information) and theCandidateInfoto evaluate. Return your calculated score. - Create the Asset: Create a ScriptableObject asset from your new scorer class.
- Assign and Weigh: Add the asset to the
Scorerslist in yourHidingSettingsSOand set itsweight. The weight determines how much influence this scorer has on the final decision.
Example: An AmmoProximityScorerSO
Let's create a scorer that makes the AI prefer hiding spots that are near ammo pickups, especially if its ammo is low.
Step 1 & 2: C# Script (AmmoProximityScorerSO.cs)
using UnityEngine;
using MaharajaStudio.HidingSystem;
using MaharajaStudio.HidingSystem.Scorer;
// Assume a central registry knows where all ammo boxes are.
public static class AmmoBoxRegistry
{
public static List<Transform> AllAmmoBoxes = new List<Transform>();
}
[CreateAssetMenu(menuName = "HidingSystem/Scorers/Custom/Ammo Proximity Scorer")]
public class AmmoProximityScorerSO : HideSpotScorerSO
{
[Tooltip("The ideal distance to be from an ammo box.")]
public float optimalDistance = 3f;
[Tooltip("Beyond this distance, the scorer has no effect.")]
public float maxDistance = 20f;
public override float Score(in HidingContext ctx, in CandidateInfo candidate)
{
// For this to work, you need a way to get the AI's ammo count.
// Let's assume the EnemyHider has a sibling component `WeaponSystem`.
var weaponSystem = ctx.enemyTransform.GetComponent<WeaponSystem>();
// If ammo is full, this scorer is irrelevant. Return a neutral score.
if (weaponSystem == null || weaponSystem.IsAmmoFull())
{
return 0.5f;
}
if (AmmoBoxRegistry.AllAmmoBoxes.Count == 0) return 0f;
// Find the closest ammo box to the candidate hiding spot.
float minDstSq = float.MaxValue;
foreach (var ammoBox in AmmoBoxRegistry.AllAmmoBoxes)
{
float d = (ammoBox.position - candidate.position).sqrMagnitude;
if (d < minDstSq)
{
minDstSq = d;
}
}
float distance = Mathf.Sqrt(minDstSq);
if (distance > maxDistance) return 0.0f;
// The score is higher the closer we are.
float score = 1.0f - (distance / maxDistance);
return Mathf.Clamp01(score);
}
}
Step 3 & 4: In the Editor
- Create the
AmmoProximityScorerSOasset. - Add it to the
Scorerslist inHidingSettingsSOand give it a weight (e.g., 1.5). Now, when an AI with this setting is low on ammo, it will strongly prefer cover spots near ammo boxes.
3. Dynamic Behavior with SearchProfile
What is it?SearchProfile is a class that allows you to temporarily override any aspect of a HidingSettingsSO for a single search. You can override the search radius, providers, and most importantly, scorer weights.
Why is it useful?
It allows you to change an AI's "mood" or "tactic" at runtime without needing to swap out its entire HidingSettingsSO asset. This is the key to creating responsive and dynamic AI.
- Low Health: Create a profile that cranks up the weight of
DistanceScorerSOandPathSafetyScorerSO. - Squad Order to Flank: Create a profile that uses only the
CrossfireScorerSO. - Player is Distracted: Create a profile that heavily favors the
AmbushScorerSO.
How to use it:
You create an instance of SearchProfile in your code, set its properties, and then pass it to enemyHider.RetreatToHide(profile).
Example: "Desperate Retreat" Profile
When an AI's health is critically low, we want it to prioritize safety above all else, even if it means running far away.
using UnityEngine;
using MaharajaStudio.HidingSystem;
using MaharajaStudio.HidingSystem.Scorer;
using System.Collections.Generic;
public class AIHealth : MonoBehaviour
{
public EnemyHider hider;
public HidingSettingsSO defaultSettings;
public int health = 100;
// References to the specific scorer assets you want to override.
public DistanceScorerSO distanceScorer;
public PathSafetyScorerSO pathSafetyScorer;
public void TakeDamage(int amount)
{
health -= amount;
if (health < 25)
{
// Health is critical! Trigger a desperate retreat.
TriggerDesperateRetreat();
}
}
private void TriggerDesperateRetreat()
{
// 1. Create a new profile.
var desperateProfile = new SearchProfile
{
// 2. Create the dictionary for overrides.
ScorerWeightOverrides = new Dictionary<HideSpotScorerSO, float>
{
// 3. Add overrides. We use the asset reference as the key.
// Massively increase the importance of being far away.
{ distanceScorer, 4.0f },
// Also be extra sure the path to get there is safe.
{ pathSafetyScorer, 3.0f }
},
// You can also override other things, like the search radius.
SearchRadius = 50f
};
// 4. Call the search with the custom profile.
hider.RetreatToHide(desperateProfile);
}
}
4. Integrating with Game Systems via Services
What are they?
The hiding system includes several static "Service" classes (HazardRegistry, ObjectiveService, ThreatMapService). These are global registries that your own game code must populate with data. The AI system then reads from these services to gain world context.
Why are they useful?
They decouple the AI from your game logic. The AI doesn't need to know what a "control point" is; it only needs to know that the ObjectiveService has a point to defend. This makes integration clean and simple.
How to use them:
From your own game manager, objective controller, or item scripts, call the Register, Unregister, or Update methods on these services.
Example: Registering a Hazard
The HazardAvoidanceScorerSO will make AI avoid any area registered as a hazard. To make it work, your fire/poison/etc. game object needs to tell the HazardRegistry about itself. The provided Hazard.cs component does this automatically.
// Simplified Hazard script logic (as provided in the files)
public class Hazard : MonoBehaviour
{
public float radius = 5f;
// When the hazard (e.g., a fire) is created, it registers itself.
private void OnEnable()
{
HazardRegistry.Register(this);
}
// When the fire goes out, it unregisters itself.
private void OnDisable()
{
HazardRegistry.Unregister(this);
}
}
By simply adding the Hazard component to your fire prefab, the AI system will now automatically know to avoid it, provided the HazardAvoidanceScorerSO is in its settings. The same principle applies to ObjectiveService (call ObjectiveService.RegisterObjective(...) from your mission script) and others.
5. Performance Optimization with IMultiThreadedScorer
What is it?IMultiThreadedScorer is a "marker interface". It has no methods. If your custom HideSpotScorerSO implements this interface, the system will automatically run its scoring logic on a background worker thread using Unity's C# Job System and Burst Compiler.
Why is it useful?
Performance. For complex scorers or a large number of AI agents, moving calculations off the main thread is essential to prevent frame rate drops. Scorers that perform heavy math or iterate over many objects are prime candidates for this optimization.
How to use it:
- Add
IMultiThreadedScorerto your scorer's class definition. - Crucially, you must ensure your
Scoremethod is "Burst-compatible". This means:- No managed types: You cannot use
List<>,Dictionary<>, strings, etc. - No Unity main-thread APIs: You cannot call
GetComponent<>,GameObject.Find,Debug.Log, or access most parts ofTransformbeyond its position/rotation. - Your logic must rely only on the data already prepared for the job system (like
playerPositions,threatPoints, etc., which are passed asNativeArrays).
- No managed types: You cannot use
Example: A Job-Safe Darkness Scorer
The provided DarknessScorerSO is a perfect example.
using UnityEngine;
using MaharajaStudio.HidingSystem.Scorer;
// Note the addition of IMultiThreadedScorer
[CreateAssetMenu(menuName = "HidingSystem/Scorers/Darkness")]
public class DarknessScorerSO : HideSpotScorerSO, IMultiThreadedScorer
{
// The regular Score method is still here.
public override float Score(in HidingContext ctx, in CandidateInfo candidate)
{
// The main-thread logic gets the ambient intensity.
float lightLevel = RenderSettings.ambientIntensity;
return Mathf.Clamp01(1f - lightLevel);
}
}
When the AsyncHidingSystemCore runs, it sees that DarknessScorerSO implements IMultiThreadedScorer. It fetches RenderSettings.ambientIntensity once on the main thread, passes it to the ScoreCandidatesJob, and the actual calculation (1f - lightLevel) happens for hundreds of candidates in parallel on a background thread.
Core Engine API Documentation
This documentation covers the internal engine of the AI Hiding System. You will rarely call these APIs directly, as the EnemyHider component manages them for you. However, understanding how they work is crucial for deep customization, performance profiling, and debugging.
The Core Data Flow
At its heart, every search follows this sequence:
- The
EnemyHidergathers all relevant world data. - It packages this data into a single, comprehensive
HidingContextstruct. - This
HidingContextis sent to one of the two core processors:SynchronousHidingSystemCoreorAsyncHidingSystemCore. - The core processor uses the context to run the Generate -> Score -> Select pipeline.
- The result, a
HidingSpotstruct, is returned to theEnemyHider, which then acts upon it.
1. Core Data Structures (The "Nouns")
These structs are the lifeblood of the system, carrying information between different stages.
HidingContext
What is it?
The HidingContext is a comprehensive "snapshot" of the world at the exact moment a search is initiated. It contains all the information that providers and scorers need to make their decisions.
Why is it important?
It is the single source of truth for a search operation. By bundling all data together, the system ensures that every provider and scorer is working with the exact same information. It also makes the Score and GenerateCandidates methods clean and predictable, as they receive all their necessary data in one package.
Key Properties:
enemyPosition/enemyTransform: The agent's current location.players/allies: Lists of all relevant friendly and enemy transforms.playerLastKnownPosition: The AI's "memory" of where it last saw a threat.settings/lodSettings: The configuration assets that define search parameters.visibilityService: The service used for line-of-sight checks.activeProfile: The temporarySearchProfileused for overrides.- Helper Properties (
EffectiveSearchRadius, etc.): These automatically resolve whether to use a value from thesettingsor the overrideprofile.
CandidateInfo
What is it?
A CandidateInfo is the most basic representation of a potential hiding spot. It is the raw output from a HideSpotProviderSO.
Why is it important?
This simple, lightweight struct is the common language between all providers and the core system. Providers generate lists of these, and the core system feeds them one-by-one to the scorers for evaluation.
Key Properties:
position: The world-space position of the potential spot.coverNormal: The direction pointing away from the surface of the cover object. It isVector3.zeroif the provider couldn't determine a cover direction (e.g.,NavMeshRingProviderSO). This normal is crucial for scorers likeDirectionalCoverScorerSO.
HidingSpot
What is it?
A HidingSpot is the final, actionable result of a successful search. It's an enhanced CandidateInfo with additional tactical data calculated after the best spot has been chosen.
Why is it important?
This is the "mission plan" given back to the EnemyHider. It contains not just where to go, but how to get there safely and what the AI can do once it arrives.
Key Properties:
position&coverNormal: The position and cover direction of the winning spot.canPeekLeft/canPeekRight: Booleans indicating if the AI can lean out from cover for a shot.peekLeftPosition/peekRightPosition: The world-space positions to move to for peeking.safePath: (Crucial) A pre-calculatedNavMeshPath. ThePathSafetyScorerSOevaluates this path to ensure it's safe. TheEnemyHiderthen usesagent.SetPath()to follow this exact route, preventing the agent from taking a different, more dangerous route than the one that was scored.IsValid: A boolean to check if the spot is valid (i.e., notHidingSpot.Invalid).
2. Core Interfaces & Services
IVisibilityService
What is it?
An interface that defines the contract for how the system performs line-of-sight (LOS) checks. DefaultVisibilityService is the standard implementation, which uses Physics.Raycast.
Why is it important?
It centralizes all visibility logic. By using an interface, you could theoretically replace the default physics-based system with something else (e.g., a custom voxel-based visibility grid, a portal-based system) without having to change any of the scorers. The scorers simply ask the service, "Is this point visible?" and get a true/false answer.
Key Methods:
AnyPlayerHasLineOfSight(ctx, point): The most common method. Returnstrueif any player can see the given point.InAnyPlayerFOVAndVisible(ctx, point): A stricter check that also ensures the point is within a player's Field of View cone.
3. Core Processors (The "Verbs")
These are the static classes that contain the main logic loop. EnemyHider chooses which one to call based on the ExecutionMode.
SynchronousHidingSystemCore
What is it?
This processor runs the entire Generate -> Score -> Select pipeline on the main thread in a single frame.
How does it work?
- It receives the
HidingContext. - It loops through all
EffectiveProvidersand collects allCandidateInfos into a single, pooled list. - It then iterates through every candidate. For each candidate, it iterates through every scorer in the
settings, calculates the raw score, applies the weight, and sums them up. - If a scorer returns
NegativeInfinity, the candidate is immediately disqualified. - It keeps track of the candidate with the highest score.
- Finally, it converts the winning
CandidateInfointo aHidingSpotby calculating the path and peek points.
API:
TryFindBestSpot(in HidingContext, out HidingSpot, out float): Finds the single best spot.TryFindTopSpots(in HidingContext, int count, out List<HidingSpot>): Finds the topNspots, sorted by score.
When to use it:
This mode is used by EnemyHider for the Synchronous ExecutionMode and for quick, non-blocking queries like CanFindHidingSpot(). It's simpler and easier to debug, but can cause performance issues with many AIs.
AsyncHidingSystemCore
What is it?
This is the high-performance processor that offloads the most expensive parts of the search (raycasting and scoring) to background worker threads using Unity's C# Job System and Burst Compiler.
How does it work?
The process is more complex but significantly faster:
- (Main Thread) Preparation:
- It receives the
HidingContext. - It generates all
CandidateInfos from providers, just like the synchronous core. - Crucially, it deconstructs all complex objects (like
Transform) into simple, "blittable" data types (Vector3,float) and copies them intoNativeArrays. This is a requirement for the Job System. - It prepares huge batches of
RaycastCommands for every visibility check needed by the scorers (e.g., 3 body points per candidate vs. every player).
- It receives the
- (Worker Threads) Execution:
- It schedules all the
RaycastCommands to run in parallel. - It then schedules a custom
ScoreCandidatesJob. This job receives all theNativeArrays of data and the raycast results. - Inside the job, each core of the CPU processes a chunk of the candidates. The scoring logic (for scorers marked with
IMultiThreadedScorer) is executed here. This code is often Burst-compiled into highly optimized machine code.
- It schedules all the
- (Main Thread) Finalization:
- The main thread
awaits the completion of the jobs. - Once the jobs are done, it retrieves the raw scores from the
ScoreCandidatesJob. - It then runs any scorers that were not marked as
IMultiThreadedScorer(like those requiring NavMesh APIs). - Finally, it finds the best score and calculates the final
HidingSpot, just like the synchronous version.
- The main thread
API:
FindBestSpotAsync(HidingContext, CancellationToken): Asynchronously finds the single best spot. Returns aTaskorUniTask.FindTopSpotsAsync(HidingContext, int, CancellationToken): Asynchronously finds and returns a list of the topNspots.
When to use it:
This is the default and recommended mode for production. It ensures that the heavy calculations of the AI have minimal impact on your game's frame rate, allowing for more agents and more complex environments.
Core Engine API Documentation
This documentation covers the internal engine of the AI Hiding System. You will rarely call these APIs directly, as the EnemyHider component manages them for you. However, understanding how they work is crucial for deep customization, performance profiling, and debugging.
The Core Data Flow
At its heart, every search follows this sequence:
- The
EnemyHidergathers all relevant world data. - It packages this data into a single, comprehensive
HidingContextstruct. - This
HidingContextis sent to one of the two core processors:SynchronousHidingSystemCoreorAsyncHidingSystemCore. - The core processor uses the context to run the Generate -> Score -> Select pipeline.
- The result, a
HidingSpotstruct, is returned to theEnemyHider, which then acts upon it.
1. Core Data Structures (The "Nouns")
These structs are the lifeblood of the system, carrying information between different stages.
HidingContext
What is it?
The HidingContext is a comprehensive "snapshot" of the world at the exact moment a search is initiated. It contains all the information that providers and scorers need to make their decisions.
Why is it important?
It is the single source of truth for a search operation. By bundling all data together, the system ensures that every provider and scorer is working with the exact same information. It also makes the Score and GenerateCandidates methods clean and predictable, as they receive all their necessary data in one package.
Key Properties:
enemyPosition/enemyTransform: The agent's current location.players/allies: Lists of all relevant friendly and enemy transforms.playerLastKnownPosition: The AI's "memory" of where it last saw a threat.settings/lodSettings: The configuration assets that define search parameters.visibilityService: The service used for line-of-sight checks.activeProfile: The temporarySearchProfileused for overrides.- Helper Properties (
EffectiveSearchRadius, etc.): These automatically resolve whether to use a value from thesettingsor the overrideprofile.
CandidateInfo
What is it?
A CandidateInfo is the most basic representation of a potential hiding spot. It is the raw output from a HideSpotProviderSO.
Why is it important?
This simple, lightweight struct is the common language between all providers and the core system. Providers generate lists of these, and the core system feeds them one-by-one to the scorers for evaluation.
Key Properties:
position: The world-space position of the potential spot.coverNormal: The direction pointing away from the surface of the cover object. It isVector3.zeroif the provider couldn't determine a cover direction (e.g.,NavMeshRingProviderSO). This normal is crucial for scorers likeDirectionalCoverScorerSO.
HidingSpot
What is it?
A HidingSpot is the final, actionable result of a successful search. It's an enhanced CandidateInfo with additional tactical data calculated after the best spot has been chosen.
Why is it important?
This is the "mission plan" given back to the EnemyHider. It contains not just where to go, but how to get there safely and what the AI can do once it arrives.
Key Properties:
position&coverNormal: The position and cover direction of the winning spot.canPeekLeft/canPeekRight: Booleans indicating if the AI can lean out from cover for a shot.peekLeftPosition/peekRightPosition: The world-space positions to move to for peeking.safePath: (Crucial) A pre-calculatedNavMeshPath. ThePathSafetyScorerSOevaluates this path to ensure it's safe. TheEnemyHiderthen usesagent.SetPath()to follow this exact route, preventing the agent from taking a different, more dangerous route than the one that was scored.IsValid: A boolean to check if the spot is valid (i.e., notHidingSpot.Invalid).
2. Core Interfaces & Services
IVisibilityService
What is it?
An interface that defines the contract for how the system performs line-of-sight (LOS) checks. DefaultVisibilityService is the standard implementation, which uses Physics.Raycast.
Why is it important?
It centralizes all visibility logic. By using an interface, you could theoretically replace the default physics-based system with something else (e.g., a custom voxel-based visibility grid, a portal-based system) without having to change any of the scorers. The scorers simply ask the service, "Is this point visible?" and get a true/false answer.
Key Methods:
AnyPlayerHasLineOfSight(ctx, point): The most common method. Returnstrueif any player can see the given point.InAnyPlayerFOVAndVisible(ctx, point): A stricter check that also ensures the point is within a player's Field of View cone.
3. Core Processors (The "Verbs")
These are the static classes that contain the main logic loop. EnemyHider chooses which one to call based on the ExecutionMode.
SynchronousHidingSystemCore
What is it?
This processor runs the entire Generate -> Score -> Select pipeline on the main thread in a single frame.
How does it work?
- It receives the
HidingContext. - It loops through all
EffectiveProvidersand collects allCandidateInfos into a single, pooled list. - It then iterates through every candidate. For each candidate, it iterates through every scorer in the
settings, calculates the raw score, applies the weight, and sums them up. - If a scorer returns
NegativeInfinity, the candidate is immediately disqualified. - It keeps track of the candidate with the highest score.
- Finally, it converts the winning
CandidateInfointo aHidingSpotby calculating the path and peek points.
API:
TryFindBestSpot(in HidingContext, out HidingSpot, out float): Finds the single best spot.TryFindTopSpots(in HidingContext, int count, out List<HidingSpot>): Finds the topNspots, sorted by score.
When to use it:
This mode is used by EnemyHider for the Synchronous ExecutionMode and for quick, non-blocking queries like CanFindHidingSpot(). It's simpler and easier to debug, but can cause performance issues with many AIs.
AsyncHidingSystemCore
What is it?
This is the high-performance processor that offloads the most expensive parts of the search (raycasting and scoring) to background worker threads using Unity's C# Job System and Burst Compiler.
How does it work?
The process is more complex but significantly faster:
- (Main Thread) Preparation:
- It receives the
HidingContext. - It generates all
CandidateInfos from providers, just like the synchronous core. - Crucially, it deconstructs all complex objects (like
Transform) into simple, "blittable" data types (Vector3,float) and copies them intoNativeArrays. This is a requirement for the Job System. - It prepares huge batches of
RaycastCommands for every visibility check needed by the scorers (e.g., 3 body points per candidate vs. every player).
- It receives the
- (Worker Threads) Execution:
- It schedules all the
RaycastCommands to run in parallel. - It then schedules a custom
ScoreCandidatesJob. This job receives all theNativeArrays of data and the raycast results. - Inside the job, each core of the CPU processes a chunk of the candidates. The scoring logic (for scorers marked with
IMultiThreadedScorer) is executed here. This code is often Burst-compiled into highly optimized machine code.
- It schedules all the
- (Main Thread) Finalization:
- The main thread
awaits the completion of the jobs. - Once the jobs are done, it retrieves the raw scores from the
ScoreCandidatesJob. - It then runs any scorers that were not marked as
IMultiThreadedScorer(like those requiring NavMesh APIs). - Finally, it finds the best score and calculates the final
HidingSpot, just like the synchronous version.
- The main thread
API:
FindBestSpotAsync(HidingContext, CancellationToken): Asynchronously finds the single best spot. Returns aTaskorUniTask.FindTopSpotsAsync(HidingContext, int, CancellationToken): Asynchronously finds and returns a list of the topNspots.
When to use it:
This is the default and recommended mode for production. It ensures that the heavy calculations of the AI have minimal impact on your game's frame rate, allowing for more agents and more complex environments.
Environmental Helpers
These components are added to objects in your scene to provide more tactical information to the AI. This includes CoverNode (manual cover spots), Hazard (danger zones), TacticalZone (strategic areas), and VolumetricCover (soft cover like smoke).
Feature Guide: CoverNode
The CoverNode component is a simple and direct way for level designers to manually place a high-quality, explicit hiding spot in the world. It gives you precise, pixel-perfect control over where an AI can take cover, ensuring that tactically important locations are always available for the AI to use.
Why Use It?
- Designer Control: Guarantees that specific locations are always considered as potential cover, overriding procedural generation.
- Tactical Precision: Perfect for creating specific flanking routes, ambush points, or defensive strongholds.
- Reliability: Ensures that cover is available even in complex or geometrically noisy environments where automatic providers might struggle.
- Intelligence: When used with the DirectionalCoverNodeProviderSO, the AI can intelligently select only the nodes that provide cover relative to the current threat direction.
Setup Guide
- In your scene, create a new Empty GameObject at the exact position where you want the AI to stand.
- Select the new GameObject and click Add Component. Search for CoverNode and add the script.
- In the Scene View, you will now see a cyan arrow gizmo originating from your GameObject. This arrow represents the direction of safety. An AI at this spot is considered "in cover" when the threat is behind them (and the arrow points away from the wall/cover).
- Adjust the Cover Direction vector in the Inspector, or simply rotate the GameObject itself, to orient the cover correctly. The arrow should point away from the physical object that provides cover.
Parameters
- coverDirection (Vector3): This vector defines the direction an agent should be facing to be considered "in cover." It's visualized by the cyan arrow in the editor. The DirectionalCoverNodeProviderSO uses this to check if the node is a valid hiding spot from the current threat.
Best Practices
- Placement: Position the CoverNode slightly away from the wall or object providing cover. This ensures the NavMesh Agent has enough space to stand without clipping into the geometry.
- Use with Directional Provider: For the best results, use the DirectionalCoverNodeProviderSO in your HidingSettings. This provider will intelligently filter the nodes, selecting only those whose coverDirection is pointing away from the player, resulting in much more believable AI behavior.
Feature Guide: EnemyHider Component
The EnemyHider component is the central brain and primary interface for the Tactical Cover & Retreat AI System. You attach this component directly to your AI agent's GameObject. It acts as a "facade," simplifying the complex process of finding cover into a set of easy-to-use commands and events.
Why Use It?
The EnemyHider is the bridge between your AI's decision-making logic (like a Behavior Tree or State Machine) and the hiding system's core functionality. It manages the search process, handles asynchronous operations, and translates the final result into movement for your AI.
- Centralized Control: Manages all settings, targets, and state for a single AI agent.
- Performance Options: Choose between a high-performance, multi-threaded Asynchronous mode or a simple, easy-to-debug Synchronous mode.
- Event-Driven: Provides UnityEvents to easily hook up animations, sound effects, or other gameplay logic without needing to write extra code.
- Extensible: Can be linked with custom ICombatAction scripts to execute complex tactical maneuvers after finding cover, instead of just moving.
Setup Guide
- Select your AI agent's GameObject in the scene.
- Click Add Component and search for EnemyHider. Add the script.
- Drag your HidingSettingsSO asset into the Settings field. This asset acts as the "rulebook" for the AI's decisions.
- Drag the NavMeshAgent component from your AI into the Agent field. The system will use this to move the AI.
(Optional)If you have a custom script that implements the ICombatAction interface, assign its GameObject to the Combat Action Game Object field.
Inspector Parameters
System Settings
- Execution Mode:
- Asynchronous: The recommended mode for best performance. It uses the Unity Job System and Burst Compiler to perform searches on background threads, preventing game freezes.
- Synchronous: A simpler mode where the search happens on the main thread. Useful for debugging as the code flow is easier to follow, but may cause performance hitches with many agents or complex searches.
Fallback Settings
- Allow Desperate Retreat: If checked, the AI will perform a second, less strict search if the first one fails to find a perfectly safe path. This is crucial for preventing the AI from getting "stuck" and doing nothing when it's surrounded or in a very open area.
Component References
- Settings: The HidingSettingsSO asset that defines the AI's behavior. This is the most critical reference.
- Agent: The NavMeshAgent component on this AI, used for pathfinding and movement.
- Combat Action Game Object: An optional field for advanced users. Assign a GameObject that has a component implementing the ICombatAction interface to override the default movement behavior with your own custom logic (e.g., peek-and-shoot, suppressive fire).
- Players / Allies: Lists for Transform components. These are primarily for quick testing in the editor. In a real game, you will likely set these lists at runtime using the public API.
Events
- On Search Started: A UnityEvent that fires the moment RetreatToHide() is called. Useful for triggering "taking cover!" voice lines or changing the AI's state to "retreating."
- On Search Success: A UnityEvent
that fires when a valid hiding spot is found. It passes the HidingSpot data, which you can use for custom logic. - On Search Failed: A UnityEvent that fires if no suitable hiding spot could be found, even after a desperate retreat. Useful for triggering a fallback behavior, like a last-stand fight.
Public API for Programmers
The EnemyHider is controlled from your own AI scripts.
// In your AI's main controller script (e.g., EnemyFSM.cs)
using MaharajaStudio.HidingSystem;
public class MyAIController : MonoBehaviour
{
private EnemyHider hider;
public Transform player;
void Start()
{
hider = GetComponent();
}
void Update()
{
// Your logic decides WHEN to hide.
if (IsUnderFire() && !hider.IsSearching)
{
// Tell the hider who to hide from.
hider.SetSinglePlayerTarget(player);
// Command the hider to find a spot and move.
hider.RetreatToHide();
}
}
bool IsUnderFire() { /* ... your custom logic ... */ return true; }
}
- RetreatToHide(): The main function. Initiates the search for a hiding spot based on the current settings and targets.
- CancelSearch(): Immediately stops an ongoing Asynchronous search.
- SetPlayers(IReadOnlyList
playerList): Sets the list of enemies to hide from. - SetAllies(IReadOnlyList
allyList): Sets the list of teammates for coordinated tactics. - IsSearching (property): A read-only boolean that is true while a search is in progress.
Feature Guide: Hazard
The Hazard component allows you to designate a spherical area in your scene as dangerous. The AI system will actively avoid choosing any hiding spots that fall within the radius of a Hazard object, making the AI appear smarter and more aware of environmental dangers.
Why Use It?
- Environmental Awareness: Prevents the AI from making foolish mistakes, like hiding in a pool of fire, an acid spill, or an active minefield.
- Dynamic Obstacles: Can be added at runtime to temporarily block off areas. For example, you could spawn a GameObject with a Hazard component when a grenade explodes to prevent AI from running into the blast zone.
- Designer-Friendly: Provides a simple and visual way to define "no-go" zones for the AI without complex NavMesh modifications.
Setup Guide
- Select the GameObject in your scene that represents a dangerous area (e.g., a fire particle effect, a poison cloud).
- Click Add Component and search for Hazard. Add the script.
- Adjust the Radius property in the Inspector to define the size of the dangerous area.
- When the GameObject is selected, a semi-transparent red sphere gizmo will be drawn in the Scene View, clearly visualizing the hazard's area of effect.
Parameters
- radius (float): The radius of the dangerous spherical area, measured in meters from the GameObject's transform origin.
How It Works
The Hazard component works in conjunction with the HazardAvoidanceScorerSO. If this scorer is included in your HidingSettingsSO, it will perform a check for every potential hiding spot. If a spot is found to be inside the radius of any active Hazard in the scene, the scorer immediately returns a score of NegativeInfinity, which instantly disqualifies that spot from being chosen.
Feature Guide: PlayerHeightInfo
The PlayerHeightInfo component is a simple but powerful tool that allows you to specify the eye height of individual player characters. This is essential for creating varied gameplay scenarios where players might have different character models, stances (crouching, prone), or even be children or large monsters.
The AI system will use this information to perform highly accurate line-of-sight (LOS) checks, making it smarter and more reactive to the actual tactical situation.
Why Use It?
- Accurate Visibility: If a player is crouching, the AI will correctly understand that they can't see over certain obstacles.
- Support for Different Characters: Allows for players of different sizes (e.g., a tall robot vs. a small goblin) to be evaluated correctly.
- Dynamic Stances: Works perfectly with player controllers that allow crouching or going prone. By changing the currentEyeHeight value at runtime, you can make the AI's perception dynamically adapt to the player's actions.
Setup Guide
- Select your Player Prefab or the player character GameObject in your scene.
- Click Add Component and search for PlayerHeightInfo. Add the script.
- That's it! The AI system will now automatically detect and use this component.
Parameters
- currentEyeHeight (float): The most important field. This value represents the height of the player's "eyes" or camera, measured in meters from the GameObject's transform origin (pivot).
- Default Value: 1.7 (a reasonable height for an average standing adult).
- Runtime Changes: If you have a player controller script, it can get a reference to this component and change this value when the player crouches or stands up. The AI will immediately use the new value in its next calculation.
Example: Integrating with a Player Controller
Here is a small code snippet showing how a player's script might update this value.
// In your PlayerController.cs or similar script
public PlayerHeightInfo playerHeight; // Assign this in the Inspector
public float standingHeight = 1.7f;
public float crouchingHeight = 0.9f;
void OnCrouch()
{
// When the player crouches, update the height info.
playerHeight.currentEyeHeight = crouchingHeight;
}
void OnStandUp()
{
// When the player stands up, set it back.
playerHeight.currentEyeHeight = standingHeight;
}
If a player GameObject does not have this component, the AI will gracefully fall back to using the default playerEyeHeight value defined in your HidingSettingsSO asset for that player.
Feature Guide: TacticalZone
The TacticalZone component allows you to define a large volume of space with a specific tactical purpose. It's a powerful tool for level designers to guide the AI's high-level strategy, encouraging it to choose hiding spots that align with a broader team objective or a specific combat style.
Why Use It?
- Strategic Guidance: Nudge AI to find cover in tactically advantageous areas. For example, you can create an "Overwatch" zone on a balcony to encourage sniper-type AI to hide there.
- Define Combat Areas: Create "Ambush" zones in forests or "Defensive" zones around a capture point to make the AI's behavior context-aware.
- Improves Provider Efficiency: The TacticalZoneProviderSO can be set to only generate candidates within a specific zone type, focusing the AI's search and improving performance.
Setup Guide
- Create an empty GameObject to represent your zone.
- Add a Collider component to it (e.g., a BoxCollider or SphereCollider). This collider defines the boundaries of the zone.
- Crucially, check the Is Trigger box on the Collider component. This prevents it from physically blocking characters.
- Click Add Component and add the TacticalZone script.
- In the TacticalZone component, select the desired Zone Type from the dropdown. The gizmo color in the scene will update to reflect your choice.
Parameters
- zoneType (TacticalZoneType): The tactical purpose of this area.
- Defensive: Best for holding a critical position or objective.
- Overwatch: Ideal for elevated positions with good sightlines (e.g., sniper nests).
- Ambush: Suited for concealed areas intended for flanking or surprise attacks.
- FlankRoute: Can be used to designate paths or areas that are good for repositioning.
How It Works
The TacticalZone component is used by the TacticalZoneProviderSO. In your HidingSettingsSO, you add this provider and configure its Target Zone Type to match the zones you want the AI to use. When the AI searches for cover, this provider will only generate potential hiding spots inside the bounds of TacticalZones that match its target type.
Feature Guide: VolumetricCover
The VolumetricCover component designates a volume of space (like a smoke cloud, dense fog, or thick foliage) as "soft cover." This type of cover provides concealment and can block an AI's line of sight, but it does not physically stop movement or projectiles.
Why Use It?
- Dynamic Gameplay: Allows the AI to intelligently use temporary cover like smoke grenades.
- Richer Environments: Makes natural environments more tactically interesting by allowing AI to hide in thick bushes or foggy areas.
- Enhanced Stealth: Provides more opportunities for both players and AI to break line of sight and reposition, leading to more dynamic stealth and combat encounters.
Setup Guide
- Select the GameObject that represents your soft cover (e.g., a smoke grenade particle effect, a large bush model).
- Add a Collider component to it (BoxCollider, SphereCollider, etc.) to define its boundaries.
- Crucially, check the Is Trigger box on the Collider. This ensures it provides concealment without physically blocking characters.
- Click Add Component and add the VolumetricCover script.
- A semi-transparent gray gizmo will be drawn in the Scene View, showing the volume of the soft cover.
How It Works
This component is used by the VolumetricCoverScorerSO. This scorer has special logic:
- It first checks if a potential hiding spot is visible to any player based on normal, solid geometry.
- If the spot is visible, the scorer then performs another check: it casts a ray from the player to the hiding spot.
- If this ray intersects with the collider of any active VolumetricCover object, the scorer considers the line of sight to be blocked.
- It then returns a high score, effectively turning an otherwise exposed position into a viable hiding spot.
Providers
Providers are specialized ScriptableObjects that generate a list of potential hiding spots (candidates). Each provider has a unique method for finding spots, allowing you to mix and match them to create complex AI behavior.
Provider: Automatic Cover Finder
AutomaticCoverProviderSO is an intelligent provider that automatically finds cover spots without manual tagging by sampling the environment and verifying that hidden locations are actually concealed by a nearby obstacle.
How It Works
This provider intelligently searches the area around the agent to find tactically sound cover positions. It operates in several steps:
- Sample Generation: It generates sample points in a series of concentric rings around the agent's current position.
- NavMesh Validation: For each sample point, it finds the closest valid location on the NavMesh to ensure the agent can reach it.
- Visibility Check: It then performs a critical check to see if the NavMesh point is visible to any player. If it has a clear line of sight to a player, the point is discarded as it is not "in cover."
- Cover Verification: If the point is hidden, the provider performs a final, crucial verification. It casts a ray from the hidden spot towards the nearest player. If this ray hits a nearby object (on the
Occluder Masklayer), it confirms that there is a physical obstacle providing the cover. - Candidate Creation: If all checks pass, the point is added as a valid candidate. Importantly, it also records the normal of the obstacle's surface that was hit. This
coverNormal(the direction pointing away from the cover) is invaluable information for advanced scoring and the peeking system.
Parameters
This provider has its own specific settings and also uses global settings from your HidingSettings asset.
- Rings: The number of concentric rings to sample around the agent. More rings mean a wider and more thorough search.
- Max Cover Distance: The maximum distance from the hidden sample point that the provider will check for a wall or obstacle. This prevents the AI from thinking it's in cover when it's just hidden in an open field far from any object.
- Search Radius (from HidingSettings): Defines the maximum radius of the outermost search ring.
- Occluder Mask (from HidingSettings): A layer mask that determines which objects are considered valid cover obstacles.
- Candidates Per Provider (from LOD Settings): Determines how many sample points are generated per ring.
Use Cases
- Dynamic Environments: Perfect for levels where cover positions can change or are not known in advance, such as procedural worlds or maps with destructible environments.
- Reducing Design Workload: A powerful "set it and forget it" provider that removes the need for designers to manually place hundreds of cover nodes.
- High-Fidelity AI: Because it verifies cover and provides a surface normal, it enables more advanced behaviors like peeking and directional cover scoring, leading to more believable AI.
Performance
This is one of the more computationally intensive providers due to its multi-step verification process. For each potential candidate, it performs: NavMesh.SamplePosition, IVisibilityService.AnyPlayerHasLineOfSight (which involves raycasts), and an additional Physics.Raycast to verify the cover. The total performance cost scales with the Rings and Candidates Per Provider settings. While extremely effective, it should be used with care, especially if you have many AI agents searching simultaneously. Consider lowering the candidate count in higher-level LOD settings.
Provider: Behind Obstacle
BehindObstacleProviderSO is a provider that finds potential hiding spots by looking for locations directly behind environmental obstacles, relative to the agent's position.
How It Works
This provider operates by casting a series of rays outward from the agent's current position in a 360-degree circle. When a ray hits an object on a layer specified by the Occluder Mask (in your HidingSettings), the provider then calculates a point a short distance behind that obstacle.
It then checks if this calculated point is on a valid NavMesh area. If it is, the point is added to the list of potential hiding spots.
A key feature of this provider is that it also stores the normal of the surface it hit. This information can be used by certain scorers (like the DefensivePositionScorerSO) and by the system's built-in peeking logic to evaluate not just the position, but also the quality and direction of the cover.
Parameters
This provider's behavior is controlled by the global settings in your HidingSettings asset.
- Search Radius: Defines how far the rays are cast from the agent to look for obstacles. A larger radius means the AI will consider cover that is farther away.
- Occluder Mask: A physics layer mask that determines which objects are considered valid obstacles (e.g., walls, large props).
- NavMesh Area Mask: Defines which NavMesh areas are considered valid for hiding spots.
- Candidates Per Provider (from LOD Settings): Controls the number of rays cast in the 360-degree circle. A higher number increases the search density and the likelihood of finding small pieces of cover, but it also increases the performance cost.
Use Cases
- Classic Stealth: This is a fundamental provider for any stealth scenario where you want AI to hide behind walls, crates, pillars, or other large objects.
- Dynamic Cover Finding: Excellent for situations where the environment is not explicitly marked up with cover points. The AI will dynamically find cover based on the physical geometry of the level.
Performance
The performance cost of this provider is directly related to the Candidates Per Provider setting. Each candidate results in a Physics.Raycast call. While generally efficient, using a very high number of candidates on many agents simultaneously could impact performance. Adjust this setting based on your target platform and game's needs.
Provider: Cover Nodes
CoverNodeProviderSO is a provider that uses a pre-defined set of hiding spots that have been manually placed in the scene by a designer.
How It Works
This provider relies on a network of "Cover Nodes" that you place throughout your level. Each node is a simple GameObject with a specific component (CoverNode, which registers it to the CoverNodeRegistry) that marks it as a potential hiding spot.
When this provider is active, it doesn't search the environment dynamically. Instead, it gets a list of all registered CoverNode objects from the central CoverNodeRegistry. It then uses the position of each node as a candidate hiding spot.
Before adding a spot, it verifies that the node's position is on a valid NavMesh area, ensuring the AI can actually reach it.
This provider does not provide a cover normal, so scorers that rely on the direction of cover will not benefit from it, and the system's peeking logic will not be engaged for spots found by this provider.
Setup
To use this provider, you must manually place CoverNode components in your scene.
- Create an empty GameObject in your scene.
- Place it where you want the AI to be able to hide (e.g., behind a crate, at the corner of a wall).
- Add the
CoverNodecomponent to this GameObject. - Repeat for all desired hiding spots.
Parameters
- NavMesh Area Mask (from
HidingSettings): Defines which NavMesh areas are considered valid. If aCoverNodeis placed on an invalid area, it will be ignored.
Use Cases
- Highly Controlled Level Design: This is the perfect provider when you want exact, deliberate control over where your AI can hide. It removes the guesswork of dynamic systems.
- Performance Optimization: This is one of the most performant providers. It avoids costly physics queries or complex calculations by relying on a simple, pre-computed list of points. It's an excellent choice for performance-critical scenarios or mobile platforms.
- Defining Tactical Positions: Use it to mark specific strategic locations in your level that AI should use for cover, ambushes, or defensive positions.
Performance
This provider has a very low performance overhead. Its main cost is iterating through the list of registered nodes. The performance impact is minimal, even with hundreds of nodes in a scene, making it significantly faster than dynamic providers like BehindObstacleProviderSO or VoxelGridProviderSO.
Provider: Current Position
CurrentPositionProviderSO is a simple but essential provider that adds the agent's own current position to the list of candidate hiding spots.
How It Works
This provider does not search the environment. Its only job is to take the agent's current position (enemyPosition from the HidingContext), check that it's on a valid NavMesh, and add it to the list of potential spots to be evaluated by the scorers.
The purpose of this is to establish a baseline. By including its current location in the evaluation, the AI can compare all other potential hiding spots to the one it's already in. If no other spot offers a significant advantage, the agent's current position might receive the highest score, causing the AI to "choose" to stay put.
This provider does not generate a cover normal.
Parameters
- NavMesh Area Mask (from
HidingSettings): Ensures the agent's current position is on a valid NavMesh area.
Use Cases
- Preventing Unnecessary Movement: This is a crucial provider for almost every configuration. It prevents the AI from "fidgeting" or abandoning a perfectly good hiding spot for another that isn't substantially better. It adds stability to the AI's decision-making.
- Establishing a "Cost of Moving": By forcing new spots to be better than the current one, you implicitly create a cost for moving. The AI will only move if it finds a spot that is demonstrably superior according to the active scorers.
Performance
This is one of the highest-performance providers available. It adds only a single candidate to the list and performs just one NavMesh.SamplePosition call. Its performance impact is negligible, and it is highly recommended to include it in most HidingSettings configurations.
Provider: Directional Cover Nodes
DirectionalCoverNodeProviderSO is an intelligent version of the CoverNodeProviderSO. It selects pre-placed CoverNode objects only if they are oriented correctly, providing cover that faces away from the player's position.
How It Works
Like the standard CoverNodeProvider, this provider uses the central CoverNodeRegistry to get a list of all manually placed cover points in the scene. However, it adds a crucial filtering step.
For each CoverNode, it checks the node's defined "cover direction." It then compares this direction to the direction from the AI agent to the player(s). The node is only considered a valid candidate if its cover direction is pointing generally away from the player.
This ensures that the AI doesn't try to take cover on the wrong side of an object, making for much more believable and effective tactical behavior.
This provider does not provide a cover normal for scorers or the peeking system to use.
Setup
The setup is the same as the CoverNodeProvider, but with one extra step:
- Create an empty GameObject and add the
CoverNodecomponent. - Crucially, adjust the
Cover Directionvector in theCoverNodecomponent's settings. This vector should point in the direction the cover is "facing." For example, for a chest-high wall, the direction should point straight up from the wall's top edge, away from the side the AI should stand on. A gizmo in the scene view will help you visualize this direction.
Parameters
- Acceptance Angle: This is a key setting on the provider asset itself. It defines how forgiving the directional check is. The value represents the total angle of the cone used for the check.
- A small angle (e.g., 30) means the cover must be almost perfectly oriented away from the player to be considered. This is strict but very precise.
- A large angle (e.g., 180) means the cover can be facing in any direction in the hemisphere away from the player. This is very lenient.
- NavMesh Area Mask (from
HidingSettings): Ensures the selected node is on a valid NavMesh area.
Use Cases
- Smart Tactical Cover: This is the primary use case. It allows you to design levels with clear tactical positions (e.g., L-shaped corners, barricades) and have the AI use them intelligently based on the threat direction.
- Flanking and Positioning: By ensuring cover is used correctly, you can create more advanced behaviors where AI effectively suppresses the player from safe positions.
Performance
The performance is excellent and very close to the standard CoverNodeProvider. It adds a simple vector math calculation (Vector3.Angle) for each node, but this is extremely fast. It remains one of the most performant providers and is a great choice for nearly any project that uses manually placed cover points.
Provider: Hide Spot Provider (Base Class)
HideSpotProviderSO is not a functional provider itself. It is an abstract base class that serves as the template for all other provider assets.
How It Works
The Hiding System is designed to be modular. Instead of having a single, monolithic way of finding hiding spots, it uses a collection of smaller, specialized "providers." Each provider is a ScriptableObject that knows one specific way to generate a list of potential hiding spots (candidates).
The HideSpotProviderSO class defines the contract that every provider must follow. It contains a single abstract method:
public abstract void GenerateCandidates(in HidingContext ctx, List results);
- HidingContext ctx: A struct containing all the information the provider might need, such as the agent's position, player locations, and system settings.
- List
results: The list that the provider must add its generated candidate spots to.
By inheriting from this class, you can create your own custom logic for finding cover, which will seamlessly plug into the main hiding system alongside the built-in providers.
Setup
To create a new, custom provider:
- Create a new C# script.
- Make the class inherit from
HideSpotProviderSO. - Add the
[CreateAssetMenu]attribute to allow you to create instances of it in the Unity Editor. - Implement the
GenerateCandidatesmethod with your custom logic for finding spots. - Create an instance of your new provider asset via the
Assets -> Createmenu. - Drag your new provider asset into the Providers array in your
HidingSettingsSO.
Use Cases
- System Extensibility: The primary use is to allow developers to extend the hiding system with novel techniques for finding cover without modifying the core system code.
- Project-Specific Logic: You can create providers tailored to the unique mechanics of your game. For example, a provider that finds hiding spots in magical portals, under brush, or in areas designated by a special trigger volume.
Performance
As a base class, it has no performance impact. The performance of a provider is entirely dependent on the logic implemented in its GenerateCandidates method.
Provider: Opposite Players Direction
OppositePlayersProviderSO is a provider designed to find hiding spots that are located in the general direction away from the player or players.
How It Works
This provider first determines the average direction from the AI agent to all known player positions. It then inverts this vector to get a primary direction that points directly away from the threat.
It then generates a number of candidate points along this "away" vector, at various distances up to the Search Radius. A small amount of random jitter is added to each point to spread the candidates out and prevent them from all falling on a single line, allowing for more flexible positioning.
Finally, each candidate point is checked to ensure it's on a valid NavMesh area before being added to the list of potential spots. This provider does not generate a cover normal.
Parameters
This provider's behavior is controlled by the global settings in your HidingSettings asset.
- Search Radius: Defines the maximum distance the provider will look for spots along the "away" vector.
- Candidates Per Provider (from LOD Settings): Controls how many sample points are generated. More candidates will create a denser search pattern in the "away" direction.
- NavMesh Area Mask: Restricts the search to specific types of NavMesh areas.
Use Cases
- Retreating and Kiting: This is the ideal provider for AI that needs to create distance from a threat. It's perfect for ranged characters who want to kite their enemies, or for "cowardly" AI that tries to flee from combat.
- Strategic Repositioning: When an AI is in a bad spot, this provider can help it find a safer location that is generally farther away from the player, forcing the player to close the distance again.
- Breaking Line of Sight: While it doesn't explicitly check for line of sight, by moving in the opposite direction of a player, the AI naturally increases its chances of putting obstacles between itself and the player.
Performance
The performance of this provider is very good. Its cost is directly proportional to the Candidates Per Provider setting, as each candidate requires a NavMesh.SamplePosition call. It does not perform any expensive physics raycasts, making it significantly cheaper than providers like BehindObstacleProviderSO. It is a lightweight and effective way to generate directional movement.
Provider: Shadow Provider
ShadowProviderSO is a specialized provider that finds potential hiding spots by locating areas on the NavMesh that are currently in shadow.
How It Works
This provider simulates how light and shadow work in the environment. It requires a reference to a dominant Light source in the scene (typically a Directional Light representing the sun).
For a number of sample points around the agent, it performs the following check:
- It finds a random, reachable point on the NavMesh within the
Search Radius. - It casts a ray from a position high above this point, aiming downwards along the direction of the dominant light.
- If this ray hits an object (on a layer specified by the
Occluder Mask) before it reaches the ground point, it means the point is in shadow. - Shadowed points that are on the NavMesh are added to the list of candidate hiding spots.
This provider does not generate a cover normal.
Parameters
- Dominant Light: A direct reference to the
Lightcomponent that should be treated as the primary shadow-casting light source in the scene. If this is not assigned, the provider will try to find the first availableLightcomponent, but it is highly recommended to assign this manually for predictable behavior. - Raycast Height Offset: Determines how far above the sample point the shadow-checking raycast begins. This value should be large enough to ensure the ray starts above any potential geometry that could cast a shadow.
- Search Radius (from HidingSettings): The radius around the agent in which to search for shadowed spots.
- Occluder Mask (from HidingSettings): A physics layer mask that determines which objects are capable of casting shadows.
- Candidates Per Provider (from LOD Settings): The number of random points to test for shadow coverage.
Use Cases
- Light-and-Shadow Stealth: This is the core provider for any game where stealth is based on hiding in the dark (e.g., Thief, Splinter Cell).
- Nocturnal or Light-Sensitive AI: Perfect for creating creatures of the night, vampires, or other AI that are harmed by or actively avoid light.
- Ambiance and Believability: Can be used subtly to make AI seem more intelligent, as they will appear to naturally use shadows to their advantage when approaching or hiding from the player.
Performance
The performance cost of this provider is directly related to the Candidates Per Provider setting. Each candidate requires a NavMesh.SamplePosition call followed by a Physics.Raycast. This makes it one of the more computationally intensive providers, with a cost similar to BehindObstacleProviderSO.
For best performance:
- Keep the
Candidates Per Providervalue as low as you can while still getting good results. - Always assign the
Dominant Lightreference manually to avoid the overhead of the system having to search for it.
Provider: Tactical Zone
TacticalZoneProviderSO is a provider that generates candidate hiding spots within specific, designer-placed volumes called Tactical Zones. This allows for a high degree of control over the AI's positioning, guiding it to use areas intended for specific tactical purposes.
How It Works
This system requires designers to place TacticalZone components on GameObjects with colliders in the scene. Each zone is assigned a type (e.g., Overwatch, Ambush, Defensive).
The TacticalZoneProviderSO asset is configured to look for a specific TacticalZoneType. When it runs, it queries the central TacticalZoneRegistry to find all zones of that type within the agent's search radius.
For each matching zone, the provider generates a number of random points within the zone's collider bounds. It then finds the closest valid point on the NavMesh for each random sample. These valid NavMesh points become the candidate hiding spots.
This effectively lets a designer say, "When you are in a defensive state, find cover inside one of these designated defensive areas."
Setup
- Create an empty GameObject in your scene to act as the zone.
- Add a collider component (e.g., BoxCollider). Mark it as a Trigger.
- Add the new
TacticalZonecomponent to this GameObject. - In the
TacticalZonecomponent, set the Zone Type to the desired tactical purpose (e.g., Defensive). - Adjust the size and position of the collider to cover the tactical area. A gizmo will show the zone's color and type in the Scene view.
- Create an instance of the
TacticalZoneProviderSOasset in your project files. - In the provider asset's Inspector, set the Target Zone Type to match the zones you want it to find.
- Add this provider asset to the Providers list in your
HidingSettingsSO.
Use Cases
- Designer-Controlled Encounters: The primary use case. Force AI snipers to use sniper nests, or ensure ambushing enemies hide in designated ambush locations.
- Controlling Flow of Combat: Guide the AI to fall back to specific, pre-defined defensive lines or to use flanking routes that you have explicitly laid out.
Performance
Since it only samples within a few known zones instead of the entire world, it can be more performant than wide-area environmental scanners if used correctly.
Provider: Vertical Layer
VerticalLayerProviderSO generates candidate hiding spots on different vertical levels from the agent, making it ideal for multi-floor environments like buildings, canyons, or areas with catwalks.
How It Works
This provider expands the search for cover into 3D space, allowing the AI to consider hiding spots above or below its current position.
- Define Layers: It uses the agent's current position as a starting point and calculates the center points for layers above and below based on the Layer Height setting.
- Generate Points: On each of these vertical layers, it generates a series of sample points in concentric rings, similar to the NavMeshRingProvider.
- NavMesh Validation: For each sample point, it uses
NavMesh.SamplePositionto find a valid, reachable location on the NavMesh. The search range for this check is limited to half the layer height to prevent it from finding spots on the wrong floor. - Optional Cover Normal: To add more intelligence, it performs a quick check for nearby cover by casting a ray from the valid NavMesh spot back towards the center of the layer. If it hits an occluder, it records the hit.normal as the
coverNormal. If not, the spot is still added, but without cover information.
Parameters
This provider's behavior is primarily controlled by its own settings.
- Layer Height: The vertical distance between floors or layers. This should be set to match the typical floor height in your level architecture.
- Layers Above: The number of floors to check above the agent's current position.
- Layers Below: The number of floors to check below the agent's current position.
- Samples Per Ring: The number of points to generate in each search ring on a given layer.
- Rings: The number of concentric search rings to generate on each layer.
- Search Radius (from HidingSettings): Controls the radius of the search rings on each layer.
- NavMesh Area Mask (from HidingSettings): Ensures the spots are found on a valid NavMesh area.
Use Cases
- Multi-Story Buildings: The primary use case. It allows an AI on the ground floor to consider hiding on the second floor, and vice-versa.
- Environments with Verticality: Excellent for levels with bridges, underpasses, catwalks, large staircases, or natural cliffs.
- Smarter Tactical Positioning: Encourages AI to use height to its advantage, creating more dynamic and challenging combat or stealth encounters.
Performance
The performance cost is directly related to the number of points it generates. The total number of candidates is (Layers Above + Layers Below) * Rings * Samples Per Ring. For each of these potential points, it performs a NavMesh.SamplePosition and a Physics.Raycast.
While very effective, it can become moderately expensive if the settings are high. It's recommended to balance the number of layers and samples with your performance budget. It can be a great addition to a high-quality LOD setting for when players are close.
Provider: Voxel Grid
VoxelGridProviderSO is a unique provider that finds potential hiding spots by sampling points in a 3D grid (a "voxel grid") around the agent and checking if these points are located inside solid geometry.
How It Works
This provider creates a uniform 3D grid of points centered on the agent's position. The size of this grid is determined by the Search Radius. For each point in the grid, it performs a Physics.OverlapBox check.
If this check returns any colliders on the Occluder Mask layer, it means the point is inside or touching a piece of geometry. That point is then added to the list of candidate hiding spots.
Unlike other providers that find spots behind or near objects, this one is specifically designed to find spots within them. This provider does not generate a cover normal.
Important Note: This provider does not check for NavMesh reachability. It is intended to be used with scorers like the ReachabilityScorerSO to filter out any points that the AI cannot actually get to.
Parameters
- Resolution: This is a key setting on the provider asset. It defines the number of points to sample along each of the three axes (X, Y, Z). The total number of sample points is
resolution * resolution * resolution. Be careful with this value, as it has a major impact on performance. - Collision Check Size: The size of the small box used for the
OverlapBoxcheck at each grid point. A larger value is more likely to detect nearby geometry but is less precise. - Search Radius (from HidingSettings): Defines the overall size of the 3D grid. The grid will be a cube with sides of length
Search Radius * 2. - Occluder Mask (from HidingSettings): The physics layer mask that determines which objects are considered valid geometry to hide inside.
Use Cases
- Hiding in "Soft" Cover: This is the perfect provider for finding spots inside non-solid or semi-solid objects like thick bushes, tall grass, or magical concealment zones.
- Complex Geometry: Can be useful for finding spots inside unusual or complex meshes that don't have a clear "behind," such as a hollowed-out log or a small cave opening.
Performance
This is potentially the most performance-intensive provider in the entire system. The number of physics checks it performs is resolution³.
resolution = 8(default) ->8 * 8 * 8 = 512physics checks.resolution = 10->10 * 10 * 10 = 1000physics checks.resolution = 16->16 * 16 * 16 = 4096physics checks.
It is critical to keep the resolution as low as possible. This provider should be used sparingly and only when its specific functionality is required. For general-purpose cover finding, providers like BehindObstacleProviderSO or CoverNodeProviderSO are far more performant. Always profile your game when using this provider.
Scorers
Scorers are specialized ScriptableObjects that evaluate the hiding spots found by the providers. Each scorer assigns a score based on a specific criterion, such as distance from the player, visibility, or path safety. The final score for a spot is a weighted average of all active scorers.
Scorer: Ambush Potential
AmbushScorerSO is a sophisticated scorer that evaluates hiding spots based on their tactical potential for an ambush. It rewards spots that are concealed from the player's current position but maintain a clear line of sight to a strategic location.
How It Works
This scorer operates on a simple but powerful principle for setting up an ambush: hide from where the player IS, but watch where the player WAS.
It checks three conditions in order:
- Is the spot hidden? It first uses the
IVisibilityServiceto check if the candidate spot is visible to any player from their current, real-time position. If the AI can be seen, the spot is immediately disqualified and gets a score of 0. - Can the spot see the target? If the spot is hidden, it then determines an "ambush target." This is the player's last known position (LKP) if available, otherwise it falls back to the closest player's current position. It then performs a raycast to see if there is a clear line of sight from the candidate spot to this ambush target. If the view is obstructed, the spot is disqualified with a score of 0.
- Is it at the right distance? If the spot is both hidden and has a clear view of the target, it receives a score based on how close it is to the
Optimal Ambush Distance. The closer to this ideal distance, the higher the score (maxing out at 1.0).
This logic creates an intelligent behavior where the AI will try to find a flanking position or overlook that allows it to surprise the player.
Parameters
- Weight: As with all scorers, this determines how much influence the
AmbushScorerhas on the final decision. A high weight means the AI will strongly prioritize setting up ambushes. - Optimal Ambush Distance: This float value, set on the scorer asset, defines the ideal range in meters for the AI to be from the ambush target point.
Use Cases
- Lying in Wait: The primary use case. Create AI that hides around a corner and waits for the player to walk past the location where they were last seen.
- Tactical Overwatch: Excellent for snipers or other ranged AI. They will find concealed positions that have a good vantage point over key areas where the player is likely to be.
- Smart Repositioning: When an AI loses sight of the player, this scorer helps it choose a new hiding spot that anticipates the player's movements, rather than just running away.
Performance and Dependencies
This is a moderately expensive scorer. For each candidate spot, it performs:
- A visibility check via the
IVisibilityService(which often involves its own raycasts). - An additional
Physics.Raycastto check the line of sight to the ambush target.
Because of this, its performance cost is higher than simple scorers like DistanceScorerSO. It's highly effective but should be used when its specific tactical behavior is desired. It also requires the EnemyHider to have seen a player to generate a playerLastKnownPosition to be maximally effective.
Scorer: Cover Height
CoverHeightScorerSO evaluates a candidate spot based on the height of the cover in front of it, ensuring the AI does not try to hide behind objects that are too short to conceal it properly.
How It Works
This scorer adds a layer of intelligence to cover selection by checking not just if there is cover, but if that cover is tall enough. It operates only on candidates that have a coverNormal provided by their generator (like AutomaticCoverProviderSO or BehindObstacleProviderSO).
For a given candidate spot, the scorer simulates the agent's head position. It then casts a short ray from just in front of the agent's head directly towards the primary threat (the player's last known position or current position).
- If the ray hits an
occluder, it means there is a solid object at head-level between the agent and the threat. The spot receives a high score (1.0), indicating good, tall cover. - If the ray does not hit anything, it implies the cover is too low (e.g., a waist-high crate), and the agent's head would be exposed. The spot receives a low score (0.0).
- If a candidate has no coverNormal, the scorer returns a neutral score (0.5) so as not to penalize providers that don't generate this data.
Parameters
- Check Offset: How far in front of the agent's head to start the raycast. This should be a small value to ensure the check is close to the agent.
- Check Distance: The maximum length of the raycast. This should be short, as it's only meant to verify the immediate cover, not see across the map.
Use Cases
Realistic Cover Selection: This is a crucial scorer for preventing silly AI behavior, like crouching behind a small rock or a low railing while thinking it's completely hidden.
Enhancing Tactical Realism: In shooters or tactical games, this ensures that AI agents prioritize cover that offers full protection, leading to more believable and challenging encounters.
Performance
The performance impact of this scorer is very low. It performs a single, very short Physics.Raycast for each candidate that has cover information. In most scenarios, its cost is negligible.
Scorer: Crossfire
CrossfireScorerSO evaluates hiding spots based on their potential to create a tactical crossfire with allied units. It rewards spots that provide a flanking angle on the player's position relative to the positions of other AI agents.
How It Works
This scorer promotes intelligent squad behavior by moving beyond simple grouping. Instead of just huddling together, it encourages agents to spread out and attack from multiple angles.
For a given candidate spot, the scorer does the following:
- It identifies the primary threat location (the player's last known or current position).
- It iterates through all of the agent's allies.
- For each ally, it calculates the angle formed at the player's position between the ally and the candidate spot (ally -> player -> candidate).
- The scorer finds the maximum angle created among all allies.
- The final score is based on how close this maximum angle is to the Optimal Angle defined in the asset. An angle of 90 degrees (a perfect "L" shape flank) would receive the highest score, while positions that are directly in line with an ally receive a low score.
Setup
- Ensure your EnemyHider components have their allies list populated correctly.
- Create an instance of the CrossfireScorerSO asset.
- In the asset's Inspector, configure the Optimal Angle (90 is a good default) and Minimum Angle (e.g., 20, to ignore insignificant angles).
- Add this scorer asset to the Scorers list in your HidingSettingsSO and give it a moderate weight (e.g., 1.0 to 2.0). Its influence should be a significant factor in repositioning decisions.
Use Cases
- Advanced Squad Tactics: The primary use case. Transform a group of AI from a simple mob into a coordinated squad that actively tries to flank and suppress the player.
- Making Combat More Challenging: A crossfire is a difficult situation for a player to deal with. This scorer directly increases the tactical challenge of combat encounters.
- Dynamic Encounters: The AI will dynamically create flanking maneuvers based on the current positions of all units, leading to less predictable and more engaging fights.
Scorer: Darkness
DarknessScorerSO is a very simple scorer that evaluates hiding spots based on the overall ambient light level of the scene. It rewards spots in environments that are generally darker.
How It Works
This scorer operates on a very simple principle: it reads the scene's global ambient light intensity value from RenderSettings.ambientIntensity.
- If the ambient intensity is high (a bright scene), it returns a low score.
- If the ambient intensity is low (a dark scene), it returns a high score.
The score is calculated as 1.0f - ambientIntensity, clamped between 0 and 1.
Important Note: This is a global scorer. At any given moment, it will return the exact same score for every single candidate spot regardless of their position. It does not check for dynamic lights or shadows cast by objects. Its purpose is to give a general "bias" towards darkness for the AI's decision-making process.
Parameters
- Weight: Determines how much the AI should care about the overall darkness of the scene when choosing a spot. If you want AI to strongly prefer hiding when the level is dark, give this a high weight.
Use Cases
- Time of Day Behavior: This scorer is perfect for influencing AI behavior based on a day/night cycle. If you change the ambient intensity at runtime to simulate the transition from day to night, this scorer will automatically make your AI more likely to hide during the night.
- General "Creature of the Dark" AI: For AI that should be more active or more likely to take cover in dark levels (e.g., caves, nighttime scenes), this scorer provides a simple and extremely performant way to achieve that.
Performance
The performance of this scorer is excellent. It is one of the fastest scorers available because it only involves reading a single float value from the render settings. Its impact on performance is negligible, making it safe to use in any configuration.
Scorer: Defensive Position Bonus
DefensivePositionScorerSO is a powerful scorer that provides "inertia" or a defensive bonus. It evaluates the agent's current position to determine if it's safe, making the AI less likely to abandon a good spot and more likely to flee a compromised one.
How It Works
This scorer is unique because it only cares about one specific candidate: the one that represents the agent's current location. For all other candidate spots, it returns a score of 0.
When it evaluates the agent's current position, it performs a critical check:
- Is the agent currently visible to any player? It uses the
IVisibilityServiceto check if the agent can be seen from its current spot.
The result of this check leads to one of two extreme outcomes:
- If VISIBLE: The agent's cover is blown. The scorer returns
float.NegativeInfinity. This is a powerful penalty that effectively disqualifies the current position, forcing the AI to choose a different hiding spot. It creates a strong "get to cover!" instinct. - If NOT VISIBLE: The agent is safe. The scorer returns the small, positive
inertiaBonus. This gives a slight advantage to staying put, ensuring the AI won't move to a new spot unless it's significantly better than the one it's already in. This prevents fidgety, unnecessary movement.
Parameters
- Weight: Determines the influence of this scorer. Because it can return
NegativeInfinity, its penalty effect is absolute regardless of weight. The weight primarily affects how influential theinertiaBonusis when compared to scores from other active scorers. - Acceptance Radius: A small radius to determine if a candidate point is close enough to be considered the agent's current position.
- Inertia Bonus: The small score bonus (e.g., 0.1 or 0.2) applied if the agent is currently safe. This value should be kept relatively low.
- See The Warning Section Below
Use Cases
- Creating Stable AI: This is the primary solution for preventing AI from constantly shuffling between two or more hiding spots that have very similar scores. The inertia bonus makes them "prefer" to stay still.
- Intelligent Cover Response: This scorer is essential for creating AI that reacts believably to being discovered. The moment it becomes visible, its top priority will be to move to a new, safer location.
- Establishing a Defensive Baseline: It should be included in most configurations to create a base level of intelligent self-preservation.
Note
- Warning: Chose the parameters value and weight in the HidingSetting for this scorer very carefully because it's very sencitive one and a wrong weight for this scorer may cause the agent will never move. so try to give it's weight as low as possible like 0.1 or less than 0.1
Performance and Dependencies
This scorer's performance is excellent. It only performs its main logic (the visibility check) for a single candidate spot per evaluation cycle.
- It is highly dependent on a correctly configured
IVisibilityService. - It is designed to be used with the
CurrentPositionProviderSO, which ensures that the agent's current position is always included in the list of candidates to be evaluated. Without this provider, theDefensivePositionScorerwill do nothing.
Scorer: Distance From Players
DistanceScorerSO is a fundamental scorer that evaluates hiding spots based on their distance from the nearest player. It encourages AI to move farther away from threats.
How It Works
This scorer calculates the distance from the candidate hiding spot to every player currently known to the system. It finds the shortest of these distances and uses that value to calculate a score.
The score is normalized by the Search Radius from your HidingSettings. The formula is essentially:
Score = (Distance to Nearest Player) / (Search Radius * normalizeByRadius)
This means a spot right next to a player will have a score near 0, while a spot at the edge of the search radius will have a score near 1.0. In short, farther away is better.
Negative Weight Trick
This scorer has a special interaction with negative weights. If you give it a negative weight in the HidingSettings (e.g., -1.0), you will invert its behavior. Instead of rewarding distance, it will reward proximity, encouraging the AI to find the closest possible (safe) hiding spot. This is useful for aggressive or hunter-style AI.
Parameters
- Weight: Determines how much the AI prioritizes distance. A high positive weight creates cowardly AI. A high negative weight creates aggressive AI.
- Normalize By Radius: This multiplier on the scorer asset adjusts the normalization.
1.0(default): The score scales linearly with theSearch Radius. A spot at the search radius distance gets a score of 1.0.< 1.0: The score will reach 1.0 before hitting the full search radius. For example, at0.5, a spot at half the search radius already gets a score of 1.0.> 1.0: The score will only approach 1.0 for distances beyond the search radius.
Use Cases
- Cowardly AI: Use with a high positive weight. This is the primary scorer for AI that should run away from the player.
- Ranged Units: Use with a moderate positive weight for archers or mages who need to maintain a safe distance.
- Stealthy Hunters: Use with a high negative weight to make the AI try to sneak as close to the player as possible while remaining hidden.
Performance
The performance of this scorer is excellent. For each candidate spot, it performs a simple Vector3.Distance calculation for each player. This is a very fast operation, and the scorer has a negligible impact on performance.
Scorer: FOV Avoidance
FOVScorerSO is a critical scorer for stealth gameplay. It evaluates spots based on whether they fall within any player's field of view (FOV). It heavily penalizes spots that the player is actively looking towards.
How It Works
This scorer performs a simple but crucial binary check using the IVisibilityService:
- It asks the
IVisibilityService: "Is this candidate spot inside any player's viewing cone AND visible from their eyes?" - Based on the answer, it returns a score:
- If YES: The spot is in the player's direct field of view. The scorer returns 0.0.
- If NO: The spot is outside the player's direct field of view (either behind them, to the side, or obscured by an object). The scorer returns 1.0.
This creates a strong incentive for the AI to stay out of the player's forward-facing view cone, encouraging flanking and sneaking behaviors.
Distinguishing from VisibilityScorer
It's important to understand the difference between this scorer and the general VisibilityScorer:
VisibilityScorer: Checks for a clear line of sight from the player's eyes to the spot, regardless of which direction the player is facing. A spot directly behind the player would be considered "visible" if there are no walls in the way.FOVScorer: Also checks for line of sight, but only if the spot is within the player's view cone angle. A spot directly behind the player will always get a perfect score of 1.0 from this scorer, because it's not in the FOV.
For true stealth gameplay, you typically use both scorers together. This tells the AI to:
- Strongly prefer spots outside the player's FOV (
FOVScorer). - Of those spots, prefer the ones that are also physically hidden behind objects (
VisibilityScorer).
Parameters
- Weight: Determines how strongly the AI will prioritize staying out of the player's view cone. For stealthy AI, this should usually have a very high weight.
Use Cases
- Classic Stealth Mechanics: This is the cornerstone of any system where AI needs to avoid a player's vision cone (like in Metal Gear Solid or Dishonored).
- Flanking Behavior: By heavily penalizing spots in front of the player, this scorer naturally encourages AI to move to the player's sides or back.
- Creating "Blind Spots": This scorer is what makes hiding behind a player a valid and rewarding tactic for the AI.
Performance and Dependencies
The performance of this scorer is entirely dependent on the underlying implementation of IVisibilityService.InAnyPlayerFOVAndVisible. This check typically involves fast vector math (a dot product to check the angle) and potentially a Physics.Raycast to check for obstructions.
It is moderately expensive, more so than simple math-based scorers, but essential for this type of gameplay. It has a hard dependency on a correctly configured IVisibilityService.
Scorer: Hazard Avoidance
HazardAvoidanceScorerSO prevents the AI from choosing hiding spots inside known environmental dangers, such as fires, poison clouds, or the blast radius of a grenade.
How It Works
This system relies on Hazard components being placed on objects that represent a threat. Any GameObject with a Hazard component will register itself with a central HazardRegistry when it becomes active.
The HazardAvoidanceScorer queries this registry to get a list of all active hazards. For each candidate hiding spot, it checks the distance to every hazard.
If the candidate's position is within the defined radius of any Hazard object, the scorer returns float.NegativeInfinity. This instantly and absolutely disqualifies the spot, forcing the AI to look for cover elsewhere. This is a critical self-preservation behavior.
Setup
On any GameObject that represents a danger (e.g., a fire particle system, a grenade prefab), add the new Hazard component.
Adjust the Radius property on the Hazard component to match the area of effect of the danger. A red gizmo in the Scene view will help you visualize this area.
Create an instance of the HazardAvoidanceScorerSO asset in your project files.
Add this scorer asset to the Scorers list in your HidingSettingsSO. It is highly recommended to give this scorer a high weight (e.g., 5.0) to ensure its decisions are always prioritized.
Use Cases
- Dynamic Dangers: Essential for making AI react to dynamic events like grenades, molotovs, or artillery strikes.
- Persistent Hazards: Prevents AI from unintelligently pathing through fires, acid pools, or other static level hazards when looking for cover.
- Enhanced Believability: Stops the AI from making immersion-breaking mistakes, making them feel more aware and intelligent.
Scorer: Height Difference
HeightDifferenceScorerSO scores a candidate based on its vertical distance from the agent, discouraging movement to different floors or extreme heights unless the spot is significantly better in other ways.
How It Works
This scorer introduces a preference for the AI to stay on its current vertical plane. It calculates the absolute difference in the Y-axis value between the agent's current position and the candidate spot's position.
The scoring is inversely proportional to this height difference:
A spot on the same level (height difference is zero) receives a perfect score of 1.0.
As the vertical distance increases, the score decreases linearly.
If the height difference exceeds the maxAcceptableHeightDifference, the score becomes 0.0.
This acts as a penalty for vertical movement, meaning a spot on another floor must be significantly better in other aspects (like visibility, safety, etc.) to overcome this penalty and be chosen.
Parameters
- Max Acceptable Height Difference: The maximum vertical distance the AI will consider before the score drops to zero. This should typically be set to a value slightly greater than the height of a single floor in your level (e.g., 4-5 meters).
Use Cases
- Controlling Vertical Movement: Its primary use is to prevent AI from erratically running up and down stairs or jumping off ledges just because a spot is slightly better. It promotes more deliberate and logical movement.
- Improving Performance: By penalizing distant vertical spots, it can indirectly favor closer, more relevant hiding locations, potentially reducing the need for long and complex pathfinding calculations.
- Defining Agent Behavior: You could give different AI types different settings. A nimble, spider-like robot might have a high maxAcceptableHeightDifference, while a heavy ground soldier would have a very low one.
Performance
This scorer is extremely fast. It only involves a few floating-point subtractions and divisions (Mathf.Abs). Its performance impact is virtually zero, making it a safe and effective scorer to add to any configuration.
Scorer: Objective Proximity
ObjectiveProximityScorerSO scores candidate spots based on their distance and relevance to a mission objective. This makes the AI "mission-aware," preventing it from abandoning a critical location it's supposed to be defending.
How It Works
This scorer relies on a new static service called ObjectiveService. Your game's own mission scripts are responsible for registering and unregistering objective locations with this service.
The ObjectiveProximityScorerSO asset is configured to care about a specific ObjectiveType (e.g., Defend, Attack).
When scoring a candidate, it finds the nearest objective of the matching type. The scoring logic then rewards proximity:
A spot very close to the objective receives a high score (1.0).
As the distance to the objective increases, the score decreases linearly.
If the distance exceeds the Max Distance setting on the scorer, the score becomes 0.0.
This creates a powerful incentive for the AI to choose cover that keeps it relevant to the mission, adding a crucial layer of tactical intelligence.
Setup
From your game logic scripts (e.g., a "King of the Hill" manager, a "Bomb Defusal" script), get a reference to the objective's position.
When the objective becomes active, call ObjectiveService.RegisterObjective(...), passing in its position and type.
When the objective is completed or moves, call ObjectiveService.UnregisterObjective(...) or ClearObjectives().
Create an instance of the ObjectiveProximityScorerSO asset.
Configure its Target Type, Optimal Distance, and Max Distance in the Inspector.
Add this scorer asset to the Scorers list in your HidingSettingsSO and give it an appropriate weight.
Use Cases
Defensive Scenarios: Essential for AI that needs to guard a flag, control point, or VIP. This scorer will force them to hide near the objective instead of running away.
Offensive Scenarios: By setting the type to Attack, you can encourage AI to select hiding spots that move them progressively closer to their target.
Dynamic Objectives: Works perfectly for objectives that move, as long as your game logic continually updates the objective's position in the ObjectiveService.
Scorer: Partial Visibility
PartialVisibilityScorerSO provides a more thorough visibility check than the standard VisibilityScorerSO. It ensures the agent's entire body is concealed by checking multiple points against player line of sight.
How It Works
While the basic VisibilityScorerSO might only check one or two points, PartialVisibilityScorerSO checks three distinct points on the agent's body relative to the candidate position:
- Head Point: At the agent's full eye height.
- Chest Point: At the agent's center of mass (e.g., 60% of eye height).
- Feet Point: Near the ground (e.g., 0.2 meters up).
It then uses the IVisibilityService to check if any player has a direct, unobstructed line of sight to any of these three points.
If all three points are hidden from all players, the spot is considered fully concealed and receives a perfect score of 1.0.
If even one point is visible to any player, the spot is considered compromised and receives a score of 0.0.
This creates a strict, all-or-nothing evaluation of cover.
Use Cases
- Creating Cautious AI: This is the ideal scorer for stealth games or for AI archetypes (like snipers or scouts) that prioritize complete concealment above all else.
- Eliminating Exploits: It prevents situations where a player can see and shoot an AI's feet or head poking out from cover, even though the AI's center point is hidden.
- High-Difficulty Settings: This can be used to make AI on higher difficulty levels much harder to spot and engage, as they will only choose locations that offer total body cover.
Performance
The performance cost is slightly higher than the basic VisibilityScorerSO because it makes up to three calls to AnyPlayerHasLineOfSight per candidate instead of one or two. However, because the underlying visibility checks are often optimized (especially in the asynchronous system), the impact is still relatively low and is a worthwhile trade-off for the increased fidelity.
Scorer: Path Safety
PathSafetyScorerSO is one of the most intelligent and important scorers for creating believable stealth AI. It evaluates the safety of the path an agent would take to reach a hiding spot, not just the destination itself. It heavily penalizes paths that would expose the agent to player view.
How It Works
For each candidate hiding spot, this scorer performs a detailed analysis of the journey from the agent's current position to that spot.
- Calculate Path: It first calculates the
NavMeshPathto the candidate spot. If no complete path exists, the spot is unreachable and is disqualified with a score ofNegativeInfinity. - Sample Path Points: It gathers all the corners of the path. To ensure safety on long, straight sections, it also adds extra sample points in the middle of any path segment longer than the
Long Segment Threshold. - Check Visibility: It then iterates through all these sample points along the path. For each point, it uses the
IVisibilityServiceto check if that point is visible to any player. - Calculate Exposure: It determines the
visibilityRatio—the percentage of sample points that were visible to a player. - Apply Penalties: The scoring logic is applied in two stages for maximum control:
- Hard Disqualification: If the
visibilityRatiois greater than theVisibility Threshold, the path is considered too dangerous. The scorer returnsNegativeInfinity, completely disqualifying it. This acts as a hard "do not cross" rule. (This threshold can be dynamically overridden per agent via theHidingContextfor the "desperate retreat" feature). - Exponential Penalty: If the path is not disqualified, its score is calculated based on its safety (
1.0 - visibilityRatio). This value is then raised to the power of thePenalty Exponent. Using an exponent greater than 1 means that even small amounts of exposure are heavily penalized, making the AI extremely cautious.
- Hard Disqualification: If the
Parameters
- Weight: Determines how much the AI prioritizes a safe path. For stealthy characters, this should have a very high weight.
- Visibility Threshold: A hard limit (0.0 to 1.0) for how much of the path can be exposed. If the visible portion exceeds this, the path is invalid.
- Penalty Exponent: Controls the scoring curve for paths that pass the threshold check. A value of
1is a linear penalty. A value of2or higher makes the AI extremely averse to even minimal exposure. - Long Segment Threshold: The length of a straight path segment that will trigger an extra visibility check at its midpoint. This prevents AI from feeling safe running down a long, open hallway just because its corners are hidden.
Use Cases
- Essential Stealth Behavior: This is a cornerstone scorer for any AI that needs to be sneaky. It's the difference between an AI that intelligently skirts cover and one that foolishly runs across an open room.
- Creating Cautious AI: By tuning the exponent, you can create AI personalities ranging from slightly cautious to extremely risk-averse.
- Preventing "Suicidal" Traverses: It stops the AI from choosing a theoretically perfect hiding spot if the only way to get there is through a field of fire.
Performance and Dependencies
This is one of the most performance-intensive scorers in the system. Its cost is significant and should be considered carefully. For every candidate spot, it must:
- Calculate a NavMesh path.
- Perform multiple visibility checks (which often involve raycasts) along the path's length.
The total cost scales with the number of candidate spots, the number of players, and the complexity (number of corners) of the potential paths.
- Recommendation: Use this scorer when high-quality stealth is required, but be mindful of the performance cost. Consider reducing the number of candidate spots if this scorer is active.
- Dependencies: It has hard dependencies on a valid
NavMeshin the scene and a correctly configuredIVisibilityService.
Scorer: Player Path Prediction
PlayerPathPredictionScorerSO is a highly intelligent scorer that allows an AI to ambush the player by predicting their future position based on their current movement velocity.
How It Works
This scorer elevates the AmbushScorer from reacting to the player's past position to anticipating their future actions. It leverages a new PlayerTrackerService to get the player's current velocity.
The scoring logic follows two critical ambush principles:
- Stay Hidden Now: The candidate spot must be hidden from the player's current position. If the AI can be seen while moving into its ambush spot, the ambush fails. This check receives a score of 0.
- See Them Later: The candidate spot must have a clear line of sight to the player's predicted future position. This position is calculated by projecting the player's current velocity forward in time by a set amount (Prediction Time).
If both conditions are met, the candidate is a perfect ambush spot and receives a high score (1.0). Otherwise, it receives a score of 0.0.
Setup
- Crucial Step: In the EnemyHider.cs script, you must add one line to the CreateHidingContext method: PlayerTrackerService.Update(players);. This is required to feed the tracker the data it needs. The updated script is provided below.
- Create an instance of the PlayerPathPredictionScorerSO asset.
- In the asset's Inspector, set the Prediction Time (e.g., 1.0 to 2.0 seconds, depending on how fast your players move).
- Add this scorer asset to the Scorers list in your HidingSettingsSO. It works well with a moderate to high weight.
Use Cases
- Creating "Smart" AI: This is a hallmark of intelligent-feeling AI. It gives the impression that the AI is thinking ahead and outsmarting the player.
- Effective Ambushes: Perfect for stealth games or for AI archetypes like assassins or trappers. The AI will correctly lead its target, hiding behind a corner and preparing to engage as the player sprints past.
- Punishing Predictable Player Behavior: This scorer naturally punishes players who run in straight lines without checking corners, as the AI will be waiting for them.
Scorer: Proximity Penalty
ProximityPenaltyScorerSO scores a candidate based on its raw proximity to the nearest player. It creates a "danger zone" around players, heavily penalizing or disqualifying spots that are too close, regardless of visibility.
How It Works
This scorer enforces a critical self-preservation behavior by ensuring the AI maintains a safe distance from threats. It doesn't care about cover or line of sight; its only concern is raw distance.
- Finds Nearest Threat: It first calculates the distance from the candidate hiding spot to every player. It only considers the distance to the closest player for its evaluation.
- Disqualification Check: It checks if this distance is within the disqualificationRadius. If it is, the spot is considered suicidally close. The scorer returns float.NegativeInfinity, which instantly removes the spot from consideration, no matter how good other scorers think it is.
- Penalty Calculation: If the spot is outside the disqualification zone but inside the larger dangerRadius, it is considered hazardous. The scorer calculates a penalty that scales linearly.
A spot right on the edge of the dangerRadius will get a score of 1.0 (no penalty).
A spot right on the edge of the disqualificationRadius will get a score of 0.0 (maximum penalty).
- Safe Zone: If the spot is outside of the dangerRadius, this scorer considers it perfectly safe and gives it a full score of 1.0.
Parameters
- Disqualification Radius: The absolute "no-go" zone. Any candidate spot within this distance of a player will be instantly rejected. This should be a small value representing the agent's immediate personal space (e.g., 2-3 meters).
- Danger Radius: A larger warning zone. Spots within this radius will be penalized. This should be a larger value representing the distance at which the agent starts to feel threatened (e.g., 7-10 meters).
Use Cases
- Essential Self-Preservation: This is a highly recommended scorer for nearly every configuration. It prevents the AI from making the naive mistake of hiding right next to an enemy.
- Enforcing Engagement Ranges: It stops the AI from retreating to a "hidden" spot that is still within melee or shotgun range of a player.
- Creating Skittish AI: By using a large dangerRadius, you can create AI characters that are more cautious and prefer to keep their distance.
Performance
This scorer is extremely high-performance. It involves a simple loop and distance calculations (Vector3.SqrMagnitude). Its impact on performance is negligible, and the behavioral improvement it provides is immense.
Scorer: Reachability
ReachabilityScorerSO is a fundamental utility scorer that evaluates spots based on whether they are reachable by the agent on the NavMesh and within a maximum travel distance.
How It Works
This scorer acts as a critical filter to ensure the AI only considers valid, reachable hiding spots. For each candidate spot, it performs the following checks:
- Path Calculation: It attempts to calculate a complete
NavMeshPathfrom the agent's current position to the candidate spot. - Path Validity: It checks the status of the calculated path. If the status is not
PathComplete(meaning the spot is on a separate NavMesh island or is blocked), the spot is considered unreachable. - Path Distance: If the path is valid, it calculates the total length of the path by summing the distances between all its corners. It then compares this length to the
Path Max Distancedefined in theHidingSettings.
The scoring logic is as follows:
- If the path is invalid, incomplete, or longer than
Path Max Distance: The scorer returnsfloat.NegativeInfinity. This is a hard penalty that completely disqualifies the candidate spot, preventing the AI from ever choosing it. - If the path is valid and within the distance limit: It returns a score between 0 and 1. The score is higher for shorter paths, calculated as
1.0 - (pathLength / pathMaxDistance). This gives a natural preference for closer spots.
Parameters
- Weight: Determines how much the AI should prioritize spots with shorter path distances.
- Path Max Distance (from
HidingSettings): This is the crucial setting for this scorer. It defines the maximum travel distance (in meters) that the AI is allowed to consider. Any spot requiring a longer path will be disqualified. - NavMesh Area Mask (from
HidingSettings): Used to constrain the path calculation to specific NavMesh area types.
Use Cases
- Filtering for "Dumb" Providers: This scorer is essential when using providers that don't perform their own NavMesh checks, such as
VoxelGridProviderSO. It acts as the filter that removes all the unreachable points generated by such providers. - Enforcing a "Leash" on AI: By setting
Path Max Distance, you can effectively "leash" your AI, preventing it from choosing a hiding spot that is technically within its search radius but would require a very long and winding path to reach. This keeps the AI's movement tactically relevant. - Performance Optimization: By running this scorer early (by placing it high in the scorer list), you can quickly disqualify many invalid or distant spots. This saves more performance-intensive scorers (like
PathSafetyScorerSO) from having to run their complex logic on spots the AI could never reach anyway.
Performance and Dependencies
This scorer is moderately expensive. NavMesh.CalculatePath is a non-trivial operation and its cost will add up when run on many candidate spots. It is more expensive than simple math-based scorers but generally cheaper than those involving multiple physics raycasts.
- Dependencies: It has a hard dependency on a valid
NavMeshbeing present and correctly baked in the scene. Without a NavMesh, it will fail to find any valid paths.
Scorer: Team Proximity
TeamProximityScorerSO is a scorer that encourages agents to hide near their allies, promoting group cohesion and squad-based behavior.
How It Works
This scorer evaluates a candidate hiding spot based on its distance to the nearest friendly unit.
- Find Nearest Ally: It iterates through the list of allies provided in the
HidingContext(sourced from theEnemyHider'sallieslist) and finds the one closest to the candidate spot. - Compare to Optimal Distance: It then compares this closest distance to the
Optimal Ally Distancedefined on the scorer asset. - Calculate Score: The score is highest (1.0) when the spot is exactly the optimal distance from the nearest ally. The score decreases as the spot gets either closer to or farther away from the ally than this ideal distance.
If the agent has no allies, this scorer returns a neutral score of 0.5, having no effect on the decision. This logic encourages agents to "stick together" but not "stand on top of each other."
Parameters
- Weight: Determines how strongly the AI will prioritize staying near its teammates. A high weight will cause agents to form tight groups.
- Optimal Ally Distance: This float value on the scorer asset defines the ideal distance (in meters) the agent should maintain from its closest ally.
Use Cases
- Squad Behavior: This is the primary use case. It makes soldier AI maintain formations and hide as a unit, rather than scattering across the map.
- Pack Animals: Perfect for creating pack-based AI like wolves or raptors that hunt and move together.
- Support Roles: Can be used to make support units (like medics or officers) stay close to the combat units they are supposed to be assisting.
- Discouraging Isolation: Prevents a single AI from wandering off on its own, which can make the overall group feel more intelligent and coordinated.
Performance and Dependencies
The performance of this scorer is excellent. For each candidate spot, it performs a series of fast distance calculations. Its impact on performance is negligible.
- Dependency: This scorer's functionality is entirely dependent on the
allieslist on theEnemyHidercomponent being correctly populated by your own controller scripts. If the list is empty, it will have no effect.
Scorer: Threat Avoidance
ThreatScorerSO is an advanced scorer that makes AI aware of and avoid areas of known danger. It evaluates spots based on their proximity to a dynamic, global "threat map."
How It Works
This scorer relies on an external system, the ThreatMapService, which is assumed to hold a list of active "threat points." These points could represent locations where a player was recently seen, where an explosion just occurred, or an area under suppressive fire.
For each candidate hiding spot, the scorer:
- Finds the Closest Threat: It iterates through all active points in the
ThreatMapServiceand finds the one closest to the candidate spot. - Calculates Score Based on Distance: The score is determined by how far the candidate spot is from this closest threat point.
- A spot right on top of a threat point gets a score of 0.0.
- A spot that is farther away than the
Safe Distancegets a score of 1.0. - The score scales linearly in between.
In short, this scorer makes AI want to move as far away from known danger zones as possible. If there are no active threats on the map, it returns a perfect score of 1.0.
Parameters
- Weight: Determines how strongly the AI will avoid known threats. For most AI, this should have a high weight to encourage self-preservation.
- Safe Distance: A float value on the scorer asset that defines the radius of influence for a threat point. Once a spot is farther than this distance from a threat, the threat no longer affects its score.
Use Cases
- Creating Persistent Awareness: This is the primary use case. It allows AI to "remember" that a certain area is dangerous, even after the player has left. If a player shoots from a window, you can add a threat point there, and AI will avoid that window for a period of time.
- Responding to Grenades and Explosions: When a grenade is thrown, you can add a temporary threat point at its location. This scorer will then naturally make all nearby AI flee from the blast radius.
- Suppressing Fire: An AI under fire can add a threat point at the source of the shots, encouraging it and its allies to find cover that is not exposed to that direction or location.
Performance and Dependencies
The performance of this scorer is very good. Its cost is proportional to the number of active threat points in the ThreatMapService, as it involves a series of distance calculations.
- Crucial Dependency: This scorer is completely dependent on the
ThreatMapService. It does nothing on its own. The service is automatically updated by theEnemyHiderwhen it creates aHidingContext, but you can also add your own threats to it from other game systems (e.g., an explosion manager).
Scorer: Visibility
VisibilityScorerSO is one of the most essential scorers in the system. It evaluates hiding spots with a simple binary question: "Can a player see this spot?" It heavily penalizes any spot that is not completely concealed from all players.
How It Works
This scorer uses the IVisibilityService to determine if a candidate hiding spot is exposed. To make the check more robust and simulate the physical presence of an agent, it checks two separate points at the candidate location:
- A high point (simulating the agent's head).
- A low point (simulating the agent's center of mass).
It then asks the IVisibilityService, "Does any player have a direct, unobstructed line of sight to either of these points?"
The scoring is binary:
- If YES: At least one player can see the spot. It is considered compromised and receives a score of 0.0.
- If NO: The spot is fully concealed from all players. It receives a perfect score of 1.0.
This scorer does not care about the player's field of view (FOV). It only checks for physical line of sight. A spot directly behind a player is still considered "visible" by this scorer if there are no walls or obstacles in between. For FOV-based logic, see the FOVScorerSO.
Parameters
- Weight: Determines how much the AI prioritizes being physically hidden. For almost all hiding behaviors, this should have a very high weight. It's a core component of what it means "to hide."
- Player Eye Height (from
HidingSettings): This global setting is used to calculate the positions of the high and low points for the visibility check.
Use Cases
- Core Hiding Mechanic: This is a foundational scorer and should be included in nearly every
HidingSettingsconfiguration. It is the primary driver for making AI take cover behind walls, crates, and other obstacles. - Breaking Line of Sight: When an AI needs to escape, this scorer ensures it chooses a location that will actually break the player's line of sight.
- Working with Other Scorers: It's often used as the primary "filter." Other scorers then rank the spots that this scorer has already deemed "safe." For example, you can combine it with the
DistanceScorerSOto find a spot that is both hidden and far away.
Performance and Dependencies
The performance of this scorer is entirely dependent on the implementation of the IVisibilityService.AnyPlayerHasLineOfSight method. This check almost always involves one or more Physics.Raycast calls.
- The cost scales with the number of players and the number of candidate spots.
- Because it performs at least two visibility checks per candidate, it is a moderately expensive scorer.
- It has a hard dependency on a correctly configured
IVisibilityService. Without it, this scorer cannot function.
Scorer: Volumetric Cover
VolumetricCoverScorerSO scores candidate spots based on their concealment inside "soft cover" volumes like smoke clouds, dense fog, or magical darkness. This teaches the AI to use visual obstruction to its advantage.
How It Works
This system is designed to complement the standard visibility system, which only understands solid objects. It uses a VolumetricCover component, which should be placed on objects like smoke cloud prefabs. These components register themselves with a VolumetricCoverRegistry.
The VolumetricCoverScorer evaluates a candidate spot that the normal visibility system considers "exposed." For each such spot, it performs its own line-of-sight check. It casts a ray from the player's eyes to the candidate spot. Then, it checks if that ray intersects with any active VolumetricCover collider.
- If the spot is exposed to the normal system, but the line of sight is blocked by a volumetric cover, it means the AI can use that smoke cloud to hide. The spot receives a high score (1.0).
- If the spot is exposed and not blocked by any soft cover, it is truly a bad spot and receives a low score (0.0).
- If the spot is already hidden behind a solid wall (according to the normal system), this scorer returns a neutral score (0.5), as it doesn't need to add its own input.
Setup
- On your smoke grenade prefab, fog effect, etc., add a collider (e.g., SphereCollider or BoxCollider) that approximates its volume. Mark it as a Trigger.
- Add the new VolumetricCover component to this GameObject.
- Create an instance of the VolumetricCoverScorerSO asset.
- Add this scorer asset to the Scorers list in your HidingSettingsSO and give it a positive weight (e.g., 1.0 - 1.5).
Use Cases
- Tactical Gameplay: Allows AI to intelligently use smoke grenades (either its own or the player's) to break line of sight and maneuver.
- Environmental Storytelling: You can fill a swamp with VolumetricCover fog volumes, and the AI will naturally understand that it can use the fog to hide and ambush the player.
- Magic Systems: Perfect for spells that create fields of magical darkness or obscuring mist.
Use Cases
These sections provide recipes for combining providers and scorers to create common AI archetypes.
Use Case: The Cowardly AI
This guide explains how to configure the Hide-And-Stealth-System to create a "Cowardly" or "Skittish" AI. This type of AI's primary goal is to run away from threats and maximize its distance from players.
Behavior Goal
- When a threat appears, the AI should flee.
- It should always try to find a hiding spot that is as far away as possible.
- It doesn't need to be clever; its main instinct is self-preservation through distance.
HidingSettings Configuration
This configuration will heavily reward distance and penalize proximity.
Providers
The providers should generate a wide variety of potential points for the AI to run to.
OppositePlayersProviderSO: This is the most important provider for this use case. It generates points in the direction away from the player, which is the core of the fleeing behavior.NavMeshRingProviderSO: This finds many reachable points around the AI at various distances. It ensures that even if there's no clear "away" direction, the AI still has plenty of places to run to.CurrentPositionProviderSO: Essential for stability. This prevents the AI from moving if it's already in a good, distant location.
Scorers
The scorers are configured to overwhelmingly prioritize being far from the player.
| Scorer | Weight | Reason |
|---|---|---|
DistanceScorerSO |
1.0 | CRITICAL. This is the main driver of the cowardly behavior. A maximum weight ensures the AI will always choose the farthest spot. |
ReachabilityScorerSO |
0.8 | CRITICAL. This filters out any spots that are too far to reach, preventing the AI from getting stuck trying to run to an impossible location. |
VisibilityScorerSO |
0.5 | Recommended. The AI isn't a brilliant tactician, but it should still prefer a spot behind a wall over one in the open. This gives it a basic sense of cover. |
DefensivePositionScorerSO |
0.3 | Good for stability. This gives a small bonus for staying put if the AI is already safe and far away, preventing it from fidgeting unnecessarily. |
Use Case: The Stealthy Hunter
This guide explains how to configure the Hide-And-Stealth-System to create a "Stealthy Hunter" or "Assassin" AI. This AI's goal is to get as close as possible to the player while remaining completely undetected.
Behavior Goal
- Prioritize remaining unseen above all else.
- Actively try to get closer to the player, not farther away.
- Use shadows, bushes, and other forms of concealment, not just hard cover.
- Move with extreme caution, never breaking stealth.
HidingSettings Configuration
This advanced configuration uses specialized providers and a negative weight on the DistanceScorer to make the AI actively seek proximity while staying hidden.
Providers
The providers should focus on finding non-obvious hiding spots that a stealthy character would use.
ShadowProviderSO: Excellent for AI that can use darkness. This will make them find and stick to shadows.VoxelGridProviderSO: The perfect provider for finding spots inside "soft" cover like tall grass or thick bushes. This is a key part of the "hunter" feel.BehindObstacleProviderSO: A good fallback for finding standard hard cover spots.CurrentPositionProviderSO: Essential for stability, ensuring the AI doesn't abandon a great hiding spot.
Scorers
The scorers are weighted to create an AI that is obsessed with stealth and proximity.
| Scorer | Weight | Reason |
|---|---|---|
VisibilityScorerSO |
1.0 | CRITICAL. The AI must be physically concealed. This is non-negotiable for a stealth character. |
FOVScorerSO |
1.0 | CRITICAL. The AI must stay out of the player's direct vision cone. This is the essence of sneaking. |
PathSafetyScorerSO |
1.0 | CRITICAL. The path taken must be as concealed as the destination. The hunter cannot afford to be seen while moving into position. |
DistanceScorerSO |
1.0 | CRITICAL. This is the key to the "hunter" behavior. By giving it a weight, you penalize distance, which effectively rewards proximity. |
ReachabilityScorerSO |
0.8 | CRITICAL. Because VoxelGridProvider can generate points inside walls, this scorer is essential to filter out all the spots the AI cannot physically reach. |
DefensivePositionScorerSO |
0.5 | Recommended. This provides stability. If the AI has found an excellent, hidden spot right next to the player, this encourages it to stay there and wait. |
Use Case: The Tactical AI
This guide explains how to configure the Hide-And-Stealth-System to create a "Tactical" AI. This AI behaves like a trained soldier, prioritizing good cover, safe movement, and smart positioning over simply running away.
Behavior Goal
- The AI should prefer to hide behind solid objects.
- It must avoid running through open areas where the player can see it.
- It should be able to use pre-defined tactical positions in the level.
- It should be "sticky" to good cover and not abandon it unless necessary.
HidingSettings Configuration
This configuration focuses on providers that find high-quality cover and scorers that evaluate safety and tactical advantage.
Providers
The providers should focus on finding well-defined cover locations.
BehindObstacleProviderSO: This is a core provider for this use case. It dynamically finds spots behind walls and other obstacles.DirectionalCoverNodeProviderSO: Use this if you have manually placedCoverNodeobjects in your scene. This allows the AI to use your pre-defined tactical points intelligently.CurrentPositionProviderSO: Absolutely essential for a tactical AI. It prevents the AI from abandoning good cover for a spot that isn't significantly better.
Scorers
The scorers are balanced to reward safety and tactical awareness. The weights are critical.
| Scorer | Weight | Reason |
|---|---|---|
PathSafetyScorerSO |
1.0 | CRITICAL. A tactical AI must not run through the open. This heavily penalizes unsafe paths, forcing the AI to move from cover to cover. |
VisibilityScorerSO |
1.0 | CRITICAL. The entire point of cover is to be hidden. This ensures the destination is concealed. |
DefensivePositionScorerSO |
0.9 | CRITICAL. This makes the AI "sticky." It will stay in its safe cover spot unless it's compromised. |
FOVScorerSO |
0.7 | Highly Recommended. This makes the AI smarter by encouraging it to move to the player's flank or rear, outside their direct vision cone. |
AmbushScorerSO |
0.5 | Optional, but powerful. Add this to give your AI the ability to lie in wait and ambush the player. |
ReachabilityScorerSO |
0.5 | Recommended. Filters out spots that are too far away, keeping the AI engaged in the fight. |
DistanceScorerSO |
0.2 | Use with low weight. A small weight here gives a gentle nudge for the AI to prefer farther cover spots, but it won't override the need for safety. |