相手の戦術おみとおし!ポケモンチャンピオンズAI支援アプリを開発して、復帰勢でも戦えるようになった話

2026-04-22 · Issue #7

Image

1. 10年ぶりのポケモン対戦復帰、知識差で負け続ける...ブリジュラスってなに?

ポケモンチャンピオンズ配信を機に、対戦を10年(?)振りにやってみました。
先日BWからポケモンバンクを経由して、HOMEに連れてきたポケモンたちと再び戦えるのを本当に待ってたんですよ。

いざやってみると、令和ポケモンたちが規格外の強さでビックリ...
オオニューラ、ブリジュラス、メガフラエッテ、イダイトウ
この辺りに何度も壊滅させられました。

でも、負け続けて感じることもありました。

  • 「これ素早さ知ってれば勝ってたかも?」
  • 「この型を知っていれば対策できたなー」
  • 「この技の威力と攻撃の値を知っていれば、なんとなくの計算で耐えることわかったのに!」

そう、ルールもタイプ相性も立ち回りの基本も覚えているのに、知らないポケモン・技が多すぎたのです
知らないということは、シングルバトルにおける6見せ時に何も考えられず、不利なじゃんけんをしているようなものです。

つまり、復帰勢の私が欲している情報は...6見せ時に

  1. 相手のポケモンのアイコンを爆速でググり
  2. とくせい・わざ・種族値を知って
  3. 相手のパーティのタイプを俯瞰して通りやすいタイプを判断し
  4. 相手の選出・コンボを予想してから
  5. 自分の選出を決めるのをサポートしてくれる何か

だったのです。


2. 作ったもの:相手の戦術を即座に可視化するアプリ

そこで、私を全力でサポートするmacOSアプリを自作しました。
こちらがそのデモです↓

上記の私が欲していた情報を全て出してくれてます。

できること:

  • 自分のポケモンのパーティの認識
  • 相手のポケモンのタイプ・種族値・特性を即座に表示
  • 使用率の高い技構成をサジェスト
  • タイプ相性から有利不利を整理
  • ダメージ計算を実施して落とせる/されるパターンを提示
  • 上記情報から相手の選出を予想し、自分の選出を提案

プレイヤーはこのアプリに何も入力しません。ゲームを普通にプレイするだけで、裏で自動的に動きます。

動作するタイミングは6体見せの選出画面のみです。対戦中の戦術判断やターンごとの操作には一切介入しません。

あくまで「選出前に相手を知るための情報提示」であり、バトル中のリアルタイム支援や自動化とは別物です。(私見ですがこれがフェアプレーの境かと考えています。規約・ガイドラインと照合は後述の章でお話ししてます。)

初見のポケモンでも、「知らないから負ける」をなくすのが目的のアプリなのです。


3. 仕組み:映像を読んで、AIが解析する

手入力をなくすために、映像から直接情報を取得する設計にしました。

HDMIキャプチャ → OCR → 画像分類 → データ照合 → 表示
  1. 映像取得:キャプチャボードでゲーム画面をMacに入力
  2. OCR:「選出してください」画面を自動検出
  3. 画像分類:選出画面の画像からポケモンの種族を特定
  4. データ照合:タイプ・種族値・特性・技構成をローカルDBから引く
  5. AI利用:材料が出揃ったら判定はAIに任せる

3-1. 映像取得

使用フレームワーク:AVFoundation

AVCaptureSession でHDMIキャプチャデバイスを自動検出し、フレームを取得します。外部キャプチャボードが見つからない場合は内蔵カメラにフォールバックします。

let discovery = AVCaptureDevice.DiscoverySession(
    deviceTypes: [.externalUnknown],
    mediaType: .video,
    position: .unspecified
)
let device = discovery.devices.first
    ?? AVCaptureDevice.default(for: .video)

フレームはデリゲートキューで受け取り、重い処理は別キューに逃がす設計にしています。3秒ごとにサンプリングするため、CPU負荷を最小限に抑えられています。

3-2. OCR

使用フレームワーク:Vision(VNRecognizeTextRequest)

3秒ごとに取得したフレームに対して日本語OCRを実行します。「選出してください」というテキストが含まれているかを判定するだけなので、全文の精度より検出速度を優先しています。

let request = VNRecognizeTextRequest { request, _ in
    let text = (request.results as? [VNRecognizedTextObservation] ?? [])
        .compactMap { $0.topCandidates(1).first?.string }
        .joined(separator: "\n")

    if text.contains("選出してください") {
        onBattleDetected(cgImage)
    }
}
request.recognitionLanguages = ["ja-JP", "en-US"]
request.recognitionLevel = .accurate

OCRの難所は誤認識への対処でした。「選出してください」の検出は1フレームの誤読で誤発火しないよう、同じ滞在中は1回だけ解析するフラグ管理を加えています。

オンデバイスなので、実行しまくれるのがいいですね!

3-3. 画像分類

使用フレームワーク:Vision(VNGenerateImageFeaturePrintRequest)

選出画面の右側にある6スロットをクロップし、それぞれのスプライト画像を約210種のポケモンスプライトDBと照合します。機械学習モデルの自作は不要で、Apple Visionの特徴量ベースマッチングを使っています。

// 起動時にスプライト全件の特徴量をキャッシュ
func buildIfNeeded() {
    for url in spriteURLs {
        let request = VNGenerateImageFeaturePrintRequest()
        try handler.perform([request])
        let fp = request.results!.first as! VNFeaturePrintObservation
        entries.append(Entry(id: url.stem, featurePrint: fp))
    }
}

// クロップ画像との距離を全件計算し最小を返す
func match(image: CGImage) throws -> Result? {
    let queryPrint = try featurePrint(for: image)
    return db.entries.min(by: {
        var d0: Float = 0, d1: Float = 0
        try? $0.featurePrint.computeDistance(&d0, to: queryPrint)
        try? $1.featurePrint.computeDistance(&d1, to: queryPrint)
        return d0 < d1
    }).map { Result(id: $0.id, distance: ...) }
}

シンプルですが実用的な精度が出ました!時々失敗するのは色違いですね。色リザードンがプテラと誤認することが時々ありますが、リトライボタンでどうにか対処しました。

これもオンデバイスなので、実行しまくれるのがいいですね!

3-4. データ照合

ローカルJSONから種族値・技使用率をマッピング

画像からポケモンが確定したら、ローカルDBからタイプ・種族値・特性・技構成を引きます。

工夫したのはOCR誤認識への対処です。自分のチーム情報はOCRで取得するため、どうぐ名・わざ名が誤認識ありで入ってきます。その場合は、リストに最も近い文字列に倒す処理で、いい感じにアプリが認識できました。

3-5. AI利用

使用モデル:Apple Intelligence / Claude Code CLI / OpenAI Codex CLI

素材が揃ったら、選出アドバイスの生成をAIに委ねます。最初は Apple Intelligence(FoundationModels) を使いました。

// 出力スキーマを宣言するだけで構造化出力が得られる
@available(macOS 26.0, *)
@Generable
struct LLMSelectionAdvice {
    @Guide(description: "本命の相手選出3匹。[先発, 後発1, 後発2] の順に3要素")
    var predictedOpponent: [String]

    @Guide(description: "自分の推奨選出3匹。[先発, 後発1, 後発2] の順に3要素")
    var recommendedMyTeam: [String]

    @Guide(description: "Markdown形式の分析本文")
    var reasoning: String
}

// 呼び出し
let session = LanguageModelSession()
let response = try await session.respond(to: prompt, generating: LLMSelectionAdvice.self)

@Generable@Guide でスキーマを宣言するだけで構造化出力が得られるのは非常に便利でした。しかしオンデバイスで動くため外部API不要・低遅延という理想的な構成だったものの、ポケモンの戦術理解という専門性の高いタスクでは出力品質が安定せず、実用には至りませんでした。

そこで最終的に Claude Code CLIOpenAI Codex CLI をサブプロセスとして呼び出す構成に落ち着きました。プロセスをforkして標準入力にプロンプトを渡し、JSONLストリームで回答を受け取ります。

process.executableURL = URL(fileURLWithPath: claudePath)
process.arguments = [
    "-p", prompt,
    "--output-format", "stream-json",
    "--verbose",
    "--model", model.rawValue,
]

AIには「相手の有力選出2パターン」「本命の相手選出3匹」「自分の推奨選出」「最も警戒すべき相手1匹」を求めます。回答末尾に必ずJSONブロックで選出をまとめさせ、UIへのマッピングを安定させています。

ClaudeもCodexも、もともとポケモンのルール・タイプ相性・主要な技・メタの傾向に関する知識を持っています。そのため「素早さ○○族抜き」「積み展開への対処」といった専門用語を解説なしで使えて、戦術的に妥当な分析が返ってきます。Apple Intelligenceとの品質差はここにありました。

3バックエンドはUI上で切り替え可能にしており、Apple Intelligenceは将来のモデル改善に期待して残してあります。

APIの方はお金かかるからね...


4. 設計でこだわったこと

AIは「判断の補助」に徹する。

AIが選出を決めるわけではありません。プレイヤーが判断するための情報を、素早く、邪魔にならない形で出す。それだけです。

設計方針は3つです:

  • 入力をなくす:ゲーム中に何かを操作させない
  • 多くの選択肢・情報を提示:あればあるだけ私は助かる派
  • ゲームの流れを止めない:ツールの存在を意識させない

「AIを使っている感」を出すのではなく、セカンドオピニオン的なアドバイザーを目指しました。
「こいつと俺の意見が一致したら間違いなさそう!」みたいな存在です。


5. 結果:知識不足による負けが減った

アプリを使い始めてから、知識不足による負けが明らかに減りました。

初見のポケモンへの対応ができるようになり、6体見せの段階で構成意図が読めるようになってきました。

もちろんプレイスキルの向上もありますが、情報格差を埋めたことが大きかったと実感しています。

作ってわかったこと

精度よりUXが先でした。

「何か入力してください」が1つあるだけで、ツールは使われなくなります。対戦中に画面を離れる行為自体がストレスになるからです。そもそもそんな時間もないですし。

このアプリの価値は強い答えを出すことではなく、自然に使えることだと思います。

この挑戦で知識差で負けることを、技術で減らせました。その体験が、このアプリを作った一番の収穫でした。

応用できるかも

オンデバイスで映像の解析をバンバン回して、AIに判断というフローは何にでも応用効くのではないかと考えています。

たとえば、将棋番組の解説させたり...、もっと世の中の役に立つ方法はあるはずです。
なにか、いい社会課題やネタを見つけたら、別の形でアプリ配信できればと思います。

ネタを磨いて、iOSDCとかにも出せると尚良いですね!!


6. このツールの立ち位置についてのお気持ち表明

開発にあたって、Pokémon Championsの利用規約やガイドラインは確認しました。

規約で明示的に禁止されているのは「ボット・Mod・スクリプト等によってゲーム内の操作を修正・自動化すること」です。このアプリはゲームクライアントに一切触れません。HDMIで出力された映像を読み取り、手元のDBと照合して情報を表示するだけで、入力の自動化もゲームデータの改変も行っていません。

また、解析が動くのは対戦前の6体見せ画面のみです。バトル中のターン判断や行動選択には関与せず、リアルタイムの戦術支援や操作の自動化とは設計上まったく異なります。

構造的には 「攻略本を手元に開いておく」に近い です。人間が目視で確認して手動で調べることを、映像解析で素早くやっているにすぎません。

一方で「本来想定していない使用方法」という観点では、なんとも言えない状態であることは認識しています。競技の文脈では、情報へのアクセス速度そのものが有利不利になりえます。

このアプリは個人使用・非配布を前提に開発しており、公式大会での使用は想定していません。あくまで「復帰勢が新しいポケモンを覚えるための補助輪」として作ったものです。ゲーム自体を楽しむための道具として、その範囲で使うのが適切だと考えています。

やろうと思えば

ここまで読んでくれた方は予想がつくと思いますが、正直なところ「ポケモンバトルの全自動化」は技術的には超余裕で実装できます👀

金策ニンフィアをはじめとするUSB-Cにコントローラー入力情報を送る仕組みを使えば、AIの判断結果に応じて情報を送るだけなので。定石通り動くだけなら、AIすらいらないですね。

終わりに

ご質問等は𝕏にお気軽にどうぞ🙇