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.HANDText
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)Calling show() replaces any instruction currently displayed, but that instruction remains visible until another instruction replaces it or clear_instruction() clears it. Because draw_frozen calls show() only during the frozen range, clear the instruction at the start of every frame. During the frozen range, draw_frozen then shows the tap instruction again. Outside that range, the instruction area remains empty. Replace guide/tutorial/update.py with the following code:
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:
Vec2(0, 1)is the judgment-line position in the engine coordinate system from chapter 03.- Sprites receive the skin transform automatically, but
InstructionIcons.hand.paintexpects screen coordinates. skin_transform().transform_vec(Vec2(0, 1))converts the judgment-line position to screen coordinates.- 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.