DXジーニアスフルボトルをよりジーニアスな玩具に改造する

大変ご無沙汰しておりました。およそ二ヶ月半ぶりの更新になります。ちょっと本業が忙しくなってしまっていて(今も)、なかなか時間がとれずじまいでした。ちょくちょく見にきてくださっていた方々、申し訳ありません、ありがとうございます。

 

さて、今回のお題は仮面ライダービルド『DXジーニアスフルボトル』です。

この玩具、個人的な感想ですが、非常に完成度が高いです。ビルド系の玩具は『作り込みがあと一歩足りないなあ』と感じることが多かった気がしますが(特にハザードトリガー)、これについては文句なしです。Amazon のレビューも好意的なものが多いようです。

特にすごいなあと思うのが、この玩具の一番の売りと思われる『視覚表現』です。ジーニアスフルボトルが60本のフルボトルの力を持つということで、ジーニアスフルボトルの中に多数のボトルが存在しているに見え、かつ、それらがドライバーと連動して振られているように見えるという。最初CMでこれを見たときは「どうなってんだこれは??」と素直にびっくりしました。

th_genius_1

購入後早速分解してわかったのですが、これはマジックミラーの性質をものすごくうまく使ったギミックだったのですね。マジックミラーは『エグゼイド』の『マイティブラザーズガシャット』でも使われていたものですが(←詳しくはこちら)、『透過』と『反射』の両方の性質を持ちます。つまり、こういうことです。

th_genius_29

合わせ鏡によってボトルのオブジェが無限増殖し、それをマジックミラーの透過方向から見ることで、多数のボトルが存在するように見えているのですね。さらにマジックミラーの対向側のミラーがドライバーに連動して傾くことで、中のボトルが動いているように見えるという。これはもう、よくもまあこんなギミック思いつくものだなあと感心しきりです。作った人こそが天っ才だと思います。

 

さて、こんな感じで大変完成度の高いDXジーニアスフルボトル。ハザードトリガーに比べると改造事例はほとんどありません。自分が見た限りでは、電源スイッチの追加改造をされている方が何名かいるのみです。おそらく、ほとんど手を加える必要がないほど完成度・満足度の高いアイテム、ということなのだと思います。素晴らしいです。

ただ、自分としては「こんなことができたらもっと面白い玩具になるのでは?」という思いがぼんやりとあったので、せっかくなので手を加えてみることにしました。下手に手を加えて完成度を下げないように気をつけながら、「全てのボトルの力を持つ」という設定を最大限に活かして、「より玩具としておもしろいもの」を目指して作ってみます。

 

 

th_genius_23

で、こちらが今回改造してみたジーニアスフルボトルです。前作のハザードトリガーと同様、見た目にはほとんど変化はありません。

th_genius_28

裏面にスライド式の電源スイッチがついています。これがOFFになっているときは通常のDXジーニアスフルボトルとして動作するようになっていて、これをONにすると今回新規に追加したモードが使用可能になるようになっています。この辺りの詳しい仕組みについては後述します。

 

さて、今回新規に追加した機能ですが、大別すると3つで、細かく分けると5つあります。

  1. 『Be The One』再生機能
  2. 『60本フルボトル』モード
    1. 全音声読み上げ
    2. ベストマッチ選択
  3. 『レジェンドライダー』モード
    1. 全音声読み上げ
    2. 必殺技発動

以下、一つずつ説明していきます。

1. 『Be The One』再生機能

これは当初は入れるつもりはなかったのですが、桐生戦兎(葛城巧)が桐生戦兎としての記憶を取り戻してジーニアスフォームへ初変身したシーンがとてもカッコ良かったので、それをやりたいがためだけにこの機能を入れ込むことにしました。最近のCSMでも定番の機能ですし。やっぱり主題歌が流れると燃えますもんね。

入れ方としては、ジーニアスフルボトルでは単に回るだけの飾りになっていたボトルのフタにこの機能を割り当てることにしました。これで、ボトルのフタを回すことに意義が生まれます。

2. 60本フルボトルモード

A. 全音声読み上げ

自分が初めてジーニアスフルボトルの存在を知ったときに、まず真っ先に思いついたのがこの機能です。これができたら、なんだか劇場版の変身シーンみたいな感じになってカッコ良いんじゃないかと。実戦(?)を考えると、こんな長い読み上げなんかやっている間にエボルトさんにぶっ飛ばされてしまいそうですが、まあ玩具としてはなんかカッコ良くて良いかなと。

B. ベストマッチ選択

「全部のフルボトルの力を持っているのなら、どのベストマッチにだってなれるのでは?」と思い、それができたら玩具として面白いだろうということで、入れ込むことにしました。まあ、おそらく全てのベストマッチの上位互換であろうジーニアスフォームが存在する以上、実戦的(?)にはほとんど意味のない機能ではあるのですが。

「ベストマッチの選択方法をどうするか?」を考えたとき、一番最初に考えたのは、本体に小さいDIPスイッチをつけておいて、それで二進数でベストマッチの番号を指定する、というものでした。でもいちいちベストマッチの番号を覚えるのも、それを二進数で表現し直すのも、どう考えても面倒でスマートじゃないのでさてどうしよう、とボンヤリ考えていたときに、「あ、せっかく全ボトル名読み上げてんだからそれ使えばいーじゃん!」ということに気がつきました。

ということで、A.のボトル名読み上げ中にボタンを押すと、押したときに音声再生していたボトル名に対応したベストマッチを選択するようにしました。これももちろん実戦(?)を考えると使いにくいことこの上ない(←タイミングよくボタンを押さないとなりたいベストマッチが行き過ぎてしまう)機能なのですが、玩具としては「狙ったところでボタンを押す」という遊びが加わって良いかなと。

3. レジェンドライダーモード

A. 全音声読み上げ

「 すべてのフルボトルの力を持つのであれば、レジェンドライダーフルボトルの力を持つという解釈もありでは?」というところから想像して、60本フルボトルモードのときと同じような読み上げ機能を入れてみることにしました。結果、なんだかディケイドライバーみたいなアイテムになった気がします。

60本フルボトルモードもこのレジェンドライダーモードも、ジーニアスフルボトルのメインのボタンを押すことで発動するのですが、ボタンを一瞬だけ押すと60本フルボトルモード、ちょっとだけ長めに押すとレジェンドライダーモードが発動するようにして使い分けられるようにしています。

B. 必殺技発動

最初は元々のジーニアスフルボトルの「ワンサイド!」「逆サイド!」「オールサイド!」に倣って、「ワンサイド!」に代わってクウガ〜ディケイドまでの平成一期のライダー名読み上げ、「逆サイド!」に代わってダブル〜エグゼイドまでの平成二期のライダー名読み上げの機能にしようと思っていました。

ところがある日の仕事帰りに、

  • レバーの回転ごとに各ライダーの色で順に発光して、最後にジーニアスフィニッシュに辿り着くとカッコ良いのでは?
  • 最後まで辿り着かなくても、いやむしろ途中でレバーの回転を敢えて止めることで、任意のレジェンドライダーの必殺技を発動できると楽しいのでは?

 というアイデアを思いついてしまったので、「うわコレ音源用意するの超面倒くさい」と思いながらも、やった方が玩具として絶対面白いものになると思ったので、入れ込むことにしました。レバーを何度もぐるぐる回すことの動機づけにもなりますし。

 

という感じで、一番最初の思いつき(全音声読み上げ)からすると、最終的に構想が随分膨らんでしまいましたが、なんとか全部無事に入れ込むことができました。次に、実際の中身についてご説明します。

 

まずはいつものごとく、使用した部品のご紹介です。

毎度おなじみ Arduino Pro miniです。今回は3.3V 8MHz版を使用しています。

毎度おなじみフルカラーLEDです。線一本で何個でもLEDを制御できるし、追加の抵抗不要でArduinoに直結するだけで使えるので、ほんとにもうこればっかり使っています。

毎度おなじみのMP3プレイヤーです。唯一応答速度にだけは不満があるのですが、ArduinoでMP3を扱うときには長らくこれ以上の最適解には出会っていません。今回、『Be The One』といろんな効果音を同時に鳴らすために、2個のDF Playerを使うという暴挙にでました。さながらダブルオーガンダムのツインドライヴのよう。

最初は一個でなんとかならないか(一個のDFPlayerで二つの音源を同時に再生できなきいか)と考えて、DFPlayerに搭載されているadvertise機能なども試してみたのですが、結局どちらか一方の再生中はどちらか一方の再生が停止される、という仕様でしたので、やむなく二個同時使用に踏み切ることにしました。

基本的にはリチウムイオンポリマー電池は取り扱いが煩雑ですし、決して安全なものではないので使いたくない(←それゆえハザードトリガートランスチームガン・カスタムも未使用)のですが、今回はスペース的に普通の電池を使うのがどう考えても無理でしたので、頼らせて頂くことにしました。やっぱりこのサイズでこの出力というのは利便性が高いので。

 

以上が主な部品ですが、今回初めて、リレーを使ってみました。秋月で売っている、3V駆動の小型リレーY14H-1C-3DSです。あと、秋月で手に入るディテクタスイッチESE22MH4XDKも今回多用しています。

 

 

で、これらを全部ジーニアスフルボトルに詰め込むと、

th_genius_21

こんな感じになりました。なかなかの密度感です。 以前作ったスマブラガシャットに負けず劣らずです。

これだと全然良くわからないので、回路図をベースにしながら説明していきます。

th_genius_30

順に見ていきましょう。

th_genius_31

まずリレー部分。ここは、追加モジュールの電源スイッチがOFFになっているときには、元々ジーニアスフルボトルに組み込まれているボタン電池x3と玩具基板が繋がる状態になっています。つまり、このときは、通常のDX版と同様に動作することになります。

th_genius_32

追加モジュールに電源が入ると、Arduinoがこのリレーを制御できるようになるため、玩具基板を任意にON/OFFできるようになります。今回追加した機能を使うときは、音声や発光が干渉しないよう、玩具基板は基本的にOFFにしておくのですが、ジーニアスフルボトルをドライバーに装填して変身するときの発光処理をプログラムで一から書き直すのは大変なので、そのときだけは玩具基板をONにするようにしています。

ちなみに、リレーを使うときはダイオードを併用するのが基本というか、機器を壊してしまわないために必須なんですね。初めて使ったものだから、初めはそのあたりの常識を全然知らないままに進めていってしまっていました。危なかった。。。

th_genius_33

リチウムイオンポリマー電池については、

 th_genius_17

ここ、ボトルの底の隙間に入っています。ここしかスペース的な余裕がありませんでした。。。今回はビルドドライバーと切り離されているときでも単体でArduinoを動かさなくてはいけないので、ハザードトリガーのようにビルドドライバーから電力を供給するという手段は使えませんでした。

th_genius_34

各種スイッチは全て新規で追加しています。音声再生/装填判定スイッチとドライバー連動スイッチについては、最初は元々玩具基板で使われているスイッチをそのまま使えば良いのでないかと考えていたのですが、ハザードトリガーのときとは違って、今回は玩具基板をOFFにしていることが多いので、玩具基板がOFFだとこのスイッチで電位の変化が発生しないため、玩具のスイッチと同じ場所にスイッチを追加しました。

th_genius_20

こんな感じで、ニッパーでスペースをつくったあと、グルーガンでガチガチに固めています。

th_genius_6

Be The One再生用スイッチは今回完全新規なので、フタの回転を検出して再生/停止をするためにここに取り付けています。ただ、固定の仕方が甘かったのか、フタを回転させてもうまく反応しないことがよくあり。。。ここは要改善ポイントです。

th_genius_35

先でも触れた通り、今回はDFPlayerを二つ同時に使用しています。

th_genius_16

実際こんな感じで、まさにツインドライヴという感じです。

DFPlayerが二つになってもスピーカーを一つにまとめることは多分できたと思うのですが、一回試してみたらあまり綺麗な音にならなかった気がしたので、Be The One用には一個別にスピーカーを追加することにしました。

th_genius_11

ホントにもうスペースがないので、先ほどのボトル増殖ギミックの立役者(?)であるミラーの後ろに貼り付けました。あまり半田付け部品を可動部分に取り付けるのは宜しくないのですが、やむなしです。th_genius_36

最後にフルカラーLEDです。

th_genius_8

これはこんな感じでボトルオブジェクトのサイド部分から光が入るように穴を開けました。玩具基板のLEDの上下真逆の方から光を入れる方法もあったのかもしれませんが、ボトルの真ん中らへんで発光していた方がそれっぽいかな、と思って横から光らせることにしました。ただ、実際横から光らせてみると、お互い向かい会う形で発光することになり、光が干渉してしまってイメージと違うような色に見えることがあったため、ひょっとしたら上下逆方向から光らせた方が良かったのかもしれません。

 

ハードウェアの改造ポイントは以上です。最後に、作成したプログラムの全文を載せておきます。

#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>
#include <Adafruit_NeoPixel.h>

SoftwareSerial ss_music_player(2, 3); // RX, TX
SoftwareSerial ss_effect_player(4, 5); // RX, TX
#define LED_PIN     6  
#define RELAY_PIN   7
#define BTN_PIN     8
#define DRIVER_PIN  9
#define PLAYER_PIN 10

#define NUM_OF_LED 2
#define LED_OFF 0

#define MODE_INIT      0
#define MODE_SIXTY     1
#define MODE_BESTMATCH 2
#define MODE_RIDER     3

#define DRIVER_STAGE_WAIT_CHANGE       0
#define DRIVER_STAGE_WAIT_ONE_SIDE     1
#define DRIVER_STAGE_WAIT_REVERSE_SIDE 2
#define DRIVER_STAGE_WAIT_ALL_SIDE     3
#define DRIVER_STAGE_END               4

#define EFFECT_OFFSET_BESTMATCH_1  100 // ドライバー装填時サウンド用
#define EFFECT_OFFSET_BESTMATCH_2  130 // 変身完了時サウンド用
#define EFFECT_OFFSET_RIDER_FINISH 160 // レジェンドライダー必殺技サウンド用

#define BTN_TIME_LOWER  300 // 0.3秒(ボタン中押し判定の下限)
#define BTN_TIME_UPPER  800 // 0.8秒(ボタン中押し判定の上限)
#define BTN_COUNT_LONG   80 // 10ms x 100 でおよそ 0.8秒
#define CRITICAL_INTERVAL 10000
#define COLOR_INTERVAL      500
#define LEVEL_INTERVAL       50

//--------------------- フルボトル定義 ---------------------//

#define NUM_OF_FULLBOTTLES 61
const uint8_t fullbottles[NUM_OF_FULLBOTTLES][3] PROGMEM = {
  {  0,  0,  0},  //  0. エンプティ 

  {255,  0,  0},  //  1. ラビット
  {  0,  0,255},  //  2. タンク
  {102, 51,  0},  //  3. ゴリラ
  {  0,153,204},  //  4. タイヤモンド
  {255,102,  0},  //  5. タカ
  {153,153,153},  //  6. ガトリング
  {102,  0,102},  //  7. 忍者
  {255,255,  0},  //  8. コミック
  {255,255,255},  //  9. パンダ
  { 51,153,255},  // 10. ロケット
  {128,128,128},  // 11. ハリネズミ
  {255,  0,  0},  // 12. 消防車
  {255,204,  0},  // 13. ライオン  
  { 51,153,153},  // 14. 掃除機
  {  0,  0,255},  // 15. ドラゴン
  {204,153,  0},  // 16. ロック
  {  0, 51,204},  // 17. 海賊
  {  0,153,  0},  // 18. 電車
  {204,  0,153},  // 19. オクトパス  
  {255,255, 51},  // 20. ライト

  {255,  0,  0},  // 21. フェニックス
  {153,153,153},  // 22. ロボット
  {128,128,128},  // 23. ウルフ
  {  0, 51,204},  // 24. スマホ
  {  0,153,255},  // 25. ユニコーン
  {255,255,255},  // 26. 消しゴム
  {255,  0, 51},  // 27. ローズ
  { 51,153,255},  // 28. ヘリコプター
  {  0,255,  0},  // 29. タートル
  {153,153,153},  // 30. ウォッチ
  {153, 51,  0},  // 31. カブト虫
  {  0,153,255},  // 32. カメラ  
  {255,255,  0},  // 33. クマ
  {255,255,255},  // 34. テレビ
  {153,102, 51},  // 35. ドッグ
  {128,128,128},  // 36. マイク
  {255,  0,  0},  // 37. サンタクロース
  {255,204,204},  // 38. ケーキ
  {102,  0,153},  // 39. スパイダー
  {255,255,255},  // 40. 冷蔵庫

  {255,255,  0},  // 41. トラ
  {255, 51,102},  // 42. UFO
  {  0, 51,255},  // 43. クジラ
  {  0,102,255},  // 44. ジェット
  {  0, 51,153},  // 45. シカ
  {255,153,  0},  // 46. ピラミッド
  {255,204,  0},  // 47. キリン
  {  0, 51,255},  // 48. 扇風機
  {128,128,128},  // 49. ペンギン
  {  0,204,  0},  // 50. スケボー  
  {  0,153,102},  // 51. オバケ
  {  0,  0,255},  // 52. マグネット
  {  0, 51,153},  // 53. サメ
  {255, 51,  0},  // 54. バイク
  {102,  0,102},  // 55. バット
  {255,  0,  0},  // 56. エンジン
  {255,255,  0},  // 57. ハチ
  {  0, 51,204},  // 58. 潜水艦
  {255,255,255},  // 59. サイ
  {255,  0,  0}   // 60. ドライヤー
};

#define NUM_OF_RIDERBOTTLES 19
const uint8_t riderbottles[NUM_OF_RIDERBOTTLES][3] PROGMEM = {
  {  0,  0,  0},  // 0. エンプティ 

  {255,  0,  0},  //  1. クウガ
  {255,255,  0},  //  2. アギト
  {255,  0,  0},  //  3. 龍騎
  {255,  0,  0},  //  4. ファイズ
  {  0,  0,255},  //  5. ブレイド
  {102,  0,153},  //  6. 響鬼
  {255,  0,  0},  //  7. カブト
  {255,  0,  0},  //  8. 電王
  {255,  0,  0},  //  9  キバ
  {255,  0,255},  // 10. ディケイド
  {  0,255,  0},  // 11. W
  {255,  0,  0},  // 12. オーズ
  {255,255,255},  // 13. フォーゼ
  {255,  0,  0},  // 14. ウィザード
  {255,102,  0},  // 15. 鎧武
  {255,  0,  0},  // 16. ドライブ
  {255,102,  0},  // 17. ゴースト
  {255,  0,204}   // 18. エグゼイド
};

DFRobotDFPlayerMini music_player;
DFRobotDFPlayerMini effect_player;
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUM_OF_LED, LED_PIN, NEO_GRB);

uint8_t last_btn_state    = HIGH;
uint8_t last_driver_state = HIGH;
uint8_t last_player_state = HIGH;
uint8_t btn_state    = HIGH;
uint8_t driver_state = HIGH;
uint8_t player_state = HIGH;

uint8_t mode = MODE_INIT;
unsigned long btn_pressed_time  = 0;
unsigned long btn_released_time = 0;
unsigned long btn_time_diff = 0;
unsigned long color_change_time = 0;
unsigned long before_color_change_time = 0;
unsigned long level_change_time = 0;
unsigned long before_level_change_time = 0;
unsigned long critical_start_time = 0;
uint8_t btn_counter = 0;
boolean isPlayingMusic = false;
boolean isDriverIn = false;
boolean isCriticalReady = false;

uint8_t bottle_counter  = 1;
uint8_t selected_bestmatch = 0;
uint8_t rider_counter = 0;
uint8_t light_level = 10;
uint8_t driver_stage = DRIVER_STAGE_WAIT_CHANGE;

void playMusic(int index){
  if(!ss_music_player.isListening()){
    ss_music_player.listen();
  }
  music_player.playMp3Folder(index);
}

void pauseMusic(){
  if(!ss_music_player.isListening()){
    ss_music_player.listen();
  }
  music_player.pause();
}

void playEffect(int index){
  if(!ss_effect_player.isListening()){
    ss_effect_player.listen();
  }
  effect_player.playMp3Folder(index);
}

void pauseEffect(){
  if(!ss_effect_player.isListening()){
    ss_effect_player.listen();
  }
  effect_player.pause();
}

void fullbottle_led_ctrl(uint8_t index_1, uint8_t lv_1, uint8_t index_2, uint8_t lv_2){
    uint8_t r_1 = 0;
    uint8_t g_1 = 0;
    uint8_t b_1 = 0;
    uint8_t r_2 = 0;
    uint8_t g_2 = 0;
    uint8_t b_2 = 0;

    if(index_1 != LED_OFF){
      r_1 = pgm_read_byte(&(fullbottles[index_1][0]));
      g_1 = pgm_read_byte(&(fullbottles[index_1][1]));
      b_1 = pgm_read_byte(&(fullbottles[index_1][2]));
    }
    if(index_2 != LED_OFF){
      r_2 = pgm_read_byte(&(fullbottles[index_2][0]));
      g_2 = pgm_read_byte(&(fullbottles[index_2][1]));
      b_2 = pgm_read_byte(&(fullbottles[index_2][2]));
    }

    pixels.setPixelColor(0, pixels.Color(uint8_t(r_1*0.1*lv_1), uint8_t(g_1*0.1*lv_1), uint8_t(b_1*0.1*lv_1)));
    pixels.setPixelColor(1, pixels.Color(uint8_t(r_2*0.1*lv_2), uint8_t(g_2*0.1*lv_2), uint8_t(b_2*0.1*lv_2)));

    pixels.show();
}

void riderbottle_led_ctrl(uint8_t index_1, uint8_t lv_1, uint8_t index_2, uint8_t lv_2){
    uint8_t r_1 = 0;
    uint8_t g_1 = 0;
    uint8_t b_1 = 0;
    uint8_t r_2 = 0;
    uint8_t g_2 = 0;
    uint8_t b_2 = 0;

    if(index_1 != LED_OFF){
      r_1 = pgm_read_byte(&(riderbottles[index_1][0]));
      g_1 = pgm_read_byte(&(riderbottles[index_1][1]));
      b_1 = pgm_read_byte(&(riderbottles[index_1][2]));
    }
    if(index_2 != LED_OFF){
      r_2 = pgm_read_byte(&(riderbottles[index_2][0]));
      g_2 = pgm_read_byte(&(riderbottles[index_2][1]));
      b_2 = pgm_read_byte(&(riderbottles[index_2][2]));
    }

    pixels.setPixelColor(0, pixels.Color(uint8_t(r_1*0.1*lv_1), uint8_t(g_1*0.1*lv_1), uint8_t(b_1*0.1*lv_1)));
    pixels.setPixelColor(1, pixels.Color(uint8_t(r_2*0.1*lv_2), uint8_t(g_2*0.1*lv_2), uint8_t(b_2*0.1*lv_2)));

    pixels.show();
}

void setup() {
  Serial.begin(115200);
  pinMode(RELAY_PIN, OUTPUT);
  pinMode(BTN_PIN,    INPUT_PULLUP);
  pinMode(DRIVER_PIN, INPUT_PULLUP);
  pinMode(PLAYER_PIN, INPUT_PULLUP);

  // ---------- MP3プレイヤーセットアップ ----------
  ss_music_player.begin(9600);
  ss_effect_player.begin(9600);

  ss_music_player.listen();
  if (!music_player.begin(ss_music_player)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin music_player:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true);
  }
  Serial.println(F("music_player online."));
  music_player.setTimeOut(500); //Set serial communictaion time out 500ms
  music_player.volume(20);  //Set volume value (0~30).

  ss_effect_player.listen();
  if (!effect_player.begin(ss_effect_player)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin effect_player:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true);
  }
  Serial.println(F("effect_player online."));
  effect_player.setTimeOut(500); //Set serial communictaion time out 500ms
  effect_player.volume(15);  //Set volume value (0~30).

  // ---------- フルカラーLEDセットアップ ----------
  pixels.begin();

  for(int8_t i=0;i<NUM_OF_LED;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
  pixels.show();

  // ---------- リレーセットアップ ----------
  // リレーをLOWにすると玩具基板がON、リレーをHIGHにすると玩具基板がOFF。
  // 基本的に玩具基板はOFFにしておく。
  digitalWrite(RELAY_PIN, HIGH);

  // 起動処理
  fullbottle_led_ctrl(1, 10, 2, 10); // ラビットタンク
  playEffect(66); // 「最高だ!」
  delay(2000);
  for(int i=10;i>=0;i--){
     fullbottle_led_ctrl(1, i, 2, i); // ラビットタンク
     delay(100);
  }
}

void loop() {
  // ---------- フタの一回転を検知して、『Be The One』の再生/停止 ---------- 
  player_state = digitalRead(PLAYER_PIN);
  if(last_player_state == HIGH && player_state == LOW){
    if(isPlayingMusic){ // 再生中なら、停止
      pauseMusic();
    }else{ // 停止中なら、始めから再生
      playMusic(1);
    }
    isPlayingMusic = !isPlayingMusic;
  }
  last_player_state = player_state;

  // ---------- 押ボタン処理 ---------- 
  btn_state = digitalRead(BTN_PIN);
  if(last_btn_state == HIGH && btn_state == LOW){
    // ボタンが押された
    btn_pressed_time = btn_released_time = millis(); // リリース計測用変数をここでリセット
    // 60本モードで、ボトル名読み上げ中の場合は、ベストマッチモードへ移行
    if(mode == MODE_SIXTY && bottle_counter < NUM_OF_FULLBOTTLES){
      mode = MODE_BESTMATCH;
      Serial.println(F("Mode Bestmatch."));
      selected_bestmatch = bottle_counter/2 + bottle_counter % 2;
      fullbottle_led_ctrl(selected_bestmatch*2-1, 10, selected_bestmatch*2, 10);
      playEffect(63); // 「勝利の法則は決まった!」
      delay(2000);

      // ベストマッチモード中は、玩具基板は無効にする
      digitalWrite(RELAY_PIN, HIGH);

      // LED消灯
      for(int i=10;i>=0;i--){
        fullbottle_led_ctrl(selected_bestmatch*2-1, i, selected_bestmatch*2, i);
        delay(100);
      }
    }
  }else if(last_btn_state == LOW && btn_state == HIGH){
    // ボタンが離された
    btn_released_time = millis(); 
  }
  last_btn_state = btn_state;  

  btn_time_diff = btn_released_time - btn_pressed_time;
  if(0 < btn_time_diff && btn_time_diff <= BTN_TIME_LOWER){
    // ボタン短押しで60本モード移行
    if(mode != MODE_BESTMATCH){
      mode = MODE_SIXTY;
      Serial.println(F("Mode Sixty."));
      playEffect(62); // 「さあ、実験を始めようか」
      delay(3000);
      bottle_counter = 1;
      before_color_change_time = millis();
      // 連続音声読み上げ開始
      playEffect(80);
      delay(50); // 音声と発光の同期の微修正
      // 60本モード中は、読み上げが終わるまでは玩具基板は無効にする
      digitalWrite(RELAY_PIN, HIGH);
    }
    btn_pressed_time  = 0;
    btn_released_time = 0;
  }else if(BTN_TIME_LOWER < btn_time_diff && btn_time_diff < BTN_TIME_UPPER){
    // ボタン中押しでレジェンドライダーモード移行
    if(mode != MODE_BESTMATCH){
      mode = MODE_RIDER;
      Serial.println(F("Mode Rider."));
      playEffect(64); // 「変身!」
      delay(2000); // 玩具音声待ち
      bottle_counter = 1;
      before_color_change_time = millis();
      // 連続音声読み上げ開始
      playEffect(81);
      // ライダーモード中は、玩具基板は無効にする
      digitalWrite(RELAY_PIN, HIGH);
    }
    btn_pressed_time  = 0;
    btn_released_time = 0;
  }

  // ボタン長押しによるドライバー装填認識
  if(btn_state == LOW){
    if(btn_counter < BTN_COUNT_LONG){
      btn_counter++;
    }else{
      if(!isDriverIn){
        if(mode == MODE_SIXTY){
          // ジーニアスフルボトル装填音再生
          playEffect(68);
        }else if(mode == MODE_BESTMATCH){
          // ベストマッチ音声読み上げ&発光(「A, B, ベストマッチ!」)
          playEffect(selected_bestmatch + EFFECT_OFFSET_BESTMATCH_1);
          for(int i=10;i>=0;i--){
            fullbottle_led_ctrl(selected_bestmatch*2-1, i, LED_OFF, 0);
            delay(75);
          }
          for(int i=10;i>=0;i--){
            fullbottle_led_ctrl(LED_OFF, 0, selected_bestmatch*2, i);
            delay(75);
          }
          fullbottle_led_ctrl(selected_bestmatch*2-1, 10, selected_bestmatch*2, 10);

        }else if(mode == MODE_RIDER){
          // ジーニアスフルボトル装填音再生
          playEffect(68);
        }
      }
      isDriverIn = true;
    }
  }else{
    btn_counter = 0;
    rider_counter = 0;
    if(isDriverIn){
      // 再生中の効果音は停止
      pauseEffect();
      // ドライバーから外したときの発光処理
      for(int i=10;i>=0;i--){
        fullbottle_led_ctrl(9, i, 9, i); // 白で発光させるためにパンダを指定
        delay(100);
      }
      // モードを初期状態に戻す
      mode = MODE_INIT;
      // 玩具基板を無効にしておく
      digitalWrite(RELAY_PIN, HIGH);
    }
    isDriverIn = false;
    isCriticalReady = false;

    driver_stage = DRIVER_STAGE_WAIT_CHANGE;
  }

  // ---------- ドライバー連動 ---------- 
  driver_state = digitalRead(DRIVER_PIN);
  if(last_driver_state == HIGH && driver_state == LOW && btn_state == LOW){
    if(mode == MODE_SIXTY){
      if(driver_stage == DRIVER_STAGE_WAIT_CHANGE){
        playEffect(75); // 「イェーイ!x6」
        delay(10000); // 変身音待ち
        playEffect(70);
        delay(5000); // 変身名乗り待ち
        driver_stage = DRIVER_STAGE_WAIT_ONE_SIDE;
      }else if(driver_stage == DRIVER_STAGE_WAIT_ONE_SIDE){
        critical_start_time = millis(); // 必殺技カウント開始
        isCriticalReady = true;
        playEffect(76);
        delay(1500);
        driver_stage = DRIVER_STAGE_WAIT_REVERSE_SIDE;
      }else if(driver_stage == DRIVER_STAGE_WAIT_REVERSE_SIDE){
        playEffect(77);
        delay(1500);
        driver_stage = DRIVER_STAGE_WAIT_ALL_SIDE;
      }else if(driver_stage == DRIVER_STAGE_WAIT_ALL_SIDE){
        playEffect(78);
        delay(2000);
        driver_stage = DRIVER_STAGE_END;
      }
    }else if(mode == MODE_BESTMATCH){
      if(driver_stage == DRIVER_STAGE_WAIT_CHANGE){
        delay(10000); // 変身音待ち
        playEffect(65); // 「ビルドアップ」
        delay(1500);
        playEffect(selected_bestmatch + EFFECT_OFFSET_BESTMATCH_2);
        delay(5000); // 変身名乗り待ち
        driver_stage = DRIVER_STAGE_WAIT_ONE_SIDE;
      }else if(driver_stage == DRIVER_STAGE_WAIT_ONE_SIDE){
        critical_start_time = millis(); // 必殺技カウント開始
        isCriticalReady = true;
        fullbottle_led_ctrl(selected_bestmatch*2-1, 10, LED_OFF, 0);
        playEffect(selected_bestmatch*2-1);
        delay(1500);
        driver_stage = DRIVER_STAGE_WAIT_REVERSE_SIDE;
      }else if(driver_stage == DRIVER_STAGE_WAIT_REVERSE_SIDE){
        fullbottle_led_ctrl(LED_OFF, 0, selected_bestmatch*2, 10);
        playEffect(selected_bestmatch*2);
        delay(1500);
        driver_stage = DRIVER_STAGE_WAIT_ALL_SIDE;
      }else if(driver_stage == DRIVER_STAGE_WAIT_ALL_SIDE){
        fullbottle_led_ctrl(selected_bestmatch*2-1, 10, selected_bestmatch*2, 10);
        playEffect(69);
        delay(2000);
        driver_stage = DRIVER_STAGE_END;
      }
    }else if(mode == MODE_RIDER){
      if(driver_stage == DRIVER_STAGE_WAIT_CHANGE){
        playEffect(74); // 「イェーイ!x18」
        delay(10000); // 変身音待ち
        playEffect(70);
        delay(5000); // 変身名乗り待ち
        driver_stage = DRIVER_STAGE_END;
      }else{
        if(rider_counter == 0){
          critical_start_time = millis(); // 必殺技カウント開始
          isCriticalReady = true;
        }
        rider_counter++;
        if(rider_counter < NUM_OF_RIDERBOTTLES){
          for(int i=5;i>=0;i--){
            riderbottle_led_ctrl(rider_counter, i*2, rider_counter, i*2);
            delay(40);
          }
        }else if(rider_counter == NUM_OF_RIDERBOTTLES){
          fullbottle_led_ctrl(1, 10, 2, 10); // 到達したら、ビルドカラー
          playEffect(69); // 「イェーイ!」
        }
      }
    }
  }
  last_driver_state = driver_state; 

  // 必殺技発動判定
  if(isCriticalReady && millis() - critical_start_time >= CRITICAL_INTERVAL){
    if(mode == MODE_SIXTY){
      if(driver_stage == DRIVER_STAGE_WAIT_REVERSE_SIDE){
        playEffect(71); // ジーニアスアタック
      }else if(driver_stage == DRIVER_STAGE_WAIT_ALL_SIDE){
        playEffect(72); // ジーニアスブレイク
      }else if(driver_stage == DRIVER_STAGE_END){
        playEffect(73); // ジーニアスフィニッシュ      
      }
      driver_stage = DRIVER_STAGE_WAIT_ONE_SIDE;
    }else if(mode == MODE_BESTMATCH){
      if(driver_stage == DRIVER_STAGE_WAIT_REVERSE_SIDE){
        playEffect(82); // ボルテックアタック
      }else if(driver_stage == DRIVER_STAGE_WAIT_ALL_SIDE){
        playEffect(82); // ボルテックアタック
      }else if(driver_stage == DRIVER_STAGE_END){
        playEffect(83); // ボルテックフィニッシュ      
      }
      driver_stage = DRIVER_STAGE_WAIT_ONE_SIDE;
    }else if(mode == MODE_RIDER){
      if(rider_counter >= NUM_OF_RIDERBOTTLES){
        playEffect(73); // ジーニアスフィニッシュ
        delay(10000);
        for(int i=10;i>=0;i--){
          fullbottle_led_ctrl(1, i, 2, i); // ラビットタンク
          delay(100);
        }
      }else{
        riderbottle_led_ctrl(rider_counter, 10, rider_counter, 10);
        playEffect(rider_counter + EFFECT_OFFSET_RIDER_FINISH);
        delay(10000);
        for(int i=10;i>=0;i--){
          riderbottle_led_ctrl(rider_counter, i, rider_counter, i);
          delay(100);
        }
      }
      rider_counter = 0;
    }
    isCriticalReady = false;
  }

  // ---------- 逐次処理 ----------
  if(mode == MODE_SIXTY){
    if(bottle_counter < NUM_OF_FULLBOTTLES){
      if(bottle_counter % 2 == 1){
        fullbottle_led_ctrl(bottle_counter, 10, bottle_counter-1, light_level);
      }else{
        fullbottle_led_ctrl(bottle_counter-1, light_level, bottle_counter, 10);
      }
    }

    color_change_time = level_change_time = millis();
    if(color_change_time - before_color_change_time > COLOR_INTERVAL && bottle_counter < NUM_OF_FULLBOTTLES){
      bottle_counter++;
      light_level = 10;
      before_color_change_time = color_change_time;
    }
    if(level_change_time - before_level_change_time > LEVEL_INTERVAL && light_level > 0){
      light_level--;
      before_level_change_time = level_change_time;
    }

    if(bottle_counter == NUM_OF_FULLBOTTLES){ // 最後まで到達したら、ラビットタンクで締め
      delay(COLOR_INTERVAL);
      fullbottle_led_ctrl(1, 10, 2, 10); // ラビットタンク
      // 玩具基板を有効にしておく
      digitalWrite(RELAY_PIN, LOW);
      delay(9000);
      for(int i=10;i>=0;i--){
        fullbottle_led_ctrl(1, i, 2, i); // ラビットタンク
        delay(100);
      }

      bottle_counter++; // 再実行させないため
    }

  }else if(mode == MODE_BESTMATCH){
    ; // 特に何もしない
  }else if(mode == MODE_RIDER){
    if(bottle_counter < NUM_OF_RIDERBOTTLES){
      if(bottle_counter % 2 == 1){
        riderbottle_led_ctrl(bottle_counter, 10, bottle_counter-1, light_level);
      }else{
        riderbottle_led_ctrl(bottle_counter-1, light_level, bottle_counter, 10);
      }
    }

    color_change_time = level_change_time = millis();
    if(color_change_time - before_color_change_time > COLOR_INTERVAL && bottle_counter < NUM_OF_RIDERBOTTLES){
      bottle_counter++;
      light_level = 10;
      before_color_change_time = color_change_time;
    }
    if(level_change_time - before_level_change_time > LEVEL_INTERVAL && light_level > 0){
      light_level--;
      before_level_change_time = level_change_time;
    }

    if(bottle_counter == NUM_OF_RIDERBOTTLES){ // 最後まで到達したら、ラビットタンクで締め
      delay(1000);
      fullbottle_led_ctrl(1, 10, 2, 10); // ラビットタンク
      delay(10000);
      bottle_counter++; // 再実行させないため
    }    
  }

  delay(10);  
}

気をつけなければいけないのは、今回はDFPlayerを二つ併用することになるため、ソフトウェアシリアル のTX/RXの組みを二セット用意する必要があるということです。Arduinoで二組以上のソフトウェアシリアル を使うときには、通信する前に都度listen()でソフトウェアシリアル通信の待受を切り替える必要があることに注意が必要です。

 

以上、今回の改造内容でした。結果につきましては、本記事冒頭の動画をご確認くださいませ。最後はほぼ力技でエイヤっとパーツを無理やり全部押し込んでしまった感がありますが、なんとか期待通りには動作してくれたかな、と思います。

 

th_genius_24

というわけで、ジーニアスフルボトルをより面白い玩具に改造するために色々やってみました。リチウムイオンポリマー充電池を使っていたり一部ボタンの接触不良があったりで、前回のハザードトリガーに比べると、日々使って遊ぶ玩具としての完成度としてはやや劣るかなという気がしますが、玩具としての機能の充実さではハザードトリガーを上回るものにはなったかな、と思います。

th_genius_26

最後に、改造ハザードトリガーと一緒にパチリ。一年に渡って続けてきたビルドの玩具改造もこれにておしまい、めでたしめでたし。。。

 

じゃなくて、もうちっとだけ続くんじゃ。

# 本当にもうちっとレベルですので、大掛かりなものはこれがラストです