Skip to content

07 Instructions

In this chapter, we will add instructions to the frozen part of the tap phase.

Declaring

The template's TutorialMode already connects the shared skin, effects, and particles. It also connects the tutorial-specific instructions and instruction icons declared in guide/tutorial/instructions.py. Replace that file with the following declarations for the tap text and hand icon.

The StandardInstruction and StandardInstructionIcon classes provide these built-in resources:

python
from sonolus.script.instruction import (
    StandardInstruction,
    StandardInstructionIcon,
    instruction_icons,
    instructions,
)


@instructions
class Instructions:
    tap: StandardInstruction.TAP


@instruction_icons
class InstructionIcons:
    hand: StandardInstructionIcon.HAND

Text

In guide/tutorial/note.py, add the Instructions import shown below. Then replace draw_frozen so it shows the tap instruction while the frozen range is active:

python
from guide.tutorial.instructions import Instructions


def draw_frozen(elapsed: float):
    Instructions.tap.show()
    draw_note(1)

Clear any previous instruction at the start of each frame. Replace guide/tutorial/update.py with the following code. The frozen range then shows the tap text, and no instruction remains after that range ends:

python
from sonolus.script.instruction import clear_instruction

from guide.tutorial.navigate import current_phase_time, finish_frame, reset_phase
from guide.tutorial.note import tap_phase
from guide.tutorial.stage import draw_stage


def update():
    clear_instruction()
    draw_stage()
    if tap_phase(current_phase_time()):
        reset_phase()
    finish_frame()

Icon

Next, replace draw_frozen again with the complete version that includes the hand animation. In guide/tutorial/note.py, add from math import pi at the top, add the runtime import shown below, and replace the existing Instructions import with the InstructionIcons, Instructions import. The existing interval import already provides remap_clamped.

The runtime_ui function provides the player's instruction scale and alpha settings:

python
from math import pi

from sonolus.script.runtime import runtime_ui, skin_transform

from guide.tutorial.instructions import InstructionIcons, Instructions


def draw_frozen(elapsed: float):
    Instructions.tap.show()
    draw_note(1)

    cycle = elapsed % 1
    angle = remap_clamped(0.25, 0.75, pi / 6, pi / 3, cycle)
    alpha = remap_clamped(0.5, 0.25, 0, 1, abs(cycle - 0.5))

    config = runtime_ui().instruction_config
    judgment_position = skin_transform().transform_vec(Vec2(0, 1))
    position = Vec2(0, -1).rotate(pi / 3) * (0.25 * config.scale) + judgment_position

    InstructionIcons.hand.paint(
        position=Vec2(0, 1).rotate(angle) * (0.25 * config.scale) + position,
        size=0.25 * config.scale,
        rotation=(180 * angle) / pi,
        z=0,
        a=alpha * config.alpha,
    )

elapsed % 1 creates a one-second animation cycle. During each cycle, angle sweeps the hand through the tap motion, and alpha fades it at both ends.

The hand position crosses two coordinate systems:

  1. Vec2(0, 1) is the judgment-line position in the engine coordinate system from chapter 03.
  2. Sprites receive the skin transform automatically, but InstructionIcons.hand.paint expects screen coordinates.
  3. skin_transform().transform_vec(Vec2(0, 1)) converts the judgment-line position to screen coordinates.
  4. The remaining vectors offset and animate the hand around that screen position using the player's instruction scale.

Vec2.rotate uses radians, while paint receives its rotation in degrees.

The tap text and hand animation now appear only while the note is frozen.