Overview & Architecture
Swap Core is a server-authoritative mesh-swap and regrow framework. It continuously scans the world around a player character for static meshes that match entries in a data table, removes matching instances, spawns a configurable pickup actor in their place, and later restores them with an optional animated regrow effect.
The system has no built-in dependency on any inventory or gameplay framework. Item delivery is handled entirely through Blueprint delegates and overrideable functions, so it integrates cleanly with any item system — struct-based, data-asset-based, data-table-based, or custom.
Two-Component Architecture
The system uses two components that must both be placed on the same actor (typically the player character). The swap component automatically locates the respawn component on the same owner via GetOwner().
| Component | Responsibility |
|---|---|
| UniversalFoliageSwapComponent | Detection (sphere overlap), mesh removal, harvest actor spawn/pool, payload initialization, delegate broadcasts, multicast RPCs, World Partition tracking, actor pool reconciliation. |
| FoliageRespawnComponent | Respawn timers, regrow scale animations, batch processing, distance-based instant spawn, streaming-safe pause/resume, per-instance animation state tracking. |
Complete Detection-to-Respawn Flow
Channels queried = ObjectTypesToQuery (e.g. WorldStatic + WorldDynamic)
Quick Start (5 Minutes)
Go to Edit → Plugins, search for Swap Core, enable it, and restart the editor. The plugin module name is SwapCore.
Open your player character Blueprint. In the Components panel, click Add and add UniversalFoliageSwapComponent. Add FoliageRespawnComponent the same way. Both must be on the same actor. No further wiring is required between them — the swap component finds the respawn component automatically via GetOwner() at BeginPlay.
Right-click in the Content Browser → Miscellaneous → Data Table. In the row type picker, type FoliageSwapRow and select FFoliageSwapRow. Name the asset (e.g. DT_FoliageSwap).
Add a row. Set Source Foliage Mesh to the static mesh asset you want the system to detect. Set at least one payload field: a Harvest Actor Class, a Payload Class, or a Payload Data Table + Row Name. Set Respawn Time (seconds). See Section 4 for all fields.
Select UniversalFoliageSwapComponent in the Details panel. Set Foliage Table to your DT_FoliageSwap.
If using Payload Class or Payload Data Table, also set Default Payload World Actor to the actor class that will appear in the world (your pickup BP).
In the Details panel, find Object Types To Query. Add the channels that match your foliage actor's collision object type:
- WorldStatic — foliage paint, static actors with Static mobility
- WorldDynamic — static mesh actors with Movable mobility, most placed actors
- Add both if unsure — the overhead is minimal
This is the most common cause of detection failure. Check it first.
With the component selected, click the Validate Setup button in the Details panel (it is a CallInEditor function). It logs all detected issues to the Output Log and on screen.
If you want to deliver items to an inventory system when a harvest occurs, bind OnPayloadHarvested on the component in your character's Event Graph. Use the output pins (PayloadClass, PayloadDataTable, PayloadRowName, Quantity, WorldActor, etc.) to build your item struct and pass it to your inventory. See Section 9 and Section 11.
bVerboseLogging on the component to log every overlap component type, mesh match result, and skip reason to the Output Log. This is the fastest way to understand why a mesh isn't being detected or harvested.
InteractionRadius is larger than or equal to DetectionRadius, every detected mesh will be harvested immediately before the player can react. Default: DetectionRadius = 300, InteractionRadius = 150.
Data Table Setup
The Foliage Table property on the swap component accepts a UDataTable asset whose row type is FFoliageSwapRow. Each row maps one source mesh to a complete set of harvest and respawn rules. You can have an unlimited number of rows — one per mesh type you want to make harvestable.
Creating the Asset
When the row type dialog appears, search for FoliageSwapRow and select FFoliageSwapRow.
Click the Add button. The row key (left column) is only a label — name it something descriptive like Tree_Oak or Herb_01. It has no functional effect.
This must exactly match the UStaticMesh asset used by the foliage painter, HISM component, ISM component, or standalone static mesh actor you want to harvest. The system performs a pointer comparison — the asset reference must be identical.
A row is only valid if it has a Source Foliage Mesh AND at least one of: Harvest Actor Class, Payload Class, or Payload Data Table. A row with none of these is skipped at startup.
FFoliageSwapRow — All Fields
Foliage Swap — Core Fields
| Field | Type | Default | Description |
|---|---|---|---|
| Source Foliage Mesh | UStaticMesh* | null | Required. The static mesh asset to detect and harvest. Must exactly match the mesh used in the HISM/ISM component, foliage painter, or standalone static mesh actor. The system uses pointer equality — the asset reference must be identical. Example: SM_Oak_Tree. |
| Harvest Actor Class | TSubclassOf<AActor> | null | Actor class spawned at the foliage location when the mesh is harvested. Typically a pickup actor Blueprint. Spawned at the exact world location and scale of the original mesh instance. Auto-destroyed (or returned to pool) when the foliage respawns. Optional when Payload Class or Payload Data Table is set instead. |
| Payload Class (Optional) | TSubclassOf<UObject> | null | Any UObject subclass representing what the player harvested. Can be a DataAsset subclass, item Blueprint, struct wrapper, or any other UObject. When set, OnPayloadHarvested fires on the server. If the class is not an Actor, set Payload World Actor on this row, or Default Payload World Actor on the component, to spawn a visible actor in the world. |
| Payload World Actor (Optional) | TSubclassOf<AActor> | null | Per-row override for the world pickup actor when using Payload Class. If this is non-null, it overrides DefaultPayloadWorldActor on the component for this specific row. Use when different mesh types need different pickup actor classes. Ignored if Payload Class is already an Actor subclass. |
| Payload Data Table (Optional) | UDataTable* | null | A data table containing item definitions for table-driven inventory systems. Set this together with Payload Row Name. When set, OnPayloadHarvested fires with PayloadDataTable + PayloadRowName. Mutually exclusive with Payload Class — use one or the other per row. |
| Payload Row Name (Optional) | FName | None | The row key name inside Payload Data Table that identifies the specific item to deliver (e.g. Herb_Sage). Must match an existing row key in that data table. Only relevant when Payload Data Table is set. |
| Payload Skeletal Mesh (Optional) | USkeletalMesh* | null | A skeletal mesh to automatically set on the spawned pickup actor's first USkeletalMeshComponent via SetSkeletalMesh(). Use when your pickup actor displays items using an animated skeletal mesh instead of a static mesh. Also passed through OnPayloadHarvested as the PayloadSkeletalMesh pin for manual Blueprint wiring. Leave null if the pickup uses a static mesh. |
| Custom Payload Variables | TArray<FFoliagePayloadVariable> | empty | An array of user-defined named variables attached to this row. Each variable has a name and a type-specific value (Bool, Int, Float, String, Name, Vector, or Object). All variables are passed through OnPayloadHarvested as the CustomVars array pin. Use for item rarity, quest flags, loot tier, or any per-mesh metadata your game needs. See Section 5 for details. |
| Payload Quantity | int32 | 1 | How many of the item to deliver. Passed as the Quantity pin in OnPayloadHarvested. Visible only when Payload Class or Payload Data Table is set. |
Respawn Settings
| Field | Type | Default | Description |
|---|---|---|---|
| Can Respawn | bool | true | If false, the mesh is permanently destroyed when harvested — no respawn timer is started, no harvest actor cleanup occurs. Use for story-critical or one-time objects. |
| Respawn Delay (Seconds) | float | 0.0 | Seconds before the mesh respawns. If 0, the component's DefaultRespawnDelay is used instead. If RespawnTimeMin and RespawnTimeMax are both > 0, this field is ignored in favor of the random range. Example: set 60.0 for a 1-minute tree regrow. |
| Respawn Time Min (Random Range) | float | 0.0 | Minimum seconds for a random respawn delay. If both Min and Max are > 0, a random value between them is chosen each time. Example: Min = 10, Max = 30 → each instance gets a random delay between 10–30 seconds. If either is 0, falls back to Respawn Delay. |
| Respawn Time Max (Random Range) | float | 0.0 | Maximum seconds for a random respawn delay. Must be >= Respawn Time Min. The row is considered invalid if Min > Max (both are positive). |
| Respawn Chance (0–1) | float | 1.0 | Probability that the mesh respawns after harvest. Rolled once per harvest. 1.0 = always respawn, 0.5 = 50% chance, 0.0 = never respawn. When the roll fails, the mesh is permanently destroyed for this session. Only evaluated when Can Respawn is true. |
Regrow Animation Settings
| Field | Type | Default | Description |
|---|---|---|---|
| Animation Mode | ERegrowAnimMode | Inherit | Controls whether this mesh type uses the regrow scale animation. Inherit: use the component's global bUseRegrowAnimation setting. Force On: always animate this mesh type regardless of global setting. Force Off: never animate this type (instant respawn visual). |
| Animation Duration (Seconds) | float | 1.0 | How long the scale-from-tiny-to-full animation lasts for this mesh type. If 0, uses the component's RegrowAnimationDuration global default. Ignored when Animation Mode is Force Off. |
| Scale Curve (Optional) | UCurveFloat* | null | Custom float curve controlling animation timing. X-axis: normalized time (0 to 1 over the animation duration). Y-axis: scale alpha (0 = tiny starting scale, 1 = full original scale). When null, a default ease-out curve is used. Useful for custom effects like a bounce or delayed growth. |
| Allow Distance-Based Instant Spawn | bool | true | If true, this mesh type respects the global InstantSpawnDistance setting — when the nearest player is far away, the mesh appears instantly without animation. If false, the mesh always animates regardless of player distance. Set to false for important meshes like quest trees where the animation must always play. |
World Partition Settings
| Field | Type | Default | Description |
|---|---|---|---|
| Persist Across Streaming | bool | true | If true, respawn timers continue counting when the World Partition cell containing this mesh is unloaded, and the respawn resumes when the cell streams back in. If false, the respawn is cancelled on cell unload — the mesh will be in its original state when the cell reloads. Recommended: leave true for seamless open-world gameplay. |
Row Validation Rules
A row is invalid (skipped silently at startup) if:
Source Foliage Meshis null- None of
Harvest Actor Class,Payload Class, orPayload Data Tableis set RespawnTimeMin > 0andRespawnTimeMax > 0andRespawnTimeMin > RespawnTimeMax
Invalid rows are reported by Validate Setup.
Custom Payload Variables
Every row can carry an arbitrary array of named, typed variables called Custom Payload Variables. These flow through OnPayloadHarvested as the CustomVars output pin and are accessible in Blueprint without any C++ code.
FFoliagePayloadVariable Struct
| Field | Type | Description |
|---|---|---|
| VarName | FName | The name used to identify this variable in Blueprint. Examples: Rarity, QuestID, LootTier, SpawnSFX, DropWeight. |
| VarType | EPayloadVarType | Selects which value field is active. In the data table editor, only the relevant field is shown. Options: Bool, Integer, Float, String, Name, Vector, Object. |
| BoolValue | bool | Value when VarType = Bool. Example: use IsQuestItem = true to flag a mesh as quest-critical. |
| IntValue | int32 | Value when VarType = Integer. Example: Rarity = 3 for a tier system (1=common, 5=legendary). |
| FloatValue | float | Value when VarType = Float. Example: DropMultiplier = 1.5 for bonus loot rate. |
| StringValue | FString | Value when VarType = String. Example: FlavorText = "Ancient bark covered in runes." |
| NameValue | FName | Value when VarType = Name. Example: ItemCategory = Wood for filtering in inventory. |
| VectorValue | FVector | Value when VarType = Vector. Example: SpawnOffset = (0, 0, 50) for a custom VFX offset. |
| ObjectValue | UObject* | Value when VarType = Object. Example: a USoundCue* reference for a harvest sound or a UParticleSystem* for VFX. Cast to the expected type in Blueprint. |
Example Row Configuration
A row for a rare herb might have these custom variables:
| VarName | VarType | Value | Purpose |
|---|---|---|---|
| Rarity | Integer | 4 | Item rarity tier — used by UI to choose icon border color |
| IsQuestItem | Bool | true | Prevents discarding in inventory |
| HarvestSFX | Object | SC_Herb_Pick | Sound cue asset played on harvest, cast to USoundCue |
| XPGain | Float | 25.0 | Experience awarded to the player on harvest |
Reading Custom Vars in Blueprint
In your OnPayloadHarvested event, the CustomVars pin gives you a TArray<FFoliagePayloadVariable>. Loop through the array and match by VarName:
// Example flow in Blueprint pseudocode OnPayloadHarvested → For Each Loop (CustomVars) ├─ Branch: Array Element.VarName == "Rarity" │ └─ Use IntValue → set item icon rarity border ├─ Branch: Array Element.VarName == "IsQuestItem" │ └─ Use BoolValue → lock item in inventory ├─ Branch: Array Element.VarName == "HarvestSFX" │ └─ Cast ObjectValue to SoundCue → Play Sound at Location └─ Branch: Array Element.VarName == "XPGain" └─ Use FloatValue → Add to player experience
Array Length > 0 or simply iterate — an empty For Each is a no-op.
Payload Modes Explained
There are three independent ways to configure what happens when a mesh is harvested. They can be combined — for example, a Harvest Actor for the world presence and a Payload Class for inventory delivery.
Mode A — Harvest Actor Only
Set Harvest Actor Class. No Payload Class or Data Table. The system spawns (or retrieves from pool) the specified actor at the harvest location. OnPayloadHarvested does not fire. The spawned actor manages its own interaction and inventory logic internally.
// Row config for Mode A: Source Foliage Mesh = SM_Bush_01 Harvest Actor Class = BP_BushPickup // your custom BP actor Respawn Time = 20.0 // BP_BushPickup handles its own overlap, interaction, and item grant logic
Best for: simple pickups with no inventory integration, prototype setups, or actors that initialize themselves.
Mode B — Payload Class (Blueprint/DataAsset driven)
Set Payload Class to any UObject subclass. This is the class that represents the item type — it is not spawned in the world, it is the data. OnPayloadHarvested fires on the server with this class and the quantity. To also place a visible actor in the world, set Default Payload World Actor on the component (used as a generic pickup) or Payload World Actor on the row (per-row override).
// Row config for Mode B: Source Foliage Mesh = SM_Crystal_Blue Payload Class = BP_CrystalItem // a UObject/DataAsset subclass Payload Quantity = 3 // On component: DefaultPayloadWorldActor = BP_GenericPickup // In character OnPayloadHarvested event: // PayloadClass output pin = class reference to BP_CrystalItem // Use to create instance, look up defaults, or pass to inventory AddItem(PayloadClass, Quantity)
Best for: Blueprint-class-based inventory systems, DataAsset item definitions, any system where items are represented as UClass references.
Mode C — Data Table Row (Row-Name driven)
Set Payload Data Table (your existing item data table) and Payload Row Name (the key of the row for this item). OnPayloadHarvested fires with the data table reference and row name. Your inventory system reads the row itself. Set Default Payload World Actor on the component for a world-space pickup actor. If bUseReflectionInit is enabled and the param names are configured, the system can initialize the pickup actor automatically via reflection.
// Row config for Mode C: Source Foliage Mesh = SM_Mushroom_Red Payload Data Table = DT_Items // your item definitions table Payload Row Name = Mushroom_RedCap // key in DT_Items Payload Quantity = 2 // On component: DefaultPayloadWorldActor = BP_GenericPickup // In character OnPayloadHarvested event: // PayloadDataTable + PayloadRowName → pass directly to your inventory AddItemFromRow(Table, RowName, Qty)
Best for: Any inventory system where items are defined as data table rows (struct-based inventories, data-driven systems).
Combining Modes
Harvest Actor and a Payload Class/DataTable can coexist in the same row:
// Combined: spawn a specific VFX actor AND deliver payload data Source Foliage Mesh = SM_Ore_Gold Harvest Actor Class = BP_OreNode_VFX // shows mining VFX, has its own collision Payload Data Table = DT_Items Payload Row Name = Ore_Gold Payload Quantity = 5
UniversalFoliageSwapComponent — Full Reference
Setup Group
| Property | Type | Default | Description |
|---|---|---|---|
| FoliageTable | UDataTable* | null | Required. The data table (row type: FFoliageSwapRow) that maps source meshes to harvest/respawn behavior. Without this, the component does nothing. |
| DefaultPayloadWorldActor | TSubclassOf<AActor> | null | Generic pickup actor class spawned when a row uses Payload Class or Payload Data Table but does not specify its own Harvest Actor Class or Payload World Actor. Acts as the default "world presence" for all payload-based rows. Set it once here rather than per row. The actor is initialized immediately after spawning via the reflection path or BP hook. |
| bUseReflectionInit | bool | false | When false (default), payload initialization runs only via the IFoliageHarvestable interface (if implemented) and the InitializePayloadActorBP Blueprint override. The reflection param-name fields below are greyed out and treated as None — you don't need to configure them. Enable this only when you want the C++ reflection path to auto-call a named init function on the pickup actor without any Blueprint wiring. |
| PayloadInitFunctionName | FName | None | Requires bUseReflectionInit Name of the function to call on the spawned pickup actor to initialize it (e.g. SetPickup for Narrative Pro's AItemPickup). Found by name via reflection — no compile-time dependency. Leave None to skip. |
| PayloadClassParamName | FName | None | Requires bUseReflectionInit Name of the UClass property inside the init function's first struct parameter to receive the Payload Class. Example: PickupClass in a FPickupConfig struct. |
| PayloadQuantityParamName | FName | None | Requires bUseReflectionInit Name of the int32 property inside the init function's first struct parameter to receive the quantity. Example: QuantityToGive. |
| PayloadDataTableParamName | FName | None | Requires bUseReflectionInit Name of the UDataTable* property to set when using data-table rows. Leave None if your init function doesn't accept a data table reference. |
| PayloadRowNameParamName | FName | None | Requires bUseReflectionInit Name of the FName property inside the init struct to set to the Payload Row Name. |
Detection Group
| Property | Type | Default | Description |
|---|---|---|---|
| DetectionRadius | float | 300 | Sphere radius in centimeters for the overlap query that finds foliage components. This should be large enough to detect meshes before the player can harvest them, but not so large that it scans half the level. Recommended: 200–500 cm. For first-person games, 150–250 cm is typical. |
| InteractionRadius | float | 150 | Distance in centimeters from the player's location within which a detected mesh will actually be harvested. Must be smaller than DetectionRadius. Think of it as the "arm's reach" distance. Recommended: 100–200 cm for melee range. If set too large, meshes will be harvested without the player touching them. |
| CheckInterval | float | 0.25 | Time in seconds between each detection scan. 0.25 = 4 scans per second. Lower values = more responsive but higher CPU cost per player. Higher values (0.5–1.0) reduce cost in low-priority situations. Minimum practical value is ~0.05. |
| GridSize | float | 50 | Cell size in centimeters for spatial quantization. Two instances closer together than this value map to the same grid cell and are treated as the same harvest point, preventing double-harvesting at nearly identical locations. Set to approximately half the minimum distance between adjacent mesh instances. |
| ObjectTypesToQuery | Array<ECollisionChannel> | [WorldStatic] | Collision channels the sphere overlap uses. This is the most common configuration mistake. Foliage-painted meshes use WorldStatic. Standalone placed static mesh actors with Movable mobility use WorldDynamic. If you are unsure, add both. An empty array defaults to WorldStatic only. |
Performance Group
| Property | Type | Default | Description |
|---|---|---|---|
| MaxSwapsPerCheck | int32 | 10 | Maximum number of meshes that can be harvested in a single detection tick. Prevents frame spikes in dense foliage areas where many meshes would match simultaneously. Set 0 for unlimited (not recommended). |
| MaxOverlapsPerCheck | int32 | 64 | Maximum overlap results to evaluate per detection tick. Caps memory allocation during the overlap query. Set 0 for unlimited. |
| MaxDetectionTimeMs | float | 5.0 | If a detection tick takes longer than this many milliseconds, a warning is logged. Does not limit execution — only reports performance issues. Tune down to catch regressions early. |
| bUseActorPooling | bool | true | Pre-spawns all harvest actor classes on BeginPlay and reuses existing actors instead of spawning/destroying them. Eliminates spawn latency and prevents visual flicker in high-density areas. Strongly recommended for production. Disable only when harvest actors require unique one-time initialization that can't be reset. |
| ActorsPerPool | int32 | 20 | Number of actors pre-spawned per class when pooling is enabled. Should be at least as large as the maximum number of harvestable meshes visible simultaneously. Higher values use more memory at startup but prevent mid-game spawning. |
| HarvestActorScaleMultiplier | float | 1.001 | Scale multiplier applied to harvest actors at spawn to ensure complete visual coverage over the original mesh during the swap moment. Values slightly above 1.0 prevent gaps. Values below 1.0 make the pickup slightly smaller. Keep within 0.995–1.005 for minimal visible difference. |
| bUseRenderSync | bool | true | Calls FlushRenderingCommands() during critical mesh visibility swaps to force immediate render state updates, eliminating one-frame flicker when many instances are swapped simultaneously. Slight CPU cost per harvest. Recommended for production. |
| MaxActiveHarvestables | int32 | 1000 | Maximum number of simultaneously active harvest actors (meshes in harvested state awaiting respawn). When this limit is hit, new harvests are blocked. Prevents unbounded memory growth in long sessions or high-density areas. |
Cooldown Group
| Property | Type | Default | Description |
|---|---|---|---|
| CooldownSeconds | float | 2.0 | Time in seconds a grid cell is locked after a harvest failure or cancellation (not a successful harvest — successful harvests create a respawn entry instead). Prevents rapid retry loops. The post-respawn cooldown after a successful harvest is a brief internal constant (~0.5s). |
| CooldownCleanupInterval | float | 5.0 | Seconds between cleanup passes that remove expired cooldown entries from memory. Lower values keep the cooldown map smaller but increase CPU frequency. |
| MaxCooldownEntries | int32 | 5000 | Hard cap on the number of cooldown entries stored simultaneously. When reached, the oldest entries are evicted. Prevents memory growth in very long sessions where thousands of meshes are processed. |
Replication Group
| Property | Type | Default | Description |
|---|---|---|---|
| bForceReplicateHarvestActors | bool | true | Calls SetReplicates(true) on all harvest actors spawned or retrieved from the pool. Ensures clients see pickup actors. Disable for single-player-only projects or when the pickup actors already configure their own replication. |
| bForceAlwaysRelevantHarvestActors | bool | true | Sets bAlwaysRelevant = true on harvest actors so all connected clients always receive them regardless of their distance from the actor. Disable to allow normal net relevancy distance culling on pickup actors (reduces bandwidth in large worlds with many active pickups). |
| HISMRemovalTolerance | float | 25 | Tolerance in centimeters for matching HISM instance world locations during client-side multicast sync. Larger values handle minor floating-point differences between server and client positions but may incorrectly match nearby instances. Recommended: 20–30 cm. |
| SMCVisibilityTolerance | float | 30 | Tolerance in centimeters for matching StaticMeshComponents by world location during client sync. Used when foliage is placed as individual actors rather than instanced components. Recommended: 25–40 cm. |
World Partition Group
| Property | Type | Default | Description |
|---|---|---|---|
| bHandleWorldPartition | bool | true | Enables World Partition streaming awareness. When true, the component subscribes to level stream in/out events and updates harvest tracking accordingly. Required for large open-world projects using World Partition. |
| bAutoResumeOnStreamIn | bool | true | When a World Partition cell streams back in, automatically resumes any respawn timers that were paused when the cell streamed out. Recommended: true. |
| StreamingCheckInterval | float | 2.0 | Seconds between streaming state checks that synchronize the component's harvest tracking with current cell load states. Lower values react faster to streaming but increase CPU overhead. |
Debug Group
| Property | Type | Default | Description |
|---|---|---|---|
| bDebugDraw | bool | false | Draws debug points at all detected foliage locations. HISM/ISM instances appear in Cyan. StaticMeshComponents appear in Yellow. Extremely useful for verifying that the correct mesh types are being detected. |
| DebugPointSize | float | 12 | Pixel size of debug visualization points when bDebugDraw is enabled. Increase for visibility in large scenes. |
| DebugHISMColor | FColor | Cyan | Color for HISM/ISM debug points. |
| DebugSMCColor | FColor | Yellow | Color for StaticMeshComponent debug points. |
| bVerboseLogging | bool | false | Logs detailed information to the Output Log for every detection check: each overlap component type and class, each ProcessStaticMesh entry/skip/proceed, each InitializePayloadActor path taken. The most powerful diagnostic tool in the system. Disable in shipping — guarded by UE_BUILD_SHIPPING. |
Replicated Stats
| Property | Type | Description |
|---|---|---|
| HarvestsSucceeded | int32 | Running count of successful harvests. Replicated and Blueprint-writable for custom tracking. |
| HarvestsFailed | int32 | Running count of failed harvest attempts (cooldown blocked, max limit reached, etc.). |
| RespawnsCompleted | int32 | Running count of completed respawns. |
BlueprintCallable Functions
| Function | Description |
|---|---|
| ValidateSetup() | CallInEditor Validates the entire configuration and logs all issues. Run this before PIE to catch missing table references, invalid rows, and misconfigured param names. |
| ForceDetectionScan() | Triggers an immediate detection pass outside the normal timer interval. Useful for triggering detection after a teleport or scene change. Server-authoritative. |
| PauseDetection() | Stops the detection timer. Use during cutscenes, menus, or dialogue where harvesting should be disabled. |
| ResumeDetection() | Restarts the detection timer after PauseDetection. Resumes from the next full CheckInterval. |
| GetStatsSummary() | Returns a formatted FString of all statistics (harvests, respawns, pool sizes, detection timing). Useful for debug HUDs. |
| ResetStats() | Sets all stat counters (HarvestsSucceeded, HarvestsFailed, RespawnsCompleted) to zero. |
| GetActiveHarvestCount() | Returns the number of meshes currently in the harvested state (pickup actor present, respawn timer running). |
| IsDetectionActive() | Returns true if the detection timer is currently running. |
| GetPoolSizeForClass(Class) | Returns how many actors are currently available in the pool for the given class. Useful for monitoring pool health. |
| GetRowForMesh(Mesh, OutRow) | Returns the FFoliageSwapRow configuration for the given mesh. Returns false if the mesh is not in the data table. Use to look up row data from gameplay code. |
FoliageRespawnComponent — Full Reference
Respawn Core
| Property | Type | Default | Description |
|---|---|---|---|
| DefaultRespawnDelay | float | 5.0 | Fallback respawn delay in seconds used when a row's Respawn Time is 0. Individual rows override this. Acts as the global default timing for the entire system. |
| MaxActiveRespawns | int32 | 500 | Maximum number of concurrent pending respawn entries. When full, new respawns are dropped. Prevents memory growth in very long sessions. |
| CleanupInterval | float | 10.0 | Seconds between cleanup passes that remove completed respawn entries from memory. |
| bCancelRespawnsOnUnload | bool | false | If true, respawns are permanently cancelled when their associated level cell unloads. If false (recommended), they are paused and resumed when the cell streams back in. |
Animation Settings
| Property | Type | Default | Description |
|---|---|---|---|
| bUseRegrowAnimation | bool | true | Global switch for the regrow scale animation. When true, meshes scale from a tiny start size to their full size over RegrowAnimationDuration seconds. Individual rows can override per-type via the Animation Mode field. When false, all meshes appear instantly. |
| RegrowAnimationDuration | float | 1.0 | Global default animation duration in seconds. Overridden per row by the row's own Animation Duration field. Affects all mesh types using the Inherit animation mode. |
| AnimationTickRate | float | 30 | How many times per second the scale animation is updated (Hz). 30 is smooth for most games. Reduce to 15–20 to save CPU in high-density areas. Do not exceed 60. |
| bUsePooledRegrowTimer | bool | true | When true, a single shared timer ticks all active regrow animations together instead of using one timer per animation. Significantly more efficient when many meshes respawn simultaneously. Recommended: always true. |
| StandardMinScale | float | 0.02 | Starting scale factor for non-Nanite meshes at the beginning of the regrow animation. The mesh scales from this value to 1.0. Very small values (0.01–0.05) create a dramatic "sprouting" effect. Values above 0.3 create a more subtle pop-in. |
| NaniteMinScale | float | 0.03 | Starting scale factor for Nanite-enabled meshes. Slightly higher than StandardMinScale due to Nanite rendering characteristics at extreme scales. |
| SafetyMinScale | float | 0.10 | Absolute minimum scale enforced during animation as a safety clamp. Prevents rendering artifacts if the animation start scale is set too low. The actual start scale will be the maximum of the configured min scale and this value. |
Distance & Instant Spawn Settings
| Property | Type | Default | Description |
|---|---|---|---|
| bUseDistanceBasedInstantSpawn | bool | true | When true, meshes that respawn far from all players appear instantly (no animation), saving CPU. Meshes near players still animate. This is a major performance optimization for open worlds. |
| InstantSpawnDistance | float | 3000 | Distance in centimeters from the nearest player beyond which respawning meshes use instant spawn (no animation). Example: 3000 cm = 30 meters. Players closer than this distance see the animation. |
| InstantRespawnMinDistance | float | 500 | Minimum distance in centimeters from the nearest player required before instant spawn can be used. Even if the mesh is beyond InstantSpawnDistance, if any player is within InstantRespawnMinDistance of the respawn location, the animation is used instead. Prevents meshes popping in while players are nearby. |
| RespawnRadiusCheck | float | 150 | Radius in centimeters around the respawn location checked for player presence before executing respawn. If a player pawn is within this radius, respawn is delayed briefly and retried. Prevents the mesh from appearing inside a player's character. |
Instance Matching Settings
| Property | Type | Default | Description |
|---|---|---|---|
| InstanceMatchTolerance | float | 5.0 | Primary tolerance in centimeters for finding the correct instance in an ISM/HISM component to update during animation. Used to match the scale-to-zero placeholder instance back to its original transform. If the match fails, the expanded tolerance is tried. |
| InstanceMatchToleranceExpanded | float | 15.0 | Expanded fallback tolerance (cm) when the primary match fails. Should be 2–3× larger than InstanceMatchTolerance. Used in edge cases where instance positions have minor floating-point drift. |
Batch Processing Settings
| Property | Type | Default | Description |
|---|---|---|---|
| bUseBatchProcessing | bool | true | When true, respawns are processed in batches spread over multiple frames rather than all at once. Prevents frame time spikes when many respawn timers fire simultaneously (e.g., after a player leaves an area and returns 5 minutes later). |
| MaxRespawnsPerBatch | int32 | 20 | Maximum respawns processed per batch tick. Higher values = faster catch-up after returning to an area, but potential frame spikes. Recommended: 10–30. |
| BatchProcessingDelay | float | 0.1 | Seconds between batch processing cycles. Combined with MaxRespawnsPerBatch, controls how quickly a queue of pending respawns is processed. |
World Partition Settings
| Property | Type | Default | Description |
|---|---|---|---|
| bHandleWorldPartition | bool | true | Subscribe to level stream events and manage respawn pausing. Must match the setting on the swap component. |
| bPauseRespawnsWhenUnloaded | bool | true | When the cell containing a respawning mesh unloads, pause its respawn timer (storing remaining time). Timer resumes when cell reloads. |
| bResumeRespawnsOnLoad | bool | true | When a cell loads, automatically resume all paused respawns that were waiting in that cell. |
| bInstantRespawnOnStreamIn | bool | true | When a cell streams in and respawns resume, completed timers are respawned instantly (no animation) to prevent the player seeing meshes grow in as they enter the area. |
Respawn Component Events
| Event | Parameters | Description |
|---|---|---|
| OnRespawnComplete | UStaticMesh* Mesh, FVector Location, bool bWasAnimated | Fires when a foliage instance finishes respawning. bWasAnimated = true if the scale animation played, false if instant respawn was used. |
| OnRespawnFailed | UStaticMesh* Mesh, FVector Location | Fires when a respawn attempt fails (e.g., ISM component invalid, max retries exceeded). |
| OnRespawnStarted | UStaticMesh* Mesh, FVector Location | Fires when the respawn timer begins (immediately after harvest, before the delay). |
| OnRespawnPaused | UStaticMesh* Mesh, FVector Location | Fires when a pending respawn is paused due to World Partition level unload. |
| OnRespawnResumed | UStaticMesh* Mesh, FVector Location | Fires when a paused respawn is resumed after the level cell streams back in. |
Respawn Component Blueprint Functions
| Function | Description |
|---|---|
| ForceInstantRespawnForCell(CellKey) | Immediately respawns the mesh at the given grid cell key, bypassing any remaining timer and skipping the animation. |
| CancelAndInstantRespawn(CellKey) | Cancels the pending respawn entry and immediately respawns the mesh. Useful for quest-triggered instant restores. |
| GetActiveRespawnCount() | Returns the total number of respawn entries currently pending (including those regrowing). |
| IsProcessingRespawns() | Returns true if any respawn entries are currently active. |
| GetPausedRespawnCount() | Returns the number of respawns currently paused due to World Partition streaming. |
| PauseRespawnsForLevel(Level) | Manually pauses all respawns associated with the given ULevel. For custom streaming integration. |
| ResumeRespawnsForLevel(Level) | Manually resumes paused respawns for the given level. |
| IsBatchProcessingActive() | Returns true if batch processing is enabled and the batch timer is currently running. |
| GetBatchQueueSize() | Returns how many respawns are queued for batch processing. |
Blueprint Delegates
All delegates are BlueprintAssignable — bind them via Add Dynamic or the component's bound event node in the character's Event Graph. Server-only events fire on the server; multicast events fire on all machines.
Blueprint Overrides (BlueprintNativeEvent)
Both components expose override points via BlueprintNativeEvent — functions with a C++ default implementation that you can override in a Blueprint subclass of the component, or in your character Blueprint if it owns the component. Override these by right-clicking the function name in Blueprint and choosing Add Override.
UniversalFoliageSwapComponent Overrides
Example use: Cast
Actor to your pickup Blueprint class, then call SetupPickup(Row.PayloadClass, Row.PayloadQuantity) to initialize it with your inventory data.false to prevent harvesting a specific instance. Called after the system's built-in checks (cooldown, regrow, max limit) pass. Default returns true.Example uses: Check if the player has a required tool equipped. Check if a skill level requirement is met. Prevent harvesting during a boss fight. Check if the item type is needed for an active quest.
OnHarvestFailed delegate.FoliageRespawnComponent Overrides
false to cancel the respawn entirely (it will not retry unless re-enqueued). Default returns true.Example uses: Delay respawn until a combat encounter ends. Prevent respawn in a currently-active zone. Check game state before restoring a resource node.
// Example: CanHarvestInstance override in character Blueprint function CanHarvestInstance(StaticMesh Mesh, Vector Location) → bool // Inside override: // Get equipped item slot → check if harvesting tool is equipped // Get required skill level from row (via GetRowForMesh) → compare with player skill // If conditions not met → play denial feedback → return false // Otherwise return true (call parent)
Inventory Integration Guide
Swap Core has no built-in inventory knowledge. Integration is always done in Blueprint using one of three approaches, depending on how your inventory identifies items. No C++ changes are required for any of them.
Integration Path Matrix
| Your Inventory Identifies Items By | Row Setup | Integration Method |
|---|---|---|
| Data table row (struct-based, FDataTableRowHandle) | Set Payload Data Table + Payload Row Name | Wire OnPayloadHarvested → use PayloadDataTable + PayloadRowName to build FDataTableRowHandle → pass to AddItem(RowHandle, Qty) |
| UObject/Blueprint class (TSubclassOf) | Set Payload Class | Wire OnPayloadHarvested → use PayloadClass output pin → pass to AddItem(PayloadClass, Qty) |
| Data asset (UPrimaryDataAsset subclass) | Set Payload Class to the data asset's class | Wire OnPayloadHarvested → GetClassDefaultObject(PayloadClass) → Cast to your DataAsset type → pass the CDO to AddItemFromAsset(Asset, Qty) |
| Named function on pickup actor (reflection path) | Set Payload Class or Data Table; enable bUseReflectionInit; configure param names | No Blueprint wiring needed — the system calls the named function automatically via C++ reflection |
| Custom / unique per-system | Any mode | Override InitializePayloadActorBP on the component in Blueprint — receives the full row, full control with no C++ |
Pattern A: Data Table Row Handle Integration
Most struct-based inventories use an FDataTableRowHandle to identify items. Build it from the delegate pins:
OnPayloadHarvested fires (server-side) Pins available: PayloadDataTable → UDataTable* (your item definitions table) PayloadRowName → FName (the row key, e.g. "Herb_Sage") Quantity → int32 Blueprint flow: Make DataTableRowHandle ├─ DataTable ← PayloadDataTable pin └─ RowName ← PayloadRowName pin → Inventory Component → Add Item From Row Handle(Handle, Quantity) // This works with any inventory where AddItem accepts an FDataTableRowHandle
Pattern B: Blueprint Class / DataAsset Integration
For systems where items are represented as Blueprint class references or DataAssets:
OnPayloadHarvested fires (server-side) Pins available: PayloadClass → UClass* (class reference to e.g. BP_HerbItem_Sage_C) Quantity → int32 Blueprint flow for class-based systems: → Inventory Component → Add Item By Class(PayloadClass, Quantity) Blueprint flow for DataAsset-based systems: GetClassDefaultObject(PayloadClass) // gets CDO = the data asset instance → Cast to UMyItemDataAsset → Inventory Component → Add Item From Asset(Asset, Quantity)
Pattern C: InitializePayloadActorBP Override (Zero Blueprint wiring per-mesh)
When your inventory requires calling a specific function on the pickup actor (and you can't or don't want to use the reflection path), override InitializePayloadActorBP in your character or a component subclass:
// Override InitializePayloadActorBP in your character Blueprint: function InitializePayloadActorBP(Actor Actor, FFoliageSwapRow Row) // Inside the override: Cast Actor → BP_MyPickupActor → Set ItemDataAsset = GetClassDefaultObject(Row.PayloadClass) → Cast to UMyItemDA → Set Amount = Row.PayloadQuantity → Set SkeletalMesh = Row.PayloadSkeletalMesh // if needed → Call ActivatePickup() // This runs once per harvest for every row that has a PayloadClass set. // One override handles all mesh types in the data table.
Reflection Path (bUseReflectionInit)
Enable this when your inventory pickup actor has a known initialization function that accepts the item class and quantity, and you want zero Blueprint wiring. Configure on the component:
// Component settings for a system with: SetPickup(FPickupConfig Config) function bUseReflectionInit = true PayloadInitFunctionName = SetPickup // exact function name PayloadClassParamName = ItemClass // property inside FPickupConfig PayloadQuantityParamName = Quantity // int32 property inside FPickupConfig // The system will call Actor->SetPickup(Config) automatically, // injecting Row.PayloadClass into Config.ItemClass // and Row.PayloadQuantity into Config.Quantity. // Works for both struct-param and direct-param function signatures.
Supported Mesh Types
Hierarchical Instanced Static Mesh (HISM)
The standard component used by Unreal's foliage painter. Scale-to-zero removal prevents index shifting. Regrow animation updates the instance transform directly. Multicast RPC syncs clients.
Instanced Static Mesh (ISM)
Non-hierarchical instanced meshes. Used by many custom placement tools and some PCG graphs. Handled identically to HISM with the same scale-to-zero and index-safe update strategy.
Static Mesh Component (SMC)
Individual placed static mesh actors (one actor, one SMC). Hidden during harvest, visibility restored on respawn. Supports Movable mobility — requires WorldDynamic in ObjectTypesToQuery.
PCG-Generated Meshes
Meshes spawned by the Procedural Content Generation graph. Both PCG-managed HISM and ISM components are supported. PCG callbacks re-register components after generation. See Section 16.
Foliage Paint Tool
Meshes placed with the Foliage mode tool (Edit → Foliage). These are backed by HISM components internally and are handled by the HISM path automatically.
Object Type vs Mobility
| Mesh Source | Component Type | Default Mobility | Required Channel |
|---|---|---|---|
| Foliage Paint Tool | HISM | Static | WorldStatic |
| Placed Static Mesh Actor | SMC | Static or Movable | WorldStatic or WorldDynamic |
| PCG Graph (Spawn Static Mesh node) | ISM or HISM | Static | WorldStatic |
| Blueprint ISM/HISM Component | ISM/HISM | Typically Static | WorldStatic |
| Blueprint Actor with SMC | SMC | Depends on actor mobility | WorldStatic or WorldDynamic |
WorldDynamic, not WorldStatic. The mesh will be invisible to the overlap query unless WorldDynamic is added to ObjectTypesToQuery.
Actor Pooling
Actor pooling pre-spawns harvest actors at BeginPlay and reuses them rather than spawning/destroying on every harvest. This eliminates spawn cost, garbage collection pressure, and the one-frame visual gap caused by actor registration.
How It Works
Pool Configuration
- bUseActorPooling — enables/disables the entire system
- ActorsPerPool — actors pre-spawned per class. Set to the maximum number of that mesh type visible simultaneously
- The pool grows dynamically if demand exceeds the pre-spawned count — there is no hard cap on pool size, only on total active harvestables (MaxActiveHarvestables)
Pool Size Monitoring
// In Blueprint, monitor pool health during development: GetPoolSizeForClass(BP_MyPickup_C) → how many are available right now GetActiveHarvestCount() → how many are currently in the world GetStatsSummary() → formatted string with all metrics
Pool Reconciliation
A periodic reconciliation pass (every ~10 seconds) compares the expected pool count against actual weak pointer validity. This automatically corrects drift caused by edge cases like world resets or garbage collection. The log will report corrections as [ReconcilePoolCounts] Count drift detected for class X: Tracked=N, Actual=M. Correcting....
InitializePayloadActorBP or inside the actor's own BeginPlay/Reset function. The system calls InitializePayloadActorBP every time an actor is activated from the pool.
Multiplayer & Replication
Swap Core is designed for server-authoritative multiplayer. All harvest decisions happen on the server. Clients receive multicast RPCs to mirror the visual state.
Server vs Client Responsibilities
| Operation | Who Runs It | How Clients Know |
|---|---|---|
| Detection (sphere overlap) | Server Only | No client detection — server decision is authoritative |
| HISM/ISM instance removal | Server | Multicast_RemoveHISMInstanceAt / Multicast_RemoveISMInstanceAt |
| SMC visibility hide | Server | Multicast_SetSMCVisible(false) |
| Harvest actor show | Server | Multicast_ShowHarvestActor |
| Regrow animation | Server starts | Multicast_StartRegrowAnimation / Multicast_StartSMCRegrowAnimation |
| OnPayloadHarvested | Server Only | Clients do not receive this — use OnMeshHarvested for client-side VFX |
| Inventory grant | Server Only | Replicated via your inventory system's own replication |
Setup for Multiplayer
- The
UniversalFoliageSwapComponentmust be on an actor that has authority on the server. A player-owned character pawn typically fulfills this when using a listen or dedicated server. bForceReplicateHarvestActors = true— ensures pickup actors are replicated to all clientsbForceAlwaysRelevantHarvestActors = true— ensures all clients receive the actor even when far away. Disable and let net relevancy cull for large worlds with many concurrent harvests.- The component itself has
SetIsReplicatedByDefault(true)— the component replicates stats and triggers multicast RPCs automatically.
Single-Player Configuration
For single-player games, you can save bandwidth and object overhead:
// On the component, for single-player: bForceReplicateHarvestActors = false bForceAlwaysRelevantHarvestActors = false bHandleWorldPartition = false // unless using WP
World Partition + Multiplayer Notes
In a World Partition + multiplayer combination, harvest state is tracked per-server using the grid cell key system. All connected clients receive multicast RPCs when cells stream in/out. The Multicast_PauseRegrowAnimation and Multicast_ResumeRegrowAnimation RPCs on the respawn component ensure client-side animations stay synchronized with server streaming state.
World Partition Support
World Partition levels dynamically load and unload cells as players move. Swap Core handles this via level streaming event subscriptions on both components.
How Streaming is Handled
Configuration Checklist
- Both components:
bHandleWorldPartition = true - Swap component:
bAutoResumeOnStreamIn = true - Respawn component:
bPauseRespawnsWhenUnloaded = true,bResumeRespawnsOnLoad = true - Row:
Persist Across Streaming = trueon rows where you want timers to survive streaming - Swap component:
StreamingCheckInterval = 2.0(default) — adjust if streaming occurs very rapidly
bInstantRespawnOnStreamIn = true (default), any mesh whose respawn timer has already expired while the cell was unloaded will appear instantly (no animation) when the cell loads back in. This prevents the player seeing "growing trees" as they enter a previously-visited area.
PCG (Procedural Content Generation) Support
Swap Core supports meshes spawned by Unreal's PCG system at runtime. PCG-generated HISM and ISM components are automatically detected and registered when the PCG graph completes generation.
How PCG Integration Works
- The plugin checks for the PCG plugin at build time. If found, it compiles with
SWAPCORE_PCGENABLED=1for all build targets (Editor, Development, Shipping). - The swap component subscribes to the
OnPCGGraphGeneratedcallback atBeginPlay. - When a PCG graph generates or regenerates, the component scans new components for matching meshes and adds them to the tracked set (
PCGManagedComponents). - PCG-generated ISM and HISM components are handled by the same code paths as manually-placed ISM/HISM, with the same scale-to-zero and multicast RPC system.
Requirements for PCG Meshes
- The PCG graph must use a Spawn Static Mesh node outputting to a StaticMeshComponent (generates an ISM or HISM).
- The mesh asset assigned in the PCG graph's Spawn Static Mesh node must match the
Source Foliage Meshin the data table row exactly (same asset reference). - The PCG component's actor must use
WorldStaticcollision object type (default for PCG-generated actors).
Runtime PCG Regeneration
If a PCG graph regenerates at runtime (e.g., dynamic world generation), the swap component re-scans for new components automatically via the callback. Previously-harvested locations are preserved in the active harvestables map, so regrown mesh instances from re-generation are not immediately re-harvested.
ForceDetectionScan() after the graph generation completes to manually trigger a re-scan.
Performance Tuning
Key Performance Levers
| Setting | Impact | Guidance |
|---|---|---|
| CheckInterval | Detection CPU frequency | 0.25 is the default (4 checks/s). Increase to 0.5–1.0 in performance-critical areas. Use PauseDetection during cutscenes. |
| DetectionRadius | Overlap query size | Keep as small as viable. A 300 cm radius is typical. Very large radii (>1000 cm) dramatically increase overlap results. |
| MaxOverlapsPerCheck | Memory per detection tick | 64 is rarely a bottleneck. Reduce to 16–32 in sparse areas. Increase to 128+ in very dense forests. |
| MaxSwapsPerCheck | Harvests per tick | 10 prevents frame spikes. In dense areas, extra harvests are deferred to the next tick naturally — this rarely causes gameplay issues. |
| bUseActorPooling | Spawn cost | Always enable. Pre-spawned actors cost a small amount of memory at startup but eliminate runtime spawn latency entirely. |
| ActorsPerPool | Memory at startup | Set to the maximum number of harvestable meshes a player can simultaneously be within InteractionRadius of. Typically 5–30. |
| bUseDistanceBasedInstantSpawn | Animation CPU | Always enable. Distant respawns are free (instant). Only nearby respawns animate. |
| bUseBatchProcessing | Respawn frame spikes | Always enable. Distributes the respawn burst across multiple frames when returning to a previously-harvested area. |
| AnimationTickRate | Animation smoothness vs CPU | 30 Hz is the sweet spot. Reduce to 15–20 in high-density areas to save CPU. The visual difference is minor. |
Recommended Settings by Project Type
// Small/Linear Level (corridor game, dungeon) CheckInterval = 0.25 DetectionRadius = 200 MaxOverlapsPerCheck = 32 ActorsPerPool = 10 bUseBatchProcessing = true // Open World (large terrain, many foliage instances) CheckInterval = 0.4 DetectionRadius = 300 MaxOverlapsPerCheck = 64 ActorsPerPool = 20 bUseBatchProcessing = true MaxRespawnsPerBatch = 10 bUseDistanceBasedInstantSpawn = true InstantSpawnDistance = 5000 // 50m distant = instant // Dense Forest / Gathering Game CheckInterval = 0.2 DetectionRadius = 250 MaxOverlapsPerCheck = 128 MaxSwapsPerCheck = 20 ActorsPerPool = 40 bUseRenderSync = true AnimationTickRate = 20
Debug & Stats
Debug Visualization
Enable bDebugDraw = true on the swap component to draw colored debug points at every detected foliage location during each detection pass:
- Cyan — HISM/ISM instance detected at that location
- Yellow — StaticMeshComponent detected at that location
No debug points at a foliage location means that location was not found by the sphere overlap. Check ObjectTypesToQuery.
Verbose Logging
Enable bVerboseLogging = true on both components to get detailed Output Log output. Each detection tick logs:
[RunDetection] Overlap component: [name] (Class=[class], Owner=[actor], ObjType=[channel])— every component found[ProcessStaticMesh] SKIP — no static mesh on component [name]— mesh is missing[ProcessStaticMesh] SKIP — mesh '[name]' not in MeshSwap data table— mesh not in table[ProcessStaticMesh] SKIP — mesh '[name]' at [loc] is [dist]cm away, outside InteractionRadius [r]cm— outside interaction range[ProcessStaticMesh] SKIP — CanHarvestAtLocation blocked— cooldown, regrow, or already seen[ProcessStaticMesh] PROCEEDING to harvest '[name]' at [loc]— harvest will execute
Stats
// Call in Blueprint (e.g. on a debug key): string Stats = GetStatsSummary() // Returns something like: "Harvests: 42 success / 3 failed | Respawns: 38 | Avg Detection: 0.8ms | Pool: BP_Pickup_C=15/20 available" // Individual stats: HarvestsSucceeded // replicated int32 HarvestsFailed // replicated int32 RespawnsCompleted // replicated int32 (on both components) GetActiveHarvestCount() // currently harvested meshes GetPoolSizeForClass(Class) // available actors in pool // Respawn component: GetActiveRespawnCount() // pending respawns GetPausedRespawnCount() // WP-paused respawns GetBatchQueueSize() // queued for batch
Validate Setup
With the UniversalFoliageSwapComponent selected in the Details panel, click Validate Setup. This checks and reports:
- FoliageTable not assigned
- FoliageTable has zero rows
- Rows with null Source Foliage Mesh
- Rows with no payload configured
- Rows with invalid RespawnTimeMin/Max range
- DetectionRadius ≤ InteractionRadius (likely mis-configuration)
- bUseReflectionInit = true but PayloadInitFunctionName is None
- No ObjectTypesToQuery channels (will default to WorldStatic)
- FoliageRespawnComponent not found on owner
Troubleshooting
Work through this checklist in order:
- Is the Foliage Table assigned? Select the component → check Foliage Table field is not None.
- Is the source mesh in the table? Open the data table — does a row exist with Source Foliage Mesh matching the mesh in your level?
- Does the mesh asset reference match exactly? Click the mesh in the level, note the asset path. Open the row, verify the same asset is referenced. Blueprint variants and duplicates are different assets.
- Is the correct collision channel in ObjectTypesToQuery? Enable bVerboseLogging and look for "ObjType=N". If N=1 (WorldStatic) but your mesh actors have Movable mobility, add WorldDynamic (ECC_WorldDynamic=2).
- Is DetectionRadius large enough? Default is 300 cm. Enable bDebugDraw and check if cyan/yellow points appear near the mesh. No points = mesh not found by overlap.
- Does the component have authority? The swap component only runs detection when it has server authority. In PIE, use "Play As Listen Server" or "Dedicated Server" to test. In standalone, authority is always present.
- Is the row valid? Run Validate Setup. Invalid rows are skipped — they log as warnings at startup.
- No Harvest Actor Class or Default Payload World Actor set. If the row uses Payload Class or Payload Data Table but no Harvest Actor Class, AND the component has no Default Payload World Actor, no actor is spawned in the world — only the delegate fires. Set either the per-row Payload World Actor or the component's Default Payload World Actor.
- Actor pooling failure. Enable verbose logging and look for pool exhaustion warnings. Increase ActorsPerPool.
- Actor is spawning but hidden. The actor's visibility may be set to hidden in its Blueprint default state. Check the actor's construction script or default properties for SetActorHiddenInGame(true).
- Actor spawns far away. If the spawn transform is wrong, the actor spawns at (0,0,0) or an unexpected location. Enable bDebugDraw to see the intended spawn location (yellow debug point).
- InteractionRadius is set too large. The InteractionRadius defaults to 150 cm but should always be smaller than DetectionRadius (default 300 cm). If InteractionRadius is 700+ cm, meshes will be harvested the moment the player enters the detection sphere. Set InteractionRadius to 100–200 cm.
- GridSize too large. A very large GridSize can cause nearby meshes to all map to the same cell and be treated as a single harvest.
- Row uses only Harvest Actor Class (Mode A). OnPayloadHarvested only fires when the row has a Payload Class OR a Payload Data Table set. Mode A (harvest actor only) does not fire this event. Add a Payload Class or Payload Data Table to the row.
- Event binding is on the wrong machine. OnPayloadHarvested is server-only. If you bound it in a client-only Blueprint context, it won't fire. Bind it in the server-side character Blueprint or use an RPC to propagate to the client.
- Event was re-bound after a signature change. If the delegate signature changed (parameters added/removed), the old binding is invalid. Delete and re-create the bound event node.
- SetStaticMesh not called. If your pickup actor uses a static mesh that should match the harvested mesh, you need to call SetStaticMesh in the OnPayloadHarvested event (using the Mesh output pin) or in InitializePayloadActorBP. The system does not auto-set the static mesh — only the skeletal mesh is auto-set (via PayloadSkeletalMesh).
- PayloadSkeletalMesh row field is null. If your pickup uses a skeletal mesh, set the Payload Skeletal Mesh field in the row. The system will auto-find the first SkeletalMeshComponent on the actor and call SetSkeletalMesh.
- Pooled actor state from previous use. If actor pooling is enabled, the actor retains its state from the last use. Reset the mesh in InitializePayloadActorBP or in the actor's Reset() function.
- Respawn Delay is 0 and DefaultRespawnDelay is 0. With both at 0, the respawn fires as soon as the harvest actor is checked (briefly after harvest). Set Respawn Delay > 0 in the row, or increase DefaultRespawnDelay on the FoliageRespawnComponent.
- Harvest actor was destroyed externally. If your pickup actor destroys itself (e.g. on overlap with the player), the FoliageRespawnComponent detects the missing actor and may reschedule respawn. If this is too fast, ensure your pickup actor does not call DestroyActor before the player has time to interact. The system already handles this edge case by rescheduling for 1 second if the actor is destroyed early.
- Can Respawn = false. Check the row — if Can Respawn is false, no respawn happens at all (the mesh is permanently destroyed). The pickup actor will remain, but the foliage mesh won't come back.
- bUseRegrowAnimation = false on the FoliageRespawnComponent. Set it to true.
- Row Animation Mode = Force Off. Overrides the global setting for that specific mesh. Change to Inherit or Force On.
- Player is far away → distance-based instant spawn triggered. If bUseDistanceBasedInstantSpawn = true and the player is beyond InstantSpawnDistance from the respawn location, instant spawn is used instead of animation. This is expected behavior. Walk closer to see the animation.
- Nanite mesh clipping at small scales. Nanite meshes may clip or disappear at very small scales. Increase NaniteMinScale to 0.1–0.15 or set the row's Animation Duration to 0 (instant respawn) for Nanite meshes.
This is a safety message, not an error. The pool reconciliation pass detected that the tracked count of available actors didn't match the actual count (usually due to garbage collection of weak pointers or external actor destruction). The system self-corrects automatically. If this appears frequently, check if:
- Any external code is destroying pooled actors (don't call DestroyActor on actors you got from the pool unless you know what you're doing)
- The ActorsPerPool is too low and the pool is growing dynamically under pressure
This log message appears when a Blueprint asset that references the plugin loads before the plugin module has fully initialized — typically on the first load after adding the components. It is a stale-reference warning, not a permanent error. To resolve:
- Open the affected Blueprint, recompile it, and save it.
- Restart the editor once after initially adding the components to let all references resolve.
- The warning will stop appearing after the first successful compile.
- bUseReflectionInit must be true. Without this, the reflection path is completely skipped and the param names are ignored.
- Function name must match exactly.
PayloadInitFunctionNameis case-sensitive and must match the Blueprint function name character for character. Blueprint functions are exposed via UHT — check the generated header if unsure. - Property names inside the struct must match.
PayloadClassParamNamerefers to a property INSIDE the first struct parameter of the function, not the parameter itself. Enable bVerboseLogging to see "[InitializePayloadActor] Function 'X' not found on Y" warnings. - Function must be a UFUNCTION. C++ functions not marked UFUNCTION are not accessible via FindFunction(). Blueprint functions are always accessible.
- Component is not replicating. Ensure the owner actor (player pawn/character) has replication enabled. The component itself sets
SetIsReplicatedByDefault(true)but the owner must also replicate. - bForceReplicateHarvestActors = false. Set to true so pickup actors are flagged for replication.
- Net relevancy cutoff. If bForceAlwaysRelevantHarvestActors = false, clients far from the pickup actor won't receive it. Enable it or adjust net relevancy settings.
- HISM removal not syncing. HISM instances don't replicate natively. The system uses Multicast_RemoveHISMInstanceAt. If this is not executing on clients, the owner character may not have authority on the server.
- bHandleWorldPartition is false on one component. Both the swap component and respawn component must have this enabled.
- Persist Across Streaming is false on the row. Check the row's World Partition section — if false, respawns are cancelled on cell unload.
- bPauseRespawnsWhenUnloaded / bResumeRespawnsOnLoad = false. These must both be true on the respawn component for pause/resume to work.
- Streaming event subscriptions failed. Enable verbose logging and check for "[OnLevelRemovedFromWorld]" and "[OnLevelAddedToWorld]" messages to confirm events are firing.