203 lines
8.3 KiB
Lua
203 lines
8.3 KiB
Lua
-- Song-loading (rainbow) transition backdrop, sampled from the arcade timeline.
|
|
local ok, anim = pcall(require, "anim/sampler")
|
|
local CLIP = (ok and anim and anim.safe) and anim.safe("anim/loading_song") or nil
|
|
|
|
SongTransition = {}
|
|
SongTransition.__index = SongTransition
|
|
|
|
local FADE_DELAY, FADE_MS, FADE_OUT_MS = 266.0, 266.0, 133.0
|
|
local SCENE_MS = FADE_DELAY + FADE_MS -- 532 ms: how long each scene shows
|
|
|
|
local F_IN, F_STOP, F_OUT, F_END = 5, 55, 60, 109
|
|
local F_FULL_A, F_FULL_B = 35, 78 -- the full-rainbow window
|
|
|
|
-- sub-rects inside the two atlases
|
|
local SRC_CURT_L = { x = 0, y = 0, width = 960, height = 1080 }
|
|
local SRC_CURT_R = { x = 960, y = 0, width = 960, height = 1080 }
|
|
local SRC_BAND = { x = 0, y = 0, width = 1600, height = 256 }
|
|
local SRC_GLOW = { x = 0, y = 288, width = 1600, height = 512 }
|
|
local SRC_STAR = { x = 0, y = 832, width = 128, height = 128 }
|
|
|
|
-- rest positions from texture.json; draws pass a delta off them
|
|
local REST_CHARA_L = { -120, 506 }
|
|
local REST_CHARA_R = { 1480, 506 }
|
|
local REST_HINT = { 0, 568 }
|
|
|
|
-- eleven star placements, already in screen px
|
|
local STARS = {
|
|
{ 209.75, 542.55, 0.59998 }, { 92.35, 283.00, 0.47829 },
|
|
{ 154.20, 330.75, 0.26784 }, { 292.35, 246.35, 0.44998 },
|
|
{ 1259.00, 201.85, 0.78998 }, { 1845.70, 228.45, 0.37422 },
|
|
{ 1791.70, 268.40, 0.24998 }, { 1388.50, 664.80, 0.44713 },
|
|
{ 1821.20, 423.20, 0.56000 }, { 1472.00, 607.70, 0.25038 },
|
|
{ 552.60, 628.95, 0.74998 },
|
|
}
|
|
|
|
local function clamp(v, a, b) if v < a then return a elseif v > b then return b else return v end end
|
|
|
|
-- read a track field, falling back to `default` when the table is gone
|
|
local function track(name, f, field, default)
|
|
if not CLIP then return default end
|
|
local v = CLIP:get(name, f, field)
|
|
if v == nil then return default end
|
|
return v
|
|
end
|
|
|
|
-- 演奏スキップON badge (624x104 pill at (38,54))
|
|
local SKIP_BADGE_X, SKIP_BADGE_Y = 38, 54
|
|
local SKIP_BADGE_CX = 350 -- 38 + 624/2, the EditText's centre
|
|
local SKIP_TEXT_CY = 109 -- 69 + 80/2, the EditText box's centre
|
|
local SKIP_TEXT_SIZE = 30
|
|
-- badge.png declares its own (38,54) origin, so pass it back as rest to cancel
|
|
local SKIP_REST = { 38, 54 }
|
|
|
|
function SongTransition.new(title, subtitle, is_second)
|
|
local self = setmetatable({}, SongTransition)
|
|
self.is_second = is_second and true or false
|
|
self.t0 = nil
|
|
self.f = self.is_second and F_OUT or F_IN
|
|
self.info = 0 -- band / hint opacity, 0..1
|
|
self.rainbow = tex.get_texture("global/rainbow_transition/rainbow_bg")
|
|
self.atlas_c = tex.get_texture("global/rainbow_transition/rainbow_bg_top")
|
|
self.atlas_b = tex.get_texture("global/rainbow_transition/rainbow_bg_bottom")
|
|
self.chara_l = tex.get_texture("global/rainbow_transition/chara_left")
|
|
self.chara_r = tex.get_texture("global/rainbow_transition/chara_right")
|
|
self.hint = tex.get_texture("global/rainbow_transition/chara_center")
|
|
-- the hint block is baked in Japanese; other languages draw it as text
|
|
local lang = tex.language and tex.language() or "ja"
|
|
if lang ~= "ja" and text and text.create_text then
|
|
local white, black = { 255, 255, 255, 255 }, { 0, 0, 0, 255 }
|
|
self.hint_title = text.create_text("transition_hint_title", white, black, false, 4.0, 2.0)
|
|
self.hint_line1 = text.create_text("transition_hint_line1", white, black, false, 3.0, 2.0)
|
|
self.hint_line2 = text.create_text("transition_hint_line2", white, black, false, 3.0, 2.0)
|
|
end
|
|
-- 演奏スキップON badge
|
|
self.skip_badge = tex.get_texture("loading/skip_badge/badge")
|
|
-- resolved on first draw (skip state isn't ready at construction)
|
|
self.skip_on = nil
|
|
if text and text.create_raw_text then
|
|
-- built here (not on first draw) so the glyph atlas is ready
|
|
self.skip_text = text.create_raw_text(tex.skin_text("skip_badge_text"), SKIP_TEXT_SIZE,
|
|
{ 0, 0, 0, 255 }, { 0, 0, 0, 0 }, false, 0, 2)
|
|
end
|
|
return self
|
|
end
|
|
|
|
-- resolved lazily on first draw, once init_skip() has run
|
|
function SongTransition:skip_armed()
|
|
if self.skip_on == nil then
|
|
self.skip_on = (tex.skip_enabled ~= nil) and tex.skip_enabled() or false
|
|
end
|
|
return self.skip_on
|
|
end
|
|
|
|
function SongTransition:update(current_ms, rainbow_up, song_info_fade)
|
|
if not self.t0 then self.t0 = current_ms end
|
|
local t = clamp(current_ms - self.t0, 0, SCENE_MS)
|
|
if self.is_second then
|
|
self.f = F_OUT + (F_END - F_OUT) * (t / SCENE_MS)
|
|
self.info = clamp(1.0 - t / FADE_OUT_MS, 0, 1)
|
|
else
|
|
self.f = F_IN + (F_STOP - F_IN) * (t / SCENE_MS)
|
|
-- use the engine's fade so the band tracks the title it draws on top
|
|
self.info = clamp(song_info_fade or ((t - FADE_DELAY) / FADE_MS), 0, 1)
|
|
end
|
|
end
|
|
|
|
-- draw a texture into an absolute screen rect (scale=0 -> exact x2/y2)
|
|
local function put(id, rest, x, y, w, h, a, src, blend)
|
|
if not id then return end
|
|
tex.draw_texture(id, {
|
|
x = x - rest[1], y = y - rest[2],
|
|
scale = 0, x2 = w, y2 = h,
|
|
fade = a, src = src, blend = blend,
|
|
})
|
|
end
|
|
|
|
local ORIGIN = { 0, 0 }
|
|
|
|
|
|
function SongTransition:draw_bg(total_offset, rainbow_up, mini_up, chara_down)
|
|
local f = self.f
|
|
|
|
-- 1. backdrop: two curved curtains, or the flat full-screen rainbow
|
|
if f >= F_FULL_A and f <= F_FULL_B then
|
|
put(self.rainbow, ORIGIN, 0, 0, 1920, 1080, 1)
|
|
else
|
|
local lx = track("#47@0", f, "tx", -240)
|
|
local ls = track("#47@0", f, "sx", 0.5)
|
|
local rx = track("#49@1", f, "tx", 2160)
|
|
local rs = track("#49@1", f, "sx", 0.5)
|
|
put(self.atlas_c, ORIGIN, lx - 480 * ls, 0, 960 * ls, 1080, 1, SRC_CURT_L)
|
|
put(self.atlas_c, ORIGIN, rx - 480 * rs, 0, 960 * rs, 1080, 1, SRC_CURT_R)
|
|
end
|
|
|
|
-- 2. additive glow along the bottom
|
|
local ga = track("#55@3", f, "a", 0)
|
|
if ga > 0.002 then
|
|
put(self.atlas_b, ORIGIN, 0, 568, 1920, 512, ga, SRC_GLOW, "additive")
|
|
end
|
|
|
|
-- 3. the eleven sparkles — fixed placements, one group alpha
|
|
local sa = track("#3@4", f, "a", 0)
|
|
if sa > 0.002 then
|
|
for i = 1, #STARS do
|
|
local s = STARS[i]
|
|
local d = 128 * s[3]
|
|
put(self.atlas_b, ORIGIN, s[1] - d * 0.5, s[2] - d * 0.5, d, d, sa, SRC_STAR)
|
|
end
|
|
end
|
|
|
|
-- 4. don / katsu on their arc
|
|
local da = track("don", f, "a", 1)
|
|
if da > 0.002 then
|
|
put(self.chara_l, REST_CHARA_L,
|
|
track("don", f, "tx", 160) - 280, track("don", f, "ty", 786) - 280,
|
|
560, 560, da)
|
|
end
|
|
local ka = track("katsu", f, "a", 1)
|
|
if ka > 0.002 then
|
|
put(self.chara_r, REST_CHARA_R,
|
|
track("katsu", f, "tx", 1760) - 280, track("katsu", f, "ty", 786) - 280,
|
|
560, 560, ka)
|
|
end
|
|
|
|
-- 5. the title band (opens/closes on the engine's title fade)
|
|
local info = self.info
|
|
if info > 0.002 then
|
|
local bw, bh
|
|
if self.is_second then
|
|
bw = 1600 * (1.5 - 0.5 * info)
|
|
bh = 256 * (0.1 + 0.9 * info)
|
|
else
|
|
bw = 1600 * (0.25 + 0.75 * info)
|
|
bh = 256
|
|
end
|
|
put(self.atlas_b, ORIGIN, 960 - bw * 0.5, 400 - bh * 0.5, bw, bh, info, SRC_BAND)
|
|
-- 6. the hint block on the same curve
|
|
if self.hint_title then
|
|
-- texture rows, all centred on x = 960
|
|
local function centred(t, y)
|
|
if t then t:draw({ x = math.floor(960 - t.width / 2 + 0.5), y = y, fade = info }) end
|
|
end
|
|
centred(self.hint_title, 568 + 165)
|
|
centred(self.hint_line1, 568 + 228)
|
|
centred(self.hint_line2, 568 + 282)
|
|
else
|
|
put(self.hint, REST_HINT, 0, 568, 1920, 512, info)
|
|
end
|
|
end
|
|
|
|
-- 7. 演奏スキップON badge, top-left, on the same fade
|
|
if self:skip_armed() and self.skip_badge then
|
|
put(self.skip_badge, SKIP_REST, SKIP_BADGE_X, SKIP_BADGE_Y, 624, 104, info)
|
|
if self.skip_text then
|
|
self.skip_text:draw({
|
|
x = SKIP_BADGE_CX - self.skip_text.width / 2,
|
|
y = SKIP_TEXT_CY - self.skip_text.height / 2,
|
|
fade = info,
|
|
})
|
|
end
|
|
end
|
|
end
|