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
// ...
}
}