Files
PyTaikoGreen/Scripts/background/bg_objects/timing.lua
T
2026-10-10 14:52:27 -04:00

137 lines
4.8 KiB
Lua

-- Timing helpers for the backgrounds. Lengths are multiples of
-- `unit` ms: a beat (60000 / bpm) for things that follow the song, 1 for plain ms.
-- Moves { length, distance, ease } run back to back (distance 0 is a hold); the offset
-- they describe is the sum of their attributes
local function chain_moves(moves, unit, scale)
local out, at = {}, 0
for _, m in ipairs(moves) do
local length, distance, ease = m[1], m[2], m[3]
if distance ~= 0 then
local move = anim.move(length * unit, {
total_distance = math.floor(distance * scale),
delay = at,
ease_in = ease == "in" and "quadratic" or nil,
ease_out = ease == "out" and "quadratic" or nil,
})
move:start()
table.insert(out, move)
end
at = at + length * unit
end
return out
end
-- Frames { index, length } shown back to back
local function frame_change(steps, unit, opts)
local textures, t = {}, 0
for _, step in ipairs(steps) do
table.insert(textures, { t, t + step[2] * unit, step[1] })
t = t + step[2] * unit
end
return anim.texture_change(t, textures, opts)
end
local function sum(moves)
local total = 0
for _, m in ipairs(moves) do total = total + m.attribute end
return total
end
local function concat(...)
local out = {}
for _, t in ipairs({ ... }) do
for _, v in ipairs(t) do table.insert(out, v) end
end
return out
end
-- Ambient loop: `build()` returns a table of one-shot anims (or lists of them) that is
-- rebuilt every `period` ms, so several anims stay in step without each looping alone
local Loop = {}
Loop.__index = Loop
function Loop.new(period, build)
return setmetatable({ period = period, build = build }, Loop)
end
function Loop:update(current_time_ms)
if not self.t0 or current_time_ms - self.t0 >= self.period then
self.t0 = current_time_ms
self.anims = self.build()
end
for _, a in pairs(self.anims) do
if a.update then
a:update(current_time_ms)
else
for _, m in ipairs(a) do m:update(current_time_ms) end
end
end
end
-- Pose tables: { beat, frame, x, y, to = { x, y, ease, sx =, sy =, scale =, alpha =, rot = },
-- sx =, sy =, scale =, alpha =, rot =, mirror = true, blend =, key =, hide = true }. A pose holds from its
-- beat until the next one (or the table's `length`), gliding `to` the given values on the way
-- if it has one. x, y left out keep the previous pose's (nil if none has them). pose_at(poses, t) is the pose at beat t
-- (nil before the first and from a `hide` pose). Eases match anim's quadratic ease_in/ease_out.
local EASE = {
["in"] = function(p) return p * p end,
["out"] = function(p) return p * (2 - p) end,
}
local function lerp(a, b, k) return b == nil and a or (a or 0) + (b - (a or 0)) * k end
local function pose_at(poses, t)
local x, y
for i, p in ipairs(poses) do
x, y = p[3] or x, p[4] or y
local next_p = poses[i + 1]
local finish = next_p and next_p[1] or poses.length or math.huge
if t < p[1] then return nil end
if t < finish then
if p.hide then return nil end
local at = { frame = p[2], x = x, y = y, key = p.key, mirror = p.mirror, blend = p.blend,
sx = p.sx or p.scale or 1, sy = p.sy or p.scale or 1, alpha = p.alpha or 1, rot = p.rot or 0 }
local to = p.to
if to then
local k = (t - p[1]) / (finish - p[1])
k = EASE[to[3]] and EASE[to[3]](k) or k
at.x, at.y = lerp(x, to[1], k), lerp(y, to[2], k)
at.sx, at.sy = lerp(at.sx, to.sx or to.scale, k), lerp(at.sy, to.sy or to.scale, k)
at.alpha, at.rot = lerp(at.alpha, to.alpha, k), lerp(at.rot, to.rot, k)
end
return at
end
if p.to then x, y = p.to[1] or x, p.to[2] or y end
end
return nil
end
-- Steps { frame, beats } played back to back (any number of phrases) as a pose table. A step
-- can carry pose fields too: { frame, beats, x =, y =, to =, sy =, ... }
local function steps(...)
local out, beat = {}, 0
for _, phrase in ipairs({ ... }) do
for _, s in ipairs(phrase) do
local pose = { beat, s[1], s.x, s.y }
for k, v in pairs(s) do
if type(k) ~= "number" and k ~= "x" and k ~= "y" then pose[k] = v end
end
table.insert(out, pose)
beat = beat + s[2]
end
end
out.length = beat
return out
end
return {
pose_at = pose_at,
steps = steps,
chain_moves = chain_moves,
frame_change = frame_change,
sum = sum,
concat = concat,
Loop = Loop,
}