12 Input Manager
In this chapter, we will introduce and implement the Input Manager.
Create the Input Manager
When two Notes have overlapping input windows, one tap could judge both. A central play archetype called Input Manager coordinates touch ownership so each tap judges at most one Note.
We can spawn an entity from a play archetype by calling the archetype class's spawn() method. First, add the Input Manager archetype name to guide/lib/archetype_names.py:
python
INPUT_MANAGER = "InputManager"Then create guide/play/input_manager.py:
python
from sonolus.script.archetype import PlayArchetype
from guide.lib import archetype_names
class InputManager(PlayArchetype):
name = archetype_names.INPUT_MANAGEROpen guide/play/mode.py and add InputManager to the play mode's archetype list:
python
from sonolus.script.engine import PlayMode
from guide.play.initialization import Initialization
from guide.play.input_manager import InputManager
from guide.play.note import Note
from guide.play.stage import Stage
play_mode = PlayMode(
archetypes=[Initialization, Stage, InputManager, Note],
# ...
)Open guide/play/initialization.py and spawn the Input Manager in Initialization.update_sequential:
python
from guide.play.input_manager import InputManager
class Initialization(PlayArchetype):
# ...
def update_sequential(self):
InputManager.spawn()
# ...Track Used Touches
For input blocking, we can store the touch IDs that have already been used and let notes check the collection before using a touch. Keep this collection and the helpers below in guide/play/input_manager.py with the Input Manager.
We can share this mutable data using Level Memory. A fixed-capacity VarArray stores the IDs claimed during the current frame. Its capacity is represented by the compile-time dimension type Dim:
python
from sonolus.script.array import Dim
from sonolus.script.containers import VarArray
from sonolus.script.globals import level_memory
used_touch_ids = level_memory(VarArray[int, Dim[16]])Now we can implement two functions to interact with it:
python
from sonolus.script.runtime import Touch
def touch_is_used(touch: Touch):
return touch.id in used_touch_ids
def mark_touch_used(touch: Touch):
used_touch_ids.set_add(touch.id)Lastly, add the touch callback to InputManager. It clears the collection before the Note callbacks in each input processing cycle, so a claimed ID does not remain blocked in later cycles:
python
class InputManager(PlayArchetype):
# ...
def touch(self):
used_touch_ids.clear()Block Reused Touches
With the Input Manager, we can now add input blocking in guide/play/note.py.
First, make sure Note's touch callback executes after the Input Manager's callback. Keep the existing callback body and add the decorator shown below. Use the callback decorator to set callback order. Callbacks execute from the lowest order to the highest:
python
from sonolus.script.archetype import callback
class Note(PlayArchetype):
# ...
@callback(order=1)
def touch(self):
# Keep the existing body unchanged.
...The Input Manager uses the default order of 0. In each input processing cycle, it clears the collection first. Each Note callback at order 1 can then inspect and claim touches.
In Note.touch, replace the existing if not touch.started guard with a combined guard that also rejects used touches. Then mark each accepted touch as used:
python
from guide.play.input_manager import mark_touch_used, touch_is_used
class Note(PlayArchetype):
# ...
@callback(order=1)
def touch(self):
# ...
for touch in touches():
# ...
if not touch.started or touch_is_used(touch):
continue
mark_touch_used(touch)
# Keep the remaining body unchanged.
...Checkpoint
Temporarily add a second Note close to the existing Note in guide/level.py. Run the level and tap when their input windows overlap. One tap should judge at most one Note. Remove the temporary Note before continuing.