Files
AppleHillsProduction/docs/custom_layout_guide.md
2025-11-06 15:27:08 +01:00

9.6 KiB

🎨 SlotContainer Custom Layout Guide

📋 Overview

The SlotContainer has a Custom layout mode that's perfect for artistic, hand-authored slot positions! When you select Custom, the SlotContainer doesn't automatically position your slots - it lets you manually position them however you want.


🎯 How Custom Layout Works

What Happens When Layout = Custom:

Looking at the code:

public void UpdateLayout()
{
    if (layoutType == LayoutType.Custom)
    {
        OnLayoutChanged?.Invoke();  // Just fires the event
        return;                      // Doesn't reposition anything!
    }
    
    // Other layouts (Horizontal, Vertical, Grid) reposition slots here...
}

Key Point: Custom layout means "Don't touch my slot positions!"

The SlotContainer still:

  • Registers your slots
  • Finds closest slot when dragging
  • Manages slot occupancy
  • Fires events

But it does NOT:

  • Automatically position slots
  • Apply spacing
  • Center slots
  • Use curves or grids

🛠️ Setting Up Custom Layout (Step-by-Step)

This is the easiest and most visual approach:

  1. Create SlotContainer:

    BottomRightContainer
    └── [Add Component: SlotContainer]
    
  2. Set Layout Type:

    • Layout Type: Custom
    • Auto Register Children: ✓ (keep this checked)
  3. Create Slot Children:

    BottomRightContainer
    ├── Slot_0 (DraggableSlot)
    ├── Slot_1 (DraggableSlot)
    ├── Slot_2 (DraggableSlot)
    └── Slot_3 (DraggableSlot)
    
  4. Position Slots Manually in Scene View:

    • Select Slot_0
    • Drag it to artistic position (e.g., X=-150, Y=200)
    • Select Slot_1
    • Drag it to artistic position (e.g., X=50, Y=150)
    • Select Slot_2
    • Drag it to artistic position (e.g., X=-200, Y=50)
    • Etc.
  5. Done! Your slots stay exactly where you put them.


Method 2: Script Positions (For Procedural Scatter)

If you want some randomness or code-controlled positions:

using UnityEngine;
using UI.DragAndDrop.Core;

public class CustomSlotPositioner : MonoBehaviour
{
    [SerializeField] private SlotContainer slotContainer;
    [SerializeField] private Vector2[] handAuthoredPositions;
    
    private void Start()
    {
        PositionSlots();
    }
    
    private void PositionSlots()
    {
        var slots = slotContainer.Slots;
        
        for (int i = 0; i < slots.Count && i < handAuthoredPositions.Length; i++)
        {
            if (slots[i].RectTransform != null)
            {
                slots[i].RectTransform.anchoredPosition = handAuthoredPositions[i];
            }
        }
    }
}

Then in Inspector:

  • Hand Authored Positions (Array):
    • Element 0: (-150, 200)
    • Element 1: (50, 150)
    • Element 2: (-200, 50)
    • Etc.

Method 3: Subscribe to OnLayoutChanged Event

For advanced custom positioning logic:

using UnityEngine;
using UI.DragAndDrop.Core;

public class ArtisticSlotLayout : MonoBehaviour
{
    [SerializeField] private SlotContainer slotContainer;
    [SerializeField] private float scatterRadius = 200f;
    [SerializeField] private bool useRandomSeed = true;
    [SerializeField] private int seed = 42;
    
    private void Awake()
    {
        if (slotContainer != null)
        {
            slotContainer.OnLayoutChanged += ApplyCustomLayout;
        }
    }
    
    private void OnDestroy()
    {
        if (slotContainer != null)
        {
            slotContainer.OnLayoutChanged -= ApplyCustomLayout;
        }
    }
    
    private void ApplyCustomLayout()
    {
        if (useRandomSeed)
            Random.InitState(seed);
        
        var slots = slotContainer.Slots;
        
        for (int i = 0; i < slots.Count; i++)
        {
            if (slots[i].RectTransform != null)
            {
                // Scattered circular layout
                float angle = Random.Range(0f, 360f);
                float distance = Random.Range(0f, scatterRadius);
                
                float x = Mathf.Cos(angle * Mathf.Deg2Rad) * distance;
                float y = Mathf.Sin(angle * Mathf.Deg2Rad) * distance;
                
                slots[i].RectTransform.anchoredPosition = new Vector2(x, y);
                
                // Optional: Random rotation for extra artistic flair
                slots[i].RectTransform.rotation = Quaternion.Euler(0, 0, Random.Range(-15f, 15f));
            }
        }
    }
}

🎨 Example: Artistic Scattered Boosters

Scenario: 3 booster slots scattered artistically in bottom-right

Setup:

BottomRightContainer (SlotContainer - Custom Layout)
├── Slot_0 (DraggableSlot)
│   └── Position: (-180, 220)
│   └── Rotation: (0, 0, -5)  ← Slight tilt left
├── Slot_1 (DraggableSlot)
│   └── Position: (40, 180)
│   └── Rotation: (0, 0, 8)   ← Slight tilt right
└── Slot_2 (DraggableSlot)
    └── Position: (-220, 80)
    └── Rotation: (0, 0, -3)  ← Slight tilt left

Visual Result:

            [Slot_0]  ← Tilted left, higher
                 [Slot_1]  ← Tilted right, middle
   [Slot_2]  ← Tilted left, lower

⚙️ Custom Layout Inspector Settings

When you select Custom layout type, these settings are ignored:

  • Spacing (not used)
  • Center Slots (not used)
  • Use Curve Layout (not used)
  • Position Curve (not used)
  • Curve Height (not used)

These settings still work:

  • Auto Register Children (still registers your slots)
  • OnSlotAdded/OnSlotRemoved events (still fire)
  • OnLayoutChanged event (fires when UpdateLayout is called)

🎯 Best Practices for Custom Layout

1. Use Scene View for Positioning

  • Easiest and most visual
  • See results immediately
  • Adjust in real-time

2. Add Visual Guides

  • Create a background image showing your intended layout
  • Position slots over the image
  • Delete/hide the guide after

3. Use Gizmos for Visualization

private void OnDrawGizmos()
{
    if (slotContainer == null || slotContainer.Slots == null) return;
    
    Gizmos.color = Color.yellow;
    
    foreach (var slot in slotContainer.Slots)
    {
        if (slot != null)
        {
            Gizmos.DrawWireSphere(slot.transform.position, 50f);
        }
    }
}

4. Consider Spacing

  • Even with artistic scatter, avoid overlapping slots
  • Leave enough space for hover/selection effects
  • Test with different screen sizes

5. Save Positions in Prefab

  • Once you're happy with positions, save to prefab
  • Ensures consistency across scenes

🔄 Combining Custom with Other Layouts

You can switch layout types at runtime:

public class DynamicLayoutSwitcher : MonoBehaviour
{
    [SerializeField] private SlotContainer slotContainer;
    
    public void SwitchToScattered()
    {
        // Save current positions
        Vector2[] savedPositions = new Vector2[slotContainer.SlotCount];
        for (int i = 0; i < slotContainer.SlotCount; i++)
        {
            savedPositions[i] = slotContainer.GetSlotAtIndex(i).RectTransform.anchoredPosition;
        }
        
        // Switch to custom
        // Note: You'd need to expose layoutType or use reflection
        // For now, this is just conceptual
    }
}

📋 Quick Setup Checklist

For your bottom-right booster slots:

  1. Create Container:

    BottomRightContainer
    └── Layout Type: Custom
    └── Auto Register Children: ✓
    
  2. Create 3 Slot Children:

    Slot_0, Slot_1, Slot_2
    └── Each has DraggableSlot component
    
  3. Position Each Slot:

    • Use Scene View
    • Click and drag to artistic positions
    • Optional: Add slight rotation (Z-axis)
  4. Test:

    • Drag boosters to slots
    • Verify they snap correctly
    • Adjust positions as needed
  5. Save to Prefab:

    • Once happy, save BoosterOpeningPage prefab
    • Positions are preserved

💡 Creative Ideas

Scattered Stack:

Slot_0: (-200, 250)  Rotation: -8°
Slot_1: (-180, 220)  Rotation: 3°
Slot_2: (-210, 195)  Rotation: -5°

→ Looks like a messy pile of cards!

Arc Formation:

Slot_0: (-180, 200)  Rotation: -15°
Slot_1: (0, 220)     Rotation: 0°
Slot_2: (180, 200)   Rotation: 15°

→ Gentle arc, like cards in hand

Diagonal Cascade:

Slot_0: (-150, 250)  Rotation: -10°
Slot_1: (-50, 180)   Rotation: -5°
Slot_2: (50, 110)    Rotation: 0°

→ Diagonal waterfall effect


🐛 Troubleshooting Custom Layout

Problem: Slots keep resetting position → Make sure Layout Type = Custom → Check if something is calling UpdateLayout() with a different type

Problem: Slots don't register → Ensure Auto Register Children = ✓ → Verify slots are direct children of SlotContainer

Problem: Can't drag boosters to slots → Check DraggableSlot configuration (Filter By Type, etc.) → Verify slots aren't locked

Problem: Positions lost on scene reload → Save to prefab! → Check if positions are being set in Awake/Start


📝 Summary

Custom Layout = Full Manual Control!

  • Position slots anywhere you want
  • Add rotation for artistic flair
  • No automatic repositioning
  • Perfect for hand-authored layouts
  • Still gets all SlotContainer benefits (registration, finding, events)

Recommended Workflow:

  1. Set Layout Type = Custom
  2. Create slot children
  3. Position visually in Scene View
  4. Test drag-and-drop
  5. Adjust as needed
  6. Save to prefab

🎨 Now go create some beautiful, artistic slot layouts! 🎨