Интерфейс и ввод
Кнопки и карточки выбора на холсте нажимаются и подсвечиваются при наведении; можно регистрировать горячие клавиши, выбирать точку кликом по земле, узнавать, куда указывает мышь и кого выбрал локальный игрок. Клики, горячие клавиши, применение способностей, полный текст чата, уход игроков — всё попадает в поток событий.
То, что рисует холст, теперь можно нажимать. Рантайм перехватывает ввод окна игры, и внешняя программа может:
| Возможность | Коротко | Получает ли это игра |
|---|---|---|
| Кликабельные элементы холста | Кнопки, карточки выбора, панели: клик отправляет ui.click, при наведении — автоматическая подсветка | Клик по кнопке до игры не доходит |
| Горячие клавиши | Регистрируете сочетания вроде F5 или ctrl+shift+Q, нажатие отправляет hotkey | Можно поглотить (вместе с символом, который порождает нажатие) |
| Клик по земле | Клик по миру отправляет mouse.world с координатами на земле | Можно поглотить («кликните, где поставить башню») |
| Позиция мыши | Обновляется каждый кадр: пиксели экрана, точка на земле под курсором, элемент холста под курсором | — |
| Выделение | Кого выбрал локальный игрок; при любом изменении приходит selection.changed | — |
Всё это — локальный ввод + локальная отрисовка: в поток приказов ничего не попадает, поэтому безопасно и в многопользовательской игре. Но если обработчик меняет мир (создаёт юнитов, меняет характеристики), это по-прежнему только для одиночной игры.
Python: g.ui
ui = g.ui # при первом обращении рантайм перехватывает ввод окна
ui.button("shop", "Купить зелье (50 золота)", screen=(40, 300), on_click=lambda g, ev: buy(g))
c = ui.choice("Новый уровень! Выберите награду", [("Сила +5", "крепче"), ("Скорость атаки +20%", "бьёт чаще"), ("Призвать волка", "ещё один помощник")],
pause=True, on_pick=lambda g, i: give(g, i)) # ряд карточек посреди экрана; pause=True — пока выбирают, игра на паузе
i = c.wait(timeout=30) # можно и ждать с блокировкой (события при этом обрабатываются и не теряются)
ui.hotkey("F5", lambda g, ev: g.say(hero, "Понял!")) # по умолчанию нажатие поглощается
ui.hotkey("ctrl+shift+Q", on_press=..., swallow=False)
ui.mouse(on_click, capture=True, buttons=("left", "right")) # ловить клики по земле: сообщать о левой и правой кнопке и поглощать их
xy = ui.pick_point("Кликните по земле: где поставить башню?") # блокирующая версия: следующий левый клик по земле -> (x, y); Esc или тайм-аут -> None
ui.cursor() # {'screen': (x, y), 'world': (x, y, z) или None, 'hover': 'shop'}
ui.toast("Идёт 3-я волна!", seconds=3)
ui.close() # убрать свои элементы и горячие клавиши; ввод окну возвращается, только если им не пользуется другая программа
g.close() # или отключиться целиком (можно и так: with Game(...) as g:)
Обработчики получают (g, ev) и срабатывают, когда вы вызываете g.events(), — раннеры ботов и игровых модов делают это каждый тик. Клики, для которых обработчик не задан, попадают в ui.clicks. Исключение в обработчике только записывается в лог и не мешает другим обработчикам и событиям.
Можно работать и напрямую с холстом: g.canvas.text(..., clickable=True, hover=цвет); клики приходят из потока событий, а ev.key — это key, заданный при отрисовке. Если нарисовать кликабельный элемент, перехват ввода включается автоматически — трогать g.ui заранее не нужно.
Запись горячих клавиш: F1 ~ F24, A ~ Z, 0 ~ 9, numpad0 ~ numpad9, space enter esc tab backspace insert delete home end pageup pagedown left up right down; впереди можно добавить ctrl+, shift+, alt+.
Буквы и цифры без модификаторов конфликтуют с вводом в чат и горячими клавишами самой игры. Лучше берите клавиши, которые игра не использует, вроде F5 ~ F8, или сочетания.
Новые события
В g.events() появились такие события (все поля — в протоколе W3P):
| kind | Когда | Удобные поля |
|---|---|---|
ui.click | Нажат интерактивный элемент холста | .key — key на холсте, .button ('left' / 'right'), .mods — модификаторы |
ui.hover | Мышь зашла на элемент холста / ушла с него | .key (при уходе — None) |
hotkey | Нажата зарегистрированная горячая клавиша | .key — запись клавиши, .mods |
mouse.world | Клик по миру, если включён перехват кликов по земле | .x .y — координаты на земле, .button, .value (1 = клик поглощён) |
selection.changed | Изменилось выделение локального игрока | Юнитов берите через g.selection() |
spell.cast | Юнит применил способность (способность ушла на перезарядку) | .spell — четырёхсимвольный код, .b — уровень, .value — перезарядка в секундах, .x .y — точка применения |
message | В окне сообщений на экране появилась строка | .text — полный текст, .frame — в каком окне, .chat (если это чат) |
player.left | Игрок вышел или удалён после поражения | .player |
game.ended | Выход из матча | — |
Чат и экранные сообщения
То, что игрок пишет в чат, читается прямо из поля .chat события message:
for ev in g.events():
if ev.kind == "message" and ev.chat and ev.chat["text"] == "-follow":
... # ev.chat = {'channel': 'Всем', 'sender': 'имя игрока', 'text': '-follow'}
У g.messages() свой, независимый курсор, и в нём есть ещё и подсказки игры («Нужно больше ферм», «Здесь строить нельзя»). В боте по ним видно, почему команда не выполнилась.
Из других языков
- Шлюз: методы
ui.button,ui.choice,ui.hotkey,ui.mouse,ui.cursorдоступны в шлюзе под теми же именами. Передать функцию-обработчик удалённо нельзя, поэтому клики и горячие клавиши приходят в потоке событий (событиеui.clickсодержитkey). - Прямая запись в общую память: сначала отправьте семантическую команду
input_enable(код операции W3P 74), и рантайм начнёт перехватывать ввод; в блоке вводаLocal\War3Input_<pid>вы записываете таблицу горячих клавиш и переключатели мыши, а рантайм записывает обратно позицию мыши, точку на земле под курсором и элемент под курсором. Флаг0x40у элемента холста означает «интерактивный». Раскладка — в протоколе W3P.
Несколько программ одновременно
Моды, Farsight, MCP и каждый сеанс шлюза могут одновременно ставить кнопки и регистрировать горячие клавиши в одной игре, не мешая друг другу:
- каждая программа регистрирует свои горячие клавиши и свой переключатель кликов по земле, а SDK сводит всё в одну таблицу и передаёт её рантайму. Каждая клавиша попадает в таблицу один раз, событие получают все, и каждый узнаёт свои горячие клавиши по клавише;
ui.close()убирает только своё, а ввод окну возвращается, лишь когда уходит последняя программа;- если программу завершили принудительно и она не успела прибраться, рантайм раз в 2 секунды проверяет, живы ли зарегистрированные программы; когда все они завершились, он убирает оставленные ими горячие клавиши и перехват кликов по земле, а их кнопки больше не перехватывают клики.
Замеры
2026-09-25, проверка в реальной игре на тестовом экземпляре, 16/16:
- клик по кнопке →
ui.click+ обработчик, счётчик перехватов в рантайме +1 (игра этот клик не получила); клик мимо кнопки ничего не вызывает; - F6 →
hotkey; клик по земле →mouse.world(поглощён); - создать паладина и выбрать его →
selection.changed,g.selection()совпадает; применить «Божественный щит» →spell.cast('AHds', 1, 35.0); - текст карты →
message; чат →message, в.chatразобраны отправитель и текст; - поражение компьютерного игрока →
player.left; завершение матча →game.ended.
Клики по кнопкам настоящей мышью и подсветку при наведении тоже проверили по одному.
Ограничения и замечания
- Позиция берётся от настоящей мыши: игра сама читает позицию по системному курсору, поэтому наведение и
cursor()отражают настоящую мышь. Перехват касается только нажатий. - Рисуется под указателем мыши: Warcraft каждый кадр рисует указатель как часть картинки. Холст и облачки над головой рисуются до того шага, на котором игра рисует указатель: они перекрывают интерфейс игры, а указатель перекрывает их. Только если в этом кадре указатель не рисуется (скрыт или идёт заставка), они рисуются последним шагом.
- Системное масштабирование: если в своих тестах вы отправляете клики оконными сообщениями, координаты от процесса без поддержки DPI система увеличивает (при масштабе 150% замерено ×1.5). Объявите в тестовой программе поддержку DPI. На клики живого человека это не влияет.
- В первый раз нужно прогреть шрифты — около 1 секунды. Пока кнопки ещё не нарисованы, нажать их нельзя.
- Вне матча клики по земле не сообщаются: в главном меню и на экране итогов
mouse.worldне отправляется и не поглощается. - Если нажатие было поглощено, а до отпускания вы переключились на другую программу или увели мышь за пределы окна, состояние всё равно сбрасывается, и следующее отпускание уже не поглощается.
- В 1.27 нет функций для создания новых фреймов интерфейса игры (они появились только в 1.31): кнопки и карточки здесь рисует рантайм, стиль может быть любым, но в собственной иерархии меню игры они не появляются.