Sonolus Wiki

12. Input Manager

In this chapter, we will introduce and implement Input Manager.

Input Manager

There's is one last thing missing in Note's input logic: when two notes are very close to each other and player taps, we only want the tap to be registered on one note not both.

This requires a central entity to coordinate all notes: Input Manager.

Let's set up Input Manager. Because Input Manager is only needed in play mode, we will make it a spawnable archetype and spawn it in Initialization. We can extend SpawnableArchetype({}) to make it a spawnable archetype that does not need any data for spawning:

export class InputManager extends SpawnableArchetype({}) {}
export class InputManager extends SpawnableArchetype({}) {}
export const archetypes = defineArchetypes({
    // ...
    InputManager,
    // ...
})
export const archetypes = defineArchetypes({
    // ...
    InputManager,
    // ...
})

We can spawn Input Manager in Initialization:

export class Initialization extends Archetype {
    // ...

    updateSequential() {
        archetypes.InputManager.spawn({})

        // ...
    }
}
export class Initialization extends Archetype {
    // ...

    updateSequential() {
        archetypes.InputManager.spawn({})

        // ...
    }
}

Used Touches

For input blocking, we can have a collection storing touches that are already used, and let notes check if a touch is already used before using.

How do we share mutable data? We can do so using the Level Memory block:

const usedTouchIds = levelMemory(Collection(16, TouchId))
const usedTouchIds = levelMemory(Collection(16, TouchId))

Now we can implement and export two functions to interact with it:

export const isUsed = (touch: Touch) => usedTouchIds.has(touch.id)

export const markAsUsed = (touch: Touch) => usedTouchIds.add(touch.id)
export const isUsed = (touch) => usedTouchIds.has(touch.id)

export const markAsUsed = (touch) => usedTouchIds.add(touch.id)

Lastly, we should clear used touches every frame, so that the collection won't grow indefinitely:

export class InputManager extends Archetype {
    // ...

    touch() {
        usedTouchIds.clear()
    }
}
export class InputManager extends Archetype {
    // ...

    touch() {
        usedTouchIds.clear()
    }
}

Input Blocking

With Input Manager, we can now add input blocking to Note's input logic.

But first, we need to make sure Note's touch executes after Input Manager's, we can do so by giving a higher order:

export class Note extends Archetype {
    // ...

    touchOrder = 1
    // ...
}
export class Note extends Archetype {
    // ...

    touchOrder = 1
    // ...
}

Now input blocking is simply checking if a touch has already been used before using, and marking it as used when using:

export class Note extends Archetype {
    // ...
    touch() {
        // ...

        for (const touch of touches) {
            // ...
            if (isUsed(touch)) continue

            markAsUsed(touch)

            // ...
        }
    }

    // ...
}
export class Note extends Archetype {
    // ...
    touch() {
        // ...

        for (const touch of touches) {
            // ...
            if (isUsed(touch)) continue

            markAsUsed(touch)

            // ...
        }
    }

    // ...
}