ポケモンSVにおける当ブログでは初の自動化コードです!
今回はボックスのタマゴをひたすらに孵化していくコードを作りました。(タマゴ受け取り部分は一旦おあずけです)
ポケモンSVではマイコンでの自動化がなかなか難しいのですが、ある程度安定感のあるコードに仕上げられたので、公開することにしました!
こちらに掲載しているコードは古いもので、改良版は以下になります。安定性がかなり上がっていますので、ぜひそちらをご利用ください。
ポケモンSVの孵化自動化コードは以前にも作ったのですが、改良版ができたため公開します。 [sitecard subtitle=関連記事 url=https://pokemonit.com/sv-hatch-eggs-in-the-bo[…]
この記事をご覧になる前に
マイコンをまだ導入していない方は、以下の記事を参考に導入してみてください。このブログでは、『Arduino Leonardo』というマイコンを使っているため、異なる種類のものではうまく動かない可能性があります。
マイコンと呼ばれるものをご存知でしょうか? Switchに接続するだけで様々な作業を "自動" で行ってくれるというものです。 導入すれば作業が楽になるだけでなく、寝てる間に色々稼ぐこともできちゃう便利なアイテムなわけですね! […]
今回のコードは「NintendoSwitchControlLibrary」を使用しています。まだダウンロードされていない方は、解凍したフォルダを『Arduino』フォルダの中にある『libraries』フォルダの中にコピーしておいてください。
A library for microcontrollers that uses Arduino to automate…
2022年8月末に1.3をリリースしていますので、それ以前に導入されている方はライブラリの更新をお願いいたします。
コード利用における注意点!
まず最初に大事なことを書いておくと、今回のコードは完全な放置はできません!!!
ポケモンSVは処理落ちがあったり、ところどころバグのような挙動が見られたりするのですが、それが自動化に向かい風になっています…
今回紹介するコードはだいぶ安定感を上げていますが、それでも稀にループが破綻してしまうことを確認しています。それを許容できる方のみご利用ください。
また、今回のコードに限らずなのですが、エラー落ちは想定できておりません。ポケモンSVは特にエラーが発生しやすいという話を聞いているので、そういった観点でもある程度見張れる状態での使用をおすすめいたします。
ソースコード
実際に書いたコードはこちらになります。
/**
* ボックスに眠っているタマゴを順に孵化していくスケッチ
*
* ※このスケッチはタマゴの孵化のみでタマゴ受け取りは行いません。タマゴは事前に用意しておいてください。
*
* 初期条件は以下の通り
* 1.「プラトタウン」に空を飛ぶを行い、その着地点にいること
* 2.マップを開いた際に北が上にくるようになっていること
* 3.ポケモンが連れ歩き状態でないこと
* 4.ライド状態でないこと
* 5.手持ちが「ほのおのからだ」の特性持ちであり、かつ、キャモメを倒せるモーションの短い攻撃技を一番上にした通常色の最終進化系ポケモン1匹のみであること
* 6.孵化させたいタマゴの預けてあるボックスが最初に開くこと(複数ボックス孵化させる場合は、ボックスが連続するような配置にしておいてください)
* 7.孵化させるボックスにタマゴを敷き詰めてあること(空きがあると不安定になるかも)
* 8.オフライン状態であること
* 9.無線のコントローラーが接続されていないこと
* 10.「設定」から「話の速さ」を「はやい」に、「わざ覚えスキップ」を「する」に、「ニックネーム登録」を「しない」にしておくこと
*/
////////////////// libraries ////////////////////////////////
// ライブラリを読み込むためのコード
#include <NintendoSwitchControlLibrary.h>
////////////////// options ////////////////////////////////
// 何ボックス分ループさせるか?
const int BOX_COUNT = 5;
// 1ボックスの孵化を終えるごとにレポートを書くかどうか(書く: 1, 書かない: 0)
const int REPORT_OPTION = 0;
////////////////// constant parameters ////////////////////////////////
// ボックスの列数
const int BOX_COLUMN_LENGTH = 6;
// 手持ちに入れられるタマゴの最大数
const int MAX_EGG_COUNT = 5;
// ボタン押下時の標準的な待機時間
const int BASE_PUSH_BUTTON_TIME_MS = 1000;
// カーソル操作時の標準的な待機時間
const int BASE_CURSOR_OPERATION_TIME_MS = 200;
const int HALF_CURSOR_OPERATION_TIME_MS = 100;
// メニューの開閉にかかる待機時間
const int MENU_OPEN_OR_CLOSE_TIME_MS = 1500;
////////////////// variable parameters ////////////////////////////////
// ループをカウントするための変数
int loopCount = 0;
////////////////// methods ////////////////////////////////
/**
* Switchをスリープさせる
*
* @return void
*/
void sleepGame() {
holdButton(Button::HOME, 1500);
pushButton(Button::A, BASE_PUSH_BUTTON_TIME_MS);
}
/**
* レポートを書く
*
* @return void
*/
void writeReport() {
pushButton(Button::X, MENU_OPEN_OR_CLOSE_TIME_MS);
pushButton(Button::R, 1500);
pushButton(Button::A, 4000);
pushButton(Button::A, BASE_PUSH_BUTTON_TIME_MS);
pushButton(Button::B, MENU_OPEN_OR_CLOSE_TIME_MS);
}
/**
* ボックスを開く
*
* @param bool is_init
* @return void
*/
void openBox(bool is_init = false) {
pushButton(Button::X, MENU_OPEN_OR_CLOSE_TIME_MS);
// 初回のみカーソルを合わせる
if (is_init) {
holdHat(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
holdHat(Hat::UP, 2000);
pushHat(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
}
pushButton(Button::A, 4000);
}
/**
* ボックスを閉じる
*
* @return void
*/
void closeBox() {
pushButton(Button::B, 3000);
pushButton(Button::B, MENU_OPEN_OR_CLOSE_TIME_MS);
}
/**
* ポケモンを預ける
*
* @param int box_line
* @return void
*/
void putInPokemons(int box_line) {
// 手持ちのポケモンを範囲選択する
pushHat(Hat::LEFT, HALF_CURSOR_OPERATION_TIME_MS);
pushHat(Hat::DOWN, HALF_CURSOR_OPERATION_TIME_MS);
pushButton(Button::MINUS, BASE_PUSH_BUTTON_TIME_MS);
holdHat(Hat::DOWN, 1000);
pushButton(Button::A, BASE_CURSOR_OPERATION_TIME_MS);
// 範囲選択したポケモンをボックスに預ける
pushHat(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS, box_line);
pushHat(Hat::UP, BASE_CURSOR_OPERATION_TIME_MS);
pushButton(Button::A, BASE_CURSOR_OPERATION_TIME_MS);
// ボックスがいっぱいになったら、次のボックスに移動させる
if (box_line == BOX_COLUMN_LENGTH) {
pushButton(Button::R, BASE_CURSOR_OPERATION_TIME_MS);
// 最後のボックスである場合、その後のカーソル操作は行わない
if (loopCount == BOX_COUNT - 1) return;
pushHat(Hat::RIGHT, HALF_CURSOR_OPERATION_TIME_MS);
}
// 次の処理に繋げやすくするため、カーソルを移動させる
pushHat(Hat::RIGHT, BASE_CURSOR_OPERATION_TIME_MS);
}
/**
* タマゴを引き出す
*
* @param int box_line
* @return void
*/
void pullOutEggs(int box_line) {
pushButton(Button::MINUS, BASE_PUSH_BUTTON_TIME_MS);
holdHat(Hat::DOWN, 1000);
pushButton(Button::A, BASE_CURSOR_OPERATION_TIME_MS);
pushHat(Hat::LEFT, BASE_CURSOR_OPERATION_TIME_MS, (box_line % BOX_COLUMN_LENGTH) + 1);
pushHat(Hat::DOWN, BASE_CURSOR_OPERATION_TIME_MS);
pushButton(Button::A, BASE_CURSOR_OPERATION_TIME_MS);
}
/**
* 孵化した手持ちのポケモンを全てボックスに預け、同時にタマゴを手持ちに加える
*
* @param int box_line
* @return void
*/
void doBoxOperations(int box_line) {
openBox();
putInPokemons(box_line);
// 最後の孵化のタイミングでは、タマゴの引き出しは行わない
if (!(box_line == BOX_COLUMN_LENGTH && loopCount == BOX_COUNT - 1)) {
pullOutEggs(box_line);
}
// ボックスを跨いだときは処理が重くて閉じる操作に失敗することがあるため、回避するための遅延&連打処理を用意
if (box_line == BOX_COLUMN_LENGTH) {
delay(4000);
pushButton(Button::B, 100, 3);
}
closeBox();
}
/**
* 「プラトタウン」から「コサジの小道灯台」に空を飛ぶで移動する(同じ場所に空を飛ぼうとするとAボタンが反応しないことがあるため、「プラトタウン」からのスタートとした)
*
* @return void
*/
void goFromLosPlatosToLighthouse() {
pushButton(Button::Y, 3000);
tiltLeftStick(204, Stick::MAX, 380);
delay(1000);
pushButton(Button::A, BASE_PUSH_BUTTON_TIME_MS);
pushButton(Button::A, 2000);
pushButton(Button::A, 9000);
}
/**
* 灯台に登る
*
* @return void
*/
void climbLighthouse() {
tiltLeftStick(Stick::NEUTRAL, Stick::MIN, 5000);
delay(5000);
}
/**
* ライドのON/OFFを切り替える(処理落ちで稀にボタン操作を認識しないことがあるため、連打で回避を狙う)
*
* @return void
*/
void switchRide() {
pushButton(Button::PLUS, 10, 6);
delay(BASE_PUSH_BUTTON_TIME_MS);
}
/**
* キャモメがいる場合、あらかじめ飛ばしておく
*
* @return void
*/
void flyWingulls() {
tiltLeftStick(Stick::MAX, Stick::NEUTRAL, 2400);
delay(1000);
}
/**
* 走り回って手持ちの全てのタマゴを孵化させる(ダッシュを使うと飛び出しやすい)
*
* @param long run_time_ms
* @return void
*/
void hatchAllEggs(unsigned long run_time_ms) {
int battle_time_ms = 20000;
tiltLeftStick(Stick::MAX, Stick::NEUTRAL, run_time_ms - battle_time_ms, Button::A);
pushButton(Button::A, 500, 40); // キャモメ対策で最後は移動せずにA連打する
}
////////////////// execute ////////////////////////////////
void setup() {
// Switchがマイコンを認識するまでは信号を受け付けないため、適当な処理をさせておく
pushButton(Button::LCLICK, 500, 5);
// ポジションへ移動
goFromLosPlatosToLighthouse();
climbLighthouse();
switchRide();
flyWingulls();
// 初回のボックス操作
openBox(true);
pullOutEggs(0);
closeBox();
}
void loop() {
// 指定のボックス分の孵化を終えた後、スリープ状態にして処理を終了する
if (loopCount == BOX_COUNT) {
sleepGame();
exit(0);
}
for (int box_line = 1; box_line <= BOX_COLUMN_LENGTH; box_line++) {
// タマゴの孵化
hatchAllEggs(260000); // 260秒で孵化サイクル40が全て孵化できる?
// ボックス操作
doBoxOperations(box_line);
}
// レポートオプション
if (REPORT_OPTION == 1) {
writeReport();
}
loopCount++;
}
設定の確認
マイコンを繋げる前に、以下の状態になっていることを確認してください。
- 「プラトタウン」に空を飛ぶを行い、その着地点にいること
- マップを開いた際に北が上にくるようになっていること
- ポケモンが連れ歩き状態でないこと
- ライド状態でないこと
- 手持ちが「ほのおのからだ」の特性持ちであり、かつ、キャモメを倒せるモーションの短い攻撃技を一番上にした通常色の最終進化系ポケモン1匹のみであること
- 孵化させたいタマゴの預けてあるボックスが最初に開くこと(複数ボックス孵化させる場合は、ボックスが連続するような配置にしておいてください)
- 孵化させるボックスにタマゴを敷き詰めてあること
- オフライン状態であること
- 無線のコントローラーが接続されていないこと
- 「設定」から「話の速さ」を「はやい」に、「わざ覚えスキップ」を「する」に、「ニックネーム登録」を「しない」にしておくこと
重要なものをいくつか解説します!
先頭のポケモンについて
④に関してなのですが、まずSVでは「ほのおのからだ」が効果ないと巷で言われてたりしますが、マイコンで正確に測った時間で孵化が早くなることを確認できているので、確実に効果はあります!!!
その上で、キャモメを倒せるという条件をつけているのですが、これはキャモメとの戦闘が避けられないからです。どうしても避けられないので、倒す方針にしました。倒す際に時間をかけないために、色違いでない個体であることやモーションの短い技を使うことを推奨しています。
ファイアローでも適当に用意してください。ちなみに、PPは長時間動かすとしても10程度あれば問題ないと思われるため上げなくても大丈夫です。
キャモメはLvが2だったかと思います! 倒しても孵化したポケモンはLv2~3にしかならず、ポケモンが進化してループを抜けることはないと考えています。
また、「わざ覚えスキップ」を「する」にしておくことで、「ほのおのからだ」持ちや孵化したポケモンがレベルアップしても技の確認がなくなるため、これによりループ破綻のリスクを最小限に留めています。
ボックスの状態について
オプションについて
コードの27行目、30行目がオプションになります。
// 何ボックス分ループさせるか?
const int BOX_COUNT = 1;
27行目の「1」の部分が孵化させるボックス数になります。5にすると5ボックス、10にすれば10ボックス孵化させる動きをします。
// 1ボックスの孵化を終えるごとにレポートを書くかどうか(書く: 1, 書かない: 0)
const int REPORT_OPTION = 0;
30行目は1ボックス分の孵化毎にレポートを書くかどうかのオプションになります。SVではエラー落ちが多いと聞いているので、仮にエラー落ちした場合でも被害を最小限に抑えられるよう用意しています。
デフォルトでは無効にしているので、有効にする場合は「0」を「1」にしてください。
ループ破綻を確認しているところについて
当たり判定のバグなのか、走り続けていると稀に手すりの上に乗っかってしまうことがあります。その際に、ダッシュ状態であるとその場で降りてしまうことが多いのですが、ダッシュさせなければ戻れることがほとんどであるため、ダッシュさせていないのはそれが理由だったりします。
ただ、タイミングが悪い?と落下してしまうことがあることを確認しています。具体的には、手すりの上に乗っているタイミングでボックス操作の時間が来てしまった際にそれを確認しました。
他にもループ抜けてしまう事象はあると思いますので、見つけたら共有いただければと思います。
余談:孵化させる場所の候補について
最終的には「コサジの小道灯台」で自動化することになったわけですが、ここにたどり着くまでに様々な場所で試行錯誤を重ねているので、それについても共有しておこうと思います。(場所については色々な方から候補をいただきました。ご協力感謝いたします!)
ゼロゲート(上)
最有力候補でしたが、まさかのメェークルが沸くという嫌がらせに遭遇し、断念しました。
ダッシュもできる広さがあるため、もしメェークルが沸くのがバグで今後修正されるようであれば、こちらで自動化するのが良いと考えています。
ゼロゲート(下)
ゼロゲートに空を飛んで下に降りたポイントはポケモンがわかなさそうで、かつ、天候もなさそう?なので安定しそうに思えました。が、走り回るにはちょっと狭かったですね…
ダッシュしなければ悪くはなさそうなので、キャモメでレベル上がるのが気になるようであれば、ここもいいかもしれないです…!
北3番エリア
有力候補でしたが、いかんせん重かったです…
処理落ちでライドに乗るのにも失敗したり、簡単な移動ですら位置がずれたりしてポジションにたどり着くのも安定しませんでした。。。
ダッシュさせているのに、孵化にダッシュしていないときと同じくらいの時間がかかっているくらいには重かった印象ですね。
北3番エリア灯台
ここもキャモメ湧きました! こちらのほうがキャモメのレベルが高い上にNPCもいて重そうなので「コサジの小道灯台」のほうが良いと思いました。
セルクルタウン(オリーブころがしの場所)
オリーブころがしのフィールドまで移動できれば良さげだったのですが、セルクルタウンからの距離が遠く安定しなかったです。
直接空を飛ぶことができれば良い孵化ポイントになれたと思います。
他にも有力な孵化ポジション等あればぜひ共有いただければと思います!
Q&A
最後に
今回のコードは不安要素が拭いきれていません。もし、それらの解決策など思いつくものがあれば@lefmarnaまで共有いただければと思います!
その他、ループから抜けてしまった場所があればそれも教えていただきたいです。ほとんどの場合は解決に至らないと思いますが、どんな例外が眠っているのか把握しておきたいと思っています!