画板
往游戏画面上画文字框、面板、进度条、图片、贴着地面的圈和带箭头的路线。运行时每帧自己画,不改游戏状态,多人局也安全;Python、HTTP、直接写共享内存都行。
外部程序可以往游戏画面上画文字框、面板、进度条、图片、地上的圈、地上的路线(带箭头),由运行时每帧自己画。拿来做自己的 HUD、辅助线、提示、教学标注、直播信息板,都合适。
画板和 JASS 画面函数,怎么选
| 画板(本页) | JASS 画面函数 | |
|---|---|---|
| 谁画 | 运行时自绘 | 游戏自己(浮动文字、特效、面板、头像对白……) |
| 多人局 | 安全:只画在本机画面上,不建游戏对象、不改游戏状态 | 只能单人局 |
| 样式 | 随意:中文字体、圆角、半透明、边框、任意颜色、本机图片 | 游戏原生风格 |
| 跟着东西走 | 跟单位、世界坐标、屏幕位置;地上的圈贴着地形起伏 | 看具体函数 |
| 开销 | 实测每帧 0.2 ~ 0.35 ms(9 个元素) | 每次调用约 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", "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,点在它上面的这一下游戏收不到。现成的按钮、选项卡、热键、点地面见 界面与输入。
位置(每个元素给一个):
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(任何语言)
远见后端(只监听本机):
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 协议。
好几个程序同时画
模组、远见、MCP、网关可能同时往同一局里画东西,画板只有一块。规矩是:每个程序只动自己的元素。
- 写之前先拿一把命名锁,读出现有的元素,留下别人的,换上自己的,再写回;
- 每个元素记着是谁画的(进程号 + 进程内序号),画它的程序退出了,下一次有人写时顺手清掉;它的按钮也不再拦点击;
- 元素编号从共用的计数器分配,不会撞号。
Python SDK 已经这么做了,clear() 也只清自己的。自己直接写共享内存的话照这个来,否则会把别人的东西冲掉。布局细节见 W3P 协议。
实测与注意
- 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 玩伴 的状态面板就是用画板画的:血条、正在干什么、心情、击杀和加血次数。