> 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/docs-es/ui/world-space-ui.md).

# UI en espacio del mundo

Usa la UI en espacio del mundo para dibujar texto encima de la cabeza de los jugadores y cualquier otro texto que deba parecer parte del propio mundo del juego.

La UI en espacio mundial se dibuja en el mundo 2D en lugar de en la pantalla. Úsala para placas de nombre, barras de vida, carteles y cualquier texto que deba sentirse anclado a objetos del juego.

{% hint style="info" %}
La UI en espacio mundial usa **metros** para el tamaño. Si usas tamaños de puntos en espacio de pantalla, tu UI será enorme.
{% endhint %}

### Reglas básicas

* La UI interactiva debe usar `ao_update`/`ao_late_update` (UI del jugador: `Player.ao_late_update` de ese jugador, bajo `is_local_or_server()`), nunca `ao_draw`.
* `ao_draw` es solo para elementos visuales cosméticos no interactivos.
* Para una UI anclada a una entidad en movimiento, llama a `UI.begin_world_space_ui(entity)` y `pospone UI.end_world_space_ui()` para que la interpolación de renderizado siga a esa entidad.
* Usa `UI.push_world_draw_context()` solo para dibujo en el mundo sin anclar.
* Usa metros para los tamaños y desplazamientos de los rectángulos.
* Empuja Z para un ordenamiento de profundidad correcto (normalmente usa `pos.y`).
* Usa `subrect` solo para rellenos porcentuales (como barras de vida).

### Ejemplos

#### Mostrando el nivel de un jugador

```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; // Tamaño del texto en espacio mundial
    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, "Niv %", {player.level});
}
```

#### Carteles base del jugador

```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);

    // Fondo oscuro opcional para facilitar la lectura
    UI.quad(rect, core_globals.white_sprite, {0, 0, 0, 0.6});
    UI.text(rect, ts, label);
}
```

#### Barras de vida

```go
// Nota: health/max_health son campos definidos por el usuario en tu propia clase, no campos Entity integrados.
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);
}
```

### Barras de progreso

`World_Progress_Bar` proporciona una barra estándar en espacio mundial:

```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);
}
```

Las conversiones mantienen la división en coma flotante, y el denominador acotado evita la división por cero.

Para una barra personalizada cuyo relleno cambie con el tiempo, guarda una `Float_Interpolation_Helper` por barra:

```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);
    }
}
```

Los rellenos radiales empiezan a las 12 en punto y usan cantidades con signo: `-1..0` rellena en sentido antihorario y `0..1` se rellenan en sentido horario. Para un enfriamiento radial animado, pasa `fill_history.update(clamp(amount, -1, 1))` hasta `UI.quad_fill(..., .RADIAL)`. No lo limites a `0..1` a menos que el comportamiento solo en sentido horario sea intencional.

### Flechas del tutorial

La sobrecarga con entidad incluye el desplazamiento de interpolación del objetivo:

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

Para una posición propiedad de un script que se mueve, guarda un `Position_Interpolation_Helper` y pasa su desplazamiento:

```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();
        }
    }
}
```

Después de ajustar una entidad a una nueva posición, llama a `entity.mark_teleported()` para que la interpolación no se difumine entre las posiciones anterior y nueva.

### Conversión de coordenadas

```go
// Convierte la posición del mundo a posición de pantalla
screen_pos := world_to_screen(entity.world_position);

// Convierte la posición de pantalla a posición del mundo
world_pos := screen_to_world(get_mouse_screen_position());
```

### Consejos

* Mantén la UI en el mundo minimalista y legible a distancia. Si tienes una cámara con zoom, asegúrate de ajustar el tamaño del texto cuando alejes el zoom.
* Usa `fit_aspect(texture.get_aspect())` si dibujas iconos en espacio mundial.
* Si el texto parpadea o se superpone, revisa tus valores de Z y el espaciado.
