10 Note Drawing
In this chapter, we will implement the drawing logic for Note.
Visual Time
Let's calculate a note's visual interval, which contains its start and end times, and refactor the spawn time to use the start of that Interval. In guide/play/note.py, add the Interval import and declare visual_time immediately after target_time, before spawn_time. Then replace the existing visual and spawn-time assignments in preprocess with:
python
from sonolus.script.interval import Interval
class Note(PlayArchetype):
# ...
target_time: float = entity_data()
visual_time: Interval = entity_data()
spawn_time: float = entity_data()
def preprocess(self):
# ...
self.visual_time.end = self.target_time
self.visual_time.start = self.target_time - 1
self.spawn_time = self.visual_time.startThe interval starts one second before the target time and ends at the target time.
Declaring
Just like Stage, we use a StandardSprite for our note. In guide/lib/skin.py, add note below judge_line:
python
class Skin:
judge_line: StandardSprite.JUDGMENT_LINE
note: StandardSprite.NOTE_HEAD_CYANDrawing
With both ends of the visual interval and the current time, we can calculate the note's y position. The time import already exists in guide/play/note.py. Add update_parallel with the initial position calculation:
python
class Note(PlayArchetype):
# ...
def update_parallel(self):
y = self.visual_time.unlerp(time())unlerp returns the position of a value within an interval: 0 at visual_time.start, 1 at visual_time.end, and a proportional value between them. Therefore, y moves from 0 at the note spawn position to 1 at the judgment line.
To draw the note, add these imports:
python
from sonolus.script.quad import Rect
from sonolus.script.vec import Vec2
from guide.lib.layout import Config
from guide.lib.skin import SkinThen extend update_parallel with the layout and draw call:
python
class Note(PlayArchetype):
# ...
def update_parallel(self):
# ...
layout = Rect.from_center(Vec2(0, y), Vec2(2 * Config.note_radius, -2 * Config.note_radius))
Skin.note.draw(layout, z=1)Z Fighting
Although this already works, there is a hidden issue: z-fighting.
When multiple objects are rendered with the same z value, their ordering may not be consistent from frame to frame. They may flicker if they overlap.
Later values in a z-index break ties between objects whose earlier values are equal. We can use the first value for the note layer and the negative target time as a tie-breaker, so earlier notes will always be on top of later notes:
python
class Note(PlayArchetype):
# ...
def update_parallel(self):
# ...
Skin.note.draw(layout, z=(1, -self.target_time))Reopen the development level. The note should travel from the top of the play area to the judgment line in one second.