Skip to content

20 Time Scale

In this chapter, we will add time scale to Note.

How Time Scale Works

Time scale is a common feature in rhythm games, where it is also known as soflan. It changes visual scroll speed over time.

Sonolus.py provides a function that maps level time to the visual timeline and another that provides the current scaled time.

Add Time Scale Changes to the Level

As with BPM changes, Sonolus.py provides a standard archetype for time scale changes. Open guide/level.py and import TimescaleChange. Then expand the chart's time scale changes into guide_level.data.entities after the BPM changes and before the Notes:

python
from sonolus.script.level import BpmChange, Level, LevelData, TimescaleChange

from guide.chart import BPMS, NOTE_BEATS, TIMESCALES


guide_level = Level(
    # ...
    data=LevelData(
        entities=[
            # ...

            *(
                TimescaleChange(beat=beat, timescale=timescale)
                for beat, timescale in TIMESCALES
            ),

            # ...
        ],
    ),
)

The starred generator expands the chart's time scale changes directly into the entity list, as we did for BPM changes and notes.

Apply Time Scale to Note Rendering

To implement time scale for Note, update Note.preprocess, Note.should_spawn, and Note.update_parallel in guide/play/note.py to use scaled_time for visual timing.

time_to_scaled_time converts a level time to the visual timeline. scaled_time() returns the current time on that timeline.

Do not change target_time or the input logic. Time scale changes when a Note appears and where it is drawn, but the Note must still be judged against its original target time.

python
from sonolus.script.runtime import input_offset, scaled_time, time, touches
from sonolus.script.timing import beat_to_time, time_to_scaled_time


class Note(PlayArchetype):
    # ...

    def preprocess(self):
        # ...

        self.visual_time.end = time_to_scaled_time(self.target_time)
        self.visual_time.start = self.visual_time.end - 1
        self.spawn_time = self.visual_time.start

        # ...

    def should_spawn(self) -> bool:
        return scaled_time() >= self.spawn_time

    # ...

    def update_parallel(self):
        # ...

        y = self.visual_time.unlerp(scaled_time())

        # ...

Checkpoint

Run the level through the time scale changes near the end of the chart. Note movement should speed up and then return to normal, while the music and input timing remain unchanged.