Skip to content

20 Time Scale

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

Time Scale

Time scale is a common feature in many rhythm games, and another name for it is soflan. We will be implementing time scale, and more specifically the time scale that works like slowing down/speeding up the entire world.

Because of time scale's prevalence, Sonolus has also provided convenient functions to work with time scale.

Time Scale Changes

Similar to BPM changes, there is also a special archetype name for time scale changes as well as special archetype data name for time scale value. Let's add a list of time scale changes to our test level:

TypeScript
export const data: LevelData = {
    // ...
    entities: [
        // ...

        ...chart.timeScales.map(({ beat, timeScale }) => ({
            archetype: EngineArchetypeName.TimeScaleChange,
            data: [
                {
                    name: EngineArchetypeDataName.Beat,
                    value: beat,
                },
                {
                    name: EngineArchetypeDataName.TimeScale,
                    value: timeScale,
                },
            ],
        })),

        // ...
    ],
}
JavaScript
export const data = {
    // ...
    entities: [
        // ...

        ...chart.timeScales.map(({ beat, timeScale }) => ({
            archetype: EngineArchetypeName.TimeScaleChange,
            data: [
                {
                    name: EngineArchetypeDataName.Beat,
                    value: beat,
                },
                {
                    name: EngineArchetypeDataName.TimeScale,
                    value: timeScale,
                },
            ],
        })),

        // ...
    ],
}

Note

To implement time scale for Note, we simply need to change all rendering related logics to use scaled time instead.

Notice that we are not changing input related logics, as time scale should remain a purely visual feature and should not affect how input is handled.

TypeScript
export class Note extends Archetype {
    // ...

    preprocess() {
        // ...

        this.visualTime.copyFrom(Range.l.add(timeScaleChanges.at(this.targetTime).scaledTime))

        // ...
    }

    // ...

    shouldSpawn() {
        return time.scaled >= this.spawnTime
    }

    // ...

    updateParallel() {
        // ...

        const y = Math.unlerp(this.visualTime.min, this.visualTime.max, time.scaled)

        // ...
    }
}
JavaScript
export class Note extends Archetype {
    // ...

    preprocess() {
        // ...

        this.visualTime.copyFrom(Range.l.add(timeScaleChanges.at(this.targetTime).scaledTime))

        // ...
    }

    // ...

    shouldSpawn() {
        return time.scaled >= this.spawnTime
    }

    // ...

    updateParallel() {
        // ...

        const y = Math.unlerp(this.visualTime.min, this.visualTime.max, time.scaled)

        // ...
    }
}