310 lines
7.7 KiB
Markdown
310 lines
7.7 KiB
Markdown
|
|
# 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:**
|
||
|
|
```csharp
|
||
|
|
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:**
|
||
|
|
```csharp
|
||
|
|
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 `CardData` reference
|
||
|
|
- Events for card data changes
|
||
|
|
- Integrates with `CardSystemManager`
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```csharp
|
||
|
|
CardDraggable card = GetComponent<CardDraggable>();
|
||
|
|
card.SetCardData(myCardData);
|
||
|
|
```
|
||
|
|
|
||
|
|
### CardDraggableVisual
|
||
|
|
Visual representation for cards.
|
||
|
|
|
||
|
|
**Features:**
|
||
|
|
- Uses existing `CardDisplay` component
|
||
|
|
- 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
|
||
|
|
|
||
|
|
1. **Create Slot Container:**
|
||
|
|
```
|
||
|
|
GameObject → UI → Panel (rename to "CardSlotContainer")
|
||
|
|
Add Component → SlotContainer
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Create Slots:**
|
||
|
|
```
|
||
|
|
Under CardSlotContainer:
|
||
|
|
GameObject → UI → Empty (rename to "Slot_01")
|
||
|
|
Add Component → DraggableSlot
|
||
|
|
```
|
||
|
|
Duplicate for as many slots as needed.
|
||
|
|
|
||
|
|
3. **Create Draggable Card:**
|
||
|
|
```
|
||
|
|
GameObject → UI → Image (rename to "CardDraggable")
|
||
|
|
Add Component → CardDraggable
|
||
|
|
```
|
||
|
|
|
||
|
|
4. **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)
|
||
|
|
```
|
||
|
|
|
||
|
|
5. **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:
|
||
|
|
|
||
|
|
1. 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"
|
||
|
|
|
||
|
|
2. Adjust spacing to fit your card size
|
||
|
|
|
||
|
|
## Event System
|
||
|
|
|
||
|
|
### DraggableObject Events:
|
||
|
|
```csharp
|
||
|
|
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:
|
||
|
|
```csharp
|
||
|
|
cardDraggable.OnCardDataChanged += (card, data) => { };
|
||
|
|
```
|
||
|
|
|
||
|
|
### BoosterPackDraggable Events:
|
||
|
|
```csharp
|
||
|
|
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
|
||
|
|
|
||
|
|
1. **Disable Idle Animations** if you have many cards:
|
||
|
|
```
|
||
|
|
On DraggableVisual: useIdleAnimation = false
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Reduce Follow Speed** for smoother performance:
|
||
|
|
```
|
||
|
|
On DraggableVisual: followSpeed = 20 (default: 30)
|
||
|
|
```
|
||
|
|
|
||
|
|
3. **Disable Scale Animations** if needed:
|
||
|
|
```
|
||
|
|
On DraggableVisual: useScaleAnimations = false
|
||
|
|
```
|
||
|
|
|
||
|
|
4. **Use Object Pooling** for spawning many cards
|
||
|
|
|
||
|
|
## Extending the System
|
||
|
|
|
||
|
|
### Creating Custom Draggable Types
|
||
|
|
|
||
|
|
1. Inherit from `DraggableObject`:
|
||
|
|
```csharp
|
||
|
|
public class MyCustomDraggable : DraggableObject
|
||
|
|
{
|
||
|
|
protected override void OnDragStartedHook()
|
||
|
|
{
|
||
|
|
// Your logic
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
2. Inherit from `DraggableVisual`:
|
||
|
|
```csharp
|
||
|
|
public class MyCustomVisual : DraggableVisual
|
||
|
|
{
|
||
|
|
protected override void UpdateVisualContent()
|
||
|
|
{
|
||
|
|
// Update your visuals
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Custom Slot Filtering
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// 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`:
|
||
|
|
|
||
|
|
```csharp
|
||
|
|
// 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.
|
||
|
|
|