# 画板

> 往游戏画面上画文字框、面板、进度条、图片、贴着地面的圈和带箭头的路线。运行时每帧自己画，不改游戏状态，多人局也安全；Python、HTTP、直接写共享内存都行。

来源: https://war3ai.com/docs/canvas/

外部程序可以往游戏画面上画**文字框、面板、进度条、图片、地上的圈、地上的路线（带箭头）**，由运行时每帧自己画。拿来做自己的 HUD、辅助线、提示、教学标注、直播信息板，都合适。

## 画板和 JASS 画面函数，怎么选

| | 画板（本页） | [JASS 画面函数](https://war3ai.com/docs/jass/) |
|---|---|---|
| 谁画 | 运行时自绘 | 游戏自己（浮动文字、特效、面板、头像对白……） |
| 多人局 | **安全**：只画在本机画面上，不建游戏对象、不改游戏状态 | 只能单人局 |
| 样式 | 随意：中文字体、圆角、半透明、边框、任意颜色、本机图片 | 游戏原生风格 |
| 跟着东西走 | 跟单位、世界坐标、屏幕位置；地上的圈贴着地形起伏 | 看具体函数 |
| 开销 | 实测每帧 0.2 ~ 0.35 ms（9 个元素） | 每次调用约 13 ms |

两条路可以一起用：原生风格的效果用 JASS，自定义的面板、辅助线和提示用画板。

## Python

```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", "Boss 要放大招了！", 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，点在它上面的这一下游戏收不到。现成的按钮、选项卡、热键、点地面见 [界面与输入](https://war3ai.com/docs/ui-input/)。

**位置**（每个元素给一个）：

- `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（任何语言）

远见后端（只监听本机）：

```http
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 和远见也可以：先发一次语义命令 `canvas_enable`（W3P 操作码 73），运行时建好共享内存块 `Local\War3Canvas_<pid>`：头 64 字节 + 256 条 × 112 字节 + 64 KB 文字 / 点池。按 seqlock 写（序号变奇数 → 写条目和池 → 序号变偶数），运行时每帧读一次，读到写了一半的就沿用上一帧，并回写已画帧数、元素数和异常计数。Python 参考实现是 `sdk/python/w3canvas.py`，结构定义在协议头文件里，见 [W3P 协议](https://war3ai.com/docs/protocol/)。

## 好几个程序同时画

模组、远见、MCP、网关可能同时往同一局里画东西，画板只有一块。规矩是：**每个程序只动自己的元素**。

- 写之前先拿一把命名锁，读出现有的元素，留下别人的，换上自己的，再写回；
- 每个元素记着是谁画的（进程号 + 进程内序号），画它的程序退出了，下一次有人写时顺手清掉；它的按钮也不再拦点击；
- 元素编号从共用的计数器分配，不会撞号。

Python SDK 已经这么做了，`clear()` 也只清自己的。自己直接写共享内存的话照这个来，否则会把别人的东西冲掉。布局细节见 [W3P 协议](https://war3ai.com/docs/protocol/)。

## 实测与注意

- 2026-09-25 实测（1920×1080，2 倍速）：9 个元素每帧 0.27 ~ 0.34 ms，约 63 帧 / 秒，0 次异常；写 9 条用 6 ms；英雄走动时，跟着单位的圈、文字、血条都跟得上。内容变了才重画贴图，只挪位置不重画。
- 画在游戏界面之后、鼠标指针之前：盖在游戏自己的血条、单位和界面上面，鼠标指针盖在它上面。它会避开底部操作台和顶部昼夜球，但**不会避让地图自己的面板**（右上角的排行榜、倒计时）—— 自己的面板别放右上角。
- 不在对局里（主菜单、结算页）时，放在世界坐标和单位上的元素不画，屏幕位置的照画。
- 地上的圈是把圆周上的 64 个点各自投到地面上，地形有高低时形状会跟着起伏 —— 这是对的：它画在真实的地面上。
- 第一次打开要装钩子、预热字体，约 1 秒，期间文字类元素先不画，圈和线照画。
- 绘制时出现一次异常，本次会话就不再画（和头顶气泡同一套保护），`stats()` 里的 `faults` 会变成 1。
- 文字、图片路径和点一共 64 KB，最多 256 个元素；图片路径要是游戏进程能读到的本机路径。

[AI 玩伴](https://war3ai.com/docs/companion/) 的状态面板就是用画板画的：血条、正在干什么、心情、击杀和加血次数。
