Skip to content

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

  1. BeforeApply runs
  2. If cancel == true
  3. wrapped effect is skipped
  4. AfterApply is not executed
  5. Wrapped effect executes
  6. AfterApply runs

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:

  • VFX
  • Sound
  • DebugLog
  • Conditional
  • PersistentVFX

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:

  1. Create the core effect
  2. Sort DecoratorDefinition entries by priority
  3. For each entry:
  4. Resolve the creator
  5. Instantiate the decorator
  6. Set wrappedEffect to the current effect
  7. Replace the current effect with the decorator
  8. 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