Skip to content

Drop Areas (DropAreaBase)

The DropAreaBase base class lets you create custom drop areas without knowing system internals. It handles all interaction mechanics, while the subclass defines only what to do with the items.


Two Usage Patterns

flowchart TD
    BASE["DropAreaBase"]
    BASE --> SIMPLE["Simple Consumption"]
    BASE --> DELEG["Delegation"]

    SIMPLE --> WDZ["WorldDropZone\nTrashZone\nSellZone"]
    DELEG --> IDA["InventoryDropArea"]

    style BASE fill:#7c4dff,color:#fff
    style SIMPLE fill:#00c853,color:#fff
    style DELEG fill:#ff6d00,color:#fff
Pattern Idea When to Use
Simple Consumption Override CanAcceptEntry + ProcessEntry. Source removal is automatic World zone, trash, sell --- any target without an inventory
Delegation Override GetDropProcessor(), return your own IDropProcessor Inventory areas with complex logic (policy, swap, conversion)

Simple Consumption

A minimal subclass requires just two methods:

public class TrashDropZone : DropAreaBase
{
    protected override bool CanAcceptEntry(DragEntry entry)
    {
        // Accept everything
        return entry.Stack != null && !entry.Stack.IsEmpty;
    }

    protected override bool ProcessEntry(ItemStack freshStack, DragEntry entry)
    {
        // Do nothing --- items simply disappear.
        // Source removal is handled automatically by the base class.
        return true;
    }
}

How It Works Inside

flowchart TD
    A["Player drops item"] --> B["DropAreaBase starts processing"]
    B --> C["For each entry"]
    C --> D{"CanAcceptEntry(entry)?"}
    D -->|Yes| E["Copy stack from source slot"]
    E --> F{"ProcessEntry(freshStack, entry)"}
    F -->|true| G["Remove items from source + emit events"]
    G --> H{"More entries left?"}
    H -->|Yes| C
    H -->|No| I["DropResult.Succeeded"]

Delegation

When you need complex drop logic (the transfer pipeline, policies, swaps), override GetDropProcessor():

public class CustomInventoryArea : DropAreaBase
{
    [SerializeField] private UniversalInventory _inventory;

    protected override bool TryActivateAsFocusedTarget()
    {
        if (_inventory == null) return false;
        // Custom validation...
        DragManager.PushDropTarget(this);
        return true;
    }

    public override IDropProcessor GetDropProcessor()
    {
        // Return your own processor instead of this
        return new InventoryDropProcessor(_inventory, DragManager?.GlobalRules);
    }

    public override BaseSlot GetTargetSlot() => null;
}

In this case, CanAcceptEntry and ProcessEntry of the base class are never called --- all processing goes through the returned processor.


What the Base Class Handles

DropAreaBase extends Selectable and implements IDropTarget + IDropProcessor.

Automatic Mechanics

Mechanic Description
Pointer tracking OnPointerEnter/Exit automatically push/pop to DragAndDropManager's target stack
State events Subscribes to OnDragStarted/Cancelled/Completed/Ended to manage interactable and raycast
Raycast management raycastTarget is enabled only during a drag
Gamepad support Selectable inheritance provides navigation and focus out of the box
Highlight lifecycle OnBecomeActiveTarget/OnBecomeInactiveTarget call OnHighlightChanged
Source removal In the simple consumption pattern --- automatic removal from the source slot with event emission

All Override Points

public abstract class DropAreaBase : Selectable, IDropTarget, IDropProcessor
{
    // ── Simple Consumption ──────────────────────
    // Override for non-inventory zones

    protected virtual bool CanAcceptEntry(DragEntry entry);
    // Can this zone accept the entry?
    // Default: true

    protected virtual bool ProcessEntry(ItemStack freshStack, DragEntry entry);
    // What to do with the items? Source removal is automatic.
    // Default: false (no-op)

    // ── Delegation ──────────────────────────────
    // Override for inventory-based zones

    public virtual IDropProcessor GetDropProcessor();
    // Default: this (simple consumption)

    public virtual BaseSlot GetTargetSlot();
    // Default: null

    // ── Common ──────────────────────────────────

    internal virtual bool TryActivateAsFocusedTarget();
    // Called when cursor enters during a drag.
    // Default: checks CanAcceptEntry on first entry + PushDropTarget.

    protected virtual void OnTargetDeactivated();
    // Called when cursor leaves. For resetting internal state.

    protected virtual void OnHighlightChanged(bool highlighted, bool canAccept);
    // Called when highlight state changes. highlighted = is the zone active,
    // canAccept = can it accept the current item.

    protected virtual bool RemoveFromSource { get; }
    // Whether to remove items from source after ProcessEntry.
    // Default: true. Override to false for copy/preview zones.
}

Subclass Examples

WorldDropZone --- Spawn in 3D World

The snippet below follows the current Demo2 Loot implementation.

public class WorldDropZone : DropAreaBase
{
    [SerializeField] private Transform _spawnPoint;

    protected override bool CanAcceptEntry(DragEntry entry)
    {
        // All adapters must have a 3D prefab
        foreach (var adapter in entry.Stack.Adapters)
            if (adapter is not ItemAdapterSoWith3DAdapter with3D || with3D.WorldPrefab == null)
                return false;
        return true;
    }

    protected override bool ProcessEntry(ItemStack freshStack, DragEntry entry)
    {
        foreach (var adapter in freshStack.Adapters)
        {
            var with3D = (ItemAdapterSoWith3DAdapter)adapter;
            Instantiate(with3D.WorldPrefab, _spawnPoint.position, Quaternion.identity);
        }
        return true;
    }
}

SellDropZone --- Sell for Gold

public class SellDropZone : DropAreaBase
{
    [SerializeField] private PlayerWallet _wallet;

    protected override bool CanAcceptEntry(DragEntry entry)
    {
        return entry.Stack.PrimaryAdapter is ISellable;
    }

    protected override bool ProcessEntry(ItemStack freshStack, DragEntry entry)
    {
        int total = 0;
        foreach (var adapter in freshStack.Adapters)
            total += ((ISellable)adapter).SellPrice;
        _wallet.AddGold(total);
        return true;
    }
}

PreviewDropZone --- View Without Removal

public class PreviewDropZone : DropAreaBase
{
    protected override bool RemoveFromSource => false;

    protected override bool CanAcceptEntry(DragEntry entry)
        => entry.Stack?.PrimaryAdapter is IPreviewable;

    protected override bool ProcessEntry(ItemStack freshStack, DragEntry entry)
    {
        ShowPreview(freshStack);
        return true;
    }
}

RemoveFromSource = false disables automatic source removal. Items remain in the original inventory.


Highlighting

Override OnHighlightChanged for visual feedback:

[SerializeField] private Image _highlight;
[SerializeField] private Color _validColor = Color.green;
[SerializeField] private Color _invalidColor = Color.red;

protected override void OnHighlightChanged(bool highlighted, bool canAccept)
{
    if (_highlight == null) return;
    _highlight.color = highlighted
        ? (canAccept ? _validColor : _invalidColor)
        : Color.clear;
}

canAccept is determined automatically via CanAcceptEntry on the first entry during hover.


Reference

Class Pattern Description
DropAreaBase --- Base class for all drop areas
WorldDropZone Simple Consumption Spawns 3D objects in the world
InventoryDropArea Delegation Drop into an inventory through the transfer pipeline