Skip to content

t1k:unity:dots-combat:rpg

FieldValue
Moduledots-combat
Version2.3.8
Effortmedium
Tools

Keywords: auto battler, auto-battler, battle strip, BDP, behavior tree, board placement, boss, bullet heaven, combat, combat strip, cross-demo reuse, damage numbers, damage popup, DOTS, draft shop, role-playing, round phase, RPG, spawn troops, survivor, thin demo wrapper, troop spawn, wave

/t1k:unity:dots-combat:rpg

com.the1studio.dots-rpg is DELETED. Replaced by 7 focused packages:

PackageNamespaceModules
com.the1studio.dots-coreDOTSCore.*Core, Stats, Navigation, Spawning, Structures, Camera, Save/Load
com.the1studio.dots-combatDOTSCombat.*Combat, Skills, Boss, Synergy, Talent, Summon, Placement
com.the1studio.dots-aiDOTSAI.*AI perception, aggro, patrol, party, leash
com.the1studio.dots-bdpDOTSBDP.*Behavior Designer Pro tasks + throttle system
com.the1studio.dots-inventoryDOTSInventory.*Inventory, equipment, loot, crafting, currency, set bonuses
com.the1studio.dots-progressionDOTSProgression.*Upgrade, Achievement, Faction, Quest, Dialogue, World
com.the1studio.dots-puzzleDOTSPuzzle.*Match-3/puzzle board, cascade, scoring
com.the1studio.dots-battlefieldDOTSBattlefield.*Arena geometry, terrain, NavMesh (existing)

Related skills: dots-ecs-core, dots-jobs-burst, dots-physics, dots-graphics, agents-navigation, dots-architecture, dots-inventory-grid, behavior-designer-pro


Before writing ANY combat code in a demo, audit references/cross-demo-reuse-patterns.md. The library already covers ~80% of typical combat-demo needs (damage numbers, auto-battler round/phase flow, troop spawn, waves, boss encounters, behavior trees). The remaining ~20% is authoring + genre-specific glue.

Common library entry points (full table + decision flow in the reference doc):

  • Floating damage numbers → DOTSCombat.Survivor.DamageNumberEvent + DamageNumberSystem
  • XP gem drops → DOTSCombat.Survivor.DropsXPOnDeath + XPDropSystem
  • Auto-fire weapons (bullet heaven) → DOTSCombat.Survivor.AutoWeapon + AutoWeaponSystem
  • Round/phase state machine (auto-battler) → DOTSCombat.AutoBattler.RoundPhase + RoundManagementSystem
  • Draft shop / board placement → DOTSCombat.AutoBattler.DraftShop + BoardPlacementSystem
  • Continuous troop spawn → DOTSCore.Spawning.SpawnerConfig + SpawnState + SpawnSystem
  • Ring / edge / formation spawn → RingSpawnRequest / EdgeSpawnPattern / FormationSpawnerConfig
  • Wave schedules → DOTSCore.Spawning.WaveDefinition + WaveManagerSystem
  • Boss phase + ability + combo → DOTSCombat.Boss.BossPhase + BossAbilitySelectionSystem + BossComboSystem
  • Pathfinding / avoidance → set DOTSCore.Navigation.NavigationTargetAgentNavigationBridgeSystem bridges to ProjectDawn Agents Nav
  • Canonical melee/ranger/boss BDP trees → DOTSBDP.Editor.BDPStandardTrees + BDPTreeBuilder.BuildAll(...)

Gold-standard “thin demo wrapper” template: Assets/Demos/RPG/BattleDemoSideView/ — 1 Runtime script (camera) + 4 Editor scripts; ALL gameplay is library composition.

Per rules/library-feature-discovery-protocol.md, if you need a feature this doc doesn’t mention, run the 3 grep research passes against Packages/unity-dots-library/ BEFORE implementing new code. Update this doc with whatever you find.

→ Full reference: references/cross-demo-reuse-patterns.md


  • Using DOTSCore., DOTSCombat., DOTSAI., DOTSBDP., DOTSInventory., DOTSProgression., DOTSPuzzle.*
  • Adding Health, Mana, DamageEvent, StatusEffect, StatModifier, WaveState, SummonConfig, TalentNode components
  • Implementing AI behaviors (aggro, patrol, flee, detection, party formation)
  • Setting up skill casting, projectiles, AoE, summon, talent tree effects
  • Working with inventory slots, equipment, loot drops, crafting, currency
  • Configuring spawners, respawn mechanics, wave rounds
  • Creating towers, barracks, walls, structure placement grid (Structures module)
  • Implementing boss phases, abilities, combos (Boss module)
  • Using CameraTarget, CameraTrauma, HitStopEvent (Camera module in DOTSCore)
  • Triggering save/load via SaveStateRequest, LoadStateRequest (DOTSCore)

ModulePackageKey Types
Core (24c/9s)dots-coreGameEntityTag, TeamId, MoveSpeed, NavigationTarget, Lifetime, RespawnReadyTag, PooledTag, EntityEvent, AudioEvent, CameraTarget, CameraShakeEvent, SaveableTag
Stats (9c/3s)dots-coreBaseStats, DerivedCombatStats, DerivedResourceStats, DerivedLocomotion, Level, Experience, StatModifier, StatFormulaConfig
Navigation (6c/4s)dots-coreAgentNavigationBridgeSystem, DOTSGroundingSystem, AgentCCOverrideSystem, NavigationAuthoring
Spawning (9c/5s)dots-coreSpawnerConfig, SpawnState, HasSpawnedTag, SpawnTimer, SpawnedBy, RespawnConfig, RespawnTimer
Wave Manager (6c/4s)dots-coreWaveDefinition, WaveState, WavePhase, WaveEvent, WaveConfig, WaveSpawnedTag
Camera (6c/5s)dots-coreCameraTarget, CameraShakeEvent, CameraAutoZoomTarget, CameraTrauma, HitStopEvent, CameraAccessibility, CameraFocusRequest
Save/Load (4c/3s)dots-coreSaveSlot (managed!), SaveComplete, LoadComplete, SavedComponentFlags, SaveConstants
Combat (28c/19s)dots-combatHealth, Mana, Shield, DamageEvent, HealEvent, StatusEffect (MaxStacks, StackBehavior), DeadTag, InvulnerableTag, KnockbackEvent, BattleState, AttackConfig, OnHitEffect, OnDeathEffect, AuraEffect
Skills (11c/8s)dots-combatSkillSlot, ActiveCastState, ProjectileData, ParabolicArc, AreaEffect, HomingTarget, SkillBehaviorType, RangedAttackConfig
Boss (7c/4s)dots-combatBossTag, BossPhase, BossPhaseThreshold, BossAbility, ComboSequence, ComboState, PhaseTransitionEvent
Synergy/Trait (5c/3s)dots-combatTraitEntry, SynergyDefinition, ActiveSynergy, TeamSynergyState, SynergyModifierSource
Talent Tree (5c/2s)dots-combatTalentNode, TalentProgress, TalentPoints, TalentUnlockRequest, TalentEvent
Summon (5c/4s)dots-combatSummonConfig, SummonState, SummonRequest, SummonEvent, SummonedByTag
Structure Grid (6c/4s)dots-combatStructureGrid, StructureGridCell, PlaceRequest, PlaceEvent, GridPosition, DemolishRequest
AI (14c/8s)dots-aiPerceivedEntity, DetectionRange, AggroEntry, AILeash, AIConfig, TauntData, StealthState, AlertLevel, AlertTier
Party (6c/2s)dots-aiPartyMember, PartyRole, PartyLeaderTag, PartyFormation, FormationType, PartyBuff
BDP Tasks (17 tasks)dots-bdpEvaluateFlag throttle, BDPEvaluateFlagThrottleSystem + 17 ECS task implementations
Inventory (18c/8s)dots-inventoryInventorySlot, EquippedItem, Item, LootTableEntry, EquipmentStatBonus, CraftingRecipe (currency moved → DOTSEconomy.WalletEntry; CurrencyWallet deprecated, see Gotchas)
Upgrade (4c/2s)dots-progressionUpgradeLevel, UpgradeDefinition, UpgradeRequest, UpgradeEvent
Progression (6c/2s)dots-progressionAchievementDefinition, AchievementProgress, FactionMembership, ReputationEntry
Quest/Dialogue (8c/3s)dots-progressionQuestDefinition, QuestProgress, DialogueNode, DialogueState
World (7c/2s)dots-progressionWorldTime, WorldTimeConfig, DayPhase, WeatherState, WeatherConfig
Puzzle (Board/Match)dots-puzzleBoardConfig, BoardCell, PieceData, MatchDetectionSystem, CascadeControlSystem

Detail guides: references/core-guide.md, references/combat-guide.md, references/ai-guide.md, references/inventory-guide.md, references/new-modules-guide.md


  • Namespaces: DOTSCore, DOTSCombat, DOTSAI, DOTSBDP, DOTSInventory, DOTSProgression, DOTSPuzzle
  • Assembly refs: Add separate asmdef reference per package — no single monolithic ref
  • Constants: GameplayConstants (DOTSCore), CombatConstants, AIConstants, CameraConstants, SaveConstants — never hardcode. See references/constants-guide.md
  • Teams: TeamId.Value (byte); 255 = no team
  • Events (IBufferElementData): DamageEvent, HealEvent, KnockbackEvent, StatusEffect, AudioEvent, ScoreEvent are per-entity payloaded buffers; EntityEvent is singleton global queue
  • Events (IEnableableComponent): LeveledUpEvent, WaveEvent, PhaseTransitionEvent, BuildEvent, DemolishEvent, SummonEvent, TalentEvent, UpgradeEvent, SaveComplete, LoadComplete are stateless one-frame signals
  • Event cleanup: each system group has a *EventCleanupSystem (OrderFirst) clearing its events via IJobEntity — see dots-rpg/references/event-conventions.md
  • Enableable tags: DeadTag, InvulnerableTag, CCState, KnockbackState — toggle via SetComponentEnabled
  • CC masks: CCMasks.AttackBlock, .CastBlock, .MoveBlock — never inline bitmask expressions
  • Combat formulas: CombatFormulas.ComputeAutoAttackDamage(), .EffectiveCooldown(), .FrameRngSeed() — never inline
  • Stat pipeline: BaseStats -> StatModifier buffer -> DerivedStats -> StatSyncSystem syncs Health.Max/Mana.Max
  • Burst rule: All systems Burst-compiled EXCEPT SaveSystem/LoadSystem (file I/O)

Submodule Sync — Always Pull Before Diagnosing API Drift

Section titled “Submodule Sync — Always Pull Before Diagnosing API Drift”

If Packages/unity-dots-library/ (or any DOTS library) is consumed as a git submodule, and a consumer (Assets/Demos/** or another package) reports CS0103 'Foo' does not exist, CS0246 type/namespace 'Foo' could not be found, or CS0117 'Bar' does not contain a definition for 'Baz', the first hypothesis is submodule staleness, not library deletion.

Run THIS in the submodule before doing anything else:

Terminal window
cd Packages/unity-dots-library
git fetch origin && git status -sb
git log --oneline HEAD..origin/main # what local is missing

If behind, the symbols probably exist on origin/main but were never pulled locally. git pull resolves it. Only AFTER confirming local is even with origin should you treat the missing API as a real deletion / migration target.

Why this matters: refactor commits frequently land utility helpers and consumer updates in the same commit. Pulling brings in both halves; staying stale shows only the consumer half — the helpers look “missing” when they exist remotely. Misdiagnosing this as a library API change wastes hours rewriting demos that just need a git pull.

After confirming the symbol really IS gone from origin/main, see the refactor/rename checklist in dots-architecture skill (references/refactor-rename-checklist.md).


Anti-PatternFix
Importing old com.the1studio.dots-rpgAdd specific package refs: dots-core, dots-combat, etc.
Using old DOTSRPG.* namespaceMap: Core->DOTSCore, Combat->DOTSCombat, AI->DOTSAI, BDP->DOTSBDP
Inline magic numbersUse domain constants class
Duplicate DamageEvent constructionUse DamageEvent.Create() factory
ISystem for TalentUnlockSystemUse SystemBase — Entities 1.4 BufferTypeHandle two-pass invalidation
SetComponentEnabled before buffer readsAll buffer reads MUST complete before any SetComponentEnabled
SaveSystem/LoadSystem with [BurstCompile]File I/O — Burst intentionally absent
WaypointSpeedZone mutating MoveSpeed.ValueUse WaypointFollower.SpeedMultiplier local field
Missing asmdef ref for cross-package typeEach package is separate — add explicit asmdef reference
Over-specific instance name for a library typeName the generic ROLE, not one concrete instance. ArrowRegistryTag/TeamArrowPrefabProjectile*; MageAttack*ProjectileAttack*/CasterAttack*. See Gotcha “Generic component naming” + library-quality-mandate-unity.md § “Naming charter” (SSOT)

General ECS anti-patterns: dots-ecs-core. Dimension-agnostic design: dots-architecture.

System Update Order: See references/system-ordering-guide.md for full ASCII diagram.


  • ECS damage calculation in Burst-compiled jobs cannot allocate managed memory — pre-allocate buffers or use blob assets.

  • Stat modifications in IComponentData copy on write — entity stat changes need EntityManager.SetComponentData, not field mutation.

  • Save/load of ECS entities requires SubScene serialization — runtime-spawned entities are NOT saved by default.

  • GoldInterestSystem and PlayerHPSystem belong in the library, not demos — these are game-agnostic idle/RPG mechanics (gold compounding, HP regen) that multiple demos re-implement. Extract to DOTSProgression.Idle (GoldInterestSystem) and DOTSCombat.Structures (PlayerHPSystem) when a second demo needs the same logic. Source: review-260520-round5-extraction-skills.md §B P0

  • dots-combat reward + structures systems consume the v2 DOTSEconomy wallet — NOT the deprecated DOTSInventory.CurrencyWallet/CurrencyTransaction/CurrencyWalletUtility (W7 wallet v1→v2 cutover, 2026-05-28). One coherent contract across two subsystems:

    • Reward systems (KillRewardCurrencySystem, Boss.BossRewardSystem) enqueue DOTSEconomy.WalletTransaction (was CurrencyTransaction) onto the killer’s wallet buffer: { CurrencyId, long Delta, WalletTransactionSource, FixedString64Bytes Reference }. Kill/boss loot uses WalletTransactionSource.Loot (ordinal 0, value-safe vs v1). Set Reference = default when there is no audit tag.
    • Structures systems (BuildValidationSystem, BuildExecutionSystem, DemolishSystem) query .WithAll<Wallet, WalletEntry>(), read via WalletUtility.FindEntryIndex(buffer, CurrencyId) / WalletUtility.GetBalance(...) (sentinel WalletUtility.InvalidEntryIndex == -1), and mutate WalletEntry.Amount directly (parity-first; bypasses the ledger/event audit — intentional deferred follow-up, noted in BuildExecutionSystem remarks).
    • Component currency keys are now DOTSEconomy.CurrencyId enums, not intKillRewardCurrency.CurrencyType, BossRewardCurrency.CurrencyType, BuildRequest.CurrencyType, DemolishRequest.RefundCurrencyType. CurrencyId has named tiers Soft=0/Hard=1/Premium=2/Energy=3/EventToken=4 plus Custom0..Custom7 (100-107) for game-specific currencies. BREAKING authoring change — buffers must be authored with enum values.
    • The Wallet tag is now REQUIRED on any reward/cost target (v1 had no tag). Any entity receiving rewards or paying build costs needs the full v2 buffer set: Wallet tag + WalletEntry + WalletTransaction + WalletLedger + CurrencyEarnedEvent + CurrencySpentEvent + CurrencyInsufficientEvent (easiest: route through DOTSEconomy.WalletAuthoring). Miss the tag/buffers → HasBuffer<WalletTransaction> is false, WalletSystem skips the entity, rewards/costs silently no-op. WalletEntry cap is 8 (was 4); long amounts (was int).
  • HealthBarRenderer.targetLayer MUST be set for sub-camera demos. The library renderer (DOTSCore.UI.HealthBarRenderer) calls Graphics.DrawMeshInstancedProcedural which has a layer parameter that filters cameras — cameras whose cullingMask does NOT include the layer silently SKIP the draw. Default is 0 (Default layer, rendered by Main Camera). If your demo uses a sub-camera RT (combat strip, mini-map, overlay) with a restricted cullingMask, you MUST set renderer.targetLayer = YourSubCameraLayerIndex at scene-setup time, otherwise HP bars render fine on Main Camera but never appear in the sub-camera RT — even though entities have Health + HealthBarOffset + TeamId + LocalToWorld. The HealthBarOffset component being present is necessary but not sufficient. Real incident 2026-05-26 in ChaosForge: enemies clearly taking damage (HP=40/60) but bars invisible in the combat strip RT. Fix: renderer.targetLayer = ChaosForgeCombatConstants.CombatStripWorldLayer in ChaosForgeSceneSetup.cs:869. Library API was extended in commit bc91093 (unity-dots-library) to expose targetLayer (was hardcoded 0 prior); consumer wire-up landed in DOTS-AI commit 2124a9af.

  • Generic component naming — name the ROLE, not the instance. When creating any combat component/system/tag, name the generic role (Projectile, Caster, Agent, Tile), never one concrete instance (Arrow, Mage, Sword, Knight). The Arrow* family (ArrowRegistryTag, TeamArrowPrefab, ArrowPrefabAuthoring) is over-specific: it really means “the projectile a ranged attacker fires”, and the library ALREADY has a generic ProjectileData/ProjectileCollisionSystem — so Arrow is an inconsistent island. Same smell: Mage* attack types (generic caster/projectile). A concrete instance belongs in an enum VALUE or demo content, not a type name. SSOT: rules/library-quality-mandate-unity.md § “Naming charter”.

  • Every IUIModalHandler needs a system that EMITS its EventType — modals with no producer are silent bugs. The DOTSCore.UI.Bridge.UIEventBus + UIModalDirector pattern decouples modals from triggers: a modal declares public int EventType => UIEventTypeRegistry.XYZ, registers via IUIModalHandler, and waits for the bus to dispatch. The flaw: if NO production system ever writes a UIEventBus { EventType = XYZ } entry, the modal exists, compiles, has correct internal logic, but never opens. Tests can still pass (a test fixture writes the event directly via EntityManager → modal works in isolation) while the live demo silently has a dead modal. The compiler cannot catch “modal without emitter” because the contract is data-shaped (an integer event id), not type-shaped.

    Audit pattern (run on every kit/demo before milestone gates):

    1. grep -rn 'public int EventType =>' Runtime/UI/ Runtime/**/UI/ — list every IUIModalHandler.
    2. For each, extract the event id constant name (e.g. UIEventTypeRegistry.ItemCompare).
    3. grep -rn 'EventType.*=.*<name>' Runtime/ excluding tests — find the producers.
    4. If exactly 0 producers in production code → BUG: the modal has no trigger.
    5. The fix usually lives in whatever system OWNS the state-transition the modal exists to surface (e.g. the system that owns a Pending→Hold transition is the one that should emit the corresponding compare/decision modal).

    Why this belongs in milestone gates: the bus pattern is what makes Mono-UI ↔ ECS decoupled, but the decoupling cuts both ways — the type system cannot detect the missing producer. Add this audit to every game-producer milestone gate or game-designer wiki-pass.

  • rules/library-feature-discovery-protocol.md — research the library 3× before implementing new combat functionality; always update this skill regardless of outcome
  • rules/manual-correction-implies-skill-gap-unity.md — if you find yourself injecting “use library X for Y” into a teammate brief, that knowledge belongs in this skill
  • references/cross-demo-reuse-patterns.md — canonical reuse-first reference (this skill’s combat-side companion to the discovery protocol)