Files
AppleHillsProduction/docs/card_system_playbook.md

340 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](#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 HowTos (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`
- 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
```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 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)
```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 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 `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.