> 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 mundial

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

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

### Reglas básicas

* Dibuja la UI del jugador en espacio mundial desde ese jugador `ao_late_update` bajo `is_local_or_server()`.
* Para la UI anclada a una entidad en movimiento, llama a `UI.begin_world_space_ui(entity)` y `y aplaza UI.end_world_space_ui()` para que la interpolación de renderizado siga a esa entidad.
* Usa `UI.push_world_draw_context()` solo para el dibujo mundial no anclado.
* 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 salud).

### 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, "Lvl %", {player.level});
}
```

#### Señales de 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 salud

```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 cambia con el tiempo, conserva 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);
    }
}
```

### Flechas del tutorial

La sobrecarga de 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, almacena una `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 colocar una entidad instantáneamente en una nueva posición, llama a `entity.mark_teleported()` para que la interpolación no difumine entre la posición antigua y la 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 del mundo mínima 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 el espacio mundial.
* Si el texto parpadea o se superpone, revisa tus valores de Z y el espaciado.
