10 Note Drawing
In this chapter, we will implementing drawing logic of Note.
Visual Time
Let's calculate a note's visual time, which consists of minimum and maximum visual times, and refactor spawn time to use the minimum visual time:
TypeScript
export class Note extends Archetype {
// ...
visualTime = this.entityMemory(Range)
// ...
preprocess() {
// ...
this.visualTime.copyFrom(Range.l.add(this.targetTime))
this.spawnTime = this.visualTime.min
}
// ...
}JavaScript
export class Note extends Archetype {
// ...
visualTime = this.entityMemory(Range)
// ...
preprocess() {
// ...
this.visualTime.copyFrom(Range.l.add(this.targetTime))
this.spawnTime = this.visualTime.min
}
// ...
}Declaring
Just like Stage, we use the standard sprite for our note for simplicity:
TypeScript
export const skin = defineSkin({
sprites: {
// ...
note: SkinSpriteName.NoteHeadCyan,
},
})JavaScript
export const skin = defineSkin({
sprites: {
// ...
note: SkinSpriteName.NoteHeadCyan,
},
})Drawing
With both minimum and maximum visual time, as well as the current time, we can calculate note's y position. Because we transformed screen coordinate system to go from y = 0 (top of note spawn) to y = 1 (judgment line), this greatly simplifies our calculation:
TypeScript
export class Note extends Archetype {
// ...
updateParallel() {
const y = Math.unlerp(this.visualTime.min, this.visualTime.max, time.now)
}
}JavaScript
export class Note extends Archetype {
// ...
updateParallel() {
const y = Math.unlerp(this.visualTime.min, this.visualTime.max, time.now)
}
}With y position calculated, we can draw the note:
TypeScript
export class Note extends Archetype {
// ...
updateParallel() {
// ...
const layout = Rect.one.mul(note.radius).scale(1, -1).translate(0, y)
skin.sprites.note.draw(layout, [1], 1)
}
}JavaScript
export class Note extends Archetype {
// ...
updateParallel() {
// ...
const layout = Rect.one.mul(note.radius).scale(1, -1).translate(0, y)
skin.sprites.note.draw(layout, [1], 1)
}
}Z Fighting
While this is working already, there is a hidden issue we have not solved yet: z fighting.
It is referred to when multiple objects are rendered with the same z value, their ordering may not be consistent from frame to frame and can be flickering if they overlap.
To solve this, let's set the secondary z order to be negative target time, so that earlier notes will always be on top of later notes.
TypeScript
export class Note extends Archetype {
// ...
updateParallel() {
// ...
skin.sprites.note.draw(layout, [1, -this.targetTime], 1)
}
}JavaScript
export class Note extends Archetype {
// ...
updateParallel() {
// ...
skin.sprites.note.draw(layout, [1, -this.targetTime], 1)
}
}