168 lines
5.9 KiB
Lua
168 lines
5.9 KiB
Lua
AttractChara = {}
|
|
AttractChara.__index = AttractChara
|
|
|
|
local FRAME = 1000 / 60
|
|
local START_MS = 3000
|
|
local GAP_MS = 500
|
|
|
|
local function ease_out(t) return t * (2 - t) end
|
|
local function ease_in(t) return t * t end
|
|
local function lerp(a, b, t) return a + (b - a) * t end
|
|
local function span(t, t0, t1) return math.max(0, math.min(1, (t - t0) / (t1 - t0))) end
|
|
|
|
-- Shiba: rises from below, overshoots, settles, then sinks away
|
|
local SHIBA_MS = 3100
|
|
local SHIBA_REST_Y = 202.5
|
|
local SHIBA_RISE = { from = 632.5, top = 157.5, ms = 333, settle_ms = 167 }
|
|
local SHIBA_SINK = { start = 2533, ms = 333, to = 590.5 }
|
|
local SHIBA_WAG_START = 500
|
|
-- breath puffs: { index in texture.json, ms they pop at, fades out linearly }
|
|
local PUFFS = {
|
|
{ 1, 500 }, { 2, 750, true }, { 3, 1000 },
|
|
{ 1, 1700 }, { 2, 1933, true }, { 3, 2167 },
|
|
}
|
|
local PUFF_POP_MS = 100
|
|
local PUFF_FADE_MS = 833
|
|
|
|
-- Fox-mask bear: slides in from the left, waves, slides back out
|
|
local FOX_MS = 3583
|
|
local FOX_X = { from = 11.5, rest = 246.5, to = 16.5, in_ms = 500, out_at = 3167, out_ms = 417 }
|
|
|
|
-- Chicks: { texture, flap ms per frame, first flap, path of { frame, x, y } }
|
|
local BIRDS_MS = 3917
|
|
local BIRDS = {
|
|
{ "bird_yellow", 5, 21, { { 0, 742, -16 }, { 20, 742, 128 }, { 21, 742, 133 }, { 139, 742, 598 } } },
|
|
{ "bird_pink", 5, 40, { { 0, 153, 114 }, { 15, 249, 66 }, { 16, 257, 66 }, { 28, 332, 111 }, { 39, 371, 184 }, { 40, 374, 185 }, { 159, 366, 594 } } },
|
|
{ "bird_green", 8, 39, { { 39, 501, -8 }, { 198, 501, 593 } } },
|
|
{ "bird_blue", 5, 111, { { 49, 994, 94 }, { 70, 877, 61 }, { 71, 871, 61 }, { 110, 678, 164 }, { 111, 676, 170 }, { 234, 594, 597 } } },
|
|
}
|
|
|
|
local SEQUENCES = { { "shiba", SHIBA_MS }, { "fox", FOX_MS }, { "birds", BIRDS_MS } }
|
|
local LAP_MS = 0
|
|
for _, s in ipairs(SEQUENCES) do LAP_MS = LAP_MS + s[2] + GAP_MS end
|
|
|
|
function AttractChara.new()
|
|
local self = setmetatable({}, AttractChara)
|
|
self.start_ms = tex.get_current_ms()
|
|
self.active = nil
|
|
self.t = 0
|
|
|
|
self.tail_wag = tex.get_animation(46, "title")
|
|
self.fox_poses = tex.get_animation(47, "title")
|
|
|
|
self.t_shiba = tex.get_texture("title/camera/shiba")
|
|
self.t_tail = tex.get_texture("title/camera/shiba_tail")
|
|
self.t_puff = tex.get_texture("title/camera/shiba_puff")
|
|
self.t_fox = tex.get_texture("title/camera/fox")
|
|
self.t_birds = {}
|
|
for _, b in ipairs(BIRDS) do
|
|
self.t_birds[b[1]] = tex.get_texture("title/camera/" .. b[1])
|
|
end
|
|
return self
|
|
end
|
|
|
|
function AttractChara:update(current_ms)
|
|
local now = current_ms - self.start_ms - START_MS
|
|
local active
|
|
if now >= 0 then
|
|
local at = now % LAP_MS
|
|
for _, s in ipairs(SEQUENCES) do
|
|
if at < s[2] then
|
|
active, self.t = s[1], at
|
|
break
|
|
end
|
|
at = at - s[2] - GAP_MS
|
|
if at < 0 then break end
|
|
end
|
|
end
|
|
if active ~= self.active then
|
|
self.active = active
|
|
if active == "shiba" then self.tail_wag:start() end
|
|
if active == "fox" then self.fox_poses:start() end
|
|
end
|
|
self.tail_wag:update(current_ms)
|
|
self.fox_poses:update(current_ms)
|
|
end
|
|
|
|
local function path_at(path, ms)
|
|
local frame = ms / FRAME
|
|
if frame < path[1][1] or frame > path[#path][1] then return nil end
|
|
for i = 2, #path do
|
|
local a, b = path[i - 1], path[i]
|
|
if frame <= b[1] then
|
|
local t = (frame - a[1]) / (b[1] - a[1])
|
|
return lerp(a[2], b[2], t), lerp(a[3], b[3], t)
|
|
end
|
|
end
|
|
end
|
|
|
|
function AttractChara:draw_shiba()
|
|
local t = self.t
|
|
local y
|
|
if t < SHIBA_RISE.ms then
|
|
y = lerp(SHIBA_RISE.from, SHIBA_RISE.top, ease_out(t / SHIBA_RISE.ms))
|
|
elseif t < SHIBA_RISE.ms + SHIBA_RISE.settle_ms then
|
|
y = lerp(SHIBA_RISE.top, SHIBA_REST_Y, ease_in((t - SHIBA_RISE.ms) / SHIBA_RISE.settle_ms))
|
|
else
|
|
y = lerp(SHIBA_REST_Y, SHIBA_SINK.to, span(t, SHIBA_SINK.start, SHIBA_SINK.start + SHIBA_SINK.ms))
|
|
end
|
|
local dy = y - SHIBA_REST_Y
|
|
|
|
-- the tail swings about its base, the bottom-right corner of the image
|
|
local angle = t < SHIBA_WAG_START and -70 or self.tail_wag.attribute / 10
|
|
tex.draw_texture(self.t_tail, { y = dy, origin = { 128, 152 }, rotation = angle })
|
|
|
|
local frame = 0
|
|
if t >= SHIBA_WAG_START and math.floor((t - SHIBA_WAG_START) / (10 * FRAME)) % 2 == 0 then frame = 1 end
|
|
tex.draw_texture(self.t_shiba, { y = dy, frame = frame })
|
|
|
|
for _, puff in ipairs(PUFFS) do
|
|
local u = t - puff[2]
|
|
if u >= 0 and u < PUFF_POP_MS + PUFF_FADE_MS then
|
|
local scale, fade = 1, 1
|
|
if u < PUFF_POP_MS then
|
|
scale = lerp(0.3, 1, u / PUFF_POP_MS)
|
|
else
|
|
local f = (u - PUFF_POP_MS) / PUFF_FADE_MS
|
|
fade = 0.96 * (1 - (puff[3] and f or ease_out(f)))
|
|
end
|
|
tex.draw_texture(self.t_puff, { index = puff[1] - 1, scale = scale, center = true, fade = fade })
|
|
end
|
|
end
|
|
end
|
|
|
|
function AttractChara:draw_fox()
|
|
local t = self.t
|
|
local x
|
|
if t < FOX_X.out_at then
|
|
x = lerp(FOX_X.from, FOX_X.rest, span(t, 0, FOX_X.in_ms))
|
|
else
|
|
x = lerp(FOX_X.rest, FOX_X.to, span(t, FOX_X.out_at, FOX_X.out_at + FOX_X.out_ms))
|
|
end
|
|
-- texture.json x is the rest position
|
|
tex.draw_texture(self.t_fox, { x = x - FOX_X.rest, frame = math.floor(self.fox_poses.attribute) })
|
|
end
|
|
|
|
function AttractChara:draw_birds()
|
|
for _, b in ipairs(BIRDS) do
|
|
local x, y = path_at(b[4], self.t)
|
|
if x then
|
|
local frame = 0
|
|
if self.t >= b[3] * FRAME then
|
|
frame = math.floor((self.t - b[3] * FRAME) / (b[2] * FRAME)) % 2
|
|
end
|
|
tex.draw_texture(self.t_birds[b[1]], { x = x, y = y, frame = frame })
|
|
end
|
|
end
|
|
end
|
|
|
|
function AttractChara:draw()
|
|
if self.active == "shiba" then
|
|
self:draw_shiba()
|
|
elseif self.active == "fox" then
|
|
self:draw_fox()
|
|
elseif self.active == "birds" then
|
|
self:draw_birds()
|
|
end
|
|
end
|