Sonolus Wiki

11. 音符输入

本章我们将实现音符的输入逻辑。

基本输入

让我们首先编写最基本的输入:如果玩家点击,音符消失。

touch中,我们可以循环遍历 touches 以寻找刚刚开始的触摸。如果找到,则消失并返回。

为了防止框架在updateParallel中绘制一个计划要销毁的音符,我们增加了一个简单的销毁检查。

export class Note extends Archetype {
    // ...

    touch() {
        for (const touch of touches) {
            if (!touch.started) continue

            this.despawn = true
            return
        }
    }

    updateParallel() {
        if (this.despawn) return

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

    touch() {
        for (const touch of touches) {
            if (!touch.started) continue

            this.despawn = true
            return
        }
    }

    updateParallel() {
        if (this.despawn) return

        // ...
    }
}

判定窗口期

虽然上面的逻辑是有效的,但这绝对不是节奏游戏的正常工作方式。

一个音符只有在时间在音符的判定窗口期内才能被点击:如果时间太早,点击不会触发;如果时间太晚,它会被视为未命中,并且音符会自行消失。

对于我们的引擎,假设如果您在目标时间的50 ms内点击,您将获得 Perfect 判定, 100 ms表示 Great 判定, 200 ms表示 Good 判定,更大的数值则不定义(Miss):

export const windows = {
    perfect: {
        min: -0.05,
        max: 0.05,
    },
    great: {
        min: -0.1,
        max: 0.1,
    },
    good: {
        min: -0.2,
        max: 0.2,
    },
}
export const windows = {
    perfect: {
        min: -0.05,
        max: 0.05,
    },
    great: {
        min: -0.1,
        max: 0.1,
    },
    good: {
        min: -0.2,
        max: 0.2,
    },
}

输入偏移

当玩家触摸屏幕时,会有一定的延迟,才会在 Sonolus 中注册并通过touch回调广播它。这主要是来自硬件的延迟,并且这是不可避免的。

输入偏移量让玩家告诉 Sonolus 将这部分时间考虑在内。

例如,玩家在00:01.00触摸屏幕,经过一段时间,在00:01.06被接收到。如果玩家正确校准了他们的输入,并为您提供了0.06的输入偏移量,则引擎可以从触摸时间中减去它,并根据他们的实际触摸时间00:01.00正确地对玩家的输入进行判定。

Sonolus 提供的触摸时间值已经考虑了输入偏移。但其他涉及到输入的方面,还是需要引擎手动完成,保证给所有玩家一个公平的游戏体验。

提前的输入

让我们先计算玩家最早可以点击的时间:

export class Note extends Archetype {
    // ...

    inputTime = this.entityMemory({
        min: Number,
    })

    // ...

    initialize() {
        this.inputTime.min = this.targetTime + windows.good.min + input.offset

        // ...
    }

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

    inputTime = this.entityMemory({
        min: Number,
    })

    // ...

    initialize() {
        this.inputTime.min = this.targetTime + windows.good.min + input.offset

        // ...
    }

    // ...
}

然后让touch只在最早可输入时间之后运行:

export class Note extends Archetype {
    // ...

    touch() {
        if (time.now < this.inputTime.min) return

        // ...
    }

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

    touch() {
        if (time.now < this.inputTime.min) return

        // ...
    }

    // ...
}

延后的输入

与提前的输入类似,让我们计算玩家可以点击的最晚时间:

export class Note extends Archetype {
    // ...

    inputTime = this.entityMemory({
        // ...
        max: Number,
    })

    // ...

    initialize() {
        // ...
        this.inputTime.max = this.targetTime + windows.good.max + input.offset

        // ...
    }

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

    inputTime = this.entityMemory({
        // ...
        max: Number,
    })

    // ...

    initialize() {
        // ...
        this.inputTime.max = this.targetTime + windows.good.max + input.offset

        // ...
    }

    // ...
}

如果当前的时间已经超过最大输入时间,我们就让音符自动消失。

export class Note extends Archetype {
    // ...

    updateParallel() {
        if (time.now > this.inputTime.max) this.despawn = true
        // ...
    }
}
export class Note extends Archetype {
    // ...

    updateParallel() {
        if (time.now > this.inputTime.max) this.despawn = true
        // ...
    }
}