Complete Reference · v2.1

Swap Core

Server-authoritative mesh-swap, harvest, and regrow framework for Unreal Engine 5.7. Works with any inventory system, any mesh type, any world size.

Unreal Engine 5.7 Server-Authoritative HISM · ISM · SMC · PCG · Foliage World Partition Ready Multiplayer Darksaint Games
📋 Table of Contents
🌿
Section 1

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.

Mesh Types
5
HISM · ISM · StaticMesh · PCG · Foliage Paint
Inventory Coupling
Zero
Pure delegate / override architecture
Network Mode
Full
Server-authoritative with multicast RPCs
Build Targets
All
Editor · Development · Shipping

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().

ComponentResponsibility
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

⏱ Timer fires every CheckInterval seconds (default: 0.25s)
🔍 SphereOverlapComponents at player location with radius = DetectionRadius
Channels queried = ObjectTypesToQuery (e.g. WorldStatic + WorldDynamic)
🗂 For each overlapping component → branch to ISM/HISM path or StaticMeshComponent path
📋 FindRowForMesh → look up source mesh in Foliage Table data table
🛡 CanHarvestAtLocation → check: cooldown · regrow in progress · max active · already-seen this tick · CanHarvestInstance override
✂ Remove instance (scale-to-zero for ISM/HISM, hide for SMC) · Multicast to all clients
📦 Spawn or retrieve harvest actor from pool · InitializePayloadActor (interface → reflection → BP hook)
📣 Broadcast OnPayloadHarvested (server) · OnMeshHarvested (all)
⏳ FoliageRespawnComponent enqueues timer · waits Respawn Delay seconds
🌱 Restore instance · return harvest actor to pool · optional regrow scale animation
✅ OnRespawnComplete broadcast · post-respawn cooldown prevents immediate re-harvest
🚀
Section 2

Quick Start (5 Minutes)

1
Enable the Plugin

Go to Edit → Plugins, search for Swap Core, enable it, and restart the editor. The plugin module name is SwapCore.

2
Add Both Components to Your Player Character

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.

3
Create the Foliage Data Table

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.

4
Assign the Table to the Swap Component

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).

5
Set Collision Channels

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.

6
Validate Configuration

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.

7
Wire OnPayloadHarvested (Optional — for inventory integration)

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.

💡
Use Verbose Logging to diagnose detection issues Enable 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 must be smaller than DetectionRadius The detection radius finds nearby components; the interaction radius is where harvesting actually occurs. If 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.
📋
Section 3

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

1
Right-click in Content Browser → Miscellaneous → Data Table

When the row type dialog appears, search for FoliageSwapRow and select FFoliageSwapRow.

2
Add Rows

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.

3
Set Source Foliage Mesh

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.

4
Configure at least one payload field

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.

ℹ️
One data table, multiple players All player characters can share the same data table. The table is read-only by the system — one asset works for the entire project.
📄
Section 4

FFoliageSwapRow — All Fields

Foliage Swap — Core Fields

FieldTypeDefaultDescription
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

FieldTypeDefaultDescription
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

FieldTypeDefaultDescription
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

FieldTypeDefaultDescription
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:

Invalid rows are reported by Validate Setup.

🏷
Section 5

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

FieldTypeDescription
VarNameFNameThe name used to identify this variable in Blueprint. Examples: Rarity, QuestID, LootTier, SpawnSFX, DropWeight.
VarTypeEPayloadVarTypeSelects which value field is active. In the data table editor, only the relevant field is shown. Options: Bool, Integer, Float, String, Name, Vector, Object.
BoolValueboolValue when VarType = Bool. Example: use IsQuestItem = true to flag a mesh as quest-critical.
IntValueint32Value when VarType = Integer. Example: Rarity = 3 for a tier system (1=common, 5=legendary).
FloatValuefloatValue when VarType = Float. Example: DropMultiplier = 1.5 for bonus loot rate.
StringValueFStringValue when VarType = String. Example: FlavorText = "Ancient bark covered in runes."
NameValueFNameValue when VarType = Name. Example: ItemCategory = Wood for filtering in inventory.
VectorValueFVectorValue when VarType = Vector. Example: SpawnOffset = (0, 0, 50) for a custom VFX offset.
ObjectValueUObject*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:

VarNameVarTypeValuePurpose
RarityInteger4Item rarity tier — used by UI to choose icon border color
IsQuestItemBooltruePrevents discarding in inventory
HarvestSFXObjectSC_Herb_PickSound cue asset played on harvest, cast to USoundCue
XPGainFloat25.0Experience 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
OnPayloadHarvestedFor 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
💡
No limit on variable count You can add as many variables per row as needed. Each row can have a completely different set of variable names and types. The array is empty when no variables are configured, so always check Array Length > 0 or simply iterate — an empty For Each is a no-op.
📦
Section 6

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
⚙️
Section 7

UniversalFoliageSwapComponent — Full Reference

Setup Group

PropertyTypeDefaultDescription
FoliageTableUDataTable*nullRequired. The data table (row type: FFoliageSwapRow) that maps source meshes to harvest/respawn behavior. Without this, the component does nothing.
DefaultPayloadWorldActorTSubclassOf<AActor>nullGeneric 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.
bUseReflectionInitboolfalseWhen 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.
PayloadInitFunctionNameFNameNoneRequires 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.
PayloadClassParamNameFNameNoneRequires 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.
PayloadQuantityParamNameFNameNoneRequires bUseReflectionInit Name of the int32 property inside the init function's first struct parameter to receive the quantity. Example: QuantityToGive.
PayloadDataTableParamNameFNameNoneRequires 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.
PayloadRowNameParamNameFNameNoneRequires bUseReflectionInit Name of the FName property inside the init struct to set to the Payload Row Name.

Detection Group

PropertyTypeDefaultDescription
DetectionRadiusfloat300Sphere 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.
InteractionRadiusfloat150Distance 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.
CheckIntervalfloat0.25Time 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.
GridSizefloat50Cell 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.
ObjectTypesToQueryArray<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

PropertyTypeDefaultDescription
MaxSwapsPerCheckint3210Maximum 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).
MaxOverlapsPerCheckint3264Maximum overlap results to evaluate per detection tick. Caps memory allocation during the overlap query. Set 0 for unlimited.
MaxDetectionTimeMsfloat5.0If 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.
bUseActorPoolingbooltruePre-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.
ActorsPerPoolint3220Number 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.
HarvestActorScaleMultiplierfloat1.001Scale 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.
bUseRenderSyncbooltrueCalls 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.
MaxActiveHarvestablesint321000Maximum 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

PropertyTypeDefaultDescription
CooldownSecondsfloat2.0Time 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).
CooldownCleanupIntervalfloat5.0Seconds between cleanup passes that remove expired cooldown entries from memory. Lower values keep the cooldown map smaller but increase CPU frequency.
MaxCooldownEntriesint325000Hard 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

PropertyTypeDefaultDescription
bForceReplicateHarvestActorsbooltrueCalls 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.
bForceAlwaysRelevantHarvestActorsbooltrueSets 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).
HISMRemovalTolerancefloat25Tolerance 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.
SMCVisibilityTolerancefloat30Tolerance 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

PropertyTypeDefaultDescription
bHandleWorldPartitionbooltrueEnables 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.
bAutoResumeOnStreamInbooltrueWhen a World Partition cell streams back in, automatically resumes any respawn timers that were paused when the cell streamed out. Recommended: true.
StreamingCheckIntervalfloat2.0Seconds 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

PropertyTypeDefaultDescription
bDebugDrawboolfalseDraws 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.
DebugPointSizefloat12Pixel size of debug visualization points when bDebugDraw is enabled. Increase for visibility in large scenes.
DebugHISMColorFColorCyanColor for HISM/ISM debug points.
DebugSMCColorFColorYellowColor for StaticMeshComponent debug points.
bVerboseLoggingboolfalseLogs 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

PropertyTypeDescription
HarvestsSucceededint32Running count of successful harvests. Replicated and Blueprint-writable for custom tracking.
HarvestsFailedint32Running count of failed harvest attempts (cooldown blocked, max limit reached, etc.).
RespawnsCompletedint32Running count of completed respawns.

BlueprintCallable Functions

FunctionDescription
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.
🔄
Section 8

FoliageRespawnComponent — Full Reference

Respawn Core

PropertyTypeDefaultDescription
DefaultRespawnDelayfloat5.0Fallback 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.
MaxActiveRespawnsint32500Maximum number of concurrent pending respawn entries. When full, new respawns are dropped. Prevents memory growth in very long sessions.
CleanupIntervalfloat10.0Seconds between cleanup passes that remove completed respawn entries from memory.
bCancelRespawnsOnUnloadboolfalseIf 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

PropertyTypeDefaultDescription
bUseRegrowAnimationbooltrueGlobal 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.
RegrowAnimationDurationfloat1.0Global 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.
AnimationTickRatefloat30How 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.
bUsePooledRegrowTimerbooltrueWhen 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.
StandardMinScalefloat0.02Starting 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.
NaniteMinScalefloat0.03Starting scale factor for Nanite-enabled meshes. Slightly higher than StandardMinScale due to Nanite rendering characteristics at extreme scales.
SafetyMinScalefloat0.10Absolute 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

PropertyTypeDefaultDescription
bUseDistanceBasedInstantSpawnbooltrueWhen 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.
InstantSpawnDistancefloat3000Distance 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.
InstantRespawnMinDistancefloat500Minimum 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.
RespawnRadiusCheckfloat150Radius 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

PropertyTypeDefaultDescription
InstanceMatchTolerancefloat5.0Primary 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.
InstanceMatchToleranceExpandedfloat15.0Expanded 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

PropertyTypeDefaultDescription
bUseBatchProcessingbooltrueWhen 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).
MaxRespawnsPerBatchint3220Maximum respawns processed per batch tick. Higher values = faster catch-up after returning to an area, but potential frame spikes. Recommended: 10–30.
BatchProcessingDelayfloat0.1Seconds between batch processing cycles. Combined with MaxRespawnsPerBatch, controls how quickly a queue of pending respawns is processed.

World Partition Settings

PropertyTypeDefaultDescription
bHandleWorldPartitionbooltrueSubscribe to level stream events and manage respawn pausing. Must match the setting on the swap component.
bPauseRespawnsWhenUnloadedbooltrueWhen the cell containing a respawning mesh unloads, pause its respawn timer (storing remaining time). Timer resumes when cell reloads.
bResumeRespawnsOnLoadbooltrueWhen a cell loads, automatically resume all paused respawns that were waiting in that cell.
bInstantRespawnOnStreamInbooltrueWhen 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

EventParametersDescription
OnRespawnCompleteUStaticMesh* Mesh, FVector Location, bool bWasAnimatedFires when a foliage instance finishes respawning. bWasAnimated = true if the scale animation played, false if instant respawn was used.
OnRespawnFailedUStaticMesh* Mesh, FVector LocationFires when a respawn attempt fails (e.g., ISM component invalid, max retries exceeded).
OnRespawnStartedUStaticMesh* Mesh, FVector LocationFires when the respawn timer begins (immediately after harvest, before the delay).
OnRespawnPausedUStaticMesh* Mesh, FVector LocationFires when a pending respawn is paused due to World Partition level unload.
OnRespawnResumedUStaticMesh* Mesh, FVector LocationFires when a paused respawn is resumed after the level cell streams back in.

Respawn Component Blueprint Functions

FunctionDescription
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.
📣
Section 9

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.

OnPayloadHarvested Server Only
UStaticMesh* Mesh — The source foliage mesh that was harvested
FVector Location — World location of the harvested instance
UClass* PayloadClass — The class from the row's Payload Class field (null if using DataTable mode)
int32 Quantity — Amount to deliver (from row's Payload Quantity)
AActor* WorldActor — The spawned/pooled pickup actor in the world (may be null for delegate-only setups)
UDataTable* PayloadDataTable — The data table from the row (null if using Payload Class mode)
FName PayloadRowName — The row name from the row (None if using Payload Class mode)
USkeletalMesh* PayloadSkeletalMesh — Skeletal mesh from the row (null if not set)
TArray<FFoliagePayloadVariable> CustomVars — All custom variables from the row (empty array if none defined)
The primary integration point. Fires on the server whenever a mesh with a Payload Class or Payload Data Table configured is harvested. Use this to add items to the player's inventory, grant experience, trigger quests, or run any gameplay logic. Does not fire for Harvest Actor Only rows (Mode A).
OnMeshHarvested All Clients
UStaticMesh* Mesh
FVector Location
AActor* HarvestActor
Fires on all machines (via multicast) after a mesh is successfully harvested. Safe for visual feedback such as VFX, sounds, or UI updates that should play on all clients. Does not carry payload data — use OnPayloadHarvested for item delivery.
OnHarvestFailed Server Only
UStaticMesh* Mesh
FVector Location
Fires when a harvest attempt is blocked — by cooldown, max active harvestables limit, CanHarvestInstance returning false, or pool exhaustion. Use for player feedback ("You can't harvest this right now").
OnRespawnStarted Server Only
UStaticMesh* Mesh
FVector Location
Fires immediately after harvest when the respawn timer is enqueued. At this point, the mesh is hidden and the timer is running but has not fired yet.
OnRespawnComplete Server Only
UStaticMesh* Mesh
FVector Location
bool bWasAnimated
Fires when a respawn completes (mesh is fully visible again). bWasAnimated indicates whether the scale animation played or if it was an instant respawn.
OnActorPoolInitialized Server Only
TSubclassOf<AActor> ActorClass
int32 PoolSize
Fires once per actor class during BeginPlay when the pool is initialized. Useful for tracking which classes were pooled and verifying pool sizes in development.
ℹ️
Re-binding after signature changes Whenever the delegate signature changes (e.g. a new parameter was added), all existing Blueprint bindings to that delegate become invalid and must be re-created. The parameter count/types shown above reflect the current build.
🔧
Section 10

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

SpawnHarvestActorBP(Row, SpawnTM, DesiredScale) → AActor*
Override to fully customize harvest actor spawning and pooling logic. The default C++ implementation handles pooled retrieval and fresh spawning with correct transforms. Override this when you need non-standard actor placement, custom pooling, or when a third-party plugin manages actor lifecycle. Must return a valid actor reference or null.
InitializePayloadActorBP(Actor, Row)
Called after all C++ initialization paths (interface and reflection) complete. This is the recommended override point for inventory system integration without C++. The full row is passed so you can access PayloadClass, PayloadDataTable, PayloadRowName, PayloadQuantity, PayloadSkeletalMesh, and CustomPayloadVars. The default C++ implementation is a no-op.

Example use: Cast Actor to your pickup Blueprint class, then call SetupPickup(Row.PayloadClass, Row.PayloadQuantity) to initialize it with your inventory data.
CanHarvestInstance(Mesh, Location) → bool
Override to add custom harvest conditions. Return 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.
OnHarvestSuccess(Mesh, Location, HarvestActor)
Called after a mesh is successfully removed and the harvest actor is placed. Override to play VFX, sounds, or trigger gameplay systems that should respond to every harvest regardless of payload type. HarvestActor may be null for delegate-only rows.
OnHarvestFailure(Mesh, Location)
Called when a harvest attempt is blocked. Override to show player feedback such as a screen message, shake the camera, or play a "denied" sound. Complementary to the OnHarvestFailed delegate.

FoliageRespawnComponent Overrides

CanPerformRespawn(Mesh, Location) → bool
Override to add conditions that must be met before a respawn executes. Return 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.
OnRespawnExecute(Mesh, Location, bIsAnimated)
Called after the mesh instance is restored to its original scale/visibility but before the animation begins. Override to add respawn VFX (particle burst, light flash), sounds, or gameplay notification. bIsAnimated indicates whether the scale animation will play.
OnInstantSpawnUsed(Mesh, Location)
Called when distance-based instant spawn is used instead of the animation. Override if you need different behavior for instant vs animated respawn — for example, playing a "teleport in" VFX for instant spawns while the animated version shows a growing effect.
// 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)
🎒
Section 11

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 ByRow SetupIntegration Method
Data table row (struct-based, FDataTableRowHandle)Set Payload Data Table + Payload Row NameWire OnPayloadHarvested → use PayloadDataTable + PayloadRowName to build FDataTableRowHandle → pass to AddItem(RowHandle, Qty)
UObject/Blueprint class (TSubclassOf)Set Payload ClassWire OnPayloadHarvested → use PayloadClass output pin → pass to AddItem(PayloadClass, Qty)
Data asset (UPrimaryDataAsset subclass)Set Payload Class to the data asset's classWire 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 namesNo Blueprint wiring needed — the system calls the named function automatically via C++ reflection
Custom / unique per-systemAny modeOverride 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.
💡
InitializePayloadActorBP always fires last The C++ init order is: (1) IFoliageHarvestable interface → (2) reflection path (if enabled) → (3) InitializePayloadActorBP BP override. The BP override fires regardless of which earlier path ran, so you can use it to complement reflection rather than replace it.
🌲
Section 12

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 SourceComponent TypeDefault MobilityRequired Channel
Foliage Paint ToolHISMStaticWorldStatic
Placed Static Mesh ActorSMCStatic or MovableWorldStatic or WorldDynamic
PCG Graph (Spawn Static Mesh node)ISM or HISMStaticWorldStatic
Blueprint ISM/HISM ComponentISM/HISMTypically StaticWorldStatic
Blueprint Actor with SMCSMCDepends on actor mobilityWorldStatic or WorldDynamic
⚠️
Movable actors need WorldDynamic If you place a static mesh actor in the level and its Root Component mobility is set to Movable (the default for newly placed actors in some editor modes), its collision object type will be WorldDynamic, not WorldStatic. The mesh will be invisible to the overlap query unless WorldDynamic is added to ObjectTypesToQuery.
♻️
Section 13

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

BeginPlay: Scan all rows in the data table
For each unique actor class: spawn ActorsPerPool actors, hide them, mark them available
On harvest: retrieve actor from pool → place at harvest location → show actor
On respawn: hide actor → reset transform → return to pool
If pool is empty during harvest: spawn a new actor (pool grows dynamically)

Pool Configuration

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....

⚠️
Pooled actors are re-used, not re-initialized When a pooled actor is retrieved from the pool, its state from the previous harvest may still be present unless reset. If your pickup actor accumulates state (e.g. it tracks whether it has been picked up), you must reset that state in InitializePayloadActorBP or inside the actor's own BeginPlay/Reset function. The system calls InitializePayloadActorBP every time an actor is activated from the pool.
🌐
Section 14

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

OperationWho Runs ItHow Clients Know
Detection (sphere overlap)Server OnlyNo client detection — server decision is authoritative
HISM/ISM instance removalServerMulticast_RemoveHISMInstanceAt / Multicast_RemoveISMInstanceAt
SMC visibility hideServerMulticast_SetSMCVisible(false)
Harvest actor showServerMulticast_ShowHarvestActor
Regrow animationServer startsMulticast_StartRegrowAnimation / Multicast_StartSMCRegrowAnimation
OnPayloadHarvestedServer OnlyClients do not receive this — use OnMeshHarvested for client-side VFX
Inventory grantServer OnlyReplicated via your inventory system's own replication

Setup for Multiplayer

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.

🗺
Section 15

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

Player harvests a mesh in Cell A → respawn timer starts
Player moves away → Cell A unloads (OnLevelRemovedFromWorld)
Respawn timer pauses → remaining time stored in FPendingRespawn entry
Player returns → Cell A reloads (OnLevelAddedToWorld)
Timer resumes with remaining time → mesh respawns (instant if timer already expired)

Configuration Checklist

ℹ️
Instant respawn on stream-in When 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.
🔬
Section 16

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

Requirements for PCG Meshes

  1. The PCG graph must use a Spawn Static Mesh node outputting to a StaticMeshComponent (generates an ISM or HISM).
  2. The mesh asset assigned in the PCG graph's Spawn Static Mesh node must match the Source Foliage Mesh in the data table row exactly (same asset reference).
  3. The PCG component's actor must use WorldStatic collision 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.

⚠️
PCG Graph must be loaded before BeginPlay fires If a PCG graph generates after the player's BeginPlay (due to async generation or World Partition streaming), the swap component may not register it immediately. In that case, call ForceDetectionScan() after the graph generation completes to manually trigger a re-scan.
Section 17

Performance Tuning

Key Performance Levers

SettingImpactGuidance
CheckIntervalDetection CPU frequency0.25 is the default (4 checks/s). Increase to 0.5–1.0 in performance-critical areas. Use PauseDetection during cutscenes.
DetectionRadiusOverlap query sizeKeep as small as viable. A 300 cm radius is typical. Very large radii (>1000 cm) dramatically increase overlap results.
MaxOverlapsPerCheckMemory per detection tick64 is rarely a bottleneck. Reduce to 16–32 in sparse areas. Increase to 128+ in very dense forests.
MaxSwapsPerCheckHarvests per tick10 prevents frame spikes. In dense areas, extra harvests are deferred to the next tick naturally — this rarely causes gameplay issues.
bUseActorPoolingSpawn costAlways enable. Pre-spawned actors cost a small amount of memory at startup but eliminate runtime spawn latency entirely.
ActorsPerPoolMemory at startupSet to the maximum number of harvestable meshes a player can simultaneously be within InteractionRadius of. Typically 5–30.
bUseDistanceBasedInstantSpawnAnimation CPUAlways enable. Distant respawns are free (instant). Only nearby respawns animate.
bUseBatchProcessingRespawn frame spikesAlways enable. Distributes the respawn burst across multiple frames when returning to a previously-harvested area.
AnimationTickRateAnimation smoothness vs CPU30 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
🐛
Section 18

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:

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:

💡
Diagnostic workflow 1. Enable bVerboseLogging. 2. Walk up to the target mesh. 3. Check the log. If you see "not in MeshSwap data table", the mesh reference in the row doesn't match. If you see "ObjType=[N]" where N doesn't match your ObjectTypesToQuery, add that channel. If you see "outside InteractionRadius", reduce the distance or increase the radius.

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:

🔍
Section 19

Troubleshooting

Nothing happens when I walk near a mesh

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.
Mesh disappears but no pickup actor appears
  • 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).
Mesh disappears from far away / immediately on approach
  • 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.
Mesh harvested but OnPayloadHarvested doesn't fire
  • 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.
Pickup actor appears but has no mesh / wrong mesh
  • 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.
Mesh respawns instantly / vanishes without being picked up
  • 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.
Regrow animation doesn't play
  • 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.
Pool drift warning in logs: "Tracked=N, Actual=M. Correcting..."

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
"Failed to find script package /Script/SwapCore" in logs

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.
Reflection path: function not found or parameters not injected
  • bUseReflectionInit must be true. Without this, the reflection path is completely skipped and the param names are ignored.
  • Function name must match exactly. PayloadInitFunctionName is 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. PayloadClassParamName refers 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.
Multiplayer: clients don't see mesh removal or pickup actor
  • 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.
World Partition: meshes respawn with wrong timing or not at all
  • 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.
⬇ Download This Documentation