From 4f30ba504f7124cfd4e42d6cdf4a29b05c6fe986 Mon Sep 17 00:00:00 2001 From: Savya Bikram Shah Date: Wed, 27 May 2026 13:42:31 +0545 Subject: [PATCH] docs(readme): add Spine mascot on main menu, controlled from code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New §9 MainMenu responsibility: Spine character with idle + reaction animations, driven by MenuMascotPresenter - New §32.3b MainMenu feature reference: IMenuMascotView (Play/SetSkin/ AnimationComplete), MenuMascotView wrapping SkeletonGraphic for Canvas, MenuMascotPresenter listening to model events - §10 Addressables: add Spine asset group + ShapeSO library + Shapes line - §30 Setup checklist: list required Unity packages including Spine-Unity - §31, §32.14: add MenuMascotView / MenuMascotPresenter rows Co-Authored-By: Claude Opus 4.7 (1M context) --- Readme.md | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 8 deletions(-) diff --git a/Readme.md b/Readme.md index 21437ce..0ac09b9 100644 --- a/Readme.md +++ b/Readme.md @@ -562,6 +562,14 @@ public readonly struct ArtworkSavedSignal { ## 9. Feature Responsibilities +### `MainMenu` + +- Lives in `MainMenu.unity`. Two main entry buttons: **Play** (→ `ColorBook` scene) and **Art Book** (→ `ArtBook` scene). +- Hosts a **Spine character mascot** (via `SkeletonGraphic` for Canvas). The mascot has multiple authored animations — idle loop, wave, react-to-button, victory dance. +- `MenuMascotPresenter` (pure C#) drives the mascot from code: subscribes to button hover / click events and the model's idle timer, calls `IMenuMascotView.Play(animName, loop)`. +- View is setter-only. Spine-Unity's `SkeletonGraphic.AnimationState.SetAnimation(track, name, loop)` is encapsulated behind `IMenuMascotView`. +- Mascot's skeleton + atlas ship via Addressables (see §10). + ### `DrawingCatalog` - Loads the catalog manifest (list of available template IDs). @@ -642,11 +650,12 @@ Mirror the Bus Game pattern via `IAssetProviderService`. | Asset | Why | |---|---| | `DrawingTemplate` ScriptableObject (per drawing) | Many; load on demand. | -| Shape piece sprites | Only needed when active. | -| Region sprites + polygon paths | Heavy; loaded per drawing. | +| `ShapeSO` assets | Reused across drawings; load once per drawing batch. | +| Region sprites | Heavy; loaded per drawing. | | Paper backgrounds | Per template, sometimes shared. | | Color palette SOs | Swap per theme. | | Audio clips (tap, snap, complete, sparkle) | Shared SFX bank. | +| Spine mascot (`SkeletonDataAsset` + atlas) | Heavy textures; load with `MainMenu` scene, release on scene exit. | ### What does NOT use Addressables @@ -661,9 +670,11 @@ Mirror the Bus Game pattern via `IAssetProviderService`. Drawings_Animals (label: drawing, animals) Drawings_Vehicles (label: drawing, vehicles) Drawings_Shapes (label: drawing, shapes) +Shapes_Library (label: shape) — reusable ShapeSO assets Palettes (label: palette) Audio_UI (label: sfx, ui) Audio_Coloring (label: sfx, coloring) +Spine_MainMenu (label: spine, menu) — mascot skeleton + atlas ``` ### Lifecycle @@ -1275,12 +1286,13 @@ Toddler-mode error UI: ## 30. Setup Checklist (new dev, day one) 1. Open `Colorbook.sln` at the repo root. -2. Open `Assets/Darkmatter/Scenes/Boot.unity` (currently the only scene wired). -3. Inspect the `RootLifetimeScope` GameObject — confirm its `serviceModules[]` list references the child installer MonoBehaviours (`AudioServiceModule`, `CameraServiceModule`, `InputServiceModule`, etc.). -4. Hit Play from `Boot.unity`. Other scenes (`MainMenu`, `ColorBook`, `ArtBook`) don't exist yet — they're listed in §6 / §4c as planned work. -5. When new scene scopes land, the same rule applies: never start a scene mid-flow, always enter from `Boot.unity` so the root scope exists. -6. When drawings are authored: duplicate the template folder under `Content/Gameplay/Drawings///`, edit `Template.asset` (pieces + regions), add to the appropriate Addressables group. -7. Run `Tests > EditMode` and `Tests > PlayMode` before pushing (test infra not set up yet — see §16). +2. Verify required Unity packages are installed (check `Packages/manifest.json`): VContainer, UniTask, Addressables, Input System, URP, **Spine-Unity runtime** (`com.esotericsoftware.spine.spine-unity`) for the main-menu mascot, DOTween (for snap/return tweens). +3. Open `Assets/Darkmatter/Scenes/Boot.unity` (currently the only scene wired). +4. Inspect the `RootLifetimeScope` GameObject — confirm its `serviceModules[]` list references the child installer MonoBehaviours (`AudioServiceModule`, `CameraServiceModule`, `InputServiceModule`, etc.). +5. Hit Play from `Boot.unity`. Other scenes (`MainMenu`, `ColorBook`, `ArtBook`) don't exist yet — they're listed in §6 / §4c as planned work. +6. When new scene scopes land, the same rule applies: never start a scene mid-flow, always enter from `Boot.unity` so the root scope exists. +7. When drawings are authored: duplicate the template folder under `Content/Gameplay/Drawings///`, edit `Template.asset` (pieces + regions), add to the appropriate Addressables group. +8. Run `Tests > EditMode` and `Tests > PlayMode` before pushing (test infra not set up yet — see §16). --- @@ -1302,6 +1314,7 @@ Toddler-mode error UI: | `HistoryController` | Features | `Features.History` | | `ColorBookFlowController` | Features | `Features.ColorBookFlow` | | `GalleryPresenter`, `GalleryGridView` | Features | `Features.ArtBook` | +| `MenuMascotView`, `MenuMascotPresenter` | Features | `Features.MainMenu` | | `ColorBookLifetimeScope`, `AppBoot` | App | `Darkmatter.App` | If a class's natural home doesn't match its asmdef, the architecture is bent — fix the placement, don't add a reference. @@ -1554,6 +1567,73 @@ public interface IFsmState { void Enter(); void Exit(); } --- +### 32.3b Feature — `MainMenu` *(planned)* + +Lives in `MainMenu.unity`. Hosts the **Play** / **Art Book** entry buttons plus the **Spine character mascot**. + +#### `IMenuMascotView` *(UI contract)* +Setter-only view interface. Hides Spine-Unity's API behind a tiny surface. +```csharp +public interface IMenuMascotView { + event Action AnimationComplete; // fires when a non-looping anim ends + void Play(string animName, bool loop); + void SetSkin(string skinName); // optional — character variants +} +``` + +#### `MenuMascotView : MonoBehaviour, IMenuMascotView` *(UI)* +Concrete view. Wraps a `SkeletonGraphic` component (Spine-Unity's Canvas-compatible renderer). +```csharp +public sealed class MenuMascotView : MonoBehaviour, IMenuMascotView { + [SerializeField] private SkeletonGraphic _skeleton; // Spine UI component + + public event Action AnimationComplete; + + public void Play(string animName, bool loop) { + var track = _skeleton.AnimationState.SetAnimation(0, animName, loop); + track.Complete += _ => AnimationComplete?.Invoke(animName); + } + + public void SetSkin(string skinName) { + _skeleton.Skeleton.SetSkin(skinName); + _skeleton.Skeleton.SetSlotsToSetupPose(); + _skeleton.AnimationState.Apply(_skeleton.Skeleton); + } +} +``` +- `SkeletonGraphic` lives on a child of `MainMenuCanvas`. It's a `Graphic`, so it interacts with `CanvasRenderer` just like an `Image`. +- The Spine asset (`SkeletonDataAsset`) is loaded via Addressables, assigned at scene setup, and released on scene exit. + +#### `MenuMascotPresenter` *(UI)* — `IStartable, IDisposable` +Pure C#. Subscribes to button events + idle timer, drives the view. +```csharp +// fields: IMenuMascotView _view, MainMenuModel _model, IInputReader _input +public sealed class MenuMascotPresenter : IStartable, IDisposable { + public void Start() { + _view.Play("idle", loop: true); + _model.PlayButtonHovered += () => _view.Play("hover_play", loop: false); + _model.ArtBookButtonHovered += () => _view.Play("hover_artbook", loop: false); + _view.AnimationComplete += OnAnimationComplete; + } + + private void OnAnimationComplete(string anim) { + if (anim != "idle") _view.Play("idle", loop: true); // always return to idle + } +} +``` +- Mascot reactions are pure presenter logic — the view never decides what to play. +- If you want randomized idle variants, add an idle timer in the model + a list of clip names. + +#### `MainMenuModel` *(Repository)* +Holds menu state — current selected skin, fires hover/click events from button presenters. + +#### `MainMenuModule : MonoBehaviour, IServiceModule` *(Installers)* +Registers the view (`RegisterInstance(_view)`), the presenter as a startable entry point, and the model. + +> **Package dependency:** [Spine-Unity runtime](http://esotericsoftware.com/spine-unity) (`com.esotericsoftware.spine.spine-unity`). Add to `Packages/manifest.json`. The `SkeletonGraphic` component lives in `Spine.Unity` namespace. + +--- + ### 32.4 Feature — `DrawingCatalog` *(planned)* #### `DrawingCatalogController` *(Systems)* @@ -1984,6 +2064,8 @@ Implemented as `MonoBehaviour` per feature/service so scopes can drag them in th | `CaptureController` | Feature | Capture+save orchestration | capture svc, gallery, bus | | `ColorBookFlowController` | Feature | Scene FSM | bus, catalog, builder, coloring, capture, progression | | `GalleryPresenter` | Feature | Art book listing | gallery, share, view, bus | +| `MenuMascotView` | Feature | Spine mascot UI (SkeletonGraphic wrapper) | — | +| `MenuMascotPresenter` | Feature | Drives mascot animations from model events | view, model | | `BoundedUndoStack` | Lib | Capped undo store | — | | `EventBus` | Lib | Pub/sub | — | | `Fsm` | Lib | Generic FSM | — |