15. 输入桶
在本章中,我们将给音符添加输入桶。
输入桶
输入桶是一种有关音符的东西,音符可以将判定偏移值输入其中,在结果界面上将显示每个桶的分布图表。
虽然输入桶不是必要的,但这对于玩家校准输入偏移以及提高准确率非常有用。
创建桶
我们的引擎只有一种类型的音符,所以一个桶就可以了。
在结果屏幕上,每个桶将由一个由皮肤精灵组成的图形表示,因此我们应该使其尽可能接近音符的游戏视觉效果。这里我们设置成以毫秒为单位。
export const buckets = defineBuckets({
note: {
sprites: [
{
id: skin.sprites.note.id,
x: 0,
y: 0,
w: 2,
h: 2,
rotation: 0,
},
],
unit: Text.MillisecondUnit,
},
})
export const buckets = defineBuckets({
note: {
sprites: [
{
id: skin.sprites.note.id,
x: 0,
y: 0,
w: 2,
h: 2,
rotation: 0,
},
],
unit: Text.MillisecondUnit,
},
})
设置桶窗口
接下来,我们需要设置桶窗口。
从直觉上来看,我们会在 Note 的preprocess
中编写代码。但是有一个问题:每个音符实体都会调用preprocess
,然而桶窗口只需要设置一次。
我们可以将我们的代码移至初始化的preprocess
,但这违反了单一责任原则(single responsibility principal)。
那我们应该如何清晰地构建这段代码呢?
全局预处理模式
对于任何的我们只想全局运行一次的预处理代码,我们需要把它们写在一个新的globalPreprocess
类方法中。
对于音符而言,在这里可以设置桶窗口(记得将窗口的值转换为毫秒):
// ...
const toMs = ({ min, max }: Range) => new Range(Math.round(min * 1000), Math.round(max * 1000))
export const bucketWindows = {
perfect: toMs(windows.perfect),
great: toMs(windows.great),
good: toMs(windows.good),
}
// ...
const toMs = ({ min, max }) => new Range(Math.round(min * 1000), Math.round(max * 1000))
export const bucketWindows = {
perfect: toMs(windows.perfect),
great: toMs(windows.great),
good: toMs(windows.good),
}
export class Note extends Archetype {
// ...
globalPreprocess() {
buckets.note.set(bucketWindows)
}
// ...
}
export class Note extends Archetype {
// ...
globalPreprocess() {
buckets.note.set(bucketWindows)
}
// ...
}
目前, globalPreprocess
没有在任何地方被调用。让我们在初始化里调用它:
export class Initialization extends Archetype {
preprocess() {
// ...
archetypes.Note.globalPreprocess()
}
// ...
}
export class Initialization extends Archetype {
preprocess() {
// ...
archetypes.Note.globalPreprocess()
}
// ...
}
现在音符的globalPreprocess
只会在全局调用一次,因为初始化只有一次。
然而,每当新增一个需要调用globalPreprocess
的新原型时,我们必须记得把它添加到初始化中。为了避免这种麻烦,我们可以创建原型迭代器,来调用所有具有globalPreprocess
方法的原型:
export class Initialization extends Archetype {
preprocess() {
// ...
for (const archetype of Object.values(archetypes)) {
if (!('globalPreprocess' in archetype)) continue
archetype.globalPreprocess()
}
}
// ...
}
export class Initialization extends Archetype {
preprocess() {
// ...
for (const archetype of Object.values(archetypes)) {
if (!('globalPreprocess' in archetype)) continue
archetype.globalPreprocess()
}
}
// ...
}
这就是实现的全局预处理模式的方法。
输入结果
最后,让我们设置一下输入结果桶的索引和值:
export class Note extends Archetype {
// ...
touch() {
// ...
for (const touch of touches) {
// ...
this.result.bucket.index = buckets.note.index
this.result.bucket.value = this.result.accuracy * 1000
// ...
}
}
// ...
}
export class Note extends Archetype {
// ...
touch() {
// ...
for (const touch of touches) {
// ...
this.result.bucket.index = buckets.note.index
this.result.bucket.value = this.result.accuracy * 1000
// ...
}
}
// ...
}