Skip to content

t1k:unity:dots-ai:core

FieldValue
Moduledots-ai
Version2.1.7
Effortmedium
Tools
/t1k:unity:dots-ai:core

DOTS AI Core — Perception, Alert, Leash, Stealth, Party

Section titled “DOTS AI Core — Perception, Alert, Leash, Stealth, Party”

Package: com.the1studio.dots-ai — namespace DOTSAI.* Note: This skill covers DOTSAI core systems. For BDP behavior trees, see: dots-ai-behavior-designer-pro · dots-ai-bdp-tactical-pack · dots-ai-bdp-formations-pack

  • Implementing alert escalation (Unaware → Suspicious → Alert → Combat)
  • Adding AI leash / home-radius aggro reset
  • Building party formations and party-buff systems
  • Implementing stealth detection, line-of-sight, detection cone
  • Setting up per-entity perception and threat tracking
ComponentTypePurpose
AlertLevelIComponentData + IEnableableComponentAlertTier Level, Entity DetectedEntity, float AlertTimer
AlertTierenumUnaware=0, Suspicious=1, Alert=2, Combat=3
AILeashIComponentDatafloat3 HomePosition, float LeashRadius, bool Initialized
PartyFormationIComponentDataFormationType Type, float Spacing; baked on every member
PartyMemberIComponentDataMarks entity as party member
PartyLeaderTagIComponentData (tag)Marks party leader
PartyBuffIComponentDataPassive bonus leader grants members in formation
public enum FormationType { Line, Wedge, Circle, Column }
SystemGroupRole
AlertDecaySystemAISystemGroupDecrements AlertTimer; decays AlertTier on zero; disables component at Unaware
AlertTriggerSystemAISystemGroupEscalates AlertLevel on player/target detection
AILeashInitSystemAISystemGroupInitializes HomePosition on first tick
AIRespawnResetSystemAISystemGroupClears aggro state on respawn
StealthVisibilitySystemAISystemGroupChecks LOS + detection range for stealth entities
PartyFormationSystemAISystemGroupPositions members relative to leader per FormationType
// AlertDecaySystem — IJobEntity pattern:
foreach (var (alert, enabled) in SystemAPI.Query<RefRW<AlertLevel>, EnabledRefRW<AlertLevel>>())
{
alert.ValueRW.AlertTimer -= deltaTime;
if (alert.ValueRW.AlertTimer <= 0f)
{
if (alert.ValueRO.Level == AlertTier.Unaware)
enabled.ValueRW = false; // disable component at base tier
else
alert.ValueRW.Level--; // decay one tier
}
}
// Leash check in leash system:
float distSq = math.distancesq(currentPos, leash.HomePosition);
if (distSq > leash.LeashRadius * leash.LeashRadius)
{
// clear Threat/Aggro buffers, set navigation target to HomePosition
ecb.SetBuffer<AggroEntry>(entity); // clear aggro
}
// PartyFormationSystem — per-member offset from leader:
float3 offset = GetFormationOffset(formation.Type, memberIndex, formation.Spacing);
ecb.SetComponent(member, LocalTransform.FromPosition(leaderPos + offset));
DirectionPackageDetail
Depends onDOTSCore baseLocalTransform, ECB, SystemAPI
Depends onDOTSCore.StatsHealth/Experience for damage-based aggro integration
Used byDOTSCombatAttack targeting reads AlertLevel
Used byDOTSBDPBehavior tree checks AlertLevel for combat branch
  • BattleDemo2D — enemies escalate Alert on detection; party formations for mob groups
  • ChaosForgeDemo — AILeash prevents farm-camping single rooms; alert decay enables stealth evasion
  • BackpackBattlefield — party buff scales with FormationType (wedge = +attack damage)
  • AlertLevel is IEnableableComponent — disabling via EnabledRefRW is cheaper than add/remove. NEVER use GetSingleton / HasSingleton on IEnableableComponent types. Use WithPresent<AlertLevel>() to include disabled entities in jobs that need to SET the alert.
  • AILeash.Initialized flag required — without it, HomePosition overwrites every frame. Self-init pattern: if !leash.Initialized then set leash.HomePosition = currentPos; leash.Initialized = true; in AILeashInitSystem.OnUpdate.
  • PartyFormation baked on every member, not leader — this is intentional for query efficiency. Do not try to centralize formation data on the leader entity; the query iterates members directly.
  • StealthVisibilitySystem needs explicit LOS — distance-only detection fails against obstacles. The system performs spatial hash or raycast checks. Pure distance checks are insufficient for stealth.
  • [RequireMatchingQueriesForUpdate] missingAlertDecaySystem and AlertTriggerSystem are early-game empty-query candidates. Add [RequireMatchingQueriesForUpdate] to skip frames when no entities have AlertLevel (W4.4 finding).
  • PerceivedEntity.Target == Entity.Null is NOT a valid “no enemy found” sentinel — a PerceivedEntity entry can legitimately carry Target == Entity.Null (the perception pipeline records a detected threat whose resolved entity is null, e.g. an unresolved/destroyed-this-frame target). AlertTriggerSystem originally scanned the PerceivedEntity buffer for the closest visible enemy into closestEnemy = pe.Target, then short-circuited with if (closestEnemy == Entity.Null) return;. Because a found enemy could have Target == Entity.Null, that guard returned early even when a real enemy was found → all tier classification stayed stuck at Unaware (silent, no error, full perception→alert path dead). Fix: track a dedicated bool hasEnemy = false; set to true inside the closest-enemy branch and gate on if (!hasEnemy) return; — never overload an Entity.Null comparison as the loop’s “found anything?” flag. Confirmed pre-existing bug, fixed 2026-05-29 (90405f77). General rule: when reducing a buffer to “did we find a qualifying element?”, use a separate bool, not a sentinel value the element type can legitimately hold.