【マイコン】どんなポケモンでも効率的に自動孵化させる汎用コードを作ってみた【剣盾】

マイコンを使ったポケモン剣盾の自動孵化を最適化したコードを作ってみました。

孵化厳選はレイドや化石ポケモンの自動化とは違い、何千、何万という繰り返しを行うからこそ、少しでも効率的に回したくコンマ単位でかなりこだわったコードに仕上げました。(とはいえ、ギリギリを狙いすぎると不安定になるので全体的に少し余裕は持たせています)

最終的に、600属などの孵化サイクル40のポケモンまで対応し、1時間に約26匹の自動孵化を可能にしたコードとなりました。

この記事をご覧になる前に

マイコンをまだ導入していない方は、以下の記事を参考に導入してみてください。

関連記事

マイコンと呼ばれるものをご存知でしょうか? Switchに接続するだけで様々な作業を "自動" で行ってくれるというものです。 導入すれば作業が楽になるだけでなく、寝てる間に色々稼ぐこともできちゃう便利なアイテムなわけですね! […]

マイコンを使って
ポケモン剣盾
自動化しよう!
【導入編】

※2021年2月11日追記:当ブログで使用するライブラリが「NintendoSwitchControlLibrary」へと変わりました。旧ライブラリのコードも残しておきますが、基本的には新しいライブラリをダウンロードして新しいコードを使ってください。

ダウンロードして解凍したら、『Arduino』フォルダの中にある『libraries』フォルダの中にコピーすればOKです。(展開を終えたら展開元のzipファイルは削除してしまって構いません。)

参考にしたコード

こちらのページの『孵化厳選』のコードを参考にさせてもらいました。

Qiita

背景ポケモン剣盾の孵化作業をArduinoで完全自動化するという記事で孵化厳選の自動化、ポイントアップ(ポイントマックス…

改善したかった点

実際に検証を始める前に、上のページで公開されているコードを500時間は使っていました。

元のコードも十分に素晴らしかったのですが、使っていた中でいくつか改善できそうな点や気づいた点がありました。

  • 全体的にボタンを押すまでの感覚が長すぎる
  • ボックスにポケモンを預けたあと、一度メニューを閉じてから開き直すなどの無駄な処理が含まれている

また、コードの書き換えが面倒だったので、孵化サイクル25~40のポケモンもこのコードで回していました。

すると、最後の1匹が孵化できていない(稀に2匹孵化できていないこともある)だけで、孵化サイクル25~40のポケモンであってもループの処理から抜け出してしまうなどの致命的な症状はなく回せていることに気づきました。

これを応用して最後の1周だけ2周分走れば、孵化サイクル40までのポケモンも孵化しきれる汎用性の高いコードに仕上げられると思い、改良を試みることにしました。

完成したコードについて

今回のコードは、

『一度書き込んでしまえば、どんなポケモンでも効率的に自動孵化が行える汎用的なコード』

を目標に作成しています!

実際、高いパフォーマンスと高い汎用性を兼ね備えたコードに仕上がったと思います。

ソースコード

実際に書いたコードはこちらになります。

2020年10月8日追記:砂嵐の状態かつローカルでプレイヤーが近くにいるとき、稀にうまく動作しなくなることがあったため、少し安定感を向上させました。

旧ライブラリ(NintendoSwitchControll)を使用している方はこちらをクリック
/**
* 育て屋から卵を回収→孵化→ボックスに預けるを繰り返すスケッチ
* ボックスに空きがある限り、ポケモンを孵化し続ける
*
* 初期条件は以下の通り
* 1.ハシノマはらっぱにいること
* 2.自転車に乗っていること
* 3.手持ちが1体のみのこと
* 4.Xボタンを押したときに「タウンマップ」が左上、「ポケモン」がその右にあること
* 5.ボックスが空のこと
* 6.オフライン状態であること
* 7.無線のコントローラーが接続されていないこと
* 8.「設定」から「話の速さ」を「速い」に、「手持ち/ボックス」を「自動で送る」に、「ニックネーム登録」を「しない」にしておくこと
*/

// ライブラリを読み込むためのコード
#include <auto_command_util.h>

// 孵化するまでに自転車で走り回る時間
const int TIME_TO_HATCHING_SEC = 70;  // 完成された値

// 空飛ぶタクシーでハシノマはらっぱに移動する関数
void moveToInitialPlayerPosition(){
    pushButton(Button::A, 2000);
    pushButton(Button::A, 450, 2);
    delay(2200); // 天候によって読み込み時間がやや異なる(最も重かった砂嵐でも1900で安定していたが、柱の本数や服装などの環境による差異がある可能性も考慮して少し余裕を持たせた)
}

// 初期位置から育て屋さんに移動しタマゴを受け取る関数
void getEggFromBreeder(){
    // 初期位置(ハシノマはらっぱ)から育て屋さんのところまで移動
    pushButton(Button::PLUS, 600);
    tiltJoystick(0, 0, 100, 0, 2000);
    tiltJoystick(30, -100, 0, 0, 800);
    pushButton(Button::PLUS, 450);
    // 育て屋さんから卵をもらう
    pushButton(Button::A, 450, 3);
    delay(750);
    pushButton(Button::B, 450, 10);
    delay(50);
}

// 初期位置(ハシノマはらっぱ)からぐるぐる走り回る関数
void runAround(int run_time_sec){
    // 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, 450, 38); // 余裕があるように思えるが、色違いのエフェクトを考慮するとこれ以上は厳しい
}

// 孵化した手持ちのポケモンをボックスに預ける関数
// box_line : 何列目にポケモンを預けるか
void sendHatchedPokemonToBox(int box_line){
    // ボックスを開く
    pushButton(Button::X, 500);
    pushHatButton(Hat::RIGHT, 25);
    pushButton(Button::A, 1250);
    pushButton(Button::R, 1500);
    // 手持ちの孵化したポケモンを範囲選択
    pushHatButton(Hat::LEFT, 25);
    pushHatButton(Hat::DOWN, 25);
    pushButton(Button::Y, 25);
    pushButton(Button::Y, 25);
    pushButton(Button::A, 25);
    pushHatButtonContinuous(Hat::DOWN, 600);
    pushButton(Button::A, 25);
    // ボックスに移動させる
    pushHatButton(Hat::RIGHT, 100, box_line+1);
    pushHatButton(Hat::UP, 25);
    pushButton(Button::A, 25);
    // ボックスがいっぱいになったら、次のボックスに移動させる
    if(box_line==5){
        pushHatButton(Hat::UP, 25);
        pushHatButton(Hat::RIGHT, 25);
    }
    // ボックスを閉じる
    pushButton(Button::B, 25); // ボックスが空でなかった場合でも、ボックスを閉じてループを実行し続けさせるのに必要な記述
    pushButton(Button::B, 1500);
    pushButton(Button::B, 1250);
    // メニュー画面のカーソルをタウンマップに戻す
    pushHatButton(Hat::LEFT, 25);
}

// 実際にループ内で呼び出す関数
void receiveAndHatchEggs(int box_line){
    // 手持ちが1体の状態から、卵受け取り→孵化を繰り返していく
    for(int egg_num=0; egg_num<5; egg_num++){
        moveToInitialPlayerPosition();
        getEggFromBreeder();
        pushButton(Button::X, 500);
        moveToInitialPlayerPosition();
        // 野生ポケモンとのエンカウントを避けるため初期位置から少し移動する
        tiltJoystick(100, 0, 0, 0, 500);
        runAround(TIME_TO_HATCHING_SEC);
        waitEggHatching();

        // 手持ちがいっぱいになったときの処理
        if(egg_num==4){
            // 孵化歩数の多いポケモンも孵化させてから預けるためのコード(下2行を無効にすると回転は上がるので、好みで削除すると良い)
            runAround(60);
            waitEggHatching();
            // ボックスに預ける処理を呼び出す
            sendHatchedPokemonToBox(box_line);

        // 手持ちがいっぱいでない場合は、メニューを開いてからループに戻る
        } else {
            pushButton(Button::X, 500);
        }
    }
}

// マイコンのセット時に1度だけ行われる処理
void setup(){
    // Switchがマイコンを認識するまでは信号を受け付けないため、適当な処理をさせておく
    pushButton(Button::B, 500, 5);

    // マイコンを認識したら、メニューの左上にカーソルを持っていく
    pushButton(Button::X, 600);
    pushHatButtonContinuous(Hat::LEFT_UP, 750);

    // 初回は処理が少しだけ重い?ので、moveToInitialPlayerPosition を呼び出さずに余裕をもたせる形にした
    pushButton(Button::A, 2200);
    pushButton(Button::A, 450, 2);
    delay(2200);

    // 初めのタマゴが出現するまで走り回る
    tiltJoystick(100, 0, 0, 0, 500);
    runAround(25);

    // メニューを開く動作をループに含めてしまうと、毎回メニューを閉じないといけなくなってしまうため、ループから外すことにした
    pushButton(Button::X, 500);
}

// ここに記述した内容がループされ続ける
void loop(){
    for(int box_line=0; box_line<6; box_line++){
        receiveAndHatchEggs(box_line);
    }
}
/**
 * 育て屋から卵を回収→孵化→ボックスに預けるを繰り返すスケッチ
 * ボックスに空きがある限り、ポケモンを孵化し続ける
 *
 * 初期条件は以下の通り
 * 1.ハシノマはらっぱにいること
 * 2.自転車に乗っていること
 * 3.手持ちが1体のみのこと
 * 4.Xボタンを押したときに「タウンマップ」が左上、「ポケモン」がその右にあること
 * 5.ボックスが空のこと
 * 6.オフライン状態であること
 * 7.無線のコントローラーが接続されていないこと
 * 8.「設定」から「話の速さ」を「速い」に、「手持ち/ボックス」を「自動で送る」に、「ニックネーム登録」を「しない」にしておくこと
 * 9.ちょいらくモードが「しない」であること
 * 10.まるいおまもりを所持していること
 */

// ライブラリを読み込むためのコード
#include <NintendoSwitchControlLibrary.h>

// 孵化するまでに自転車で走り回る時間
const int TIME_TO_HATCHING_SEC = 70;  // 完成された値

// 空飛ぶタクシーでハシノマはらっぱに移動する関数
void moveToInitialPlayerPosition() {
    pushButton(Button::A, 2000);
    pushButton(Button::A, 450, 2);
    delay(2200);  // 天候によって読み込み時間がやや異なる(最も重かった砂嵐でも1900で安定していたが、柱の本数や服装などの環境による差異がある可能性も考慮して少し余裕を持たせた)
}

// 初期位置から育て屋さんに移動しタマゴを受け取る関数
void getEggFromBreeder() {
    // 初期位置(ハシノマはらっぱ)から育て屋さんのところまで移動
    pushButton(Button::PLUS, 600);
    tiltRightStick(Stick::MAX, Stick::NEUTRAL, 2000);
    tiltLeftStick(166, Stick::MIN, 800);
    pushButton(Button::PLUS, 450);
    // 育て屋さんから卵をもらう
    pushButton(Button::A, 450, 3);
    delay(750);
    pushButton(Button::B, 450, 10);
    delay(50);
}

// 初期位置(ハシノマはらっぱ)からぐるぐる走り回る関数
void runAround(int run_time_sec) {
    // delayの秒数がintの最大値を越えないように30秒ごとに実行する
    for (int i = 0; i < run_time_sec / 30; i++) {
        tiltLeftAndRightStick(Stick::MAX, Stick::MAX, Stick::MIN, Stick::MIN, 30000);
    }
    tiltLeftAndRightStick(Stick::MAX, Stick::MAX, Stick::MIN, Stick::MIN, (run_time_sec % 30) * 1000);
}

// タマゴが孵化するのを待つ関数
void waitEggHatching() {
    pushButton(Button::B, 450, 38);  // 余裕があるように思えるが、色違いのエフェクトを考慮するとこれ以上は厳しい
}

// 孵化した手持ちのポケモンをボックスに預ける関数
// box_line : 何列目にポケモンを預けるか
void sendHatchedPokemonToBox(int box_line) {
    // ボックスを開く
    pushButton(Button::X, 500);
    pushHat(Hat::RIGHT, 25);
    pushButton(Button::A, 1250);
    pushButton(Button::R, 1500);
    // 手持ちの孵化したポケモンを範囲選択
    pushHat(Hat::LEFT, 25);
    pushHat(Hat::DOWN, 25);
    pushButton(Button::Y, 25);
    pushButton(Button::Y, 25);
    pushButton(Button::A, 25);
    holdHat(Hat::DOWN, 600);
    pushButton(Button::A, 25);
    // ボックスに移動させる
    pushHat(Hat::RIGHT, 100, box_line + 1);
    pushHat(Hat::UP, 25);
    pushButton(Button::A, 25);
    // ボックスがいっぱいになったら、次のボックスに移動させる
    if (box_line == 5) {
        pushHat(Hat::UP, 25);
        pushHat(Hat::RIGHT, 25);
    }
    // ボックスを閉じる
    pushButton(Button::B, 25);  // ボックスが空でなかった場合でも、ボックスを閉じてループを実行し続けさせるのに必要な記述
    pushButton(Button::B, 1500);
    pushButton(Button::B, 1250);
    // メニュー画面のカーソルをタウンマップに戻す
    pushHat(Hat::LEFT, 25);
}

// 実際にループ内で呼び出す関数
void receiveAndHatchEggs(int box_line) {
    // 手持ちが1体の状態から、卵受け取り→孵化を繰り返していく
    for (int egg_num = 0; egg_num < 5; egg_num++) {
        moveToInitialPlayerPosition();
        getEggFromBreeder();
        pushButton(Button::X, 500);
        moveToInitialPlayerPosition();
        // 野生ポケモンとのエンカウントを避けるため初期位置から少し移動する
        tiltLeftStick(Stick::MAX, Stick::NEUTRAL, 500);
        runAround(TIME_TO_HATCHING_SEC);
        waitEggHatching();

        // 手持ちがいっぱいになったときの処理
        if (egg_num == 4) {
            // 孵化歩数の多いポケモンも孵化させてから預けるためのコード(下2行を無効にすると回転は上がるので、好みで削除すると良い)
            runAround(60);
            waitEggHatching();
            // ボックスに預ける処理を呼び出す
            sendHatchedPokemonToBox(box_line);

            // 手持ちがいっぱいでない場合は、メニューを開いてからループに戻る
        } else {
            pushButton(Button::X, 500);
        }
    }
}

// マイコンのセット時に1度だけ行われる処理
void setup() {
    // Switchがマイコンを認識するまでは信号を受け付けないため、適当な処理をさせておく
    pushButton(Button::B, 500, 5);

    // マイコンを認識したら、メニューの左上にカーソルを持っていく
    pushButton(Button::X, 600);
    holdHat(Hat::UP_LEFT, 750);

    // 空飛ぶを使うことで、位置情報をリセットする
    moveToInitialPlayerPosition();

    // 初めのタマゴが出現するまで走り回る
    tiltLeftStick(Stick::MAX, Stick::NEUTRAL, 500);
    runAround(25);

    // メニューを開く動作をループに含めてしまうと、毎回メニューを閉じないといけなくなってしまうため、ループから外すことにした
    pushButton(Button::X, 500);
}

// ここに記述した内容がループされ続ける
void loop() {
    for (int box_line = 0; box_line < 6; box_line++) {
        receiveAndHatchEggs(box_line);
    }
}

設定の確認

マイコンを繋げる前に、以下の状態になっていることを確認してください。

2022年5月12日追記:必要な設定を一部追加しました。

  1. ハシノマはらっぱにいること
  2. 自転車に乗っていること
  3. 手持ちが1体のみのこと
  4. Xボタンを押したときに「タウンマップ」が左上、「ポケモン」がその右にあること
  5. ボックスが空のこと
  6. オフライン状態であること(機内モード推奨
  7. 無線のコントローラー(プロコンなど)が接続されていないこと
  8. 「設定」から「話の速さ」を「速い」に、「手持ち/ボックス」を「自動で送る」に、「ニックネーム登録」を「しない」にしておくこと
  9. ちょいらくモードが「しない」であること
  10. まるいおまもりを所持していること

コードの詳細

結構細かいところにこだわったり、検証を繰り返してきました。

孵化サイクルと歩数のチェック周期

タマゴの孵化は歩数を厳密にチェックしているわけではなく、決められた歩数(256歩?)を走るごとにカウントが1つ進み、カウントが0になったときに孵化する仕組みのようです。

タマゴのサイクル数は卵が孵化するまでの歩数に関与する値で、ポケモンの種類ごとに数値が決まっている。この数値が小さいほどタマゴが早くかえりやすい。

この値は一定歩数歩くごとに1ずつ減り、0になったとき孵化する。ただしタマゴ1個ごとに何歩歩いたかがカウントされている訳ではなく、主人公の通算の歩数がチェック周期の倍数を回るたびに一括でチェックが入り、「前回のチェックの時点でも手持ちにあったタマゴ」全てのサイクル数の値を1マイナスする(このため、タマゴを貰う→数十歩歩く→次のタマゴを貰う 等のように数十歩程度連れ歩いた歩数がズレて入るタマゴが同時にかえる事がある)。チェック周期は第二・第三・第七世代が256歩で第四世代は255歩、第五・第六世代は257歩。

出典:タマゴのサイクル数 – ポケモンWiki

この仕様のおかげで、孵化するタイミングが読めなくなってしまっていますが、走る部分が最も時間のかかる箇所なので、ここはこだわりたいと思いました。

そこで、ちょうど良いタイミングがないか『TIME_TO_HATCHING_SE』の値を60〜70まで色々とテストしました。

その結果、孵化サイクル20のポケモンは65にすれば孵化が完了することはわかったのですが、65だと孵化サイクル25のポケモンでタマゴを受け取る直前に孵化してしまい、ループから抜けてしまう自体が発生してしまうことがありました。

その後も検証を繰り返したところ、偶然なのか必然なのかはわかりませんが、元のコードの70という値が完成された値であることが判明したので、70を採用することになりました。

天候によって『そらとぶタクシー』のロード時間が異なる

最初はもう少し詰めたコードを書いていたのですが、何故かうまく動かないことがありました。

「『携帯モード』と『テレビモード』による違いだろうか?」などと仮設を立てては検証を繰り返したところ、その原因の一端が天候にあることがわかりました。

各天候で試したところ、最もロードに時間がかかっていたのは『すなあらし』状態のときでした。

もしかしたら、天候以外にもロードに影響する要因があるかもしれないので、上のコードはだいぶ余裕を持たせた形に仕上げています。

ループから外れそうな要因を考慮した

詰めるところは詰めてますが、あえて余裕をもたせている箇所が2箇所あります。

  • 色違いのエフェクトによる遅延を考慮して孵化の待ち時間に余裕を持たせた。
  • ボックスがすでに埋まっていてポケモンを預けられなかった時の処理を追加した。

このコードは完全なものではない

多くのポケモンに対応した汎用性の高いコードに仕上げていますが、孵化サイクル25のポケモンに関しては若干不安定だったりします。

ループから抜けてしまうような致命的な症状は確認できていませんが、孵化に失敗したタマゴが多く見受けられます。

そのため、孵化サイクル25のポケモンに関しては、また後日コードを用意したいと考えています。

また、孵化サイクル30~40のポケモンの場合でも、稀に孵化ができていないタマゴができてしまうことがあります。(3ボックスに1個くらい?の割合でタマゴが混じっている)

孵化に失敗しているタマゴについては、お手数ですが手動で割っていただくようお願いいたします。

Q&A

ターボに対応させないの?
ターボ処理を書くだけなら書けるのですが、位置がずれてしまうので難しいところです。
タマゴを5つ受け取ってからまとめて割ったほうが早くない?
今回のプログラムは単純な処理の繰り返しのみでできています。「タマゴができているかどうか?」の確認をするには画像認識などの高度なプログラムが必要になるのですが、ワイルドエリアのような天候にも左右される環境でそれを実装するのは難易度が高くて難しいです。

最後に

検証期間1ヶ月以上、プレイ時間でいうと300時間はかけてチェックしたものになるので、安定感に関してはおそらく大丈夫かと思います。

ですが、万一ループから抜けてしまうようなことがありましたら、@lefmarnaまでお知らせください。

スポンサーリンク