> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/all-out-docs/ru/ui/world-space-ui.md).

# UI в мировом пространстве

UI в мировом пространстве рисуется в 2D-мире, а не на экране. Используйте его для табличек с именами, полосок здоровья, знаков и любого текста, который должен ощущаться прикреплённым к игровым объектам.

{% hint style="info" %}
UI в мировом пространстве использует **метры** для размеров. Если вы используете размеры в экранных пунктах, ваш UI будет огромным.
{% endhint %}

### Основные правила

* Рисуйте UI игрока в мировом пространстве из `ao_late_update` этого игрока в `is_local_or_server()`.
* Для UI, привязанного к движущейся сущности, вызывайте `UI.begin_world_space_ui(entity)` и `defer UI.end_world_space_ui()` чтобы интерполяция рендера следовала за этой сущностью.
* Используйте `UI.push_world_draw_context()` только для не привязанного к объектам рисования в мире.
* Используйте метры для размеров и смещений прямоугольников.
* Сдвигайте Z для правильной сортировки по глубине (обычно используйте `pos.y`).
* Используйте `подпрямоугольник` только для заполнения по проценту (например, полосок здоровья).

### Примеры

#### Отображение уровня игрока

```go
draw_player_level :: proc(player: Player) {
    UI.begin_world_space_ui(player.entity);
    defer UI.end_world_space_ui();

    pos := player.entity.world_position;
    UI.push_z(pos.y);
    defer UI.pop_z();

    ts := UI.default_text_settings();
    ts.size = 0.30; // Размер текста в мировом пространстве
    ts.halign = .CENTER;
    ts.valign = .CENTER;

    text_pos := pos + v2{0, 1.7};
    rect := Rect{text_pos, text_pos}.grow(0.05, 0.4, 0.05, 0.4);
    UI.text(rect, ts, "Lvl %", {player.level});
}
```

#### Знаки базы игрока

```go
draw_base_sign :: proc(sign: Entity, label: string) {
    UI.begin_world_space_ui(sign);
    defer UI.end_world_space_ui();

    pos := sign.world_position;
    UI.push_z(pos.y);
    defer UI.pop_z();

    ts := UI.default_text_settings();
    ts.size = 0.30;
    ts.halign = .CENTER;
    ts.valign = .CENTER;

    text_pos := pos + v2{0, 2.2};
    rect := Rect{text_pos, text_pos}.grow(0.06, 0.8, 0.06, 0.8);

    // Необязательная тёмная подложка для лучшей читаемости
    UI.quad(rect, core_globals.white_sprite, {0, 0, 0, 0.6});
    UI.text(rect, ts, label);
}
```

#### Полоски здоровья

```go
// Примечание: health/max_health — это пользовательские поля в вашем собственном классе, а не встроенные поля Entity.
draw_world_ui :: proc(entity: My_Entity) {
    UI.begin_world_space_ui(entity.entity);
    defer UI.end_world_space_ui();

    pos := entity.entity.world_position;
    UI.push_z(pos.y);
    defer UI.pop_z();

    bar_pos := pos + v2{0, 1.5};
    bar_rect := Rect{bar_pos, bar_pos}.grow(0.1, 0.5, 0.1, 0.5);

    UI.quad(bar_rect, core_globals.white_sprite, {0, 0, 0, 0.8});

    health_pct := entity.health / entity.max_health;
    fill_rect := bar_rect.inset(0.02).subrect(0, 0, health_pct, 1);
    fill_color := lerp(v4{1, 0, 0, 1}, {0, 1, 0, 1}, health_pct);
    UI.quad(fill_rect, core_globals.white_sprite, fill_color);
}
```

### Индикаторы прогресса

`World_Progress_Bar` предоставляет стандартную полосу в мировом пространстве:

```go
Health :: class : Component {
    current: int;
    maximum: int = 100;
}

draw_health_bar :: proc(health: Health) {
    UI.begin_world_space_ui(health.entity);
    defer UI.end_world_space_ui();

    maximum := max(1, health.maximum);
    progress := clamp(
        health.current.(float) / maximum.(float),
        0.0,
        1.0
    );

    options := World_Progress_Bar.default_options();
    options.y_bias = 1.5;
    World_Progress_Bar.draw(health.entity.world_position, progress, options);
}
```

Приведения типов сохраняют деление в вещественных числах, а ограничение знаменателя предотвращает деление на ноль.

Для пользовательской полосы, заполнение которой меняется со временем, храните один `Float_Interpolation_Helper` на каждую полосу:

```go
Smooth_Bar :: class : Component {
    progress: float;
    fill_history: Float_Interpolation_Helper;

    draw :: method() {
        UI.begin_world_space_ui(entity);
        defer UI.end_world_space_ui();

        center := entity.world_position + v2{0, 1.5};
        rect := Rect{center, center}.grow(0.1, 0.5, 0.1, 0.5);
        UI.quad(rect, core_globals.white_sprite, {0, 0, 0, 1});

        params: Quad_Params;
        params.fill = UI.quad_fill(
            fill_history.update(clamp(progress, 0.0, 1.0)),
            .RIGHT
        );
        UI.quad(rect.inset(0.02), core_globals.white_sprite, {0.1, 1, 0.1, 1}, params);
    }
}
```

### Стрелки туториала

Перегрузка для сущности включает смещение интерполяции цели:

```go
draw_target_arrow :: proc(player: Player, target: Entity) {
    options := Tutorial_Arrow.default_options();
    Tutorial_Arrow.draw(player, target, options);
}
```

Для принадлежащей скрипту позиции, которая движется, храните `Position_Interpolation_Helper` и передавайте её смещение:

```go
Player :: class : Player_Base {
    objective_position: v2;
    objective_history: Position_Interpolation_Helper;

    draw_objective_arrow :: method() {
        options := Tutorial_Arrow.default_options();
        offset := objective_history.update(objective_position);
        Tutorial_Arrow.draw(this, objective_position, options, offset);
    }

    ao_late_update :: method(dt: float) {
        if is_local_or_server() {
            draw_objective_arrow();
        }
    }
}
```

После перемещения сущности в новую позицию вызовите `entity.mark_teleported()` чтобы интерполяция не размазывалась между старой и новой позициями.

### Преобразование координат

```go
// Преобразовать мировую позицию в экранную позицию
screen_pos := world_to_screen(entity.world_position);

// Преобразовать экранную позицию в мировую позицию
world_pos := screen_to_world(get_mouse_screen_position());
```

### Советы

* Держите мировой UI минимальным и читаемым на расстоянии. Если у вас есть камера с масштабированием, не забудьте уменьшать размер текста при отдалении.
* Используйте `fit_aspect(texture.get_aspect())` если вы рисуете иконки в мировом пространстве.
* Если текст мерцает или накладывается друг на друга, проверьте значения Z и интервалы.
