Files
AppleHillsProduction/docs/card_system_playbook.md

14 KiB
Raw Blame History

Apple Hills Card System Designer Playbook

This playbook is for designers working with the raritybased Card System in Apple Hills. It provides a highlevel architecture overview, fast TL;DR playbooks for common tasks (with code-first snippets), and deeper guidance on how the system fits together.

Table of Contents

Architecture at a Glance

The system is split into two main layers with clear responsibilities and a lightweight event bridge.

  • Data Layer (Assets/Scripts/Data/CardSystem)

    • CardSystemManager (singleton)
      • Source of truth for all card definitions, booster logic, and player CardInventory.
      • Emits events on collection changes and booster activity.
    • CardInventory
      • Players owned cards and booster pack counters.
      • Utility APIs for filtering cards (by rarity, zone, etc.).
    • CardDefinition (ScriptableObject)
      • Authoring asset that defines a card (name, visuals, zone, rarity tiering).
    • CardData
      • Runtime instance of a card in the player collection (ID, rarity, copies owned, links back to CardDefinition).
    • CardVisualConfig (ScriptableObject)
      • Central mapping for rarity/zone → visual treatment (colors, frames, shapes).
  • UI Layer (Assets/Scripts/UI/CardSystem)

    • CardAlbumUI
      • Entry point for the card UI (menu, album browser, booster opening).
    • UIPageController + UIPage
      • Stack-based navigation (push/pop) and shared transitions.
    • Page Components
      • CardMenuPage Card hub/menu
      • AlbumViewPage Grid album view with filter/sort options
      • BoosterOpeningPage Interactive three-card reveal
    • CardUIElement
      • Renders a single card using CardData and CardVisualConfig
    • BoosterNotificationDot
      • Shows available booster pack count
  • Events (Data ↔ UI)

    • OnBoosterCountChanged
    • OnBoosterOpened
    • OnCardCollected
    • OnCardRarityUpgraded
  • Lifecycle

    • Initialize during boot: BootCompletionService.RegisterInitAction(...)
    • CardSystemManager.Instance is the access point from UI and tools (e.g., CardSystemTester)

TL;DR Playbooks (Code-First)

These are the most common tasks youll perform in code. Paste the snippets into a temporary test MonoBehaviour, a unit test, or hook them into your gameplay systems.

Open Booster Packs

using AppleHills.Data.CardSystem;
using UnityEngine;

public class BoosterOpenerSample : MonoBehaviour
{
    private void Start()
    {
        // Subscribe to events (optional, for feedback/UI updates)
        CardSystemManager.Instance.OnBoosterOpened += cards =>
        {
            Debug.Log($"Opened booster with {cards.Count} cards");
        };
        CardSystemManager.Instance.OnCardCollected += card =>
        {
            Debug.Log($"Collected: {card.Definition.Name} (rarity: {card.Rarity})");
        };
        CardSystemManager.Instance.OnCardRarityUpgraded += card =>
        {
            Debug.Log($"Upgraded rarity: {card.Definition.Name} -> {card.Rarity}");
        };

        // Give the player some boosters and open one
        CardSystemManager.Instance.AddBoosterPack(1);
        var revealed = CardSystemManager.Instance.OpenBoosterPack();
        foreach (var cd in revealed)
        {
            Debug.Log($"Revealed: {cd.Definition.Name}");
        }
    }
}

Grant a Specific Card

using System.Linq;
using AppleHills.Data.CardSystem;
using UnityEngine;

public class GrantCardSample : MonoBehaviour
{
    [SerializeField] private string cardName = "Apple Picker"; // example

    private void Start()
    {
        var def = CardSystemManager.Instance
            .GetAllCardDefinitions()
            .FirstOrDefault(d => d.Name == cardName);
        if (def == null)
        {
            Debug.LogWarning($"CardDefinition not found: {cardName}");
            return;
        }

        var cardData = CardSystemManager.Instance.AddCardToInventory(def);
        Debug.Log($"Granted: {cardData.Definition.Name} (copies: {cardData.CopiesOwned})");
    }
}

Query the Collection

using AppleHills.Data.CardSystem;
using UnityEngine;

public class QueryCollectionSample : MonoBehaviour
{
    private void Start()
    {
        var inv = CardSystemManager.Instance.GetCardInventory();

        // All cards
        var all = inv.GetAllCards();
        Debug.Log($"Total cards owned: {all.Count}");

        // By rarity (example)
        var rares = inv.GetByRarity(CardRarity.Rare);
        Debug.Log($"Rares: {rares.Count}");

        // By zone (example)
        var forest = inv.GetByZone(CardZone.Forest);
        Debug.Log($"Forest: {forest.Count}");

        // Counts (progression checks)
        var byRarity = inv.CountByRarity();
        var byZone = inv.CountByZone();
        // Iterate dictionaries as needed
    }
}

Clear the Collection

using AppleHills.Data.CardSystem;
using UnityEngine;

public class ClearCollectionSample : MonoBehaviour
{
    private void Start()
    {
        CardSystemManager.Instance.ClearAllCards();
        Debug.Log("Card collection cleared (dev/test only).");
    }
}

Authoring with the Card Editor (No Manual Setup)

Designers should use the dedicated editor window: AppleHills/Card Editor.

  • Open via Unity menu: AppleHills/Card Editor (menu path defined in CardEditorWindow)
  • The window provides a card list, detail editor, and a live preview of the UI card.
  • You dont need to create folders or assets manually—the tool handles setup and discovery.

What happens automatically (under the hood)

The Assets/Editor/CardSystem/CardEditorWindow.cs implements the following behaviors:

  • Ensures the data directory exists: Assets/Data/Cards (creates it on first run).
  • Discovers all CardDefinition assets under Assets/Data/Cards automatically via AssetDatabase.FindAssets("t:CardDefinition") and keeps the list sorted by Name.
  • Creates new card assets as Card_<Name>.asset inside Assets/Data/Cards.
  • Loads a UI preview prefab from Assets/Prefabs/UI/Cards/SIngleCardDisplayUI.prefab to render a live card preview inside the editor using PreviewRenderUtility.
  • Tries to load CardVisualConfig at Assets/Data/Cards/CardVisualConfig.asset for consistent rarity/zone styling in the preview.
  • Responds to Undo/Redo and Editor recompiles to stay in sync.
  • When you duplicate or delete cards from the window, the corresponding assets are created/removed accordingly.

Result: Designers focus on content (name, description, zone, art, etc.) in the Card Editor. The system takes care of folders, discovery, previewing, and syncing definitions with runtime.

In-Depth Overview

Data Layer Details

  • CardDefinition (authoring)

    • What it is: a ScriptableObject that defines a cards identity and static presentation (name, description, zone, default visuals).
    • Typical fields: Name, Description, Zone, rarity metadata, sprites/textures, optional tags.
    • Helpers: CreateCardData() to generate a runtime CardData instance for inventory.
  • CardData (runtime instance)

    • Purpose: Represents what the player owns unique ID, rarity state, copies owned.
    • Behavior: When duplicates are collected, CardData may upgrade rarity (e.g., duplicate thresholds).
    • Links: Holds reference to its CardDefinition for display and classification.
  • CardInventory (player collection)

    • Responsibilities: Store cards, track counts, support filters/lookups (by rarity/zone), manage booster counters.
    • Common operations: AddCard(def), GetAllCards(), GetByRarity(), GetByZone(), CountByRarity(), CountByZone().
    • Notes: Designed for efficiency and clarity; returns collections for the UI to display.
  • CardSystemManager (singleton coordinator)

    • Responsibilities: Owns CardInventory, holds the card definition catalog, controls booster generation/opening, exposes public APIs.
    • Events: OnBoosterCountChanged, OnBoosterOpened, OnCardCollected, OnCardRarityUpgraded.
    • Typical APIs:
      • GetAllCardDefinitions()IReadOnlyList<CardDefinition>
      • GetCardInventory()CardInventory
      • AddBoosterPack(int count)
      • OpenBoosterPack()List<CardData> (or similar)
      • AddCardToInventory(CardDefinition def)
      • ClearAllCards()
  • CardVisualConfig (visual mappings)

    • Purpose: Central place to establish the brand/look per rarity and zone.
    • Implementation: ScriptableObject with dictionaries for quick lookups by rarity/zone.

UI Layer Details

  • UIPageController + UIPage

    • Navigation: PushPage(page), PopPage(), Clear(); optional transition animations.
    • Pattern: Each page is a UIPage subclass registered under CardAlbumUI.
  • CardAlbumUI (system entry)

    • Role: Wires pages together, responds to CardSystemManager events, routes to AlbumViewPage or BoosterOpeningPage.
  • AlbumViewPage

    • Role: Displays a grid of CardUIElement items from CardInventory with filtering/sorting UI.
    • Notes: Uses TextMeshPro components and simplified presentation (no stack/slot system).
  • BoosterOpeningPage

    • Role: Handles the interactive reveal flow for 3 cards per booster; flip animations, particle FX, rarity feedback.
    • Notes: Timings and art are still under refinement; supports individual reveal clicks.
  • CardUIElement

    • Role: Displays a single CardData name, art, frame, rarity chips, zone styling.
    • Data Source: Uses CardVisualConfig for color/frame mapping.
  • BoosterNotificationDot

    • Role: Indicates current booster count; listens to OnBoosterCountChanged.

Event Flow Examples

  • Opening a booster

    • Player code calls CardSystemManager.OpenBoosterPack() → generates/awards 3 cards → raises OnBoosterOpened.
    • Inventory updated; for each card, OnCardCollected (and maybe OnCardRarityUpgraded) fires.
    • BoosterOpeningPage animates reveals; AlbumViewPage updates when returning.
  • Adding a card via quest reward

    • Gameplay script calls AddCardToInventory(def).
    • Inventory updates; events fire to update UI.

Rarity & Duplicates

  • The system tracks copies owned per card.
  • Duplicates can trigger rarity upgrades based on thresholds (implemented in CardData).
  • UI can react with special effects via OnCardRarityUpgraded (e.g., distinct VFX/SFX per rarity).

Designer HowTos (Quick Reference)

Author a New Card (use the editor tool)

  1. Open AppleHills/Card Editor.
  2. Click New/Duplicate, edit Name, Description, Zone, and assign art.
  3. Save—asset is created as Assets/Data/Cards/Card_<Name>.asset. Its auto-discovered by runtime.

Grant a Card by Code (quest reward)

CardSystemManager.Instance.AddCardToInventory(rewardDefinition);

Open a Booster by Code

CardSystemManager.Instance.AddBoosterPack(1);
CardSystemManager.Instance.OpenBoosterPack();

Filter Cards for a Page

var inv = CardSystemManager.Instance.GetCardInventory();
var list = inv.GetByZone(CardZone.Quarry);

Best Practices & Tips

  • Always access data via CardSystemManager.Instance to keep a single source of truth.
  • Keep CardDefinition assets lightweight and consistent; use tags/zones for filtering.
  • When adding new rarities/zones, update CardVisualConfig and verify UI styling.
  • For smoother reveals, coordinate with VFX/SFX to differentiate rarities.
  • Large collections: consider paging or virtualized grids if performance becomes a concern.

Known Gaps

  • Save/Load not implemented test sessions wont persist player collection.
  • Booster Opening polish (timings/art/audio) is ongoing.
  • Collection statistics/achievements UI pending.

FAQ

  • Q: My new CardDefinition doesnt show up in the album.

    • A: Ensure the asset is in Assets/Data/Cards (the editor tool places it there) and that definitions load before UI opens. Check the CardSystemManager discovery and your zones/rarities.
  • Q: How do I trigger the card UI from another part of the game?

    • A: Use CardAlbumUIs public entry points or route through your UI flow to push the relevant page via UIPageController.
  • Q: Can I guarantee a specific rarity in a booster for a tutorial?

    • A: Add a temporary tutorial hook in CardSystemManager to override booster generation rules for guided moments, or directly grant specific CardDefinitions for the tutorial step.
  • Q: How do duplicates upgrade rarity?

    • A: The logic lives in CardData; when copies cross thresholds, it fires OnCardRarityUpgraded. Coordinate with design to set thresholds and with UI to show feedback.

Technical Reference (Quick)

  • Initialize after boot: BootCompletionService.RegisterInitAction(InitializePostBoot)
  • Data access: CardSystemManager.Instance
  • Navigation: UIPageController.Instance.PushPage() / PopPage()
  • Show a card in UI: CardUIElement.SetupCard(cardData)
  • Test harness: CardSystemTester (add to scene, link CardAlbumUI)

Change Log

  • v1.1: Added Table of Contents, inline code formatting, code-first TL;DR playbooks, and an authoring section based on CardEditorWindow.
  • v1.0: Initial designer playbook, aligned with Implementation Plan and Integration & Testing Guide.