Skip to content

05 Note Lifetime

In this chapter, we will create WatchNote and give each note a lifetime on the scaled watch timeline.

Note Archetype

Create guide/watch/note.py with this chapter's complete note implementation:

python
from sonolus.script.archetype import StandardImport, WatchArchetype, entity_data
from sonolus.script.interval import Interval
from sonolus.script.timing import beat_to_bpm, beat_to_time, time_to_scaled_time

from guide.lib import archetype_names


class WatchNote(WatchArchetype):
    name = archetype_names.NOTE

    beat: StandardImport.BEAT

    target_time: float = entity_data()
    visual_time: Interval = entity_data()

    def preprocess(self):
        self.target_time = beat_to_time(self.beat)
        self.visual_time.end = time_to_scaled_time(self.target_time)
        self.visual_time.start = self.visual_time.end - 120 / beat_to_bpm(self.beat)

    def spawn_time(self) -> float:
        return self.visual_time.start

    def despawn_time(self) -> float:
        return self.visual_time.end

Sonolus matches archetypes to level entities by name. WatchNote uses the same name as the play-mode Note archetype, so it handles the same entities.

Registration and Life

Register the new archetype in guide/watch/mode.py. The watch import block should now read:

python
from guide.watch.initialization import WatchInitialization
from guide.watch.note import WatchNote
from guide.watch.stage import WatchStage
from guide.watch.update_spawn import update_spawn

Then replace only the archetypes argument with:

python
archetypes=[WatchInitialization, WatchStage, WatchNote],

Now edit guide/watch/initialization.py. Import init_life and WatchNote, then call init_life(WatchNote) between score and UI setup. The completed file should contain:

python
from sonolus.script.archetype import WatchArchetype

from guide.lib import archetype_names
from guide.lib.buckets import init_buckets
from guide.lib.layout import init_layout
from guide.lib.note import init_life, init_score
from guide.lib.options import Options
from guide.lib.ui import init_ui
from guide.watch.note import WatchNote


class WatchInitialization(WatchArchetype):
    name = archetype_names.INITIALIZATION

    def preprocess(self):
        init_buckets()
        init_score()
        init_life(WatchNote)
        init_ui()
        init_layout(Options.note_size)

This reuses the life configuration from Play chapter 17.

Target and Visual Times

preprocess runs before spawn_time and despawn_time. The entity_data fields keep its calculated values available to those later callbacks. target_time stores the note's time on the level timeline. time_to_scaled_time converts that value to the scaled timeline selected in chapter 02 and stores it as visual_time.end. Subtracting 120 / beat_to_bpm(self.beat) sets a baseline visual duration of two beats at the note's BPM. Time-scale and nearby BPM changes can affect the actual fall time. This reuses the calculation from Play chapter 21.

Lifetime

The note is active from visual_time.start until visual_time.end. At this checkpoint, the note has a lifetime but is not visible and produces no feedback. The next four chapters add drawing, result data, sound, and particles. Chapter 10 changes the despawn time during replay to use the recorded hit time.