Drop Areas (DropAreaBase)
La clase base DropAreaBase te permite crear drop areas personalizadas sin conocer los internals del sistema. Se encarga de toda la mecánica de interacción, mientras que la subclase define únicamente qué hacer con los items.
Dos patrones de uso
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
| Patrón | Idea | Cuándo usarlo |
|---|---|---|
| Simple Consumption | Sobrescribe CanAcceptEntry + ProcessEntry. La eliminación del source es automática |
Zona del mundo, papelera, venta: cualquier target sin inventario |
| Delegation | Sobrescribe GetDropProcessor(), devuelve tu propio IDropProcessor |
Áreas de inventario con lógica compleja (policy, swap, conversión) |
Simple Consumption
Una subclase mínima solo necesita dos métodos:
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;
}
}
Cómo funciona internamente
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
Cuando necesitas lógica compleja de drop (el pipeline de transferencia, policies, swaps), sobrescribe 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;
}
En este caso, CanAcceptEntry y ProcessEntry de la clase base nunca se llaman: todo el procesamiento pasa a través del processor devuelto.
Qué maneja la clase base
DropAreaBase hereda de Selectable e implementa IDropTarget + IDropProcessor.
Mecánicas automáticas
| Mecánica | Descripción |
|---|---|
| Seguimiento del puntero | OnPointerEnter/Exit hace push/pop automático en la target stack de DragAndDropManager |
| Eventos de estado | Se suscribe a OnDragStarted/Cancelled/Completed/Ended para gestionar interactable y raycast |
| Gestión de raycast | raycastTarget solo está activado durante un drag |
| Soporte para gamepad | La herencia de Selectable aporta navegación y focus de serie |
| Lifecycle de highlight | OnBecomeActiveTarget/OnBecomeInactiveTarget llaman a OnHighlightChanged |
| Eliminación del source | En el patrón simple consumption, eliminación automática del source slot con emisión de eventos |
Todos los puntos de override
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.
}
Ejemplos de subclase
WorldDropZone --- Spawn en mundo 3D
El fragmento siguiente sigue la implementación actual de Demo2 Loot.
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 --- Vender por oro
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 --- Ver sin eliminar
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 desactiva la eliminación automática del source. Los items permanecen en el inventario original.
Highlighting
Sobrescribe OnHighlightChanged para añadir feedback visual:
[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 se determina automáticamente mediante CanAcceptEntry sobre la primera entry durante el hover.
Referencia
| Clase | Patrón | Descripción |
|---|---|---|
DropAreaBase |
--- | Clase base para todas las drop areas |
WorldDropZone |
Simple Consumption | Crea objetos 3D en el mundo |
InventoryDropArea |
Delegation | Drop dentro del inventario vía el pipeline de transferencia |