スマホでSwitchを操作できるようにして、隙間時間でポケモンマスターを目指す

LAPRAS夏の自由研究リレー - 8/24枠の記事です。 自由研究リレーとはその名の通り、LAPRASのメンバーが自由研究したことを次々にアウトプットしていくというもので、すでに沢山の記事が公開されています。ぜひご覧ください。

ちなみに当エントリは自由研究と言いつつも「趣味で作った作品の開発ログ + 申し訳程度の技術Tips」という構成になっています。小学生や中学生の自由研究においても、特に何かを研究をするわけでもなくただ単に工作だけしてお茶を濁すというのはよくある話です。あなたのお友達にもいたはずです。このエントリもそんな工作枠として受け入れられることを祈っています。

それでは本編です。

f:id:rocky_manobi:20200824143410p:plain

背景

2019.11.15にポケモン最新作「ソード&シールド」が発売された。自分は金銀以降まったくプレイしていなかったのだが、弊社CTOの @showwin がこの日のために休みをとっていたのを見て試しに買ってみたら、見事に対戦沼にはまってしまった。やるからには当然勝ちたいという欲望が湧いてくるのだが、ここ20年でポケモンは数もシステムも様変わりしており、もう自分の知っているポケモンではなくなっていた。このギャップを埋めるためにはとにかく多く対戦経験を積む必要があった。

Switchよりも手軽に、コッソリとポケモンバトルができる状態を作る

対戦経験を積むといっても時間は無限にあるわけではない。仕事もあれば子供もいる。 隙間時間でササッとポケモンができれば、ながら対戦ができれば、もっと効率よく場数を踏めると考えた。

そのために以下の課題を解決する必要があった。

  • スマホは余裕でいじれるがSwitch本体の操作は無理というシチュエーションが多い
    • 最近の子供の寝かしつけはほぼ隣で横たわっているだけで、光さえ枕で遮断すればスマホは問題ない
  • Switchをやっていると完全にゲームをやっているとバレて無邪気な攻撃をうける
  • Switchをやっていないときでも、無秩序に攻撃が飛んでくることがある
  • 攻撃をうけると一時的に画面を見られない状態になる
  • 子供のオムツ関連事故が発生したときにも、一時的に画面を見られない時間が生じる
  • ながらでやったとしても、選出や立ち回り、ダメージ感覚はあとから振り返りたい
  • 30過ぎたおじさんが満員電車でSwitchでポケモンをやっていたら物理的にも精神的にも迷惑をかけそうだった (これは通勤自体が消滅したので解決した
  • 電車の中で電波が不安定になった時に対戦切断扱いになるのが怖かった (これは通勤自体が消滅したので解決した

Switchよりも手軽に、コッソリとポケモンバトルができる状態を作ることでこれを解決していくことにする。

作ったもの

というわけで出来上がったのが「ポケモン剣盾のランクマッチにスマホで潜れる装置」だ。これを使えば子供の寝かしつけ時や、キッズランド等での付き添い時、電車での移動中、駐車場で買い物待機をしている間にもコッソリとポケモンの修行ができる。 普通にポケモン対戦をすることができる機能に加え、テキスト読み上げやメッセージログ表示など、ながら作業をしていても情報を逃すことがないようなアシスト機能を備えている。AirPods等のイヤホンをつけて使うことを想定している。

特徴

  • 手軽
    • スマホのサイズと操作性
    • 操作手順の削減
    • ずっと集中していなくても重要な情報を落とさない情報表示
  • コッソリ
    • スマホは多用途なのでバレない
    • イヤホンをつけて戦況を音声読み上げで把握するなども可能
  • その他
    • あとで振り返ることができるための、選出、使用した技、入ったダメージ等のロギング

仕組み

仕組みを簡単に説明すると以下のようになる。詳しいやり方は「作り方」を見ていただきたい。

  • スマホで操作をするとインターネット経由でPCに繋げているSwitchにコマンドが送られ、実行される
  • Switchの映像をキャプチャボードを使ってPC側で読み取り、しかるべき解析をしたのちに要所の画像をスマホに送って表示する

基本機能

基本的な画面構成は、選出、行動選択、その他の3つ。順に紹介していく。

f:id:rocky_manobi:20200824143410p:plain

選出

「画面上で選出したいポケモンにチェックを入れる」->「確定」の操作をすると、あとはそのとおりにSwitchにコマンドを送ってくれる。初めはSwitchの各種ボタンを一つ一つ押す操作をすれば問題ないだろうと考えていたが、操作に時間がかかり過ぎて選出を時間内に完了できないケースが多発したため、このような形になった。動きはこのGIFアニメのようなイメージ(右側にあるのがスマホの録画)

f:id:rocky_manobi:20200824143042g:plain

右下のスマホ画面で「確定」を選んだ後にSwitch側で選出操作が行われているのがわかる。 このときアイコンの画像認識で相手のポケモンの一覧も画像から判定しており、対戦中の動画解析結果と合わせることで自分と相手それぞれの選出のログを残せるようになっている。

行動選択

こちらもSwitchの操作一つ一つを入力する方式ではなく、利用できる技や入れ替えたいポケモンを直接選択できるようにしている。ダイマックスもできる。

f:id:rocky_manobi:20200824125612p:plain

その他

非戦闘中における操作は特別なUIを設けず、Switchのボタンを一つ一つWeb上に用意することにした。行動選択や選出のUIで不備がでた場合のバックアップとしても使っている(それから「降参」のための操作も実装していないので、降参したい場合はこれを使う)。以下の画像で一瞬でイメージは湧くと思う。

対戦ログ

自分の選出、相手の選出、使った技、誰にダイマックスを切ったかなどをログに残しくれて、あとから振り返ることができる。 正直これは開発をしていたら面白くなってしまって、あれもできるならこれも...とやっていった中で出来上がったものなので、解決したい課題に対して特にクリティカルには効いていない。(PC向けのバトルアシストツールを作っているのでそちらには応用する気満々)

## 自分
- ドラパルト[先発]
  - ドラゴンアロー,ゴーストダイブ
- ドリュウズ[dymax]
  - ダイスチル
- バンギラス
  - ステルスロック,でんじは,がんせきふうじ,
## 相手
- ドリュウズ[先発]
  - じしん,
- カバルドン
  - ステルスロック,ほえる,あくび,
- ラプラス[dymax]
  - キョダイセンリツ,ダイストリーム,なみのり,

ダメージログ

2ターン目
-[自分の] ドラパルトがカバルドンにドラゴンアロー - 57% のダメージ
3ターン目
-[相手の] カバルドンがバンギラスにステルスロック -
4ターン目
-[自分の] バンギラスがカバルドンにステルスロック -
-[相手の] カバルドンがバンギラスにほえる -
5ターン目
-[自分の] ドラパルトがカバルドンにゴーストダイブ -
-[相手の] カバルドンがドラパルトにあくび -
6ターン目
-[自分の] ドラパルトがカバルドンにゴーストダイブ - 8%(以上) のダメージ
7ターン目
-[自分の] ドラパルトがラプラスにゴーストダイブ -
-[相手の] ラプラスがドラパルトにキョダイセンリツ -
8ターン目
-[自分の] ドラパルトがラプラスにゴーストダイブ - 35% のダメージ
-[相手の] ラプラスがドラパルトにキョダイセンリツ - 88%(以上) のダメージ
9ターン目
-[自分の] バンギラスがラプラスにでんじは -
-[相手の] ラプラスがバンギラスにダイストリーム - 59% のダメージ
10ターン目(9)
-[自分の] バンギラスがラプラスにがんせきふうじ - 26% のダメージ
-[相手の] ラプラスがパ のンギラスになみのり - 24%(以上) のダメージ

ながら対戦アシスト機能

操作可能時サウンドアラート

操作する必要があるシチュエーションになったら「ピコ!」と音を鳴らしてくれる。具体的には、対戦相手がみつかって選出画面に遷移したとき、新しいターンに入って行動を選択できるようになったとき、ポケモンが倒されてポケモンを変えなければならないとき、の3つのシチュエーションで音がなる。ながらでやっているので必須。そもそも普通に公式にも実装して欲しい機能でもある。

ちなみに ON/OFFが可能。

バトルメッセージログ表示

公式に実装して欲しい機能その2。戦闘中に表示されたメッセージのログを見ることができる。 ながらで対戦をしているので、画面をずっと見ているわけにはいかない。見ていない間に何が起こったのかが分からないと行動を選択することができないので必須(そもそも普通に対戦していても相手の特性を見逃していて焦ったりする)

テキスト読み上げ

バトルメッセージをText2Speechで音声読み上げしてくれる機能。ON/OFFが可能(流石に)

いかにバトルメッセージのログを見られるとはいえ、行動決定に許された時間はわずか45秒。できればターンが回ってきたそのときには現状を把握している状態が望ましい。ながら対戦では画面をずっと見ておくことはできないので、音声読み上げでこれを補う。子供の寝かしつけのとき、公園で遊んでいるときなどなど、非常に重宝した。

作り方 / 開発記録

ここからは全体の構造や利用している技術、開発時に課題となり工夫を要した箇所などについて触れていく。

PCでSwitchを操作できるようにする

多くのサイトで紹介されているように Arduino LEONARDでSwitchを操作できるようにするとともに、FT232RL USBシリアル変換モジュールを組み合わせて、そのArduinoに対してPCからUSBシリアル通信で命令を送れるようにしている。

image.png

Switchの映像をPCに取り込む

普通にキャプチャボードを使ってHDMI経由で映像をPCに取り込む。 ちなみにキャプチャボードはこれを買った。

今回は下心としてWebで画像解析の真似事などをしてみたいという欲求もあったので、Webで実装した。以下のように、Webカメラの映像を取り込む時となんら変わりない方法で映像を取得できる。

f:id:rocky_manobi:20200824160523p:plain

f:id:rocky_manobi:20200824130730p:plain

全体構成

これらに、サーバサイド画像解析等々を加えるとこんな感じになる。

  • CLOUD
    • Appサーバ (Node.js / Socket.io)
  • Arduino LEONARDO + USBシリアルモジュール
    • シリアル通信で受け取った命令をSwitchにバイパスする
  • PC
    • Switch操作アプリ(Node.js)
      • Socket.ioメッセージ <=> USBシリアル通信変換app
    • 動画解析機(Webフロント)
    • 画像解析 / OCR等踏み台 サーバ(Node.js)
  • スマホ
    • スマホコントローラ(Webフロント)

各プレイヤーのやり取りは、動画解析機と画像解析サーバ間を除いて全てSocket.ioで行なっている。 書く前から感じていたが、これは図にしないとだめ。

戦況解析

先に紹介したUIを実現するには、どの画面が表示されているか、技やポケモンの選択肢、ダイマックス可否などを映像から判定する必要がある。これらの大半はフロントエンドで判定をしている。OpenCV.js等を使うと重さに耐えられなかった(バトルメッセージの表示速度を鑑みると解析頻度は10FPSは欲しい)のと、興味もあったので生のCanvas、特にUint8ClampedArrayなどのナマモノををかなり触った。

開発方法 - 機械学習の中の人を人力でやる

最初に一気にテストコードを書いて、期待の判定になるようにルールを書いていった。 テストデータは何戦か録画をして、そこから判定したいシチュエーションの画像を抽出して得た。

こんな画像を用意して

f:id:rocky_manobi:20200824132742p:plain

こんなテストコードを書いて

describe("getHp", () => {
  it("画面から相手と自分のポケモンのHPを読み取る(%)", () => {
    testCases.forEach((testCase) => {
      const ctx = getDrawedCanvas(testCase.file);
      const result = getHp(ctx);
      expect(result.myHp).toEqual(30)
      expect(result.opposingHp).toEqual(74)
    });
  });
});

あとはひたすらにロジックの中身を書き続ける。 ちなみにHPの判定はHPバーにしめる緑色/黄色/赤色ではない色の割合でパーセンテージを出している。

辛いところ

いくら直接HDMIから取り込んでいるからといっても、リアルタイムにエンコードしている関係上、画像の数値は同じ画面でも、同じメッセージ表示でも完全にピクセルの数値が一致することはない。黒い部分でも白い部分との境界近くではグレーに近づいたりなどもする。

画面の判定

(本当はもっとあるが)画面の種類としては主に以下のどれが表示されているかを判定する必要があった。これに関しても各画面で特徴的な部分のルールを書いて対処している(例えばバトルメッセージ表示中の判定は、適当に2値下して、下の方は黒だが、全体は黒ではなく、メッセージが表示される領域がxx%以上黒ドットではない、のような泥臭いロジックを書いている)。これもあらかじめ全ケースをテストコードを書いておくことで、精度を上げるために他の画面と誤検知した〜などの事故を起こさずに実装することができた。(そろそろ限界だけど)

  • 戦闘待機画面
  • 選出画面(相手側表示/非相手側表示)
  • 行動選択画面
  • バトルメッセージ表示中か否か
  • ポケモンのステータスやHPの変化を取得してもよい画面か否か

OCR

ポケモンの名前、技の名前など、文字の検出にはGoogle Cloud VisionOCRを使っている。 お金がかかるので、少しでもリクエスト回数を減らすためにローカルの画像処理で可能な限り重複リクエストを避けるようにしたり、複数の言語が混じっている場合(相手のポケモンが海外産だと名前も英語になる)でも精度を下げないような前処理を入れたりするなどの工夫をしている。今はおおよそ1マッチ100リクエスト程度(多分10円以下)。この辺りについては機会があればまとめておきたい。

複数の言語が混じっている場合への対処だけに触れると

f:id:rocky_manobi:20200824134116p:plain

こういうのを...

f:id:rocky_manobi:20200824133756p:plain

このように、スペースが空いているところを改行に見えるように画像を加工してからAPIを叩いてあげると飛躍的に精度が向上したりする。 こうすることで、戦況ログを保存するにあたっては十分な制度が得られた(逆にうまくいかなければおそらく作れなかった)

その他機能

音/テキスト読み上げ

音声の再生はWeb Audio API、テキスト読み上げはChromeのWeb Speech APIを使っている。 特筆することは特にないが、iOSではテキスト読み上げ機能はChromeのみで使用できる。ゆえにHome画面にショートカットを置いて起動した場合などには使えない。

本編終了

おしまい

おわりに

以上でスマホでSwitchを操作してポケモンランクマッチに潜るための装置の紹介を一旦終わります。読んでいただいた方、ありがとうございました。 予告した通り、開発記録+申し訳程度の開発Tipsだったとは思いますが、割と興味本位で無茶に無駄に工数をかけてしまっていることだけは分かっていただけると思います。

ちなみにこいつ自体は結構重宝して動いてくれてはしましたが、まだポケモンの方に結果がでていないので効果のほどはなんとも言えません。 (明確な開発成果といえば、過去2度試みて失敗しているVim->VSCodeの移行に成功したということくらい)

とりあえずこの先、ゲームをやるべきかツールをつくるべきかの天秤で悩んだりしつつあれもやりたいこれもやりたい試したいととっちらかってきているので、もし使ってみたかったり、一緒に作ってみたいという方がいらっしゃればTwitter DMででも連絡をいただければ幸いです。

おまけ - これから

新しいポケモンもさらに増えて困ったことになりそうな11月に向けて、次は直接的に戦闘をアシストしてくれるようなツールを作ってみようと思っています(作っている)。 戦況を自動で判別しつつ、相手の育成パターンによってこちらの技がどの程度入るのかをざっくりみられたり、逆にこちらが打った技がn%はいったのでおそらく耐久に振っている、、など、経験が浅くてダメージ感覚のない人でも初見ポケモンに殺されないようなものができると嬉しいです。

戦況解析からのダメージ計算の実現はとても複雑なドメインで、モデリングの勉強にとてもなることがわかりました。そのあたりを考えるのが楽しくてここ最近まったく進捗がでていないのですが、完成したらモデリング文脈でもなにかアウトプットできるといいなと思っています。

image.png

(あとすばやさ関係もいい感じのUIで表現したい)

それでは、次の自由研究に乞うご期待です。

f:id:rocky_manobi:20200824155000p:plain:w30