Notes: the cabinet's flight and big-note sparkle trail #70

Merged
GanYiZhong merged 1 commits from GanYiZhong/YataiDONNijiiro:feat/big-note-kirakira-lua into main 2026-10-14 21:54:36 +02:00
3 changed files with 179 additions and 0 deletions

No files matched your search

Binary file not shown.
+82
View File
@@ -146,5 +146,87 @@
336 336
] ]
] ]
},
"firework": {
"crop": [
[
0,
0,
256,
256
],
[
256,
0,
256,
256
],
[
512,
0,
256,
256
],
[
768,
0,
256,
256
],
[
0,
256,
256,
256
],
[
256,
256,
256,
256
],
[
512,
256,
256,
256
],
[
768,
256,
256,
256
],
[
0,
512,
256,
256
],
[
256,
512,
256,
256
],
[
512,
512,
256,
256
],
[
768,
512,
256,
256
],
[
0,
768,
256,
256
]
]
} }
} }
+94
View File
@@ -0,0 +1,94 @@
-- This skin's note arc: the parent's script with the flight and the big-note sparks redone as
-- the cabinet's (onp_jump):
-- * the note flies from the judge circle to 魂 in 30 frames on the cabinet's path, here a
-- cubic Bezier fitted to it (within 6 px), P1 arcing up and P2 down, each with its own fit;
-- it then waits at 魂 until the engine's flight (31 frames) ends
-- * a big note leaves 9 sparks (hit_effect/firework, the cabinet's 13-frame spark on black),
-- the first at frame 5 and then every 3 frames, each centred where the note was about 2
-- frames before, at full size, added onto the scene
-- The balloon keeps the parent's flight and rainbow (the cabinet's balloon has its own path).
-- Everything else is the parent's file, loaded from its own copy.
-- the parent skin's copy of a script: the second match on package.path (the first is this file)
local function load_parent(name)
local seen = false
for tmpl in package.path:gmatch("[^;]+") do
local chunk = loadfile((tmpl:gsub("%?", name)))
if chunk then
if seen then return chunk end
seen = true
end
end
end
assert(load_parent("game/note_arc"), "game/note_arc: the parent skin has no note_arc script")()
local FRAME_MS = 16.67
local FLIGHT_MS = 30 * FRAME_MS
-- control points as offsets from the start (c1) and from the end (c2)
local CONTROL = {
[1] = { c1x = 196.8, c1y = -474.8, c2x = -300.4, c2y = -419.3 },
[2] = { c1x = 178.5, c1y = 486.4, c2x = -314.9, c2y = 414.3 },
}
-- sparks: spawn frames, and where each sits on the 30-frame flight (frames)
local SPARK_COUNT = 9
local SPARK_SPAWN, SPARK_SPAWN_STEP = 5, 3
local SPARK_AT, SPARK_AT_STEP = 2.58, 3.085
local parent_new = NoteArc.new
function NoteArc.new(note_tex_type, big, is_balloon, player_num, start_x, start_y)
local self = parent_new(note_tex_type, big, is_balloon, player_num, start_x, start_y)
local c = CONTROL[self.p2 and 2 or 1]
self.c1x, self.c1y = self.sx + c.c1x, self.sy + c.c1y
self.c2x, self.c2y = self.ex + c.c2x, self.ey + c.c2y
-- a spark is centred on the note: both textures' own offsets and sizes
local note = tex.get_texture_info("notes", tostring(note_tex_type))
local spark = self.t_firework and tex.get_texture_info("hit_effect", "firework")
if note and spark then
self.spark_dx = note.x[1] - spark.x[1] + (note.x2[1] - spark.x2[1]) / 2
self.spark_dy = note.y[1] - spark.y[1] + (note.y2[1] - spark.y2[1]) / 2
end
return self
end
local parent_point = NoteArc.point
local parent_draw = NoteArc.draw
-- the cabinet's flight, progress 0..1 over its 30 frames
function NoteArc:point(progress)
if self.is_balloon then return parent_point(self, progress) end
local t = math.max(0, math.min(1, progress))
local u = 1 - t
local b0, b1, b2, b3 = u * u * u, 3 * u * u * t, 3 * u * t * t, t * t * t
return b0 * self.sx + b1 * self.c1x + b2 * self.c2x + b3 * self.ex,
b0 * self.sy + b1 * self.c1y + b2 * self.c2y + b3 * self.ey
end
function NoteArc:draw_fireworks(y, elapsed_ms)
if not self.spark_dx then return end
for i = 0, SPARK_COUNT - 1 do
local age_ms = elapsed_ms - (SPARK_SPAWN + SPARK_SPAWN_STEP * i) * FRAME_MS
local frame = math.floor(age_ms / FRAME_MS)
if age_ms >= 0 and frame < self.firework_frames then
local fx, fy = self:point((SPARK_AT + SPARK_AT_STEP * i) * FRAME_MS / FLIGHT_MS)
tex.draw_texture(self.t_firework, {
frame = frame, center = true, blend = "additive",
x = fx + self.spark_dx, y = y + fy + self.spark_dy,
})
end
end
end
function NoteArc:draw(y, elapsed_ms, duration_ms)
if self.is_balloon then return parent_draw(self, y, elapsed_ms, duration_ms) end
if self.big and self.t_firework and self.firework_frames > 0 then
self:draw_fireworks(y, elapsed_ms)
end
if elapsed_ms < duration_ms then
local x_i, y_i = self:point(elapsed_ms / FLIGHT_MS)
tex.draw_texture(self.t_note, { x = x_i, y = y + y_i })
end
end
return NoteArc