340 lines
14 KiB
Markdown
340 lines
14 KiB
Markdown
|
|
# 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_<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 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<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 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_<Name>.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.
|