キャンバス
ゲーム画面にテキストボックス、パネル、プログレスバー、画像、地面に沿った円、矢印付きのルートを描きます。ランタイムが毎フレーム自前で描画し、ゲームの状態は変えないので、マルチプレイでも安全です。Python、HTTP、共有メモリへの直接書き込みのどれでも使えます。
外部プログラムから、ゲーム画面にテキストボックス、パネル、プログレスバー、画像、地面の円、矢印付きの地面のルートを描けます。描画はランタイムが毎フレーム自前で行います。独自の HUD、補助線、ヒント、チュートリアルの注釈、配信用の情報ボードなどに向いています。
キャンバスと JASS の画面系関数、どちらを使うか
| キャンバス(このページ) | JASS の画面系関数 | |
|---|---|---|
| 描くのは誰か | ランタイムが自前で描画 | ゲーム自身(フローティングテキスト、エフェクト、パネル、ポートレート会話……) |
| マルチプレイ | 安全:自分のマシンの画面に描くだけで、ゲームオブジェクトを作らず、ゲームの状態も変えない | シングルプレイのみ |
| 見た目 | 自由:中国語フォント、角丸、半透明、枠線、任意の色、ローカルの画像 | ゲーム本来のスタイル |
| 対象への追従 | ユニット、ワールド座標、画面位置に追従。地面の円は地形の起伏に沿う | 関数による |
| コスト | 実測で 1 フレームあたり 0.2 〜 0.35 ms(要素 9 個) | 1 回の呼び出しで約 13 ms |
2 つの方法は併用できます。ゲーム本来のスタイルの演出は 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(): ... # 多数の変更をまとめ、共有メモリへの書き込みは 1 回だけ
c.stats() # drawnFrames が増えている = 実際に描画されている
各要素は key で識別します。同じ key でもう一度描くと、更新になります。
クリック可能:テキストボックスとパネルに clickable=True を付けると(ホバー時の色は hover= で指定)、クリックされたときにイベントストリームに ui.click が 1 件届き、ev.key がその key になります。その要素の上でのクリックはゲームには届きません。既成のボタン、選択カード、ホットキー、地面のクリックについては UI と入力 を参照してください。
位置(各要素に 1 つ指定):
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, ...) | プログレスバー:HP、クールダウン、詠唱ゲージ | 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)を 1 回送ると、ランタイムが共有メモリブロック Local\War3Canvas_<pid> を作成します。構成は、先頭 64 バイト + 256 エントリ × 112 バイト + 64 KB のテキスト / 点プールです。書き込みは seqlock で行います(シーケンス番号を奇数にする → エントリとプールを書く → シーケンス番号を偶数にする)。ランタイムは毎フレーム 1 回読み、書きかけのデータを読んだ場合は前のフレームの内容を使い続けます。また、描画済みフレーム数、要素数、異常カウントを書き戻します。Python のリファレンス実装は sdk/python/w3canvas.py で、構造体の定義はプロトコルのヘッダーファイルにあります。W3P プロトコル を参照してください。
複数のプログラムが同時に描く
MOD、Farsight、MCP、ゲートウェイが同じ試合に同時に描くことがありますが、キャンバスは 1 つしかありません。ルールは、各プログラムは自分の要素だけを扱うことです。
- 書き込む前に名前付きロックを取り、既存の要素を読み出して、他のプログラムの分は残し、自分の分を差し替えてから書き戻します。
- 各要素には描いたのが誰かが記録されます(プロセス ID + プロセス内の連番)。描いたプログラムが終了すると、次に誰かが書き込むときについでに消されます。そのボタンもクリックを横取りしなくなります。
- 要素の番号は共有カウンターから割り当てるので、重複しません。
Python SDK はすでにこのとおりに動作し、clear() も自分の分だけを消します。共有メモリに直接書き込む場合もこのルールに従ってください。そうしないと、他のプログラムの要素を消してしまいます。レイアウトの詳細は W3P プロトコル を参照してください。
実測と注意点
- 2026-09-25 の実測(1920×1080、2 倍速):要素 9 個で 1 フレームあたり 0.27 〜 0.34 ms、約 63 フレーム / 秒、異常 0 回。9 件の書き込みに 6 ms。ヒーローが移動しても、ユニットに追従する円、テキスト、HP バーはきちんとついてきました。内容が変わったときだけテクスチャを描き直し、位置が動くだけなら描き直しません。
- 描画はゲームの UI の後、マウスカーソルの前に行われます。そのため、ゲーム自身の HP バー、ユニット、UI の上に重なり、マウスカーソルはさらにその上に描かれます。下部の操作パネルと上部の昼夜の時計は避けますが、マップ独自のパネル(右上のリーダーボード、カウントダウン)は避けません —— 自分のパネルは右上に置かないでください。
- 試合中でないとき(メインメニュー、リザルト画面)は、ワールド座標やユニット上に置いた要素は描かれません。画面位置に置いた要素は通常どおり描かれます。
- 地面の円は、円周上の 64 点をそれぞれ地面に投影したものです。地形に高低差があると形も起伏します —— これは正しい動作で、実際の地面の上に描いているからです。
- 初回はフックの導入とフォントのウォームアップに約 1 秒かかり、その間テキスト系の要素はまだ描かれません。円と線は描かれます。
- 描画中に異常が 1 回でも起きると、そのセッションでは以後描画しません(頭上の吹き出しと同じ保護機構)。
stats()のfaultsが 1 になります。 - テキスト、画像パス、点の合計は 64 KB まで、要素は最大 256 個です。画像パスは、ゲームプロセスから読めるローカルのパスである必要があります。
AI コンパニオン のステータスパネルもキャンバスで描いています:HP バー、いま何をしているか、気分、撃破数と回復回数。