10. 音符绘制
本章我们将实现音符的绘制逻辑。
可视时间
让我们计算一个音符的可视时间,它由最小和最大可视时间组成,并重构生成时间,让它使用最小可视时间:
export class Note extends Archetype {
// ...
visualTime = this.entityMemory({
min: Number,
max: Number,
})
// ...
preprocess() {
// ...
this.visualTime.max = this.targetTime
this.visualTime.min = this.visualTime.max - 1
this.spawnTime = this.visualTime.min
}
// ...
}
export class Note extends Archetype {
// ...
visualTime = this.entityMemory({
min: Number,
max: Number,
})
// ...
preprocess() {
// ...
this.visualTime.max = this.targetTime
this.visualTime.min = this.visualTime.max - 1
this.spawnTime = this.visualTime.min
}
// ...
}
声明
就像舞台原型一样,为简单起见,我们为音符使用标准化的精灵:
export const skin = defineSkin({
sprites: {
// ...
note: SkinSpriteName.NoteHeadCyan,
},
})
export const skin = defineSkin({
sprites: {
// ...
note: SkinSpriteName.NoteHeadCyan,
},
})
绘制
通过最小和最大可视时间以及当前时间,我们可以计算音符的y
位置。因为我们将屏幕坐标系变成了从y = 0
(音符顶部生成)到y = 1
(判定线),这大大简化了我们的计算:
export class Note extends Archetype {
// ...
updateParallel() {
const y = Math.unlerp(this.visualTime.min, this.visualTime.max, time.now)
}
}
export class Note extends Archetype {
// ...
updateParallel() {
const y = Math.unlerp(this.visualTime.min, this.visualTime.max, time.now)
}
}
计算出y
后,我们就可以绘制音符了:
export class Note extends Archetype {
// ...
updateParallel() {
// ...
const layout = Rect.one.mul(note.radius).scale(1, -1).translate(0, y)
skin.sprites.note.draw(layout, 0, 1)
}
}
export class Note extends Archetype {
// ...
updateParallel() {
// ...
const layout = Rect.one.mul(note.radius).scale(1, -1).translate(0, y)
skin.sprites.note.draw(layout, 0, 1)
}
}
Z 轴堆叠冲突
虽然目前我们已经成功运行了,但还有一个隐藏的问题我们还没有解决:Z 轴堆叠冲突。
它指的是当使用相同的 Z 值渲染多个对象时,它们的顺序可能在帧与帧之间不一致,并且如果它们重叠则可能会闪烁。
为了解决这个问题,让我们将 Z 轴堆叠顺序设置为1000
减去目标时间,这样较早的音符将始终位于较晚的音符之上。
这也是音符的一个不变的属性,所以我们应该计算一次并存储在 Entity Memory 中以供重复使用。然而这次我们将在initialize
而不是preprocess
中计算它,因为它不会在生成逻辑中使用,所以我们可以推迟计算来缩短关卡加载时间:
export class Note extends Archetype {
// ...
z = this.entityMemory(Number)
// ...
initialize() {
this.z = 1000 - this.targetTime
}
updateParallel() {
// ...
skin.sprites.note.draw(layout, this.z, 1)
}
}
export class Note extends Archetype {
// ...
z = this.entityMemory(Number)
// ...
initialize() {
this.z = 1000 - this.targetTime
}
updateParallel() {
// ...
skin.sprites.note.draw(layout, this.z, 1)
}
}