14. 输入判定
本章我们将为音符添加输入判定。
输入原型
您可能已经注意到,尽管我们的音符功能齐全,但 Sonolus 并没有按照正常的处理音符的方式处理它们:您可以立即跳过整个关卡,并且在结果界面上,显示所有统计数据为 0 个音符。
为了让一个实体被 Sonolus 视为可播放的音符,它的原型必须有输入。
export class Note extends Archetype {
hasInput = true
// ...
}
export class Note extends Archetype {
hasInput = true
// ...
}
有了这个设置后:
- Sonolus 将知道并计算所有具有输入原型的实体。
- 只有当所有输入实体都消失时,玩家才会允许跳过剩余的关卡。
- 输入实体可以访问实体输入块(Entity Input block),该代码可用于告诉 Sonolus 玩家的表现。
- Sonolus 将根据输入结果自动计算得分、连击数、Perfect 计数等统计数据。
- 当出现新的输入结果时,相关的 UI 也会更新和播放动画效果。
- 指定桶(bucket)的输入结果,能够在结果界面上得到判定图表。
输入结果
要告诉 Sonolus 玩家在某个音符上的表现如何,我们只需修改this.result
即可。
对于this.result.judgment
,我们可以手动分配Judgment.Miss
、 Judgment.Perfect
、 Judgment.Great
或Judgment.Good
。然而,更好的方法是直接使用input.judge
辅助函数。
对于this.result.accuracy
,我们应该以秒为单位提供时间差。
export class Note extends Archetype {
// ...
initialize() {
// ...
this.result.accuracy = windows.good.max
}
// ...
touch() {
// ...
for (const touch of touches) {
// ...
this.result.judgment = input.judge(touch.startTime, this.targetTime, windows)
this.result.accuracy = touch.startTime - this.targetTime
// ...
}
}
// ...
}
export class Note extends Archetype {
// ...
initialize() {
// ...
this.result.accuracy = windows.good.max
}
// ...
touch() {
// ...
for (const touch of touches) {
// ...
this.result.judgment = input.judge(touch.startTime, this.targetTime, windows)
this.result.accuracy = touch.startTime - this.targetTime
// ...
}
}
// ...
}
判定和连击 UI
在玩家玩游戏时提供判定和连击 UI,为玩家提供即时反馈也很重要。
我们在初始化原型中设置它们:
export class Initialization extends Archetype {
preprocess() {
// ...
ui.judgment.set({
anchor: { x: 0, y: -0.4 },
pivot: { x: 0.5, y: 0 },
size: new Vec(0, 0.15).mul(ui.configuration.judgment.scale),
rotation: 0,
alpha: ui.configuration.judgment.alpha,
horizontalAlign: HorizontalAlign.Center,
background: false,
})
ui.combo.value.set({
anchor: { x: screen.r * 0.7, y: 0 },
pivot: { x: 0.5, y: 0 },
size: new Vec(0, 0.2).mul(ui.configuration.combo.scale),
rotation: 0,
alpha: ui.configuration.combo.alpha,
horizontalAlign: HorizontalAlign.Center,
background: false,
})
ui.combo.text.set({
anchor: { x: screen.r * 0.7, y: 0 },
pivot: { x: 0.5, y: 1 },
size: new Vec(0, 0.12).mul(ui.configuration.combo.scale),
rotation: 0,
alpha: ui.configuration.combo.alpha,
horizontalAlign: HorizontalAlign.Center,
background: false,
})
}
// ...
}
export class Initialization extends Archetype {
preprocess() {
// ...
ui.judgment.set({
anchor: { x: 0, y: -0.4 },
pivot: { x: 0.5, y: 0 },
size: new Vec(0, 0.15).mul(ui.configuration.judgment.scale),
rotation: 0,
alpha: ui.configuration.judgment.alpha,
horizontalAlign: HorizontalAlign.Center,
background: false,
})
ui.combo.value.set({
anchor: { x: screen.r * 0.7, y: 0 },
pivot: { x: 0.5, y: 0 },
size: new Vec(0, 0.2).mul(ui.configuration.combo.scale),
rotation: 0,
alpha: ui.configuration.combo.alpha,
horizontalAlign: HorizontalAlign.Center,
background: false,
})
ui.combo.text.set({
anchor: { x: screen.r * 0.7, y: 0 },
pivot: { x: 0.5, y: 1 },
size: new Vec(0, 0.12).mul(ui.configuration.combo.scale),
rotation: 0,
alpha: ui.configuration.combo.alpha,
horizontalAlign: HorizontalAlign.Center,
background: false,
})
}
// ...
}
最后,让我们也给我们的判定和连击 UI 一些动画效果,让它们更生动:
export const ui: EngineConfigurationUI = {
// ...
judgmentAnimation: {
// ...
alpha: {
from: 1,
to: 0,
duration: 0.2,
ease: 'outCubic',
},
},
comboAnimation: {
scale: {
from: 1.2,
to: 1,
duration: 0.2,
ease: 'inCubic',
},
// ...
},
// ...
}
export const ui = {
// ...
judgmentAnimation: {
// ...
alpha: {
from: 1,
to: 0,
duration: 0.2,
ease: 'outCubic',
},
},
comboAnimation: {
scale: {
from: 1.2,
to: 1,
duration: 0.2,
ease: 'inCubic',
},
// ...
},
// ...
}