# キャンバス

> ゲーム画面にテキストボックス、パネル、プログレスバー、画像、地面に沿った円、矢印付きのルートを描きます。ランタイムが毎フレーム自前で描画し、ゲームの状態は変えないので、マルチプレイでも安全です。Python、HTTP、共有メモリへの直接書き込みのどれでも使えます。

出典: https://war3ai.com/ja/docs/canvas/

外部プログラムから、ゲーム画面に**テキストボックス、パネル、プログレスバー、画像、地面の円、矢印付きの地面のルート**を描けます。描画はランタイムが毎フレーム自前で行います。独自の HUD、補助線、ヒント、チュートリアルの注釈、配信用の情報ボードなどに向いています。

## キャンバスと JASS の画面系関数、どちらを使うか

| | キャンバス（このページ） | [JASS の画面系関数](https://war3ai.com/ja/docs/jass/) |
|---|---|---|
| 描くのは誰か | ランタイムが自前で描画 | ゲーム自身（フローティングテキスト、エフェクト、パネル、ポートレート会話……） |
| マルチプレイ | **安全**：自分のマシンの画面に描くだけで、ゲームオブジェクトを作らず、ゲームの状態も変えない | シングルプレイのみ |
| 見た目 | 自由：中国語フォント、角丸、半透明、枠線、任意の色、ローカルの画像 | ゲーム本来のスタイル |
| 対象への追従 | ユニット、ワールド座標、画面位置に追従。地面の円は地形の起伏に沿う | 関数による |
| コスト | 実測で 1 フレームあたり 0.2 〜 0.35 ms（要素 9 個） | 1 回の呼び出しで約 13 ms |

2 つの方法は併用できます。ゲーム本来のスタイルの演出は 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", "ボスが大技を撃つぞ！", 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 と入力](https://war3ai.com/ja/docs/ui-input/) を参照してください。

**位置**（各要素に 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 のバックエンド（ローカルでのみ待ち受け）：

```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 や Farsight を経由しなくても使えます。まずセマンティックコマンド `canvas_enable`（W3P オペコード 73）を 1 回送ると、ランタイムが共有メモリブロック `Local\War3Canvas_<pid>` を作成します。構成は、先頭 64 バイト + 256 エントリ × 112 バイト + 64 KB のテキスト / 点プールです。書き込みは seqlock で行います（シーケンス番号を奇数にする → エントリとプールを書く → シーケンス番号を偶数にする）。ランタイムは毎フレーム 1 回読み、書きかけのデータを読んだ場合は前のフレームの内容を使い続けます。また、描画済みフレーム数、要素数、異常カウントを書き戻します。Python のリファレンス実装は `sdk/python/w3canvas.py` で、構造体の定義はプロトコルのヘッダーファイルにあります。[W3P プロトコル](https://war3ai.com/ja/docs/protocol/) を参照してください。

## 複数のプログラムが同時に描く

MOD、Farsight、MCP、ゲートウェイが同じ試合に同時に描くことがありますが、キャンバスは 1 つしかありません。ルールは、**各プログラムは自分の要素だけを扱う**ことです。

- 書き込む前に名前付きロックを取り、既存の要素を読み出して、他のプログラムの分は残し、自分の分を差し替えてから書き戻します。
- 各要素には描いたのが誰かが記録されます（プロセス ID + プロセス内の連番）。描いたプログラムが終了すると、次に誰かが書き込むときについでに消されます。そのボタンもクリックを横取りしなくなります。
- 要素の番号は共有カウンターから割り当てるので、重複しません。

Python SDK はすでにこのとおりに動作し、`clear()` も自分の分だけを消します。共有メモリに直接書き込む場合もこのルールに従ってください。そうしないと、他のプログラムの要素を消してしまいます。レイアウトの詳細は [W3P プロトコル](https://war3ai.com/ja/docs/protocol/) を参照してください。

## 実測と注意点

- 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 コンパニオン](https://war3ai.com/ja/docs/companion/) のステータスパネルもキャンバスで描いています：HP バー、いま何をしているか、気分、撃破数と回復回数。
