An inverted Suika game riff with 3D shuffleboard physics.
Something went wrong. Try again.
Phase 6: Presentation, UI & WASM Optimization #
Bring the game to life visually. This phase produces the first fully playable build in both native and browser (WASM) targets.
Status at phase start: 15/15 tests passing, WASM target compiles clean.
Context / Key Decisions #
| Topic | Decision |
|---|---|
| Sphere rendering | Separate VisualPlugin using OnAdd<Sphere> observer — physics tests unaffected |
| Tier labels | Floating 3D world-space billboard text above each sphere |
| Color palette | Warm perceptual gradient: violet → sky blue → lime → orange → gold (13 tiers) |
| Arena geometry | Visible flat table surface + low side walls |
| Colorblind mode | Phase 6, triggered by keyboard C key and visible UI button |
| Colorblind visuals | Distinct per-tier pattern/icon overlaid on sphere surface |
| HUD | Score + current order target + next-sphere-in-queue preview |
| Preview sphere | Shows tier color at normal opacity; no special blocked state |
| Loss boundary | Dashed line across full arena width at the launch/loss Z position |
| Obstruction blocking | No distinct visual needed — if a sphere is at the launch Z, the game is already lost |
Architecture Overview #
New files #
src/visuals.rs—VisualPlugin: sphere mesh/materials, arena geometry, billboard labels, colorblind togglesrc/hud.rs—HudPlugin: score, order, next-sphere, colorblind button
Modified files #
src/game_state.rs— addColorblindMode(bool)resource; add arena dimension fields toGameSettingssrc/main.rs— registerVisualPlugin+HudPlugin; remove inlinesetupfn (superseded byVisualPluginStartup)src/launcher.rs— remove hardcoded preview sphere material (now managed byVisualPlugin)
Proposed Changes (Detail) #
src/game_state.rs #
- Add
ColorblindMode(bool)resource (default:false) - Add to
GameSettings:arena_half_width: f32,arena_depth: f32,wall_height: f32
src/visuals.rs — VisualPlugin #
Constants / helpers:
TIER_COLORS: [Color; 13]— warm perceptual gradient array (violet → gold)fn tier_material(tier: u8, colorblind: bool) -> StandardMaterial
Resources:
TierMaterials— pre-computedHandle<StandardMaterial>array for normal and colorblind modes; rebuilt whenColorblindModetoggles
Startup system:
- Spawn table floor (thin
Cuboid, matte material) - Spawn left / right / back walls (
Cuboidgeometry) - Spawn dashed loss-boundary line (row of small flat cubes at
launcher_z) - Spawn
LauncherPreviewentity (moved frommain.rs)
Observer — OnAdd<Sphere>:
- Attach
Mesh3d+MeshMaterial3dchild entity usingTierMaterialshandle for tier - Spawn
Text3dchild billboard entity above sphere displaying tier number
Update systems:
update_colorblind_toggle— readsKeyCode::Cand UI button interaction; togglesColorblindMode; rebuildsTierMaterials; updatesMeshMaterial3don allSphereVisualentitiesupdate_preview_material— keepsLauncherPreviewmaterial in sync withDispenserQueue::currenttier
src/hud.rs — HudPlugin #
Startup system — spawn HUD root:
- Top-left: Score label
- Top-center: Order label (e.g. "Make a Tier 4!")
- Top-right: Next sphere preview (colored swatch + tier number)
- Bottom-right: Colorblind toggle button (text toggles "Colorblind: OFF" / "Colorblind: ON")
Update systems:
update_score_hud— re-renders score text whenScore::totalchangesupdate_order_hud— re-renders order text whenActiveOrder::target_tierchangesupdate_next_sphere_hud— updates swatch fromDispenserQueue::nexthandle_colorblind_button— detectsInteraction::Pressed, flipsColorblindMode
src/main.rs #
- Declare
mod visuals;andmod hud; - Register
VisualPluginandHudPlugin - Remove the inline
setupfn
Task Checklist #
Preparation #
src/visuals.rs #
src/hud.rs #
src/main.rs #
src/launcher.rs #
Verification #
Key Bevy 0.18 Notes (for session resume) #
- Component-based spawning — use
.spawn((Mesh3d(h), MeshMaterial3d(m), Transform::…))not bundles - Observers —
app.add_observer(on_sphere_added)wherefn on_sphere_added(trigger: Trigger<OnAdd, Sphere>, …) Query::single/single_mutreturnResult, not panic — alwayslet Ok(x) = q.single() else { return; }ReadRapierContext::single()— same Result pattern- Physics tests — use
TransformPlugin + TimePlugin + RapierPhysicsPlugin(NOTMinimalPlugins) for any test involving Rapier bevy_rapier3dfeatures — currentlydefault-features = false, features = ["dim3", "simd-stable"]to avoid asset/scene resource deps in headless tests