# Lienzo

> Dibuja sobre la imagen del juego cuadros de texto, paneles, barras de progreso, imágenes, círculos pegados al suelo y rutas con flecha. El runtime lo dibuja él mismo en cada fotograma sin cambiar el estado del juego, así que también es seguro en multijugador; desde Python, por HTTP o escribiendo directamente en memoria compartida.

Fuente: https://war3ai.com/es/docs/canvas/

Un programa externo puede dibujar sobre la imagen del juego **cuadros de texto, paneles, barras de progreso, imágenes, círculos en el suelo y rutas en el suelo (con flecha)**, y el runtime los dibuja él mismo en cada fotograma. Sirve para tu propio HUD, líneas de ayuda, avisos, anotaciones didácticas o paneles de información para directos.

## Lienzo o funciones visuales de JASS: cuál elegir

| | Lienzo (esta página) | [Funciones visuales de JASS](https://war3ai.com/es/docs/jass/) |
|---|---|---|
| Quién dibuja | El runtime, por su cuenta | El propio juego (texto flotante, efectos, paneles, diálogo con retrato…) |
| Multijugador | **Seguro**: solo dibuja en tu pantalla, no crea objetos del juego ni cambia su estado | Solo en partidas de un jugador |
| Estilo | Libre: fuentes propias (también con caracteres chinos), esquinas redondeadas, semitransparencia, bordes, cualquier color, imágenes locales | Estilo nativo del juego |
| Seguir algo | Unidades, coordenadas del mundo, posiciones de pantalla; los círculos en el suelo siguen el relieve del terreno | Depende de la función |
| Coste | Medido: 0.2 ~ 0.35 ms por fotograma (9 elementos) | Unos 13 ms por llamada |

Se pueden usar las dos a la vez: JASS para los efectos con estilo nativo, el lienzo para paneles propios, líneas de ayuda y avisos.

## Python

```python
c = g.canvas                               # la primera vez, el runtime instala el hook de dibujo (unos 0.1 s)
c.text("title", "Hola, esto es el lienzo", screen=(40, 110), color=(255, 220, 80),
       bg=(0, 0, 0, 170), border="#C49C40", size=22, bold=True)
c.panel("status", "Compañero · Luz", ["Ánimo: feliz", "Bajas: 12"], screen=(16, 330))
c.bar("hp", 0.62, unit=hero, lift=260, width=100, height=12, color=(80, 220, 80), text="62%")   # sigue a la unidad
c.text("tag", "¡El jefe va a lanzar su ataque definitivo!", unit=boss, lift=320, color=(255, 80, 80), size=22, bold=True)
c.circle("danger", (x, y), 300, color=(255, 60, 60), fill=(255, 60, 60, 60), width=3)          # zona peligrosa en el suelo
c.circle("aura", hero, 450, color=(80, 200, 255, 220))                                         # círculo que sigue a la unidad
c.path("route", [(x1, y1), (x2, y2), (x3, y3)], color=(255, 220, 0), width=5, arrow=True)
c.image("icon", "icon.png", screen=(40, 170), width=64, height=64)
c.remove("danger"); c.hide("tag"); c.clear()   # clear solo borra lo que dibujaste tú
c.expire("tag", 5)                         # desaparece por sí mismo a los 5 s
with c.batch(): ...                        # cambia muchos elementos y escribe una sola vez en memoria compartida
c.stats()                                  # si drawnFrames sube, de verdad se está dibujando
```

Cada elemento se identifica con una `key`: dibujar otra vez con la misma key lo actualiza.

**Clicables**: añade `clickable=True` a un cuadro de texto o a un panel (el color al pasar el ratón se fija con `hover=`); al hacer clic llega un `ui.click` al flujo de eventos, con `ev.key` igual a esa key, y el juego no recibe ese clic. Para botones, tarjetas de elección, atajos de teclado y clics en el suelo ya listos, ver [Interfaz y entrada](https://war3ai.com/es/docs/ui-input/).

**Posición** (una por elemento):

- `screen=(x, y)`: píxeles de pantalla; los negativos cuentan desde la derecha / desde abajo; `center=True` alinea por el centro;
- `frac=(0.5, 0.1)`: fracción de la pantalla;
- `world=(x, y)`: coordenadas del mundo;
- `unit=unidad`: sigue a una unidad. El texto y las barras sobre el mundo o sobre una unidad se anclan por el punto medio del borde inferior; `lift` los sube.

Por defecto, los elementos del mundo y de unidades evitan la consola de abajo y el orbe de día y noche de arriba (`over_ui=True` los dibuja por encima). Los **colores** se pueden escribir como `(r, g, b)`, `(r, g, b, a)`, `"#RRGGBB"` o `"#RRGGBBAA"`.

| Método | Qué dibuja | Parámetros habituales |
|---|---|---|
| `text(key, texto, ...)` | Cuadro de texto; `\n` para varias líneas | `color`, `bg` color de fondo (transparente si no se indica), `border`, `size`, `bold`, `shadow`, `width` (ajusta las líneas a este ancho), `radius` esquinas redondeadas |
| `panel(key, título, [líneas...], ...)` | Panel (fondo oscuro semitransparente, borde dorado) | Igual que `text` |
| `bar(key, 0..1, ...)` | Barra de progreso: vida, enfriamiento, barra de carga | `width`, `height`, `color`, `bg`, `border`, `text` |
| `image(key, ruta, ...)` | Imagen local (png / jpg / bmp / gif) | `width`, `height` (tamaño original si no se indican) |
| `circle(key, unidad_o_punto, radio, ...)` | Círculo en el suelo, pegado al terreno | `color` color de línea, `fill` relleno (con transparencia), `width` grosor de línea |
| `path(key, [puntos...], ...)` | Línea quebrada en el suelo | `color`, `width`, `arrow` flecha al final; los puntos pueden ser coordenadas o unidades |

## HTTP (cualquier lenguaje)

Backend de Farsight (solo escucha en local):

```http
POST /api/instances/20/canvas
{"set": [
   {"key": "banner", "kind": "text", "text": "Lienzo desde HTTP", "frac": [0.5, 0.12], "center": true,
    "color": "#FFDC50", "bg": [0, 0, 0, 180]},
   {"key": "hp", "kind": "bar", "value": 0.8, "unit": 596125988, "lift": 260, "text": "80%"},
   {"key": "zone", "kind": "circle", "center": 596125988, "radius": 600, "color": [255, 200, 0], "width": 4},
   {"key": "route", "kind": "path", "points": [[-4587, -9092], [-5387, -8792]], "color": "#50C8FF"}
 ],
 "remove": ["old"], "clear": false}

GET  /api/instances/20/canvas        qué elementos hay dibujados ahora + cuántos fotogramas se han dibujado
```

`kind` es el nombre del método de Python y los parámetros se llaman igual; para una unidad se escribe su dirección `addr` de la instantánea.

## Escribir directamente en memoria compartida

También se puede sin pasar por Python ni por Farsight: primero se envía una vez el comando semántico `canvas_enable` (código de operación 73 de W3P) y el runtime crea el bloque de memoria compartida `Local\War3Canvas_<pid>`: cabecera de 64 bytes + 256 entradas × 112 bytes + un pool de 64 KB para texto / puntos. Se escribe con seqlock (el número de secuencia pasa a impar → se escriben las entradas y el pool → el número de secuencia pasa a par); el runtime lo lee una vez por fotograma, si encuentra una escritura a medias sigue usando el fotograma anterior, y devuelve el número de fotogramas dibujados, el número de elementos y el contador de fallos. La implementación de referencia en Python es `sdk/python/w3canvas.py` y las estructuras están definidas en el header del protocolo; ver [Protocolo W3P](https://war3ai.com/es/docs/protocol/).

## Varios programas dibujando a la vez

Los mods, Farsight, MCP y la pasarela pueden dibujar a la vez en la misma partida, pero solo hay un lienzo. La regla es: **cada programa solo toca sus propios elementos**.

- Antes de escribir se toma un bloqueo con nombre, se leen los elementos que ya hay, se dejan los de los demás, se ponen los propios y se vuelve a escribir;
- Cada elemento guarda quién lo dibujó (ID de proceso + número de secuencia dentro del proceso); si el programa que lo dibujó termina, se limpia la próxima vez que alguien escriba, y sus botones dejan de interceptar clics;
- Los números de elemento salen de un contador compartido, así que no chocan.

El SDK de Python ya lo hace así, y `clear()` solo borra lo propio. Si escribes tú directamente en la memoria compartida, sigue estas reglas; si no, borrarás lo que han dibujado otros. Los detalles de la estructura están en [Protocolo W3P](https://war3ai.com/es/docs/protocol/).

## Medidas y precauciones

- Medido el 25 de septiembre de 2026 (1920×1080, velocidad 2×): 9 elementos, 0.27 ~ 0.34 ms por fotograma, unos 63 fotogramas / s, 0 fallos; escribir 9 entradas lleva 6 ms; cuando el héroe se mueve, los círculos, el texto y las barras de vida que lo siguen van a la par. La textura solo se vuelve a generar si cambia el contenido; si solo cambia la posición, no.
- Se dibuja después de la interfaz del juego y antes del puntero del ratón: cubre las barras de vida, las unidades y la interfaz del propio juego, y el puntero queda por encima. Evita la consola de abajo y el orbe de día y noche de arriba, pero **no evita los paneles del propio mapa** (la tabla de clasificación y la cuenta atrás de la esquina superior derecha): no pongas tus paneles arriba a la derecha.
- Fuera de una partida (menú principal, pantalla de resultados), los elementos colocados en coordenadas del mundo o sobre unidades no se dibujan; los que están en una posición de pantalla sí.
- Los círculos en el suelo proyectan sobre el terreno cada uno de los 64 puntos de la circunferencia, así que cuando el terreno tiene desniveles la forma sube y baja con él; es lo correcto: se dibuja sobre el suelo real.
- La primera vez hay que instalar el hook y precalentar las fuentes, lo que lleva alrededor de 1 segundo; mientras tanto no se dibujan los elementos de texto, pero los círculos y las líneas sí.
- Si se produce un fallo durante el dibujo, deja de dibujar durante el resto de la sesión (la misma protección que las burbujas); `faults` en `stats()` pasa a 1.
- Texto, rutas de imágenes y puntos comparten 64 KB, con un máximo de 256 elementos; las rutas de imagen deben ser rutas locales que el proceso del juego pueda leer.

El panel de estado del [compañero IA](https://war3ai.com/es/docs/companion/) está dibujado con el lienzo: barra de vida, qué está haciendo, ánimo, bajas y curas.
