RevFramework – Pickups • Decorators¶
Folder: Runtime/Systems/Pickups/Decorators
The Decorators folder contains effect wrappers — small, composable units that add behaviour
before and/or after a pickup effect executes, without modifying the effect itself.
Decorators allow effects to remain clean, modular, and single‑purpose while still being fully extensible.
✔ Extend effects safely
✔ Add visuals, audio, conditions, or debugging
✔ Chainable & order‑driven
✔ Zero reflection
✔ Fully integrated with the effect factory pipeline
🎯 Purpose¶
Decorators provide:
- Before / After hooks around an effect
- Optional cancellation of wrapped effects
- Per‑effect modular behaviours (VFX, SFX, conditions, debug messages)
- A fully chainable, order‑controlled composition pipeline
- Clean separation of responsibilities (core effects stay minimal)
Decorators never modify an effect — they wrap it.
🧩 Key Base Class¶
PickupEffectDecorator¶
The abstract base class for all pickup decorators.
- Inherits from
PickupEffect - Wraps an inner effect via:
public PickupEffect wrappedEffect;
- Provides overridable hooks:
protected virtual void BeforeApply(IDamageable target, GameObject context, ref bool cancel);
protected virtual void AfterApply(IDamageable target, GameObject context);
🔁 Execution Flow¶
BeforeApplyruns- If
cancel == true→ - wrapped effect is skipped
AfterApplyis not executed- Wrapped effect executes
AfterApplyruns
This flow is implemented entirely inside PickupEffectDecorator and requires no special handling by callers.
📦 Built‑in Decorators (accurate)¶
| Decorator | Behaviour |
|---|---|
| DebugLogDecorator | Logs effect activation (optionally appending wrapped effect name). |
| VFXDecorator | Spawns a one‑shot VFX prefab, optional parenting. |
| PersistentVFXDecorator | Spawns an attached or persistent VFX with optional lifetime and destroy‑with‑actor behaviour. |
| SoundDecorator | Plays one‑shot audio via AudioSFXChannel or falls back to AudioSource.PlayClipAtPoint. |
| ConditionalDecorator | Gates execution using health %, tag checks, and/or random chance. |
These behaviours map one‑to‑one with their runtime implementations.
🧱 DecoratorType¶
Enum used by definitions to declare which decorator type to apply:
VFXSoundDebugLogConditionalPersistentVFX
Decorator creators map these enum values to concrete decorator implementations.
🧪 Decorator Creators (accurate)¶
Located in:
Runtime/Systems/Pickups/Decorators/Creators
Each creator implements:
public interface IPickupDecoratorCreator
{
bool CanHandle(DecoratorType type);
PickupEffect Create(PickupEffect baseEffect, PickupEffectDefinitionBase def);
}
Creators are responsible for:
- Instantiating the correct decorator
- Copying settings from the definition
- Assigning
wrappedEffect - Returning the new decorator node
No reflection is used — everything is strongly typed and deterministic.
🏭 Factory Integration (truth‑aligned)¶
PickupEffectFactory.BuildEffect() processes decorators in ascending priority order.
Construction flow:
- Create the core effect
- Sort
DecoratorDefinitionentries bypriority - For each entry:
- Resolve the creator
- Instantiate the decorator
- Set
wrappedEffectto the current effect - Replace the current effect with the decorator
- Return the outermost decorator
Ordering rule:
- Lower priority values wrap earlier (inner)
- Higher priority values wrap later (outer)
This ordering is deterministic and consistent across all usages.
📚 Registry¶
DefaultPickupDecoratorRegistry¶
Ships with built‑in creators:
- Sound
- VFX
- PersistentVFX
- Conditional
- DebugLog
The registry can be extended or replaced at runtime:
PickupEffectFactory.RegisterCreator(new MyCustomDecoratorCreator());
PickupEffectFactory.UnregisterCreator(existingCreator);
PickupEffectFactory.SetRegistry(customRegistry);
➕ Adding Your Own Decorator¶
1️⃣ Create the decorator¶
public class MyCustomDecorator : PickupEffectDecorator
{
protected override void BeforeApply(IDamageable target, GameObject context, ref bool cancel)
{
// pre‑effect logic
}
protected override void AfterApply(IDamageable target, GameObject context)
{
// post‑effect logic
}
}
2️⃣ Create the creator¶
public class MyCustomDecoratorCreator : IPickupDecoratorCreator
{
public bool CanHandle(DecoratorType type) => type == DecoratorType.Custom;
public PickupEffect Create(PickupEffect baseEffect, PickupEffectDefinitionBase def)
{
var deco = ScriptableObject.CreateInstance<MyCustomDecorator>();
deco.wrappedEffect = baseEffect;
return deco;
}
}
3️⃣ Register (optional)¶
To enable global usage:
PickupEffectFactory.RegisterCreator(new MyCustomDecoratorCreator());
4️⃣ Assign via a Definition¶
Add a DecoratorDefinition entry to your ScriptableObject definition.
The factory handles ordering and chaining automatically.
📘 See Also¶
- Core — effect pipeline & factory
- Definitions — ScriptableObject effect authoring
- Effects — concrete gameplay logic
- Runtime — world pickups, triggers, authority