# Apple Hills Card System – Designer Playbook This playbook is for designers working with the rarity‑based Card System in Apple Hills. It provides a high‑level 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](#architecture-at-a-glance) - [TL;DR Playbooks (Code-First)](#tldr-playbooks-code-first) - [Open Booster Packs](#open-booster-packs) - [Grant a Specific Card](#grant-a-specific-card) - [Query the Collection](#query-the-collection) - [Configure Visuals by Code (optional)](#configure-visuals-by-code-optional) - [Clear the Collection](#clear-the-collection) - [Authoring with the Card Editor (No Manual Setup)](#authoring-with-the-card-editor-no-manual-setup) - [What happens automatically (under the hood)](#what-happens-automatically-under-the-hood) - [In-Depth Overview](#in-depth-overview) - [Designer How‑Tos (Quick Reference)](#designer-how-tos-quick-reference) - [Best Practices & Tips](#best-practices--tips) - [Known Gaps](#known-gaps) - [FAQ](#faq) - [Technical Reference (Quick)](#technical-reference-quick) - [Change Log](#change-log) ## 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` - Player’s 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 you’ll perform in code. Paste the snippets into a temporary test `MonoBehaviour`, a unit test, or hook them into your gameplay systems. ### Open Booster Packs ```csharp 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 ```csharp 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 ```csharp 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 ```csharp 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 don’t 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_.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 card’s 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` - `GetCardInventory()` → `CardInventory` - `AddBoosterPack(int count)` - `OpenBoosterPack()` → `List` (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 How‑Tos (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_.asset`. It’s auto-discovered by runtime. Grant a Card by Code (quest reward) ```csharp CardSystemManager.Instance.AddCardToInventory(rewardDefinition); ``` Open a Booster by Code ```csharp CardSystemManager.Instance.AddBoosterPack(1); CardSystemManager.Instance.OpenBoosterPack(); ``` Filter Cards for a Page ```csharp 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 won’t persist player collection. - Booster Opening polish (timings/art/audio) is ongoing. - Collection statistics/achievements UI pending. ## FAQ - Q: My new `CardDefinition` doesn’t 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 `CardAlbumUI`’s 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 `CardDefinition`s 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.