はじめに
Arduino LeonardoをNintendo SwitchのUSBポートに挿すだけで、ボックスに空きがある限りポケモン剣盾の孵化作業を全自動でやり続けるプログラムを作成しました。これで会社に行っている間も寝ている間も孵化厳選が捗ります。
ポケモン剣盾の孵化作業自動化するプログラムできた!
— みがわり (@interimadd) January 13, 2020
arduinoを使って、卵を受け取って孵化するのを無限界繰り返す
これで明日から仕事に行ってる間も厳選ができるようになるというわけ pic.twitter.com/ev2RylxHrS
きっかけ
- ワット稼ぎ自動化の記事を見てSwitchのコントローラーの入力をHIDの形式でできることを知った
- 記事で紹介されているライブラリだと実装が少し煩雑に感じたので、自分好みのライブラリを作りたかった
- すでに先駆者がいるが、Arduino単体でできるようにすれば、環境構築やプログラムの作成が簡単だし、追加の部品や電子工作が不要だし、Switchに挿すだけで動かせるので便利そうだと思った
- 孵化厳選がつらい
使い方
孵化作業自動化のスケッチを動かすまでの流れについて説明します。
ハードウェアの準備
Arduino Leonardo
Arduino Leonardoまたは互換性のあるボードを用意します。今回はお値段の関係で3個で3000円くらいの互換品を購入しました。
Arduino LeonardoとSwicthを接続するUSBケーブル
Swicthに直接接続する場合はTypeCオス-microBオスのUSBケーブル、充電ドックを介して接続する場合はTypeAオス-microBオスのUSBケーブルを用意します。
環境設定
Arduino IDEのインストール
Arduino IDEを公式のページからダウンロードしてインストールします。
ライブラリのインストール
Arduinoのライブラリのあるディレクトリ(/Users/{username}/Documents/Arduino/libraries/ など)に、Switch操作用のライブラリとサンプルスケッチのレポジトリをcloneする/ダウンロードして展開します。
Arduino LeonardoのデバイスIDを書き換える
hardware/arduino/avr/boards.txt内のleonardoのvidとpidを設定している個所を書き換えます。
leonardo.vid=0x0f0d
leonardo.pid=0x0092
※ 複数ある場合は相当する箇所を全て書き換えます。
※ 見つからない場合はArduino IDEの「ファイル>スケッチ例>SPI」から適当なスケッチを開き、「スケッチ>スケッチの場所を開く」で開いたディレクトリから遡っていくと、boards.txtが見つかります。
実行
自動孵化用のスケッチを書き込む
孵化作業自動化のスケッチ(SwitchController\examples\pokemon\auto_hatching\auto_hatching.ino)
を開き、ArduinoをPCに繋いで、Arduino IDE経由で書き込みます。
孵化までにかかる時間を設定するところがあるので、必要な場合は適宜調節してください。
ポケモン剣盾のソフト側の準備をする
ポケモンのソフトを起動して下記の準備をします。
- 育て屋でポケモンを預ける
- 自転車に乗る
- 手持ちを「ほのおのからだ」持ちの一匹だけにする
- Xボタンを押したときに「タウンマップ」が左上にあるようにする
- ボックスを、その次以降のボックスも含めて空にする
Switchに接続する
SwicthのUSBポートにUSBケーブルを使ってLeonardoを接続します。
接続して数回の入力の後、入力を受け付け始めます。
Switchをドックに刺した状態で、ドックのUSBポートにLeonardoを接続する方法でも動かすことができます。
コードの解説
下記のレポジトリの中のライブラリとスケッチの要点を説明します。
https://github.com/interimadd/NintendoSwitchControll
ライブラリ
ゲーム内で同じ動作を繰り返す際に、これだけの関数を組み合わせれば実装できる、というものをまとめてライブラリにしてみました。具体的には、ボタン入力を数秒おきに繰り返したり、ジョイスティックを一定時間倒したりというような操作を1行で書けるように関数化しています。これで例えば日付を変えるバグでワットを稼ぐような動きも20行程度で実装することができます。
#include "auto_command_util.h"
// ボタンを押してから離すまでの時間
const uint16_t BUTTON_PUSHING_MSEC = 100;
/**
* @brief Switchコントローラーのボタンを押す
*
* @param button 押すボタン
* @param delay_after_pushing_msec ボタンを押し終えた後の待ち時間
* @param loop_num ボタンを押す回数
*/
void pushButton(Button button, int delay_after_pushing_msec, int loop_num=1)
{
for(int i=0; i<loop_num; i++)
{
SwitchController().pressButton(button);
delay(BUTTON_PUSHING_MSEC);
SwitchController().releaseButton(button);
delay(delay_after_pushing_msec);
}
delay(BUTTON_PUSHING_MSEC);
}
/**
* @brief Switchコントローラーの矢印ボタンを押す
*
* @param button 押す矢印ボタン
* @param delay_after_pushing_msec ボタンを押し終えた後の待ち時間
* @param loop_num ボタンを押す回数
*/
void pushHatButton(Hat button, int delay_after_pushing_msec, int loop_num=1)
{
for(int i=0;i<loop_num;i++)
{
SwitchController().pressHatButton(button);
delay(BUTTON_PUSHING_MSEC);
SwitchController().releaseHatButton();
delay(delay_after_pushing_msec);
}
delay(BUTTON_PUSHING_MSEC);
}
/**
* @brief Switchコントローラーの矢印ボタンを指定時間の間押し続ける
*
* @param button 押す矢印ボタン
* @param pushing_time_msec ボタンを押す時間の長さ
*/
void pushHatButtonContinuous(Hat button, int pushing_time_msec)
{
SwitchController().pressHatButton(button);
delay(pushing_time_msec);
SwitchController().releaseHatButton();
delay(BUTTON_PUSHING_MSEC);
}
/**
* @brief Switchのジョイスティックの倒し量を設定する
*
* @param lx_per LスティックのX方向倒し量[%] -100~100の範囲で設定
* @param ly_per LスティックのY方向倒し量[%] -100~100の範囲で設定
* @param rx_per RスティックのX方向倒し量[%] -100~100の範囲で設定
* @param ry_per RスティックのY方向倒し量[%] -100~100の範囲で設定
* @param tilt_time_msec スティックを倒し続ける時間
*/
void tiltJoystick(int lx_per, int ly_per, int rx_per, int ry_per, int tilt_time_msec)
{
SwitchController().setStickTiltRatio(lx_per, ly_per, rx_per, ry_per);
delay(tilt_time_msec);
SwitchController().setStickTiltRatio(0, 0, 0, 0);
delay(BUTTON_PUSHING_MSEC);
}
スケッチ
育て屋から卵を回収→孵化→手持ちが一杯になったらボックスに預ける、を繰り返すスケッチです。
ボックスが一杯になるタイミングで次のボックスに移動する処理を入れているので、ボックスに空きがある限り、無限にポケモンを孵化し続けます。
ポイントとしては、空飛ぶタクシーを使うとプレイヤーの位置をリセットできるので、移動による誤差が蓄積しないことです。(こちらの動画を参考にしました)
#include <auto_command_util.h>
// 孵化するまでに自転車で走り回る時間
const int TIME_TO_HATCHING_SEC = 120;
// 空飛ぶタクシーでハシノマはらっぱに移動
void moveToInitialPlayerPosition(){
pushButton(Button::X, 1000);
pushHatButtonContinuous(Hat::LEFT_UP, 1000);
pushButton(Button::A, 2500);
pushButton(Button::A, 1500, 2);
delay(2000);
}
// 初期位置から育て屋さんに移動し卵を受け取る
void getEggFromBreeder(){
// 初期位置(ハシノマはらっぱ)から育て屋さんのところまで移動
pushButton(Button::PLUS, 1000);
tiltJoystick(0, 0, 100, 0, 2000);
tiltJoystick(30, -100, 0, 0, 2000);
pushButton(Button::PLUS, 1000);
// 育て屋さんから卵をもらう
pushButton(Button::A, 1000, 4);
pushButton(Button::B, 500, 10);
}
// 初期位置(ハシノマはらっぱ)からぐるぐる走り回る
void runAround(int run_time_sec){
// 野生ポケモンとのエンカウントを避けるため初期位置から少し移動する
tiltJoystick(100, 0, 0, 0, 600);
// delayの秒数がintの最大値を越えないように30秒ごとに実行する
for(int i=0; i<run_time_sec/30; i++){
tiltJoystick(100, 100, -100, -100, 30000);
}
tiltJoystick(100, 100, -100, -100, (run_time_sec%30)*1000);
}
// 卵が孵化するのを待つ
void waitEggHatching(){
pushButton(Button::B, 500, 40);
}
// 孵化した手持ちのポケモンをボックスに預ける
// box_line : 何列目にポケモンを預けるか
void sendHatchedPoemonToBox(int box_line){
// ボックスを開く
pushButton(Button::X, 1000);
pushHatButtonContinuous(Hat::LEFT_UP, 1000);
pushHatButton(Hat::RIGHT, 500);
pushButton(Button::A, 2000);
pushButton(Button::R, 2000);
// 手持ちの孵化したポケモンを範囲選択
pushHatButton(Hat::LEFT, 500);
pushHatButton(Hat::DOWN, 500);
pushButton(Button::Y, 500, 2);
pushButton(Button::A, 500);
pushHatButtonContinuous(Hat::DOWN, 2000);
pushButton(Button::A, 500);
// ボックスに移動させる
pushHatButton(Hat::RIGHT, 500, box_line+1);
pushHatButton(Hat::UP, 500);
pushButton(Button::A, 500);
// ボックスを閉じる
pushButton(Button::B, 1500, 3);
}
// ボックスを次のボックスに移動させる
void moveToNextBox(){
// ボックスを開く
pushButton(Button::X, 1000);
pushHatButtonContinuous(Hat::LEFT_UP, 1000);
pushHatButton(Hat::RIGHT, 500);
pushButton(Button::A, 2000);
pushButton(Button::R, 2000);
// ボックスを移動
pushHatButton(Hat::UP, 500);
pushHatButton(Hat::RIGHT, 500);
// ボックスを閉じる
pushButton(Button::B, 1500, 3);
}
// 手持ちが1体の状態から、卵受け取り→孵化を繰り返す
void reciveAndHatchEggs(){
for(int egg_num=0; egg_num<5; egg_num++){
moveToInitialPlayerPosition();
getEggFromBreeder();
moveToInitialPlayerPosition();
runAround(TIME_TO_HATCHING_SEC);
waitEggHatching();
}
}
// 孵化シーケンスを実行
void execHatchingSequence(){
for(int box_line=0; box_line<6; box_line++){
reciveAndHatchEggs();
sendHatchedPoemonToBox(box_line);
}
moveToNextBox();
}
void setup(){
pushButton(Button::B, 500, 5);
// 初めの卵が出現するまで走り回る
moveToInitialPlayerPosition();
runAround(20);
}
void loop(){
execHatchingSequence();
}
終わりに
一度FWを書き込んでしまえば単体で動かせるので、使い勝手はかなりいい感じです。
ボックスの中のポケモンを逃がすモード等への切り替えができるとさらに便利になる気がするので、外部ボタンをつけてボタンでモードを切り替えられるようにしたりするといいかもしれません。いい方法があれば知りたいです。
今のところ孵化自動化とワット稼ぎの自動化しかできていませんが、レイドバトルやリーグ周回あたりを自動化するスケッチを誰か実装してくれないかなと思っています。
(2020/01/20 追記) レイドバトルはボックスに預けるところも含めて自動化できました。気が向いたら記事にしようと思います。
(2020/02/01 追記) IDくじの自動化と乱数消費のスケッチを追加していただきました。
参考
【ポケモン剣盾】全自動で卵を孵化させる装置
Arduinoでポケモン剣盾自動化してみた ~自動ワット稼ぎ編~
Switch Control Library