7.7 KiB
Drag and Drop Card System
A robust, touch-compatible drag-and-drop system for Unity UI, inspired by Balatro's visual feel. Supports cards, booster packs, and any other draggable objects with smooth visual effects.
Architecture Overview
The system is built on a separation of concerns:
- Logic Layer (
DraggableObject) - Handles dragging, slot snapping, events - Visual Layer (
DraggableVisual) - Follows the logic object with lerping, tilting, animations - Slot System (
DraggableSlot+SlotContainer) - Manages positions and layout
Core Components
1. DraggableObject (Abstract Base Class)
Base class for any draggable UI element.
Key Features:
- Touch-compatible via Unity's pointer event system
- Smooth movement toward pointer (configurable)
- Automatic slot snapping on release
- Selection support with visual offset
- Comprehensive event system
Usage:
public class MyDraggable : DraggableObject
{
protected override void OnDragStartedHook()
{
// Custom logic when drag starts
}
}
2. DraggableVisual (Abstract Base Class)
Visual representation that follows the DraggableObject.
Key Features:
- Lerps toward parent position (not instant)
- Tilt based on movement velocity
- Auto-tilt idle animation (sine/cosine wobble)
- Manual tilt when hovering
- Scale animations on hover/drag/select
Usage:
public class MyVisual : DraggableVisual
{
protected override void UpdateVisualContent()
{
// Update your visual elements here
}
}
3. DraggableSlot
Represents a position where draggables can snap.
Key Features:
- Occupancy management (one object per slot)
- Type filtering (restrict which types can occupy)
- Lock/unlock functionality
- Swap support
4. SlotContainer
Manages a collection of slots.
Key Features:
- Multiple layout types (Horizontal, Vertical, Grid, Custom)
- Curve-based positioning for horizontal layouts
- Automatic slot registration
- Find closest slot algorithm
Layout Types:
- Horizontal - Slots arranged in a horizontal line (with optional curve)
- Vertical - Slots arranged in a vertical line
- Grid - Slots arranged in a grid pattern
- Custom - Manually position slots
Card-Specific Implementations
CardDraggable
Card-specific draggable implementation.
Features:
- Holds
CardDatareference - Events for card data changes
- Integrates with
CardSystemManager
Example:
CardDraggable card = GetComponent<CardDraggable>();
card.SetCardData(myCardData);
CardDraggableVisual
Visual representation for cards.
Features:
- Uses existing
CardDisplaycomponent - Shadow effects on press
- Automatic visual refresh when card data changes
BoosterPackDraggable
Booster pack implementation.
Features:
- Double-click to open support
- Opening state management
- Events for opening
BoosterPackVisual
Visual representation for booster packs.
Features:
- Glow particle effects
- Opening animation (scale + rotation)
- Sprite customization
Setup Guide
Basic Setup
-
Create Slot Container:
GameObject → UI → Panel (rename to "CardSlotContainer") Add Component → SlotContainer -
Create Slots:
Under CardSlotContainer: GameObject → UI → Empty (rename to "Slot_01") Add Component → DraggableSlotDuplicate for as many slots as needed.
-
Create Draggable Card:
GameObject → UI → Image (rename to "CardDraggable") Add Component → CardDraggable -
Create Visual Prefab:
Create a prefab with: - Root: Empty GameObject with CardDraggableVisual component - Child: Canvas (for sorting control) - Child: TiltParent (Transform for tilt effects) - Child: ShakeParent (Transform for punch effects) - Child: CardDisplay (your existing card visual) -
Link Visual to Draggable:
On CardDraggable: - Assign your visual prefab to "Visual Prefab" - Set "Instantiate Visual" to true
Advanced: Curved Hand Layout
For a Balatro-style curved card hand:
-
On SlotContainer:
- Set Layout Type to "Horizontal"
- Enable "Use Curve Layout"
- Edit "Position Curve" (try: keys at 0,0.5,1 with values 0,1,0)
- Set "Curve Height" (e.g., 50)
- Enable "Center Slots"
-
Adjust spacing to fit your card size
Event System
DraggableObject Events:
draggable.OnDragStarted += (obj) => { };
draggable.OnDragEnded += (obj) => { };
draggable.OnPointerEntered += (obj) => { };
draggable.OnPointerExited += (obj) => { };
draggable.OnPointerDowned += (obj) => { };
draggable.OnPointerUpped += (obj, longPress) => { };
draggable.OnSelected += (obj, selected) => { };
draggable.OnSlotChanged += (obj, slot) => { };
CardDraggable Events:
cardDraggable.OnCardDataChanged += (card, data) => { };
BoosterPackDraggable Events:
boosterDraggable.OnBoosterOpened += (pack) => { };
Touch Support
The system is fully touch-compatible out of the box! Unity's Event System automatically routes touch events through the pointer interfaces.
Supported Gestures:
- Single touch drag
- Tap to select
- Double-tap (on booster packs)
- Long press detection
Note: For multi-touch support, the system uses PointerEventData which handles the first touch automatically. Additional touch support can be added by extending the pointer event handlers.
Performance Tips
-
Disable Idle Animations if you have many cards:
On DraggableVisual: useIdleAnimation = false -
Reduce Follow Speed for smoother performance:
On DraggableVisual: followSpeed = 20 (default: 30) -
Disable Scale Animations if needed:
On DraggableVisual: useScaleAnimations = false -
Use Object Pooling for spawning many cards
Extending the System
Creating Custom Draggable Types
- Inherit from
DraggableObject:
public class MyCustomDraggable : DraggableObject
{
protected override void OnDragStartedHook()
{
// Your logic
}
}
- Inherit from
DraggableVisual:
public class MyCustomVisual : DraggableVisual
{
protected override void UpdateVisualContent()
{
// Update your visuals
}
}
Custom Slot Filtering
// On DraggableSlot component:
filterByType = true
allowedTypeNames = { "CardDraggable", "BoosterPackDraggable" }
Troubleshooting
Cards don't snap to slots:
- Ensure SlotContainer has slots registered
- Check that slots aren't locked
- Verify type filtering isn't blocking the card
Visuals don't follow smoothly:
- Check followSpeed value (try 20-40)
- Ensure TiltParent and ShakeParent are assigned
- Verify the visual prefab has correct hierarchy
Touch not working:
- Ensure EventSystem exists in scene
- Check Canvas Raycast Target is enabled
- Verify GraphicRaycaster is on Canvas
Cards jitter or shake:
- Reduce followSpeed
- Disable idle animation
- Check for conflicting tweens
Integration with Card System
The drag-and-drop system integrates seamlessly with your existing CardSystemManager:
// Spawn a draggable card from CardData
CardData cardData = CardSystemManager.Instance.GetAllCollectedCards()[0];
GameObject cardObj = Instantiate(cardDraggablePrefab, slotContainer.transform);
CardDraggable card = cardObj.GetComponent<CardDraggable>();
card.SetCardData(cardData);
// Assign to first available slot
DraggableSlot slot = slotContainer.GetAvailableSlots().FirstOrDefault();
if (slot != null)
{
card.AssignToSlot(slot, false);
}
Credits
Inspired by the excellent feel of Balatro's card system (mixandjam/balatro-feel on GitHub).
Adapted for AppleHills card collection game with full touch support and Unity UI integration.