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¶
AnimatorTriggerPickupDefinitionShieldPickupDefinitionTeleportPickupDefinitionVfxBurstPickupDefinition
Supported Built-in Effect Types¶
AnimatorTriggerEffectCompositeEffectTeleportEffectVfxBurstEffect
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:
VFXSoundDebugLogConditionalPersistentVFX
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¶
ConditionalDecoratorCreatorDebugLogDecoratorCreatorPersistentVFXDecoratorCreatorSoundDecoratorCreatorVFXDecoratorCreatorDefaultPickupDecoratorRegistry
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¶
PickupSuccessSFXPickupSuccessVFXPickupFailSFXPickupFailFlash
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, andHoldToPickupmodes- an authority check, since 1.3.0 — resolved in
TryPickupbeforeDoPickupruns, so subclasses do not write one. A no-op where noIPickupAuthorityis in reach - optional facing checks, measured in the plane
facingPlanenames (PickupFacingPlane.XZ/XY/Auto), against anIFacingProviderfound on the actor or its children - optional respawn behaviour
- prompt UI support
- success/failure feedback hooks
- trigger-forwarded interaction flow
PickupMode¶
Interaction modes:
AutoPressToPickupHoldToPickup
Trigger Relay Components¶
TriggerRelay2DTriggerRelay3D
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/Exitare forwarded toIPickupTriggerReceiver.OnEnter/OnStay/OnExit- the collider on the same GameObject is set to
isTriggeronAwake - 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.
Awakeruns duringInstantiate, so aTriggerPickup(orInteractablePickupBasesubclass) 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 ownAwakeand 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:
PickupDimensionCollider2DTypeCollider3DTypePickupPrefabSettings
Other Supported Runtime Components¶
PickupBillboardShieldSystem
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
MonoBehaviourhelpers - 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.