Skip to content

04 Screen Coordinate System

This chapter introduces the screen coordinate system and defines an engine coordinate system for sprites and particles.

Default

Sonolus uses a default screen coordinate system where (0, 0) is at the center of the screen. The y-coordinate ranges from -1 at the bottom to 1 at the top. The aspect_ratio() function determines the horizontal boundaries: x ranges from -aspect_ratio() at the left to aspect_ratio() at the right.

The screen() function returns these boundaries as a Rect.

Engine Coordinate System

Although an engine can draw sprites and particles in the default coordinate system, it is often more convenient to use an engine-specific coordinate system for them.

In a vertically scrolling rhythm game, a useful engine coordinate system has a y-coordinate range from 0 at the note spawn position to 1 at the judgment line. The center lane lies at x = 0, and each unit along the x-axis represents one lane. For example, x = 2 would be two lanes to the right of the center lane.

Configure the Coordinate System

Let's configure sprites and particles to use this coordinate system.

In the default screen coordinate system, we will give notes a radius of 0.2. They will spawn just outside the player's view at screen().t + note_radius and fall to the judgment line at y = -0.6. Because our engine only has one lane, we can scale x by the same amount as y.

Defining a coordinate system also requires a way to convert its positions to screen positions. A transform describes that conversion. Transform2d represents a two-dimensional transform built from operations such as scaling and translation. set_skin_transform and set_particle_transform tell Sonolus to apply it when drawing sprites and particles.

The engine coordinate system will not change during play, so we can calculate it once in an init_layout function. We will make the note radius adjustable later, so the function accepts a note_size multiplier. Create guide/lib/layout.py:

python
from sonolus.script.runtime import screen, set_particle_transform, set_skin_transform
from sonolus.script.transform import Transform2d
from sonolus.script.vec import Vec2


def init_layout(note_size: float):
    note_radius = 0.2 * note_size
    judge_line_y = -0.6

    t = screen().t + note_radius
    b = judge_line_y
    h = t - b

    transform = Transform2d.new().scale(Vec2(h, -h)).translate(Vec2(0, t))

    set_skin_transform(transform)
    set_particle_transform(transform)

Then open guide/play/initialization.py. Add the init_layout import, and call it from preprocess. Pass 1 for now:

python
from guide.lib.layout import init_layout
from guide.lib.ui import init_ui


class Initialization(PlayArchetype):
    def preprocess(self):
        init_ui()
        init_layout(1)

The chained transform operations are applied from left to right. In the engine coordinate system, y = 0 maps to t, the note spawn position, while y = 1 maps to b, the judgment line. The negative y-scale makes y increase downward, and the translation puts the top edge at the note spawn position.