Sonolus Wiki

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)
        }
    },

    // ...
}