Files
YataiDONNijiiro/Scripts/background/bg_objects/ending.lua
T
TaikoZhongandClaude Fable 5.1 f1744e859b Ending banners: replay the original clips (clear / full combo / donderful / fail)
Uses the engine's new Background:handle_ending / draw_ending hook. The four
ending clips are exported frame by frame (Scripts/game/ending_anim, textures
under game/ending_<kind>_anim) and replayed on the lane at 60 fps: the banner
text reveal, the fans, the two dancers and the confetti all move as in the
original, then loop. Without the engine hook the skin's static plates still
draw as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-09 14:07:48 +08:00

96 lines
3.3 KiB
Lua

-- Ending banner (clear / full combo / donderful / fail), replayed from the cabinet
-- movie data exported by scratchpad/lyric/clip_export.py:
-- Scripts/game/ending_anim/ending_<kind>.lua -- frames[f+1] = {{shape, tx, ty, sx, sy, rot, a}, ...}
-- Graphics/game/ending_<kind>_anim/s<c>.png -- one texture per shape
-- The clip plays once from frame 0 and then loops from `loop_at`, at 60 fps, on the
-- 1920x264 stage the original places at the lane (screen y 276 for 1P; the 2P
-- lane adds the skin's game_2p_offset). The engine keeps its own ending
-- animation running for the sounds and hands the drawing to this object through
-- Background:handle_ending / draw_ending.
local Ending = {}
Ending.__index = Ending
local FRAME_MS = 1000.0 / 60.0
local STAGE_Y = 276
-- the movie root places every ending clip at (1209, 109.5) on its 1920x264 stage;
-- the exported frames are in clip-local coordinates, so add that placement
local CLIP_X, CLIP_Y = 1209, 109.5
local CLIPS = { donderful = "ending_donderful", full_combo = "ending_fullcombo",
clear = "ending_clear", fail = "ending_fail" }
function Ending.available(kind)
local name = CLIPS[kind]
if not name then return false end
local ok, data = pcall(require, "game/ending_anim/" .. name)
return ok and type(data) == "table" and data.frames ~= nil
end
function Ending.new(kind, player_num, path)
local name = CLIPS[kind]
local ok, data = pcall(require, "game/ending_anim/" .. name)
if not ok or type(data) ~= "table" or not data.frames then return nil end
local self = setmetatable({}, Ending)
self.kind = kind
self.subset = name .. "_anim"
self.path = path or "game"
tex.load_folder(self.path, self.subset)
self.data = data
self.ids = {}
self.t0 = nil
self.y = STAGE_Y
if player_num == 2 then
local off = tex.get_skin_config("game_2p_offset")
self.y = STAGE_Y + ((off and off.y) or 0)
end
return self
end
function Ending:id_of(shape)
local id = self.ids[shape]
if id == nil then
id = tex.get_id(self.subset, "s" .. shape) or false
self.ids[shape] = id
end
return id or nil
end
function Ending:frame(current_ms)
if not self.t0 then self.t0 = current_ms end
local f = math.floor((current_ms - self.t0) / FRAME_MS)
local n, loop_at = self.data.nframes, self.data.loop_at
if f >= n then
local span = n - loop_at
f = (span > 0) and (loop_at + (f - loop_at) % span) or (n - 1)
end
return f
end
function Ending:draw(current_ms)
local rows = self.data.frames[self:frame(current_ms) + 1]
if not rows then return end
local shapes = self.data.shapes
for i = 1, #rows do
local r = rows[i]
local s = shapes[r[1]]
local id = s and self:id_of(r[1])
if id then
local sx, sy = r[4], r[5]
tex.draw_texture(id, {
x = CLIP_X + r[2],
y = self.y + CLIP_Y + r[3],
x2 = s[1] * (sx - 1),
y2 = s[2] * (sy - 1),
origin = { s[3] * sx, s[4] * sy },
rotation = r[6],
fade = r[7],
})
end
end
end
function Ending:destroy()
tex.unload_folder(self.path, self.subset)
end
return Ending