P6.2: score popups & match FX (sx, iOS sim)
Add a purely-visual, transient juice layer over a committed move — score popups + a tinted particle/flash burst at the clears — with no change to the model, score, moves, or settled board. - assets/fx/particle.png: key the painted transparency checkerboard out of the provided particle art to real alpha (8-connected border flood fill + smooth luminance falloff that preserves the soft glow), downscaled to a 256x256 RGBA white sparkle. tools/key_particle.sx is the reproducible tool. - board_fx.sx: BoardFx (live particle bursts + one "+points" popup) and BoardFxAssets. The engine image path samples texture*white (no draw-time tint), so the white sprite is tinted per gem colour at LOAD time into one texture per colour; a burst animates by scale (grow -> shrink) and the soft texture edges carry the fade. Combos (cascade depth > 1) burst bigger and the popup is larger + gold. All driven by delta_time and self-pruning. - board_anim.sx: AnimMove carries the model's cascade.awarded so the popup shows the real payout without re-deriving any scoring in the view. - board_view.sx / main.sx: wire BoardFx + the tinted assets, tick each frame, spawn on a legal commit, and render bursts (clipped to the grid) under the popups (drawn on top). Input-lock (BoardAnim.active) is untouched; FX never gate input and may outlast the move slightly before vanishing. Goldens (iPhone-17-class sim, iOS 26): p6_fx.png (combo: gold "+480" + bursts mid-cascade), p6_fx_match.png (single match: "+30" + red burst), p6_fx_after.png (settled board, FX fully gone). Gate: ios-sim build links, 15/15 logic tests green (scoring/cascade goldens unchanged).
This commit is contained in:
18
main.sx
18
main.sx
@@ -16,6 +16,7 @@
|
||||
#import "board.sx";
|
||||
#import "board_view.sx";
|
||||
#import "board_anim.sx";
|
||||
#import "board_fx.sx";
|
||||
|
||||
#run configure_build();
|
||||
|
||||
@@ -56,10 +57,17 @@ g_drag : *DragInput = null;
|
||||
// frames, so the timeline state must persist across BoardView's per-frame rebuild.
|
||||
g_anim : *BoardAnim = null;
|
||||
|
||||
// Transient match FX + score popups (P6.2). Heap-allocated like the animation:
|
||||
// a committed move spawns short-lived bursts/popups that play out (and prune
|
||||
// themselves) over many later frames. `g_fxassets` holds the per-colour tinted
|
||||
// particle textures, loaded once. Purely visual; neither gates input.
|
||||
g_fx : *BoardFx = null;
|
||||
g_fxassets : *BoardFxAssets = null;
|
||||
|
||||
// Rebuilt each frame inside the pipeline's arena; carries the current safe-area
|
||||
// insets so the grid stays inside the notch / home-indicator region.
|
||||
build_ui :: () -> View {
|
||||
BoardView.{ board = g_board, assets = g_assets, sel = g_sel, drag = g_drag, anim = g_anim, safe = g_safe_insets }
|
||||
BoardView.{ board = g_board, assets = g_assets, sel = g_sel, drag = g_drag, anim = g_anim, fx = g_fx, fxassets = g_fxassets, safe = g_safe_insets }
|
||||
}
|
||||
|
||||
frame :: () {
|
||||
@@ -87,6 +95,7 @@ frame :: () {
|
||||
// Advance the in-flight move animation by this frame's delta before rendering,
|
||||
// so the board view draws the timeline slice for the current wall-clock time.
|
||||
if g_anim != null { g_anim.tick(g_delta_time); }
|
||||
if g_fx != null { g_fx.tick(g_delta_time); }
|
||||
|
||||
inline if OS == .ios {
|
||||
// Lazy-attach Metal once -[SxAppDelegate didFinishLaunching:] has
|
||||
@@ -167,6 +176,13 @@ main :: () -> void {
|
||||
g_anim = xx context.allocator.alloc(size_of(BoardAnim));
|
||||
g_anim.init();
|
||||
|
||||
g_fx = xx context.allocator.alloc(size_of(BoardFx));
|
||||
g_fx.init();
|
||||
|
||||
g_fxassets = xx context.allocator.alloc(size_of(BoardFxAssets));
|
||||
g_fxassets.init();
|
||||
g_fxassets.load(g_pipeline.gpu);
|
||||
|
||||
g_pipeline.set_body(closure(build_ui));
|
||||
|
||||
g_plat.run_frame_loop(closure(frame));
|
||||
|
||||
Reference in New Issue
Block a user