14 KiB
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
- TL;DR Playbooks (Code-First)
- Authoring with the Card Editor (No Manual Setup)
- In-Depth Overview
- Designer How‑Tos (Quick Reference)
- Best Practices & Tips
- Known Gaps
- FAQ
- Technical Reference (Quick)
- 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.
- Source of truth for all card definitions, booster logic, and player
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).
- Runtime instance of a card in the player collection (ID, rarity, copies owned, links back to
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/menuAlbumViewPage– Grid album view with filter/sort optionsBoosterOpeningPage– Interactive three-card reveal
CardUIElement- Renders a single card using
CardDataandCardVisualConfig
- Renders a single card using
BoosterNotificationDot- Shows available booster pack count
-
Events (Data ↔ UI)
OnBoosterCountChangedOnBoosterOpenedOnCardCollectedOnCardRarityUpgraded
-
Lifecycle
- Initialize during boot:
BootCompletionService.RegisterInitAction(...) CardSystemManager.Instanceis the access point from UI and tools (e.g.,CardSystemTester)
- Initialize during boot:
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
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 inCardEditorWindow) - 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
CardDefinitionassets underAssets/Data/Cardsautomatically viaAssetDatabase.FindAssets("t:CardDefinition")and keeps the list sorted byName. - Creates new card assets as
Card_<Name>.assetinsideAssets/Data/Cards. - Loads a UI preview prefab from
Assets/Prefabs/UI/Cards/SIngleCardDisplayUI.prefabto render a live card preview inside the editor usingPreviewRenderUtility. - Tries to load
CardVisualConfigatAssets/Data/Cards/CardVisualConfig.assetfor 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
ScriptableObjectthat 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 runtimeCardDatainstance for inventory.
- What it is: a
-
CardData(runtime instance)- Purpose: Represents what the player owns – unique ID, rarity state, copies owned.
- Behavior: When duplicates are collected,
CardDatamay upgrade rarity (e.g., duplicate thresholds). - Links: Holds reference to its
CardDefinitionfor 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()→CardInventoryAddBoosterPack(int count)OpenBoosterPack()→List<CardData>(or similar)AddCardToInventory(CardDefinition def)ClearAllCards()
- Responsibilities: Owns
-
CardVisualConfig(visual mappings)- Purpose: Central place to establish the brand/look per rarity and zone.
- Implementation:
ScriptableObjectwith 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
UIPagesubclass registered underCardAlbumUI.
- Navigation:
-
CardAlbumUI(system entry)- Role: Wires pages together, responds to
CardSystemManagerevents, routes toAlbumViewPageorBoosterOpeningPage.
- Role: Wires pages together, responds to
-
AlbumViewPage- Role: Displays a grid of
CardUIElementitems fromCardInventorywith filtering/sorting UI. - Notes: Uses TextMeshPro components and simplified presentation (no stack/slot system).
- Role: Displays a grid of
-
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
CardVisualConfigfor color/frame mapping.
- Role: Displays a single
-
BoosterNotificationDot- Role: Indicates current booster count; listens to
OnBoosterCountChanged.
- Role: Indicates current booster count; listens to
Event Flow Examples
-
Opening a booster
- Player code calls
CardSystemManager.OpenBoosterPack()→ generates/awards 3 cards → raisesOnBoosterOpened. - Inventory updated; for each card,
OnCardCollected(and maybeOnCardRarityUpgraded) fires. BoosterOpeningPageanimates reveals;AlbumViewPageupdates when returning.
- Player code calls
-
Adding a card via quest reward
- Gameplay script calls
AddCardToInventory(def). - Inventory updates; events fire to update UI.
- Gameplay script calls
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)
- Open
AppleHills/Card Editor. - Click New/Duplicate, edit
Name,Description,Zone, and assign art. - Save—asset is created as
Assets/Data/Cards/Card_<Name>.asset. It’s 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.Instanceto keep a single source of truth. - Keep
CardDefinitionassets lightweight and consistent; use tags/zones for filtering. - When adding new rarities/zones, update
CardVisualConfigand 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
CardDefinitiondoesn’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 theCardSystemManagerdiscovery and your zones/rarities.
- A: Ensure the asset is in
-
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 viaUIPageController.
- A: Use
-
Q: Can I guarantee a specific rarity in a booster for a tutorial?
- A: Add a temporary tutorial hook in
CardSystemManagerto override booster generation rules for guided moments, or directly grant specificCardDefinitions for the tutorial step.
- A: Add a temporary tutorial hook in
-
Q: How do duplicates upgrade rarity?
- A: The logic lives in
CardData; when copies cross thresholds, it firesOnCardRarityUpgraded. Coordinate with design to set thresholds and with UI to show feedback.
- A: The logic lives in
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, linkCardAlbumUI)
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.