문서 게임플레이 확장

UI와 입력

캔버스 위의 버튼과 선택 카드를 클릭할 수 있고, 마우스를 올리면 자동으로 강조됩니다. 단축키를 등록하고, 지면을 클릭해 위치를 고르고, 마우스가 가리키는 곳을 읽고, 로컬 플레이어가 무엇을 선택했는지 알 수 있습니다. 클릭, 단축키, 스킬 시전, 채팅 전문, 플레이어 퇴장이 모두 이벤트 스트림에 들어옵니다.

캔버스로 그린 것을 이제 클릭할 수 있습니다. 런타임이 게임 창의 입력을 넘겨받으므로, 외부 프로그램은 다음을 할 수 있습니다.

기능한 줄 설명게임에 전달되는가
클릭 가능한 캔버스 항목버튼, 선택 카드, 패널: 클릭하면 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()를 호출할 때 실행됩니다 — Bot과 게임플레이 모드의 러너는 매 틱 이를 호출합니다. 콜백을 지정하지 않은 클릭은 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 4자 코드, .b 레벨, .value 쿨다운 초, .x .y 시전 지점
message화면 메시지 영역에 한 줄이 표시됨.text 전체 텍스트, .frame 어느 영역인지, .chat(채팅일 때)
player.left플레이어가 나가거나 패배 판정으로 제거됨.player
game.ended게임에서 나감—

채팅과 화면 메시지

플레이어가 채팅창에 입력한 내용은 message 이벤트의 .chat에서 바로 읽습니다.

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()는 별도의 커서를 가지며, 게임 안내(“농장이 더 필요합니다”, “그곳에는 건설할 수 없습니다”)도 여기에 들어 있습니다. Bot을 작성할 때 이것을 쓰면 명령이 왜 실패했는지 알 수 있습니다.

다른 언어에서 쓰기

  • 게이트웨이: 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()는 실제 마우스를 반영합니다. 가로채기는 누르는 입력(클릭과 키)에만 적용됩니다.
  • 마우스 포인터 아래에 그립니다: 워크래프트는 포인터를 화면의 일부로 매 프레임 그려 넣습니다. 캔버스와 머리 위 말풍선은 모두 게임이 포인터를 그리는 단계 직전에 그리므로, 게임 UI는 덮고 포인터에는 덮입니다. 해당 프레임에 포인터를 그리지 않을 때(숨겨졌거나 컷신)만 마지막 단계에서 그립니다.
  • 시스템 배율: 직접 테스트를 작성해 창 메시지로 클릭을 보낼 때, DPI를 인식하지 않는 프로세스가 보낸 좌표는 시스템이 확대합니다(150% 배율에서 실측 ×1.5). 테스트 프로그램은 먼저 DPI 인식을 선언하세요. 사람이 직접 클릭하는 경우에는 영향이 없습니다.
  • 처음에는 글꼴 워밍업이 필요하며 약 1초가 걸립니다. 그동안에는 버튼이 아직 그려지지 않아 클릭할 수 없습니다.
  • 게임 중이 아닐 때는 지면 클릭을 알리지 않습니다: 메인 메뉴와 결과 화면에서는 mouse.world를 보내지도, 가로채지도 않습니다.
  • 눌렀을 때 가로챈 클릭이라도, 떼기 전에 다른 프로그램으로 전환하거나 마우스를 창 밖으로 끌고 나가면 상태가 초기화되므로, 다음 떼기까지 가로채는 일은 없습니다.
  • 1.27에는 새 게임 UI 프레임을 만드는 함수가 없습니다(1.31부터 있음). 여기의 버튼과 카드는 모두 런타임이 그린 것이라 스타일은 자유롭지만, 게임 자체의 메뉴 계층에는 나타나지 않습니다.