Skip to content

Pickups System — Public API

This page defines the supported, stable public API for the RevFramework Pickups System.

This is the contract

If something is not listed here, it is not supported as a public integration point — even if it appears accessible in code.

Info

Audience: Developers integrating Pickups into gameplay, tools, or UI
Scope: Runtime public API, supported runtime components, and supported extension seams
Stability: Breaking changes to items listed on this page are avoided or clearly versioned


Core Concepts

  • Pickups are built around effects (PickupEffect) and definitions (PickupEffectDefinitionBase).
  • World pickups and interactables are provided as Unity-facing runtime components.
  • Effect construction is factory-driven and supports decorator composition.
  • Public APIs are intentionally kept smaller than the total code surface.
  • Internal helpers, internal concrete implementations, and test-only visibility hooks are not part of the supported contract.

Core Runtime Effect Model

PickupEffect

The base class for pickup effects using the standard apply path.

Responsibilities

  • Defines the core effect contract for pickup execution
  • Applies cooldown gating on the standard apply path
  • Enforces null-damageable policy unless the effect explicitly allows otherwise
  • Provides the base extension surface for custom pickup effects

IEffectAllowsNullDamageable

Marker interface for effects that can run without an IDamageable.

Typical uses include:

  • VFX-only effects
  • transform/movement effects
  • animator-trigger effects
  • other context-only behaviour

IPickupEffectWithContext

Optional richer effect entrypoint for effects that need item/source context.

Supported scenarios include:

  • inventory-driven item use
  • slot-aware effects
  • ownership/source-sensitive logic
  • telemetry / analytics
  • custom context-sensitive logic

Important

An effect using this interface and nothing else is invoked directly by PickupEffectRunner and does not automatically go through the standard PickupEffect.ApplyTo(...) gating path — no null-damageable check, no cooldown — and TryApply reports it as delivered whatever it did.

IPickupEffectReportsDelivery wins over it. PickupEffectRunner.TryApply(...) tests the reporting seam first, so an effect implementing both takes the reporting path and does get the base gating. That ordering is deliberate: a report the caller can read is worth more than the advisory context, and a world pickup is not an item use.


IPickupEffectReportsDelivery

Optional capability for an effect whose payload can be refused — an item into a full bag, currency a cap declines, anything the actor might not actually receive.

bool TryApply(IDamageable target, GameObject context);

Without it, applying and delivering are the same event as far as the caller can tell, and a pickup deciding whether to destroy itself cannot distinguish a full bag from a clean grant. That is how a payload is consumed without ever being received.

Implement the body in TryApply and have the OnApply override delegate to it, so both paths behave identically:

protected override void OnApply(IDamageable target, GameObject ctx) => TryApply(target, ctx);

public bool TryApply(IDamageable target, GameObject ctx)
{
    var result = svc.GiveExact(ctx, stack, container);
    return result.Success;
}

Read the answer with PickupEffect.TryApplyTo(...) or PickupEffectRunner.TryApply(...). ApplyTo(...) still exists and still returns nothing — it now delegates to the reporting path and discards the result, so existing callers are unaffected.

An effect that does not implement this is assumed to have applied

That is what the void path assumed silently before this existed, and it is true for effects that cannot fail — a VFX burst, an animator trigger. Only opt in when a refusal is genuinely possible.


ItemUseContext

Structured advisory context passed to context-aware effects.

Contains:

  • item definition
  • owner
  • slot index

All values are advisory and may be partially populated.


PickupEffectRunner

Canonical helper for applying pickup effects across:

  • world pickups
  • inventory-driven use
  • integrations that need consistent dispatch between standard and context-aware effects
// Reports whether the payload reached the actor. Prefer this.
bool TryApply(PickupEffect effect, IDamageable damageable, GameObject target);

// Fire-and-forget. Same gating, answer discarded.
void Apply(PickupEffect effect, IDamageable damageable, GameObject target);
void Apply(PickupEffect effect, IDamageable damageable, GameObject target, ItemUseContext ctx);

TryApply dispatches in this order: IPickupEffectReportsDelivery (base gates, then the effect's own answer) → IPickupEffectWithContext (called directly, gates skipped, assumed delivered) → everything else (base gates, then assumed delivered). A missing effect reports false.

Anything that consumes on apply — a pickup destroying itself — should read TryApply. Apply is for effects that cannot be refused.


PickupEffectFactory

Builds decorated pickup effect chains from authorable definitions.

Responsibilities:

  • creates the core effect from a definition
  • applies decorators in priority order
  • allows registry replacement
  • allows custom decorator creator registration

PickupEffectStaticCooldowns

Global in-memory cooldown store used by the standard pickup effect path.

Supported for:

  • reset flows
  • deterministic test setup
  • explicit owner cooldown cleanup
  • runtime cooldown clearing when required by project logic

Definitions & Authoring Surface

PickupEffectDefinitionBase

The abstract ScriptableObject base for pickup effect definitions.

Derive from this type to create custom authorable pickup definitions.


DecoratorDefinition

Authoring record describing:

  • decorator type
  • decorator priority

Used by PickupEffectDefinitionBase.decorators.


Supported Built-in Definition Types

  • AnimatorTriggerPickupDefinition
  • ShieldPickupDefinition
  • TeleportPickupDefinition
  • VfxBurstPickupDefinition

Supported Built-in Effect Types

  • AnimatorTriggerEffect
  • CompositeEffect
  • TeleportEffect
  • VfxBurstEffect

CompositeEffect implements IPickupEffectReportsDelivery: it reports delivery only when at least one child ran and no child refused. An empty composite reports a refusal, because it delivered nothing.

Two or more refusable children can re-deliver on a retry

A composite holds no memory of which children have already run. If child A delivers and child B refuses, the pickup is correctly left in the world — and a later attempt runs A again. Keeping that memory would mean keying it by (composite asset, actor), and the only actor key available is a runtime hash Unity may reuse after collection, so a wrong "already delivered" could silently lose a payload for a different actor. Re-delivery is visible and recoverable; a swallowed payload is not. For a pickup that must not re-deliver, put the payloads on the pickup component — it can track them per instance — rather than inside a shared composite asset.

Some built-in definitions may create internal concrete effect implementations.
Those internal implementation types are not automatically supported as public extension points unless listed on this page.


Decorator Extension Surface

PickupEffectDecorator

Base class for decorator-style pickup effects.

Use this when you want to wrap another effect and run logic:

  • before application
  • after application
  • conditionally cancel the wrapped effect

PickupEffectDecorator implements IPickupEffectReportsDelivery and forwards the wrapped effect's answer. A decorator over a refused payload reports a refusal; cancelling in BeforeApply reports a refusal; a decorator with nothing wrapped reports a refusal. AfterApply runs only when the wrapped effect delivered, so a "collected!" sound or VFX cannot fire over a payload the actor never received.

You do not implement the reporting yourself

The apply path is sealed on the base, so every decorator — shipped, or one you write — carries the forwarding. Override BeforeApply / AfterApply as before.


DecoratorType

Well-known decorator categories used by the factory and creator pipeline.

Built-in categories:

  • VFX
  • Sound
  • DebugLog
  • Conditional
  • PersistentVFX

IPickupDecoratorCreator

Extension contract for custom decorator creation.

Failure Handling

A creator that cannot build its decorator is skipped, and the chain continues from the last good effect. This applies whether it fails by throwing or by returning null (or a destroyed effect) — both are treated the same, and both log a warning in the editor.

A skipped decorator never removes the effect built so far. BuildEffect returns null only when the definition is null or its core effect could not be created, never because a decorator failed.

Returning null is not a way to opt out

If a decorator should apply only under some condition, wrap that logic in the decorator itself — see ConditionalDecoratorCreator. Returning null from Create is treated as a failure and logged as one, not as a deliberate skip.


IPickupDecoratorRegistry

Registry contract used by PickupEffectFactory.


Supported Built-in Creator Types

  • ConditionalDecoratorCreator
  • DebugLogDecoratorCreator
  • PersistentVFXDecoratorCreator
  • SoundDecoratorCreator
  • VFXDecoratorCreator
  • DefaultPickupDecoratorRegistry

Authority Surface

IPickupAuthority

Defines whether an actor is allowed to consume a pickup.


PickupAuthority

Resolver/cache for locating an active IPickupAuthority from scene, hierarchy, or fallback runtime context.


PickupAuthorityBinder

Default MonoBehaviour implementation of IPickupAuthority.

Suitable for simple single-player setups, demos, or placeholder authority behaviour.


Feedback Surface

IPickupFeedback

Success feedback contract.

IPickupFailFeedback

Failure feedback contract.


Built-in Feedback Components

  • PickupSuccessSFX
  • PickupSuccessVFX
  • PickupFailSFX
  • PickupFailFlash

Unity Runtime Components

TriggerPickup

Trigger-driven world pickup that applies a PickupEffect when an allowed actor enters.

Supported behaviour includes:

  • layer filtering
  • optional tag filtering
  • optional authority checks
  • duplicate-consume protection for multi-collider actors

InteractablePickupBase

Abstract Unity-facing base class for interactable pickups supporting:

  • Auto, PressToPickup, and HoldToPickup modes
  • an authority check, since 1.3.0 — resolved in TryPickup before DoPickup runs, so subclasses do not write one. A no-op where no IPickupAuthority is in reach
  • optional facing checks, measured in the plane facingPlane names (PickupFacingPlane.XZ / XY / Auto), against an IFacingProvider found on the actor or its children
  • optional respawn behaviour
  • prompt UI support
  • success/failure feedback hooks
  • trigger-forwarded interaction flow

PickupMode

Interaction modes:

  • Auto
  • PressToPickup
  • HoldToPickup

Trigger Relay Components

  • TriggerRelay2D
  • TriggerRelay3D

These forward Unity trigger callbacks into the pickup trigger receiver contract, and force the attached collider to act as a trigger.

Supported behaviour:

  • OnTriggerEnter / Stay / Exit are forwarded to IPickupTriggerReceiver.OnEnter/OnStay/OnExit
  • the collider on the same GameObject is set to isTrigger on Awake
  • the forwarded actor is the attached Rigidbody's GameObject when there is one, otherwise the collider transform's root — so a child collider still reports the actor, not the limb

One receiver, resolved once, in Awake

A relay calls GetComponent<IPickupTriggerReceiver>() once, in Awake, and holds the single component it found on the same GameObject for its lifetime. It does not re-resolve, and it does not fan out to several receivers.

Two consequences, and both are silent when you get them wrong:

  • The receiver must be authored on the prefab. Awake runs during Instantiate, so a TriggerPickup (or InteractablePickupBase subclass) added after spawning is never wired to the relay. No error, no warning — the pickup simply never fires. Adding a relay late is fine, because the relay resolves in its own Awake and finds a receiver that is already there.
  • Only the first receiver on a GameObject is used. If an object carries two, the second gets nothing. Put a second pickup behaviour on a child object with its own collider and relay.

IPickupTriggerReceiver

Shared trigger-forwarding contract used by trigger relays and pickup runtime components.

Implemented by TriggerPickup and by InteractablePickupBase (and therefore by every subclass you write). Implement it yourself when you want a relay to drive something that is neither.

A supported world pickup prefab is therefore three things authored together: a collider, a relay, and exactly one receiver.


Prefab Authoring Surface

Used by pickup prefab builders and tooling:

  • PickupDimension
  • Collider2DType
  • Collider3DType
  • PickupPrefabSettings

Other Supported Runtime Components

  • PickupBillboard
  • ShieldSystem

ShieldSystem is a supported runtime component used by shield-based pickup flows.


Explicitly Not Supported

Not supported public extension points

The following are not supported public extension points unless explicitly documented elsewhere:

  • internal concrete decorator implementations
  • internal utility MonoBehaviour helpers
  • reflection into pickup internals
  • test-only visibility files
  • private/internal runtime state manipulation
  • relying on internal bridge/helper classes

TL;DR

TL;DR

If a type appears on this page, treat it as a supported public API surface.

If it does not appear here, treat it as internal implementation detail, even if it is visible in code.