Sonolus Wiki

10. Replay

In this chapter, we will implement replay logic of Note.

Judgment and Accuracy

Replay recreates the recorded gameplay in watch mode.

In order to achieve that, we need the judgment and accuracy information:

export class Note extends Archetype {
    // ...

    import = this.defineImport({
        // ...
        judgment: { name: EngineArchetypeDataName.Judgment, type: DataType<Judgment> },
        accuracy: { name: EngineArchetypeDataName.Accuracy, type: Number },
    })

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

    import = this.defineImport({
        // ...
        judgment: { name: EngineArchetypeDataName.Judgment, type: Number },
        accuracy: { name: EngineArchetypeDataName.Accuracy, type: Number },
    })

    // ...
}

Despawn Time

We accuracy, we can adjust despawn time accordingly:

export class Note extends Archetype {
    // ...

    despawnTime() {
        return replay.isReplay
            ? timeScaleChanges.at(this.targetTime + this.import.accuracy).scaledTime
            : this.visualTime.max
    }

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

    despawnTime() {
        return replay.isReplay
            ? timeScaleChanges.at(this.targetTime + this.import.accuracy).scaledTime
            : this.visualTime.max
    }

    // ...
}

SFX

Similarly, we can adjust SFX based on both judgment and accuracy:

export class Note extends Archetype {
    // ...

    preprocess() {
        // ...

        if (replay.isReplay) {
            const hitTime = this.targetTime + this.import.accuracy

            switch (this.import.judgment) {
                case Judgment.Perfect:
                    effect.clips.perfect.schedule(hitTime, 0.02)
                    break
                case Judgment.Great:
                    effect.clips.great.schedule(hitTime, 0.02)
                    break
                case Judgment.Good:
                    effect.clips.good.schedule(hitTime, 0.02)
                    break
            }
        } else {
            effect.clips.perfect.schedule(this.targetTime, 0.02)
        }

        // ...
    }

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

    preprocess() {
        // ...

        if (replay.isReplay) {
            const hitTime = this.targetTime + this.import.accuracy

            switch (this.import.judgment) {
                case Judgment.Perfect:
                    effect.clips.perfect.schedule(hitTime, 0.02)
                    break
                case Judgment.Great:
                    effect.clips.great.schedule(hitTime, 0.02)
                    break
                case Judgment.Good:
                    effect.clips.good.schedule(hitTime, 0.02)
                    break
            }
        } else {
            effect.clips.perfect.schedule(this.targetTime, 0.02)
        }

        // ...
    }

    // ...
}

Particle Effect

Lastly, we should skip playing particle effect if player did not hit the note:

export class Note extends Archetype {
    // ...

    terminate() {
        // ...
        if (replay.isReplay && !this.import.judgment) return

        // ...
    }

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

    terminate() {
        // ...
        if (replay.isReplay && !this.import.judgment) return

        // ...
    }

    // ...
}