14 Input Judgment
In this chapter, we will add input judgment to Note.
Mark Note as Scored
Sonolus does not yet treat Notes as scored entities. You can skip the entire level immediately, and the result screen shows zero Notes for every statistic.
For Sonolus to treat an entity as a playable note, its play archetype must set is_scored. Add this attribute to Note in guide/play/note.py:
python
class Note(PlayArchetype):
is_scored = True
# ...This setting has three visible effects:
- Sonolus includes each Note in the level's note count and prevents skipping while scored Notes remain.
- Each Note can report a result. Sonolus uses these results to calculate score, combo, and judgment counts and to update their UI.
- After chapter 15 adds a bucket, its assigned results appear in a judgment graph on the result screen.
Input Result
Record each Note's result in Note.initialize and Note.touch in guide/play/note.py.
We could manually assign Judgment.MISS, Judgment.PERFECT, Judgment.GREAT, or Judgment.GOOD to self.result.judgment. It is simpler to let our JudgmentWindow judge the touch time. We assign the timing difference in seconds to self.result.accuracy.
Each scored archetype provides self.result for recording its judgment and accuracy.
In initialize, set the initial accuracy to the late edge of the Good window. This is the temporary result state for an untouched Note: if the Note times out, it keeps the default Miss judgment and this accuracy. A successful touch replaces both values in touch:
python
class Note(PlayArchetype):
# ...
def initialize(self):
# ...
self.result.accuracy = note_window.good.end
@callback(order=1)
def touch(self):
# ...
for touch in touches():
# ...
self.result.judgment = note_window.judge(touch.start_time, self.target_time)
self.result.accuracy = touch.start_time - self.target_time
# ...Show Judgment and Combo
The judgment and combo UI give the player immediate feedback while playing.
Open guide/lib/ui.py and add their layouts to init_ui. The runtime UI exposes separate layouts for the combo value and combo text:
python
from sonolus.script.runtime import HorizontalAlign, runtime_ui, safe_area, screen
from sonolus.script.vec import Vec2
def init_ui():
ui = runtime_ui()
# ...
ui.judgment.update(
anchor=Vec2(0, -0.4),
pivot=Vec2(0.5, 0),
dimensions=Vec2(0, 0.15) * ui.judgment_config.scale,
rotation=0,
alpha=ui.judgment_config.alpha,
horizontal_align=HorizontalAlign.CENTER,
background=False,
)
ui.combo_value.update(
anchor=Vec2(screen().r * 0.7, 0),
pivot=Vec2(0.5, 0),
dimensions=Vec2(0, 0.2) * ui.combo_config.scale,
rotation=0,
alpha=ui.combo_config.alpha,
horizontal_align=HorizontalAlign.CENTER,
background=False,
)
ui.combo_text.update(
anchor=Vec2(screen().r * 0.7, 0),
pivot=Vec2(0.5, 1),
dimensions=Vec2(0, 0.12) * ui.combo_config.scale,
rotation=0,
alpha=ui.combo_config.alpha,
horizontal_align=HorizontalAlign.CENTER,
background=False,
)Lastly, let's give the judgment and combo UI some animations to make them more lively:
python
from sonolus.script.ui import (
EaseType,
UiAnimation,
UiAnimationTween,
UiConfig,
UiJudgmentErrorPlacement,
UiJudgmentErrorStyle,
)
ui_config = UiConfig(
judgment_animation=UiAnimation(
scale=UiAnimationTween(start=1, end=1, duration=0, ease=EaseType.NONE),
alpha=UiAnimationTween(start=1, end=0, duration=0.2, ease=EaseType.OUT_CUBIC),
),
combo_animation=UiAnimation(
scale=UiAnimationTween(start=1.2, end=1, duration=0.2, ease=EaseType.IN_CUBIC),
alpha=UiAnimationTween(start=1, end=1, duration=0, ease=EaseType.NONE),
),
judgment_error_style=UiJudgmentErrorStyle.NONE,
judgment_error_placement=UiJudgmentErrorPlacement.CENTER,
judgment_error_min=0,
)Store this as ui_config in guide/lib/ui.py. The template already imports this configuration and passes it to EngineData in guide/project.py, so no additional project wiring is needed.
Checkpoint
Run the level and hit or miss several Notes. The judgment should appear after each result, the combo should increase after consecutive hits, and the result screen should count the Notes.