Interfaz y entrada
Los botones y las tarjetas de elección del lienzo se pueden pulsar y se resaltan solos al pasar el ratón; registra atajos de teclado, haz clic en el suelo para elegir una posición, lee a qué apunta el ratón y qué tiene seleccionado el jugador local. Clics, atajos, habilidades lanzadas, el texto completo del chat y los jugadores que se van: todo entra en el flujo de eventos.
Lo que dibuja el lienzo ahora se puede pulsar. El runtime captura la entrada de la ventana del juego, y un programa externo puede:
| Capacidad | En una frase | ¿Lo recibe el juego? |
|---|---|---|
| Elementos clicables del lienzo | Botones, tarjetas de elección, paneles: al hacer clic se emite ui.click, y se resaltan solos al pasar el ratón | El clic sobre el botón no le llega |
| Atajos de teclado | Registra combinaciones como F5 o ctrl+shift+Q; al pulsarlas se emite hotkey | Opcionalmente se tragan (junto con el carácter que generan) |
| Clic en el suelo | Un clic sobre el mundo emite mouse.world, con las coordenadas del suelo | Opcionalmente se traga («haz clic en un punto para poner una torre») |
| Posición del ratón | Se actualiza en cada fotograma: píxeles de pantalla, punto del suelo al que apunta, elemento del lienzo bajo el cursor | — |
| Selección | A quién tiene seleccionado el jugador local; en cuanto cambia se emite selection.changed | — |
Todo es entrada local + dibujo local: no entra en el flujo de órdenes, así que también es seguro en multijugador. Pero si en un callback cambias el mundo (crear unidades, cambiar atributos), eso sigue funcionando solo en partidas de un jugador.
Python: g.ui
ui = g.ui # la primera vez, el runtime captura la entrada de la ventana
ui.button("shop", "Comprar una poción (50 de oro)", screen=(40, 300), on_click=lambda g, ev: buy(g))
c = ui.choice("¡Has subido de nivel! Elige una recompensa", [("Fuerza +5", "Aguanta más"), ("Vel. de ataque +20%", "Pega más"), ("Invocar lobo", "Un ayudante más")],
pause=True, on_pick=lambda g, i: give(g, i)) # una fila de tarjetas en el centro de la pantalla; pause=True pausa el juego mientras eliges
i = c.wait(timeout=30) # también puedes esperar bloqueando (mientras tanto se siguen procesando los eventos, no se pierde ninguno)
ui.hotkey("F5", lambda g, ev: g.say(hero, "¡Recibido!")) # por defecto se traga
ui.hotkey("ctrl+shift+Q", on_press=..., swallow=False)
ui.mouse(on_click, capture=True, buttons=("left", "right")) # captura los clics en el suelo: avisa de izquierdo y derecho, y se los traga
xy = ui.pick_point("Clic en el suelo: ¿dónde va la torre?") # versión bloqueante: el siguiente clic izquierdo en el suelo -> (x, y); Esc o timeout -> None
ui.cursor() # {'screen': (x, y), 'world': (x, y, z) o None, 'hover': 'shop'}
ui.toast("¡Llega la oleada 3!", seconds=3)
ui.close() # retira tus controles y atajos; solo devuelve la entrada de la ventana si ningún otro programa la está usando
g.close() # o desconecta del todo (también se puede escribir with Game(...) as g:)
Los callbacks reciben (g, ev) y se disparan cuando llamas a g.events(); los ejecutores de Bots y de mods de juego lo llaman en cada tick. Los clics sin callback van a ui.clicks. Si un callback lanza una excepción, solo se registra en el log; no afecta a los demás callbacks ni a los eventos.
También puedes usar directamente la capa de abajo, el lienzo: g.canvas.text(..., clickable=True, hover=color); los clics se reciben del flujo de eventos y ev.key es la key que diste al dibujar. Dibujar un elemento clicable activa la entrada automáticamente; no hace falta tocar antes g.ui.
Cómo se escriben los atajos: F1 ~ F24, A ~ Z, 0 ~ 9, numpad0 ~ numpad9, space enter esc tab backspace insert delete home end pageup pagedown left up right down, con ctrl+, shift+ o alt+ delante.
Las letras y los números sin tecla modificadora chocan con la escritura en el chat y con los atajos del propio juego. Usa mejor teclas que el juego no ocupa, como F5 ~ F8, o combinaciones.
Eventos nuevos
g.events() ahora también entrega estos (los campos completos están en Protocolo W3P):
| kind | Cuándo | Campos de conveniencia |
|---|---|---|
ui.click | Se hace clic en un elemento interactivo del lienzo | .key key del lienzo, .button ('left' / 'right'), .mods teclas modificadoras |
ui.hover | El ratón entra en un elemento del lienzo o sale de él | .key (None al salir) |
hotkey | Se pulsa un atajo registrado | .key el atajo tal como se escribió, .mods |
mouse.world | Con los clics en el suelo activados, un clic sobre el mundo | .x .y coordenadas del suelo, .button, .value (1 = se lo tragó) |
selection.changed | Cambia la selección del jugador local | Obtén las unidades con g.selection() |
spell.cast | Una unidad lanza una habilidad (la habilidad entra en enfriamiento) | .spell código de cuatro caracteres, .b nivel, .value segundos de enfriamiento, .x .y punto de lanzamiento |
message | Aparece una línea en un marco de mensajes de la pantalla | .text texto completo, .frame qué marco, .chat (si es chat) |
player.left | Un jugador se va o es eliminado por derrota | .player |
game.ended | Se sale de la partida | — |
Chat y mensajes en pantalla
Lo que el jugador escribe en el chat se lee directamente de .chat en el evento message:
for ev in g.events():
if ev.kind == "message" and ev.chat and ev.chat["text"] == "-follow":
... # ev.chat = {'channel': 'Todos', 'sender': 'nombre del jugador', 'text': '-follow'}
g.messages() tiene además su propio cursor independiente, y ahí también están los avisos del juego («Necesitas más granjas», «No se puede construir ahí»). Al escribir un Bot, con él sabes por qué un comando no se llevó a cabo.
Desde otros lenguajes
- Pasarela: los métodos
ui.button,ui.choice,ui.hotkey,ui.mouseyui.cursorestán disponibles con el mismo nombre en la pasarela. Un cliente remoto no puede aportar funciones de callback, así que los clics y los atajos se reciben en los eventos enviados (el eventoui.clicklleva lakey). - Escribir directamente en memoria compartida: primero envía el comando semántico
input_enable(código de operación 74 de W3P) y el runtime empieza a capturar la entrada; en el bloque de entradaLocal\War3Input_<pid>tú escribes la tabla de atajos y los interruptores del ratón, y él escribe la posición del ratón, el punto del suelo al que apunta y el elemento bajo el cursor. El flag0x40de un elemento del lienzo significa «interactivo». La disposición está en Protocolo W3P.
Varios programas a la vez
Los mods, Farsight, MCP y cada sesión de la pasarela pueden poner botones y registrar atajos a la vez en la misma partida sin molestarse:
- Cada programa registra sus propios atajos y su interruptor de clics en el suelo, y el SDK junta los de todos en una sola tabla para el runtime. Cada tecla aparece una sola vez, los eventos llegan a todos y cada uno reconoce sus atajos por la tecla;
ui.close()solo retira lo propio, y la entrada de la ventana solo se devuelve cuando se va el último programa;- Si un programa se cierra a la fuerza sin poder recoger: el runtime comprueba cada 2 segundos y, cuando todos los programas registrados han terminado, borra los atajos y la intercepción de clics en el suelo que dejaron, y los botones que dibujaron dejan de interceptar clics.
Medido
2026-09-25, verificación en vivo en la instancia de prueba, 16/16:
- Clic en un botón →
ui.click+ callback, y el contador de intercepciones del runtime sube en 1 (el juego no recibió ese clic); un clic fuera del botón no lo dispara; - F6 →
hotkey; clic en el suelo →mouse.world(tragado); - Crear un paladín y seleccionarlo →
selection.changed, yg.selection()coincide; lanzar Escudo divino →spell.cast('AHds', 1, 35.0); - Texto del mapa →
message; chat →message, y.chatsepara quién habla y qué dice; - Declarar derrotado al ordenador →
player.left; terminar la partida →game.ended.
También se comprobaron uno a uno, con el ratón de una persona real, los clics en los botones y el resaltado al pasar por encima.
Límites y precauciones
- La posición es la del ratón real: el juego lee la posición del cursor del sistema, así que el hover y
cursor()reflejan el ratón real. La intercepción solo actúa sobre las pulsaciones. - Se dibuja debajo del puntero del ratón: Warcraft dibuja el puntero como parte de la imagen en cada fotograma. El lienzo y las burbujas de diálogo se dibujan justo antes del paso en que el juego dibuja el puntero: cubren la interfaz del juego y el puntero queda por encima. Solo si en ese fotograma no se dibuja el puntero (está oculto o hay una cinemática) se vuelve a dibujar en el último paso.
- Escalado del sistema: si escribes tus propias pruebas y envías clics con mensajes de ventana, las coordenadas que envía un proceso que no declara reconocimiento de DPI las amplía el sistema (medido: ×1.5 con un escalado del 150%). Declara primero el programa de pruebas como compatible con DPI. Los clics de una persona real no se ven afectados.
- La primera vez hay que precalentar las fuentes, alrededor de 1 segundo. Durante ese tiempo los botones aún no se han dibujado y no se pueden pulsar.
- Fuera de una partida no se avisan los clics en el suelo: en el menú principal y en la pantalla de resultados,
mouse.worldni se emite ni se traga. - Si un clic se tragó al pulsar y, antes de soltar, cambias a otro programa o arrastras el ratón fuera de la ventana, el estado también se reinicia: no se traga además la siguiente vez que sueltes.
- La 1.27 no tiene funciones para crear marcos de interfaz del juego (llegaron en la 1.31): aquí los botones y las tarjetas los dibuja el runtime, con el estilo que quieras, pero no aparecen en la jerarquía de menús del propio juego.