05. Note Display
In this chapter, we will implement note display component.
Note Display Component
Let's first set up note display:
export const noteDisplay = {}
export const noteDisplay = {}
// ...
const components = [initialization, stage, noteDisplay] as const
// ...
// ...
const components = [initialization, stage, noteDisplay]
// ...
Declaring
Declare note's skin sprite:
export const skin = defineSkin({
sprites: {
// ...
note: SkinSpriteName.NoteHeadCyan,
},
})
export const skin = defineSkin({
sprites: {
// ...
note: SkinSpriteName.NoteHeadCyan,
},
})
Modes
Note display component will be responsible for drawing note in various segments, so let's declare a mode enum to represent it:
enum Mode {
None,
Overlay,
Fall,
Frozen,
}
const Mode = {
None: 0,
Overlay: 1,
Fall: 2,
Frozen: 3,
}
To store the current mode, we can use a value in Tutorial Memory block:
let mode = tutorialMemory(DataType<Mode>)
let mode = tutorialMemory(Number)
Lastly, the component can expose a set of methods to change the current mode:
// ...
export const noteDisplay = {
showOverlay() {
mode = Mode.Overlay
},
showFall() {
mode = Mode.Fall
},
showFrozen() {
mode = Mode.Frozen
},
clear() {
mode = Mode.None
},
}
// ...
export const noteDisplay = {
showOverlay() {
mode = Mode.Overlay
},
showFall() {
mode = Mode.Fall
},
showFrozen() {
mode = Mode.Frozen
},
clear() {
mode = Mode.None
},
}
Drawing
Let's draw the note display.
When we are in none mode, there's nothing to draw and we simply exit:
// ...
export const noteDisplay = {
update() {
if (!mode) return
},
// ...
}
// ...
export const noteDisplay = {
update() {
if (!mode) return
},
// ...
}
When we are in overlay mode which is used by intro segment, we should draw an enlarged note near center of the screen.
Note that intro segment lasts for 1 second, so we make the note start fading out starting from 0.75 seconds.
// ...
export const noteDisplay = {
update() {
// ...
if (mode === Mode.Overlay) {
const a = Math.unlerpClamped(1, 0.75, segment.time)
const layout = Rect.one
.mul(2 * note.radius)
.scale(1, -1)
.translate(0, 0.5)
skin.sprites.note.draw(layout, 1000, a)
}
},
// ...
}
// ...
export const noteDisplay = {
update() {
// ...
if (mode === Mode.Overlay) {
const a = Math.unlerpClamped(1, 0.75, segment.time)
const layout = Rect.one
.mul(2 * note.radius)
.scale(1, -1)
.translate(0, 0.5)
skin.sprites.note.draw(layout, 1000, a)
}
},
// ...
}
When we are in fall mode which is used by fall segment, we should draw a note falling from top to judgment line.
Note that fall segment lasts for 2 seconds, so we should calculate y
position accordingly.
When we are in frozen mode which is used by frozen segment, we should draw a note frozen at the judgment line. We can reuse the fall mode code by simply setting y
to 1
.
// ...
export const noteDisplay = {
update() {
// ...
if (mode === Mode.Overlay) {
// ...
} else {
const y = mode === Mode.Fall ? Math.unlerp(0, 2, segment.time) : 1
const layout = Rect.one.mul(note.radius).scale(1, -1).translate(0, y)
skin.sprites.note.draw(layout, 1000, 1)
}
},
// ...
}
// ...
export const noteDisplay = {
update() {
// ...
if (mode === Mode.Overlay) {
// ...
} else {
const y = mode === Mode.Fall ? Math.unlerp(0, 2, segment.time) : 1
const layout = Rect.one.mul(note.radius).scale(1, -1).translate(0, y)
skin.sprites.note.draw(layout, 1000, 1)
}
},
// ...
}