Decals and hit reactions
Non-breaking hit reactions with bounded optional impulse, and deterministic impact decals with a per-object limit.
Both features are opt-in and disabled by default. They cover the hits that do not destroy an object — the majority of hits in a melee-heavy game.
Hit reactions
A hit reaction is a request emitted for a valid hit that leaves the object standing.
| Property | Default | Meaning |
|---|---|---|
bEnableHitReactions | false | Master switch |
MinimumHitReactionPower | VeryLight | Power gate for reactions |
bApplyBuiltInPhysicsReaction | true | Apply a bounded impulse when the intact component already simulates physics |
HitReactionImpulseMultiplier | 0.2 | Fraction of the incoming impulse used |
MaximumHitReactionImpulse | 250.0 | Hard cap on the applied impulse |
When the impulse applies
The impulse never turns simulation on
The built-in impulse is applied only when the intact primitive component is simulating physics at that moment. A hit reaction never switches simulation on by itself.
Whether the intact simulates is decided by its physics mode, at BeginPlay:
- New generations use
Simulatedby default, so their intact prop is already simulating and the built-in impulse can move it; Static,Keep Existingand older setups that were not migrated are not changed silently — a static prop stays static, and the impulse is skipped.
See Intact physics and fall damage.
The request is emitted either way. An object that does not simulate physics still reports the reaction, so your own code can play an animation, a shader wobble or a sound instead.
The request payload
FSDFHitReactionRequest:
| Field | Meaning |
|---|---|
SourceActor | The object that was hit |
Location | World-space impact position |
Direction | Direction of the incoming hit |
RequestedImpulse | Impulse the profile computed, after multiplier and cap |
DestructionPower | Power of the causing hit |
RandomSeed | Seed for deterministic reaction selection |
bPhysicsImpulseApplied | Whether the built-in impulse was actually applied |
Listen on OnHitReactionRequested, or read GetLastHitReactionRequest on demand.
bPhysicsImpulseApplied tells you unambiguously whether the object moved because of SDF, which is
what your own reaction logic needs in order not to double up.
Typical use
- A hanging lantern that swings when struck — physics simulating, built-in impulse enabled.
- A stone statue that flashes and sparks but does not move — reactions enabled, Physics
Static. - A crate that only reacts to real attacks — raise
MinimumHitReactionPowerabove the value your light attacks carry.
Impact decals
| Property | Default | Meaning |
|---|---|---|
bEnableImpactDecals | false | Master switch |
bEnableBuiltInDecalSpawning | true | Disable when an external listener owns decal presentation |
MinimumDecalPower | VeryLight | Power gate |
MaximumImpactDecals | 3 | Per-object limit (0–32) |
ImpactDecals | empty | Weighted variations |
Decal variations
FSDFWeightedDecal:
| Property | Default | Meaning |
|---|---|---|
Material | none | Soft UMaterialInterface reference |
Weight | 1.0 | Relative selection weight |
Size | (8, 16, 16) | Decal projection box |
LifeSpan | 20.0 | Seconds before the decal fades out |
FadeScreenSize | 0.01 | Screen size below which the decal stops rendering |
bRandomizeRoll | true | Randomise roll around the projection axis |
Deterministic orientation
SelectWeightedDecalVariation(Variations, RandomSeed, ImpactNormal, OutRotation) picks the variation
and computes the rotation. Decals are projected along the impact normal; with bRandomizeRoll
enabled the roll is derived from the seed, so repeated hits do not produce visibly identical stamps —
and the same seed reproduces the same stamp exactly.
The per-object limit
MaximumImpactDecals caps how many decals one object carries. Once the limit is reached, further
decal requests are still emitted but marked bSuppressedByObjectLimit, so an external system knows
the framework deliberately declined rather than failed.
GetActiveImpactDecalCount returns the current count. Tracked decals are destroyed when the object
is cleaned up, so a destroyed crate does not leave its dents floating in the level.
The request payload
FSDFDecalRequest:
| Field | Meaning |
|---|---|
SourceActor | The object that was hit |
Location | World-space impact position |
ImpactNormal | Projection normal |
Rotation | Resolved rotation |
Size | Resolved projection size |
SelectedMaterial | The variation picked |
RandomSeed | Seed used |
SelectedVariationIndex | Index into the profile array |
bBuiltInSpawnRequested | Built-in spawning was configured |
bBuiltInSpawnAttempted | A spawn was attempted |
bBuiltInSpawnSucceeded | The decal component was created |
bSuppressedByObjectLimit | The per-object limit declined this request |
Listen on OnImpactDecalRequested, or read GetLastImpactDecalRequest.
Cost guidance
Decals are cheap individually and expensive in aggregate. In a room with thirty destructible props,
a limit of 3 per object is already up to ninety decal components. Two practical rules:
- keep
MaximumImpactDecalsat1or2for common props, and reserve3+ for hero objects; - keep
LifeSpanshort — a decal that outlives the fight adds cost without adding information.
Last updated: 2026-09-15