Skip to content

Spawning System

Summary: Project Eternal's spawning systems handle items, projectiles, AOE effects, and enemies. UItemSpawner manages ground-validated item placement with spacing checks. ULootComponent generates drops using loot tables. Projectiles spawn via animation notifies with pattern support. All spawning is server-authoritative.

Table of Contents


Why This Architecture

Design Goals

The spawning system is built around four principles:

  1. Server Authority - All spawning validated and executed on server
  2. Location Validation - Ground traces and spacing prevent bad placements
  3. Animation-Driven - Projectiles sync with combat animations
  4. Data-Driven Patterns - Complex attacks configured in data assets

Spawn Type Ownership

Spawn Type System Server Authority
Items UItemSpawner Required
Loot Drops ULootComponent Required
Projectiles UProjectileAbility Required
AOE Effects AEternalAOEActor Required
Enemies Level/Spawner Required

Spawning Architecture

+----------------+
|  Spawn Types   |
+----------------+
        |
        +-- Items
        |       +-- UItemSpawner (Location Validation)
        |       +-- ULootComponent (Drop Generation)
        |       +-- AItemActor (World Pickup)
        |
        +-- Projectiles
        |       +-- UProjectileAbility
        |       +-- UAnimNotify_SpawnProjectile
        |       +-- AProjectileBase / AHomingProjectile
        |       +-- UProjectilePatternDataAsset
        |
        +-- AOE Effects
        |       +-- AEternalAOEActor (Shape Detection)
        |
        +-- Enemies
                +-- AEnemySpawner (Placement + Filtering)
                +-- UEncounterDataAsset (Weighted Enemy Pool)
                +-- UEnemyDataAsset (Enemy Configuration + Tags)
                +-- AEternalEnemy (Character)

Item Spawning

Location Validation

Items must spawn on valid ground with proper spacing:

FindValidSpawnLocation(DesiredLocation)
    |
    v
+-------------------+
| Trace: Start      |
| = Desired + 70 Z  |
| End = Desired-500Z|
+-------------------+
    |
    v
+-------------------+
| Hit ground?       |----NO----> Use DesiredLocation
+-------------------+
    | YES
    v
+-------------------+
| Check spacing vs  |
| batch locations   |
+-------------------+
    |
    +-- Too close? --> Offset by ItemSpacing
    |
    +-- OK --> Add to batch, return location

Spawn Configuration

Parameter Default Purpose
DropSpawnAngleMin -85 Random spread min
DropSpawnAngleMax 85 Random spread max
DropSpawnDistanceMin 10 Min distance from source
DropSpawnDistanceMax 50 Max distance from source
RelativeSpawnElevation 70 Trace start height
LootSpawnRadius 100 Max spawn area
ItemSpacing 50 Min distance between items

Spawn Methods

Method Use Case Generates New Item?
SpawnNewItem Create from manifest Yes (with modifiers)
SpawnNewItemAtLocation Create at specific transform Yes (with modifiers)
SpawnExistingItem Drop owned item No (preserves properties)

Batch Spawning

When spawning multiple items, track positions to prevent overlap:

SpawnLootInWorld(Items, DropLocation)
    |
    v
Clear CurrentBatchSpawnLocations
    |
    v
For each Item:
    |
    +-- Random offset within LootSpawnRadius
    +-- SpawnNewItemAtLocation
            |
            +-- FindValidSpawnLocation
                    |
                    +-- Add to CurrentBatchSpawnLocations

Loot System

Loot Component Configuration

Property Type Purpose
LootTable ULootTableDataAsset* Drop table reference
LevelOverride int32 Override source level
SourceContextTags FGameplayTagContainer Additional context
QuantityMultiplier float Scale drop count
bGuaranteedDrop bool Always drop specific item
GuaranteedItemManifest UItemManifestDataAsset* Guaranteed item

Drop Flow

DropLoot(InstigatingController)
    |
    +-- HasAuthority()? --> NO: return
    |
    +-- Build LootContext
    |       - SourceLevel
    |       - SourceTags
    |
    +-- Generate from LootTable
    |       ULootGenerator::GenerateLoot(
    |           LootTable,
    |           Context,
    |           QuantityMultiplier
    |       )
    |
    +-- SpawnLootInWorld(GeneratedItems)
    |
    +-- (if bGuaranteedDrop)
            SpawnGuaranteedItem()

Context Building

FLootSourceContext
    |
    +-- SourceLevel: LevelOverride or Owner level
    +-- SourceTags: Component-configured tags

Projectile Spawning

Ability-Based Spawning

Projectiles spawn from abilities with damage spec:

UProjectileAbility::SpawnProjectile()
    |
    +-- HasAuthority()? --> NO: return nullptr
    |
    +-- Determine SpawnTransform
    |       +-- bSpawnAtSocket? --> Get socket transform
    |       +-- else --> Actor transform + offset
    |
    +-- Create DamageEffectSpec
    |
    +-- SpawnActor<AProjectileBase>
    |
    +-- InitializeProjectile(Config, Instigator, Spec, Target)

Projectile Configuration

Parameter Type Purpose
Behavior EProjectileBehavior Movement type
InitialSpeed float Starting velocity
MaxSpeed float Speed cap
Acceleration float Speed increase/second
GravityScale float Gravity multiplier
HomingAccelerationMagnitude float Turn rate
MaxHomingAnglePerSecond float Turn limit
HoverDuration float DelayedHoming wait time

Projectile Behaviors

Behavior Description
Straight Linear trajectory, no guidance
Homing Immediate target tracking
DelayedHoming Hover, then track target
Arc Ballistic parabola

Animation Notify Spawning

UAnimNotify_SpawnProjectile::Notify()
    |
    +-- HasAuthority()? --> NO: return
    |
    +-- Get AbilitySystemComponent
    |
    +-- Find active ProjectileAbility
    |
    +-- bSpawnFullPattern?
            +-- YES --> SpawnProjectilePattern()
            +-- NO  --> SpawnProjectile()

Projectile Patterns

Pattern Architecture

UProjectilePatternDataAsset
    |
    +-- TArray<FProjectilePatternEntry> PatternEntries
    |       +-- PositionOffset
    |       +-- RotationOffset
    |       +-- SpawnDelay
    |
    +-- bRelativeToCaster (local vs world space)
    +-- bShareHomingTarget (all track same target)
    +-- bUseStaggeredSpawn (time between spawns)

Pattern Entry

Field Type Purpose
PositionOffset FVector Spawn offset
RotationOffset FRotator Direction offset
SpawnDelay float Delay before spawn

Built-in Pattern Generators

Generator Parameters Result
Circle NumProjectiles, Radius, Height Ring around origin
Line NumProjectiles, Spacing Row of projectiles
Cone NumProjectiles, ConeAngle Fan pattern

Pattern Generation Example: Circle

GenerateCirclePattern()
    |
    v
AngleStep = 360 / NumProjectiles
    |
    v
For i in 0..NumProjectiles:
    |
    +-- Angle = AngleStep * i (radians)
    |
    +-- Position = (Cos(Angle) * Radius,
    |               Sin(Angle) * Radius,
    |               Height)
    |
    +-- Rotation = (0, AngleStep * i, 0)
    |
    +-- Delay = bStaggered ? StaggerDelay * i : 0

Pattern Spawning Flow

SpawnProjectilePattern()
    |
    +-- Get base transform
    +-- Get shared target (if enabled)
    +-- Create damage spec
    |
    +-- For each PatternEntry:
    |       |
    |       +-- Calculate spawn transform
    |       |       (relative or world space)
    |       |
    |       +-- SpawnDelay > 0?
    |               +-- YES --> SetTimer, spawn later
    |               +-- NO  --> Spawn immediately

AOE Spawning

AOE Shapes

Shape Detection Method Use Case
Cone Multi-trace fan Breath attacks
Arc Sweeping trace Melee swings
Sphere Overlap sphere Explosions
Cylinder Ground circle Ground effects

Shape Configuration

Parameter Type Purpose
Shape EAOEShape Detection shape
Range float Max distance
Angle float Cone/arc angle
Radius float Trace thickness
TraceSegments int32 Cone trace count
OriginSocket FName Attach point
LocalOffset FVector Position offset
EffectDuration float Lifetime
bTraceComplex bool Complex collision

AOE Detection Flow

OnAoeSpawned()
    |
    +-- SpawnEffect() (Niagara)
    |
    +-- ExecuteDetection()
    |       |
    |       +-- Switch on Shape:
    |               Cone --> DetectCone()
    |               Arc --> DetectArc()
    |               Sphere --> DetectSphere()
    |               Cylinder --> DetectCylinder()
    |
    +-- DeduplicateHits()
    |
    +-- For each unique hit:
    |       OnAOEHit.Broadcast(HitActor, Hit)
    |
    +-- SetLifeSpan(EffectDuration)

Cone Detection

DetectCone()
    |
    +-- Origin = Actor location
    +-- Forward = Actor forward
    +-- HalfAngle = Angle / 2
    +-- AngleStep = Angle / TraceSegments
    |
    +-- For each segment:
    |       |
    |       +-- CurrentAngle = -HalfAngle + (AngleStep * i)
    |       +-- TraceDirection = Forward.RotateAngleAxis(CurrentAngle)
    |       +-- TraceEnd = Origin + (TraceDirection * Range)
    |       |
    |       +-- SweepMultiByObjectType
    |               (sphere sweep along trace)
    |
    +-- Collect all hits

Hit Deduplication

Multiple traces may hit the same actor:

DeduplicateHits(AllHits)
    |
    +-- For each hit:
    |       |
    |       +-- Skip if actor == instigator
    |       +-- Skip if already in HitActors set
    |       |
    |       +-- Add to HitActors set
    |       +-- Add to UniqueHits array
    |
    +-- Return UniqueHits

Enemy Spawning

Encounter Resolution

Enemy spawners resolve their encounter through a layered chain. Each layer is optional; the system falls through to the next until it finds a valid encounter.

Per-room-type encounter (DomainDungeonConfig::RoomTypeEncounters)
    └─ fallback ─> Current encounter (set by WorldMap node or domain default)
                       └─ fallback ─> nullptr (no enemies spawn)

Per-room-type encounters let domains define different enemy pools for different room types. A treasure room gets a lighter encounter, optional rooms get elite packs, combat rooms get the standard mix. Configured on UDomainDungeonConfig::RoomTypeEncounters — unmapped room types fall back to the domain's DefaultEncounter.

Current encounter is set once per dungeon run via DungeonSubsystem::SetAreaContext(), resolved from the world map node's encounter or the domain's default.

For standalone maps (no dungeon pipeline), ADebugAreaContext bootstraps the area context directly.

Spawn Filtering

Two controls determine which enemies appear at which spawner:

Enemy Tags (FEnemyData::EnemyTags) classify enemies by role, tier, or family — e.g., Enemy.Role.Melee, Enemy.Role.Ranged, Enemy.Tier.Elite. Tags are set per enemy data asset.

Spawner Filter (AEnemySpawner::SpawnFilter) is a FGameplayTagQuery that restricts which enemies from the encounter pool this spawner can produce. A tower spawner filters for Enemy.Role.Ranged, a ground spawner for Enemy.Role.Melee. Empty filter accepts everything.

When filtering, SelectEnemy() only considers pool entries whose enemy tags match the query. If no entries match, it falls back to unfiltered selection (graceful degradation).

Spawner Configuration

Each AEnemySpawner placed in a room level has:

Property Purpose
RoomType Which room type this spawner is in (default: Combat). Drives encounter resolution.
SpawnFilter Tag query filtering which enemies this spawner can produce
SpawnCountRange Min/max enemies to spawn (clamped to available spawn points)
bSpawnOnBeginPlay Auto-spawn on level load or wait for explicit call

Spawner Events

Delegate Payload When Fired
OnEnemySpawned AEternalEnemy* After each enemy is spawned and initialized

Used by ARoomEncounterActor to track enemies for room completion. Encounter actors in the same streaming level bind to all spawners' OnEnemySpawned delegates to register enemies for death tracking.

Spawn points are defined by child USceneComponents on the spawner actor. If none exist, the actor's own transform is used as a single spawn point.


Enemy Initialization

Enemy Spawn Flow

AEternalEnemy spawned (placed or runtime)
    |
    v
PossessedBy(Controller)
    |
    +-- HasAuthority()? --> NO: return
    |
    +-- Cache AIController
    |
    +-- AIController->Initialize(EnemyDataAsset)
    |       |
    |       +-- Run BehaviorTree
    |       +-- Configure Blackboard
    |
    +-- InitAbilityActorInfo()
    |       |
    |       +-- Init ASC
    |       +-- Apply PrimaryAttributes
    |       +-- Apply SecondaryAttributes
    |       +-- Grant StartupAbilities
    |
    +-- ApplyBalanceScaling() (if BalanceConfig set)
    |       |
    |       +-- UBalanceSubsystem->CalculateScaledStats()
    |       +-- Init Health, Armor, Poise, BaseDamage
    |
    +-- SetupHitTracing()

Balance System Integration

Enemies can be scaled dynamically via FEnemyBalanceConfig:

Parameter Purpose
AreaLevel Base difficulty (dungeon depth, zone)
ThreatTier Encounter intensity (Normal → Boss)
Archetype Combat role (Balanced, Brute, Skirmisher)

The Balance System applies multipliers from JSON config files in Config/Balance/: - EnemyScaling.json - Growth curves per attribute - ThreatTiers.json - Tier multipliers and loot bonuses - Archetypes.json - Role-based stat modifiers

Enemy Components

Component Purpose
UEternalAbilitySystemComponent GAS integration
UEternalAttributeSet Health, damage stats
UEnemyCombatComponent Hit reactions
ULootComponent Death drops
USkeletalMeshComponent (weapon) Visual weapon

Data Asset Initialization

InitAbilityActorInfo()
    |
    +-- EternalAbilitySystemComponent->InitAbilityActorInfo()
    |
    +-- if (EnemyDataAsset):
            |
            +-- Apply PrimaryAttributes GE
            +-- Apply SecondaryAttributes GE
            |
            +-- For each StartupAbility:
                    GiveAbility(FGameplayAbilitySpec)

Source References

Item Spawning

  • UItemSpawner class - Source/ProjectEternal/Public/Inventory/Items/ItemSpawner.h:15
  • FindValidSpawnLocation() - Source/ProjectEternal/Private/Inventory/Items/ItemSpawner.cpp:78
  • AItemActor - Source/ProjectEternal/Public/Inventory/Items/ItemActor.h:18

Loot System

  • ULootComponent class - Source/ProjectEternal/Public/Loot/Components/LootComponent.h:22
  • DropLoot() - Source/ProjectEternal/Private/Loot/Components/LootComponent.cpp:45
  • ULootGenerator - Source/ProjectEternal/Public/Loot/LootGenerator.h:12

Projectile System

  • UProjectileAbility class - Source/ProjectEternal/Public/Abilities/ProjectileAbility.h:25
  • SpawnProjectile() - Source/ProjectEternal/Private/Abilities/ProjectileAbility.cpp:67
  • AProjectileBase - Source/ProjectEternal/Public/Combat/Projectiles/ProjectileBase.h:28
  • FProjectileConfiguration - Source/ProjectEternal/Public/Combat/Projectiles/ProjectileTypes.h:18

Projectile Patterns

  • UProjectilePatternDataAsset - Source/ProjectEternal/Public/Combat/Projectiles/Data/ProjectilePatternDataAsset.h:35
  • Pattern generators - Source/ProjectEternal/Private/Combat/Projectiles/Data/ProjectilePatternDataAsset.cpp:78

Animation Notify

  • UAnimNotify_SpawnProjectile - Source/ProjectEternal/Public/Combat/AnimNotifies/AnimNotify_SpawnProjectile.h:12

AOE System

  • AEternalAOEActor class - Source/ProjectEternal/Public/Actor/AOE/EternalAOEActor.h:28
  • Detection methods - Source/ProjectEternal/Private/Actor/AOE/EternalAOEActor.cpp:89
  • FAOEShapeConfig - Source/ProjectEternal/Public/Types/AOETypes.h:15

Enemy System

  • AEternalEnemy class - Source/ProjectEternal/Public/AI/Enemy/EternalEnemy.h:22
  • PossessedBy() - Source/ProjectEternal/Private/AI/Enemy/EternalEnemy.cpp:67
  • InitAbilityActorInfo() - Source/ProjectEternal/Private/AI/Enemy/EternalEnemy.cpp:98


Recent Changes

Date Change Reason
2026-03 Added OnEnemySpawned delegate Encounter tracking binds to spawner events
2026-03 Added spawn control: enemy tags, spawner filtering, per-room-type encounters Control which enemies spawn where
2026-02 Added Balance System integration Enemy stat scaling at spawn time
- Initial documentation Document spawning architecture

Future Considerations

Enhancement Benefit Complexity
Object Pooling Reduce allocation overhead Medium
Spawn Rate Limiting Prevent frame drops Low
Navmesh Validation Ensure reachable items Medium
Spawn Prediction Reduce perceived latency High
Spawn Statistics Debug spawn patterns Low