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 return value of 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.