頭上の吹き出しとローカルモデル
ゲーム内の任意のユニットの頭上に、任意の役柄で吹き出しを表示します。ローカル LLM につなげば、1 文を入力すると 1 文の返答がユニットの頭上に表示されます。
吹き出しは観賞用のレイヤーです。勝敗には影響せず、配信、実況、デバッグに向いています。
- 任意のユニットが任意の役柄で話せます。複数のユニットが同時に話すこともできます。
- 吹き出しごとに、フォントサイズ、色、幅、しっぽ、透明度、タイピング速度を個別にカスタマイズできます。
- ローカル LLM(LM Studio)に直接接続でき、ストリーミング出力に対応します。生成しながら吹き出しを更新します。
Bot から使う
最も簡単なのは SDK 組み込みの say です。
g.say(hero, "俺に続け!", seconds=4)
起動と UI
いちばん手軽なのは Farsight のトップページ「コントロールセンター」です。まず「ローカル LLM → 起動してモデルを読み込む」(LM Studio のローカルサーバー + 設定済みのモデルを VRAM に読み込む)をクリックし、次に「頭上のチャット吹き出し → 起動」をクリックします。カードではログの確認、停止、再起動ができます。
UI は Farsight 左側の「頭上の吹き出し」ページにあります。ユニットに話させる(ユニットを選ぶ、テキストを書く、スタイルを調整する、モデルと会話する)、農民の井戸端会議、カメラ会話、戦況トリガー、モデル設定があり、上部バーで選んだインスタンスに対して操作します。
コマンドラインでも起動できます。
python speech/speak_launch.py # ローカルモデルサーバーを起動 + モデルを読み込んでウォームアップ + 吹き出し API を起動
python speech/speak_launch.py --restart # コードを変更したあとに API を再起動
python speech/speak_launch.py --stop # API を停止し、モデルを VRAM からアンロード
各ステップは「すでにあればスキップ」するので、何度実行しても副作用はありません。
HTTP API
デフォルトは http://127.0.0.1:8872/(ポートは openwar3.json の ports.speech)で、どのプログラムからでも呼び出せます。
ユニットに話させる POST /api/say
{
"inst": 16,
"bubbles": [
{ "unit": "0x14A12614", "name": "マウンテンキング", "text": "俺に続け!" },
{ "unit": "0x14A12924", "name": "アークメイジ", "text": "ブリザードは任せろ。",
"style": { "font_px": 26, "text_color": "#FFE080", "bg_color": "#C0102040" } },
{ "screen": [960, 110], "key": 1, "name": "ナレーション", "text": "オークの第 1 波到着まであと 30 秒。",
"style": { "tail": false, "type_ms": 0 } },
{ "world": [-4684, 2644], "key": 2, "text": "集合地点", "style": { "font_px": 16 } }
]
}
| フィールド | 説明 |
|---|---|
unit / world / screen | いずれか 1 つ:ユニットに追従(HP バーがあればその真上に表示)/ マップ座標 / 画面ピクセル(ナレーション用) |
name | 1 行目に表示する話者名。自由に書けて、そのユニットである必要はありません |
text | 本文。自動で折り返します |
duration_ms | 表示時間。0 = 自動で 3 〜 5 秒 |
key | ワールド / 画面の吹き出しの番号。同じ key の新しいメッセージが古いものを置き換えます |
update | 同じ吹き出しがすでにある場合、テキストだけを差し替え、タイマーはリセットしません(ストリーミング出力用) |
style | font_px、max_width_px、text_color、bg_color、border_color、tail、side_px、opacity、type_ms、font…… |
同時に表示できる吹き出しは最大 32 個です。1 フレームあたりのコストは平均約 0.1 〜 0.2 ms です。
ローカルモデルと会話する POST /api/chat
{
"inst": 16, "unit": "0x14A12614", "name": "マウンテンキング",
"persona": "あなたは Warcraft のマウンテンキング、ムラディンを演じます。豪快で酒好き。口語で 1〜2 文、40 文字以内。",
"message": "この先にオーガの群れがいる。突っ込むか?",
"stream": true
}
返り値は {"reply": "...", "first_token_ms": 283, "total_ms": 342} で、この返答はすでにそのユニットの頭上に表示されています。同じユニットは直近 6 往復の会話を記憶します。
その他
| エンドポイント | 説明 |
|---|---|
GET /api/instances | 実行中のゲーム |
GET /api/units?inst=16&mine=true&heroes=true | ユニット一覧(中国語名、座標、HP 付き) |
POST /api/clear | 吹き出しを 1 つ、またはすべて消去 |
GET /api/llm、POST /api/llm | モデル設定の確認 / 変更(base_url、model、max_tokens、temperature) |
POST /api/banter | 農民の井戸端会議:拠点のワーカーがキャラ設定に沿って順番にぼやき、開始時に口上を述べます(戦況はすべて実データ) |
POST /api/camtalk | カメラ会話:画面に映っているヒーローと従者が役柄に応じて会話します |
POST /api/events | 戦況トリガー:開戦、戦闘終了、ヒーロー戦死、ティアアップ、建物破壊……何かが起きたときだけ話します |
ローカルモデルの選び方
RTX 5090 1 枚での実測(ゲームのセリフ 5 本):
| モデル | VRAM | 速度 | 1 回の返答 | 結論 |
|---|---|---|---|---|
| Qwen3.6-35B-A3B(MoE、毎回 3B のみアクティブ)、Q4、思考オフ | 20.6 GB | 約 142 token/s | 約 0.3 秒(最初のトークンまで約 0.27 秒) | 推奨:速く、中国語のロールプレイが自然 |
| gpt-oss-20b(MXFP4)、推論 low | 11.3 GB | 約 280 token/s | 0.3 〜 0.8 秒 | VRAM が厳しいとき向け。中国語はやや平板 |
| Qwen3.6-27B(dense)、Q4 | 17.2 GB | 約 39 token/s | 5.5 秒経ってもまだ思考中 | リアルタイム会話には不向き |
- 速度は「毎回アクティブになるパラメータ数」で決まり、総パラメータ数ではありません:35B の MoE は 3B しかアクティブにならず、27B の dense モデルより 3 〜 4 倍速くなります。
- 「思考」は必ずオフにしてください:オフにしないと、トークンがすべて思考に使われ、1 文字も返答しません。
- 吹き出しは 1 秒あたり約 22 文字の速さで 1 文字ずつ表示されるため、生成速度はもはやボトルネックではありません。体験を本当に左右するのは最初のトークンまでの遅延です。
セリフを「本物らしく」する
モデルに渡す戦況は必ず実データ(試合数、勝敗、兵力、備蓄)にし、「これらの事実だけを使うこと」と明示してください。実測では、この制約がないとモデルは起きていない戦闘をでっち上げました。