캔버스
게임 화면에 텍스트 상자, 패널, 진행 바, 이미지, 지면에 붙는 원, 화살표가 달린 경로를 그립니다. 런타임이 매 프레임 직접 그리며 게임 상태를 바꾸지 않으므로 멀티플레이 게임에서도 안전합니다. Python, HTTP, 공유 메모리 직접 쓰기를 모두 지원합니다.
외부 프로그램이 게임 화면에 텍스트 상자, 패널, 진행 바, 이미지, 지면의 원, 화살표가 달린 지면 경로를 그릴 수 있으며, 런타임이 매 프레임 직접 그립니다. 나만의 HUD, 보조선, 안내, 교육용 표시, 방송 정보판을 만들기에 알맞습니다.
캔버스와 JASS 화면 함수, 어느 쪽을 쓸까
| 캔버스(이 페이지) | JASS 화면 함수 | |
|---|---|---|
| 그리는 주체 | 런타임이 직접 그림 | 게임 자체(떠다니는 텍스트, 특수 효과, 패널, 초상화 대사……) |
| 멀티플레이 게임 | 안전: 이 컴퓨터의 화면에만 그리며, 게임 오브젝트를 만들지 않고 게임 상태도 바꾸지 않음 | 싱글플레이 게임만 가능 |
| 스타일 | 자유로움: CJK 글꼴, 둥근 모서리, 반투명, 테두리, 임의의 색, 로컬 이미지 | 게임 기본 스타일 |
| 대상 따라가기 | 유닛, 월드 좌표, 화면 위치를 따라감. 지면의 원은 지형 기복에 맞춰 붙음 | 함수마다 다름 |
| 비용 | 실측 프레임당 0.2 ~ 0.35 ms(요소 9개) | 호출 1회에 약 13 ms |
두 방법은 함께 쓸 수 있습니다. 게임 고유 스타일의 효과는 JASS로, 직접 디자인한 패널, 보조선, 안내는 캔버스로 그리세요.
Python
c = g.canvas # 처음 쓸 때 런타임이 그리기 훅을 설치(약 0.1초)
c.text("title", "안녕하세요, 캔버스입니다", screen=(40, 110), color=(255, 220, 80),
bg=(0, 0, 0, 170), border="#C49C40", size=22, bold=True)
c.panel("status", "동료 · 빛나", ["기분: 기쁨", "처치: 12"], screen=(16, 330))
c.bar("hp", 0.62, unit=hero, lift=260, width=100, height=12, color=(80, 220, 80), text="62%") # 유닛을 따라감
c.text("tag", "보스가 궁극기를 쓰려고 합니다!", 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) # 지면의 위험 지역
c.circle("aura", hero, 450, color=(80, 200, 255, 220)) # 유닛을 따라가는 원
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는 자기가 그린 것만 지움
c.expire("tag", 5) # 5초 뒤 저절로 사라짐
with c.batch(): ... # 여러 개를 한 번에 바꾸고, 공유 메모리는 한 번만 씀
c.stats() # drawnFrames가 늘고 있으면 = 실제로 그리는 중
각 요소는 key 하나로 식별합니다. 같은 key로 다시 그리면 갱신됩니다.
클릭 가능: 텍스트 상자와 패널에 clickable=True를 붙이면(호버 색은 hover=로 지정) 클릭했을 때 이벤트 스트림에 ui.click이 하나 들어오고, ev.key가 바로 그 key입니다. 그 위를 누른 클릭은 게임에 전달되지 않습니다. 바로 쓸 수 있는 버튼, 선택 카드, 단축키, 지면 클릭은 UI와 입력을 참고하세요.
위치(요소마다 하나 지정):
screen=(x, y): 화면 픽셀. 음수는 오른쪽 / 아래쪽에서부터 거꾸로 셉니다.center=True이면 중심 기준으로 정렬합니다.frac=(0.5, 0.1): 화면 비율.world=(x, y): 월드 좌표.unit=유닛: 유닛을 따라갑니다. 월드와 유닛 위의 텍스트, 진행 바는 아래쪽 가운데를 그 지점에 맞추며,lift만큼 위로 올립니다.
월드와 유닛 위의 요소는 기본적으로 하단 조작 패널과 상단 낮밤 시계를 피합니다(over_ui=True이면 그 위를 덮음). 색은 (r, g, b), (r, g, b, a), "#RRGGBB", "#RRGGBBAA"로 쓸 수 있습니다.
| 메서드 | 그리는 것 | 자주 쓰는 인자 |
|---|---|---|
text(key, 텍스트, ...) | 텍스트 상자. 여러 줄은 \n | color, bg 배경색(없으면 투명), border, size, bold, shadow, width(이 너비에서 줄바꿈), radius 둥근 모서리 |
panel(key, 제목, [줄...], ...) | 패널(어두운 반투명 배경, 금색 테두리) | text와 같음 |
bar(key, 0..1, ...) | 진행 바: 체력, 쿨다운, 시전 바 | width, height, color, bg, border, text |
image(key, 경로, ...) | 로컬 이미지(png / jpg / bmp / gif) | width, height(없으면 원본 크기) |
circle(key, 유닛 또는 지점, 반지름, ...) | 지면의 원, 지형에 붙음 | color 선 색, fill 채우기(투명도 포함), width 선 굵기 |
path(key, [지점...], ...) | 지면의 꺾은선 | color, width, arrow 끝 화살표. 지점은 좌표나 유닛 |
HTTP(모든 언어)
Farsight 백엔드(로컬에서만 수신 대기):
POST /api/instances/20/canvas
{"set": [
{"key": "banner", "kind": "text", "text": "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 지금 그려진 요소 목록 + 그린 프레임 수
kind는 Python의 메서드 이름이고, 인자 이름도 같습니다. 유닛은 스냅샷의 주소 addr로 씁니다.
공유 메모리에 직접 쓰기
Python과 Farsight를 거치지 않을 수도 있습니다. 먼저 시맨틱 명령 canvas_enable(W3P 연산 코드 73)을 한 번 보내면, 런타임이 공유 메모리 블록 Local\War3Canvas_<pid>를 만듭니다. 구성은 헤더 64바이트 + 항목 256개 × 112바이트 + 64 KB 텍스트 / 지점 풀입니다. seqlock 방식으로 씁니다(시퀀스 번호를 홀수로 → 항목과 풀 쓰기 → 시퀀스 번호를 짝수로). 런타임은 매 프레임 한 번 읽고, 반쯤 쓰인 상태를 읽으면 이전 프레임 내용을 그대로 쓰며, 그린 프레임 수, 요소 수, 예외 횟수를 되써 줍니다. Python 참조 구현은 sdk/python/w3canvas.py이며, 구조체 정의는 프로토콜 헤더 파일에 있습니다. W3P 프로토콜을 참고하세요.
여러 프로그램이 동시에 그릴 때
모드, Farsight, MCP, 게이트웨이가 같은 게임에 동시에 그릴 수 있지만, 캔버스는 하나뿐입니다. 규칙은 각 프로그램은 자기 요소만 건드린다입니다.
- 쓰기 전에 이름 있는 락을 먼저 잡고, 기존 요소를 읽어 다른 프로그램의 것은 남기고 자기 것만 바꾼 뒤 다시 씁니다.
- 요소마다 누가 그렸는지(프로세스 ID + 프로세스 안 일련번호)를 기록합니다. 그린 프로그램이 종료되면 다음에 누군가 쓸 때 함께 정리되고, 그 프로그램의 버튼도 더 이상 클릭을 가로채지 않습니다.
- 요소 번호는 공용 카운터에서 할당하므로 겹치지 않습니다.
Python SDK는 이미 이렇게 동작하며, clear()도 자기 것만 지웁니다. 공유 메모리에 직접 쓴다면 이 규칙을 따르세요. 그러지 않으면 다른 프로그램의 것을 덮어써 버립니다. 레이아웃 세부 사항은 W3P 프로토콜을 참고하세요.
실측과 주의 사항
- 2026-09-25 실측(1920×1080, 2배속): 요소 9개에 프레임당 0.27 ~ 0.34 ms, 약 63프레임/초, 예외 0회. 9개를 쓰는 데 6 ms. 영웅이 움직일 때 유닛을 따라가는 원, 텍스트, 체력 바가 모두 잘 따라갑니다. 내용이 바뀔 때만 텍스처를 다시 그리고, 위치만 옮길 때는 다시 그리지 않습니다.
- 게임 UI를 그린 뒤, 마우스 포인터를 그리기 전에 그립니다. 게임 자체의 체력 바, 유닛, UI 위를 덮고, 마우스 포인터는 그 위에 그려집니다. 하단 조작 패널과 상단 낮밤 시계는 피하지만, 맵 자체의 패널(오른쪽 위의 리더보드, 카운트다운)은 피하지 않습니다 — 직접 만든 패널은 오른쪽 위에 두지 마세요.
- 게임 중이 아닐 때(메인 메뉴, 결과 화면)는 월드 좌표와 유닛에 붙은 요소는 그리지 않고, 화면 위치에 둔 요소는 그대로 그립니다.
- 지면의 원은 원주 위 64개 점을 각각 지면에 투영한 것이라, 지형에 높낮이가 있으면 모양도 따라 굴곡집니다 — 이것이 맞습니다. 실제 지면 위에 그리기 때문입니다.
- 처음 열 때 훅 설치와 글꼴 워밍업에 약 1초가 걸리며, 그동안 텍스트 계열 요소는 그리지 않고 원과 선은 그대로 그립니다.
- 그리는 중 예외가 한 번이라도 나면 이번 세션에서는 더 이상 그리지 않습니다(머리 위 말풍선과 같은 보호 장치).
stats()의faults가 1이 됩니다. - 텍스트, 이미지 경로, 지점을 합쳐 64 KB, 요소는 최대 256개입니다. 이미지 경로는 게임 프로세스가 읽을 수 있는 로컬 경로여야 합니다.
AI 동료의 상태 패널도 캔버스로 그린 것입니다. 체력 바, 지금 하는 일, 기분, 처치 수와 치유 횟수를 보여 줍니다.