【劇中発光+α】ベイクマグナムを改修する

今回はDXベイクマグナムのシンプルなグレードアップ改造です。

開発経緯

酸賀先生が好きです。これに尽きる。

DXベイクマグナム、発売してくれたことがまず嬉しいです。ただ、少量生産だったのか、近所のジョーシンでは発売日に入荷してる気配すらなかったです(←結局Amazonで買った)。

内容としては、ゴチゾウの個別認識機能はおろか発光もないので、割高感は多少あるものの、造形はリデコ元のヴァレンバスターから大幅に変えていて、ダイナミックなガブガブアクションもこれはこれで楽しく、まずまず満足できる内容でした。

が、劇中ではやっぱり発光はしていたので、それぐらいは入れ込みたいところです。ということで、より完成度を高めるために手を加えることにしました。

 

特徴

見た目はほぼDXベイクマグナムそのままです。

今回の改造の本筋ではありませんが、銃身の裏面は金塗装を追加しました。ここが片面だけなのはちょっと違和感があったので。

一点、元々はなかったところとして、本体から銃身に向かって動力パイプのようなものを延ばしています。この理由は追々。

動力パイプを銃身のロックパーツ部分に接続しているので、銃身を開いて本体にロックすることは出来なくなっています。が、元々銃身が段階ロックされるような仕様なので、特に遊びづらくなるということはありません。

ゴチゾウをセットすると、セット部が紫に発光。以降、ガブガブさせるたびにセット部が紫に発光します。

3回目のガブガブで変身待機状態になると、セット部がオレンジ色でゆっくり明滅。このあたりの発光パターンはなるべく劇中仕様に合わせました。

 

トリガーを引いて変身。劇中では、このときはゴチゾウセット部は特に光っておらず、変身エフェクトが発生していたと思うので、ここは独自解釈で、変身エフェクトをイメージしたパターンでゴチゾウセット部を発光させています。

トリガーを引くと、銃撃音と共に銃口がオレンジに発光します。また、動画ではわからないと思いますが、発砲時の衝撃を疑似体験させるために銃身が振動します。

トリガー長押しだと、連射状態で発光。こちらも長押し中は銃身が振動します。

 

必殺技(ベイキング・バーニング)のときは、銃口の発光が紫色になります。こちらも劇中準拠になります。バーニングは使っていなかった気もするけれど(5/4時点)。

以上。改造内容としては非常にシンプルです。

 

ハードウェア解説

まずは具材から。何はともあれDXベイクマグナムです。

しかしこれ、再販されるのか…?

続いてマイコン。最近こればっかり使っている気がしますが、Seeeduino XIAOです。

フルカラーLED。定番のWS2812B (NeoPixel)。今回は砲弾型より少し背が低い帽子型(?)にしました。

モータードライバ。振動モーターを駆動させるのに使用。

振動モーター自体は、昔に電子部品屋さんで適当に購入していたものを使用しました。150円。よくあるミニ四駆サイズ(130)です。

電池ボックス。銃身が、丁度単四2本が横並びで入る幅だったので、単四の2本用と1本用を組み合わせて使用しました。最近はリチウムイオンポリマー電池は極力使用しないようにしています。捨て方がわからないので。

円形のアクリル板。銃口の保護パーツとして。

今回、ベイクマグナムの本体と銃身をコードで接続する必要がありました。なるべく内側に隠す方法も考えられますが、どうせ銃身を大きく開くと剥き出しになるので、それなら始めから見せる前提で作ったらいいやん、ということで、スプリングの中にコードを収めて、その外側をメタルパイプで覆うことにしました。プラモのディティールアップのやり方ですね。

あとは電源ON/OFF用のスライドスイッチと、コードとかピンソケットとか。それから、各種部品を銃身に綺麗に収めるための治具は3Dプリンタで作ります。こんな感じ。

回路図はこんな感じになります。

ポイントは、元々ベイクマグナムに入っている音声基板と、こちらで用意したマイコンの接続点かなと思います。

本体の音声基板にはスイッチが3つ接続されています。トリガー用と、ゴチゾウ認識用と、銃身の開閉用。トリガー用はコードで接続されていますが、他二つは基板に直接はんだ付けされています。

これらのスイッチについて、基板部分の接点の電圧を測定してみると、それぞれ、片方は常時0V、もう片方は通常3Vでスイッチが押されると0Vに変化します(電源ON時)。この後者の方をマイコンと繋げば、HIGH = OFF、LOW = ONで検出できます。ただし、ベイクマグナム側の電源が入っていないと、強制的にLOW = ONの扱いになる点だけ注意。

あとは本体側の加工。基本的にはスペースに余裕がある構造なので大幅加工は不要ですが、それでも何箇所かは必要です。

まず銃口の穴あけ。

カバー用に内側から直系20mmのアクリル板を当てこんで、治具で固定するようにしています。

それから、銃身のロックパーツの部分を少し加工して、本体側からのコードの接続コネクタに。

私は丁度良いところに穴があるなと思ってこういう形にしましたが、シンプルに側面に穴を開けて通す方法でも全然良いと思います。

実は最初の方ではライジングヴァレンバスターと同様に、電池やマイコンを外付けにする構想があって、その場合にはコードを着脱できるようにしておいた方が良いかなと思って、この形になりました。最終的にはぜんぶ埋め込んでしまったので、あんまり意味がありませんでしたが。

続いて、本体側のコードを通す部分。ここは穴開け必須。

以下のような感じで、下顎につけたLEDや本体側の基板に繋がっていきます。

あとは、上顎下顎、それぞれLEDを通す部分の通り道。

上顎・下顎の内側にアルミテープを貼っているのは、こうしておいた方が口内(?)の発光が少しは強くなるかな、と思ったため。口内がフルカラーLED×2個+ゴチゾウの裏から照射だと明るさがちょっと不安だったのでこうしましたが、結果的には、部屋をちょっと暗めにすると丁度良いぐらいの発光の強さになりました。明るい場所だと、ちょっと物足りないかもしれません。

最終的な配線はこんな感じになりました。

3Dプリンタで作った治具に、単四電池3本、マイコン、振動モーター、モータードライバ、銃口用LEDを取り付け。

これを銃身の中にセット。

シンプルな改造の割には、なかなかめんどくさかったです。

 

ソフトウェア解説

意外とややこしかったのが状態遷移図です。

ガブガブアクションの回数管理がめんどくさくて、最終的にはガブガブアクションの回数は状態(state)とは別にステージ(stage)という変数を作って、そちらで管理するようにしました。

ソースコード的には、そんなに目新しいことはしていないかなと思います。ベイクマグナムの電源がOFFの状態でマイコンの電源を入れると、マイコンとしては全部のスイッチがONになっていると認識してしまうので(←電圧が0Vになっているから)、最初のsetup関数の中でトリガースイッチのON/OFF状態をチェックするようにしたり、全部のスイッチがONの状態が一定時間続くとLEDや振動モーターを強制OFFにするような対策を入れたり、をしているぐらいです。

ソースコードの全文は最後に掲載致します。

 

まとめ

以上、DXベイクマグナムのグレードアップ改造でした。

実は、銃身の自動開閉とか、ギミック盛り盛りの方向性もしばらく考えてはいたのですが、だいぶごちゃごちゃした感じになりそうなのと、自動開閉はヴァレンバスターでもうやったので、最終的には、シンプルにカッコよく、スッキリまとめることを優先して作ることにしました。

追悼、酸賀研造。人としてはどうかと思いますが、技術者としては、非常に好きなキャラクターでした。是非TTFCでスピンオフを作って頂きたいところです。

 

ソースコード

全文は以下になります。

////////// 基本定義 ////////////////////////////////////////////////////////////

#define LOOP_DELAY_MS 20

#define PIN_SW_TRIGGER     0
#define PIN_SW_GOCHIZOU    1
#define PIN_SW_BARREL      2

#define PIN_LED            4
#define PIN_MOTOR_BARREL_1 5
#define PIN_MOTOR_BARREL_2 6

#define ON  LOW
#define OFF HIGH

uint8_t sw_trigger  = OFF;
uint8_t sw_gochizou = OFF;
uint8_t sw_barrel   = OFF;
uint8_t prev_sw_trigger  = OFF;
uint8_t prev_sw_gochizou = OFF;
uint8_t prev_sw_barrel   = OFF;

#define STATE_UNSET_OPEN  0
#define STATE_UNSET_CLOSE 1
#define STATE_SET_READY   2
#define STATE_SET_CHANGE  3
uint8_t state = STATE_UNSET_OPEN;
uint8_t prev_state = STATE_UNSET_OPEN;

#define TO_SHOOTING_MS   500
#define SHOOTING_MAX_MS 5500
unsigned long shooting_start_point_ms = 0;

#define STAGE_SHOOT    0
#define STAGE_SHOOTING 1
#define STAGE_INIT     2
#define STAGE_BITE_1   3
#define STAGE_BITE_2   4
#define STAGE_BITE_3   5
#define STAGE_BITE_4   6
uint8_t stage = STAGE_INIT;
uint8_t prev_stage = STAGE_INIT;
uint8_t before_stage = STAGE_INIT;
bool is_changed = false;

void change_state(uint8_t new_state){
  state = new_state;
  Serial.print(F("STATE: "));
  switch(state){
  case STATE_UNSET_OPEN:  Serial.println(F("UNSET_OPEN"));  break;
  case STATE_UNSET_CLOSE: Serial.println(F("UNSET_CLOSE")); break;
  case STATE_SET_READY:   Serial.println(F("SET_READY"));   break;
  case STATE_SET_CHANGE:  Serial.println(F("SET_CHANGE"));  break;
  default: ;
  }
}

void change_stage(uint8_t new_stage){
  before_stage = stage;
  stage = new_stage;
  Serial.print(F("  STAGE: "));
  switch(stage){
  case STAGE_SHOOT:    Serial.println(F("SHOOT"));    break;
  case STAGE_SHOOTING: Serial.println(F("SHOOTING")); break;
  case STAGE_INIT:     Serial.println(F("INIT"));     break;
  case STAGE_BITE_1:   Serial.println(F("BITE_1"));   break;
  case STAGE_BITE_2:   Serial.println(F("BITE_2"));   break;
  case STAGE_BITE_3:   Serial.println(F("BITE_3"));   break;
  case STAGE_BITE_4:   Serial.println(F("BITE_4"));   break;
  default: ;
  }
}

 ////////////////////////////////////////////////////////////
#include <Adafruit_NeoPixel.h>
#define N_LED 3
#define NUM_LED_MUZZLE 0
#define NUM_LED_GOCHIZOU_START 1
#define NUM_LED_GOCHIZOU_END 2

struct color_rgb {
  uint8_t r;
  uint8_t g;
  uint8_t b;
};

struct color_rgb COLOR_ORANGE = {255, 165,   0};
struct color_rgb COLOR_PURPLE = {255,   0, 255};

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(N_LED, PIN_LED, NEO_RGB);

///////////////

// 銃口とゴチゾウの発光が同時に起こることはないので、制御用の変数は共用で構わない
unsigned long led_pattern_start_point_ms = 0;
int prev_interval_ms = 0;
uint8_t prev_steps = 0;
unsigned long prev_action_point_ms = 0;
unsigned long inc_dim_start_point_ms = 0;
bool is_blink_on = false;
bool is_inc = true;

void led_base_pattern_off(){
  // 共通ですべてOFFにする
  for(uint8_t i=0;i<N_LED;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
}

void led_base_pattern_on_muzzle(struct color_rgb *color){
  pixels.setPixelColor(NUM_LED_MUZZLE, pixels.Color(color->r,color->g,color->b));
  // ゴチゾウ側はOFFにする
  for(uint8_t i=NUM_LED_GOCHIZOU_START;i<=NUM_LED_GOCHIZOU_END;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
}

void led_base_pattern_on_gochizou(struct color_rgb *color){
  // 銃口はOFFにする
  pixels.setPixelColor(NUM_LED_MUZZLE, pixels.Color(0,0,0));
  for(uint8_t i=NUM_LED_GOCHIZOU_START;i<=NUM_LED_GOCHIZOU_END;i++){
    pixels.setPixelColor(i, pixels.Color(color->r,color->g,color->b));
  }
}

void led_base_pattern_on_gochizou_single(struct color_rgb *color){
  // 下顎以外はOFFにする
  for(uint8_t i=0;i<=NUM_LED_GOCHIZOU_START;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
  pixels.setPixelColor(NUM_LED_GOCHIZOU_END, pixels.Color(color->r,color->g,color->b));
}

void led_base_pattern_dim_muzzle(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;
  pixels.setPixelColor(NUM_LED_MUZZLE, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));
  // ゴチゾウ側はOFFにする
  for(uint8_t i=NUM_LED_GOCHIZOU_START;i<=NUM_LED_GOCHIZOU_END;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
}

void led_base_pattern_inc_gochizou(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;

  for(uint8_t i=NUM_LED_GOCHIZOU_START;i<=NUM_LED_GOCHIZOU_END;i++){
    pixels.setPixelColor(i, pixels.Color(r_step*current_step, g_step*current_step, b_step*current_step));
  }
  // 銃口側はOFFにする
  pixels.setPixelColor(NUM_LED_MUZZLE, pixels.Color(0,0,0));
}

void led_base_pattern_dim_gochizou(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;
  for(uint8_t i=NUM_LED_GOCHIZOU_START;i<=NUM_LED_GOCHIZOU_END;i++){
    pixels.setPixelColor(i, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));
  }
  // 銃口側はOFFにする
  pixels.setPixelColor(NUM_LED_MUZZLE, pixels.Color(0,0,0));
}

void led_base_pattern_dim_gochizou_single(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;
  pixels.setPixelColor(NUM_LED_GOCHIZOU_END, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));
  // 下顎以外はOFFにする
  for(uint8_t i=0;i<=NUM_LED_GOCHIZOU_START;i++){
    pixels.setPixelColor(i, pixels.Color(0,0,0));
  }
}

void led_base_pattern_blink_muzzle(struct color_rgb *color, unsigned long now_ms, int inverval_ms){
  if(now_ms - prev_action_point_ms >= inverval_ms){
    if(is_blink_on){
      pixels.setPixelColor(NUM_LED_MUZZLE, pixels.Color(0,0,0));
    }else{
      pixels.setPixelColor(NUM_LED_MUZZLE, pixels.Color(color->r,color->g,color->b));
    }
    is_blink_on = !is_blink_on;
    prev_action_point_ms = now_ms;
    // ゴチゾウ側はOFFにする
    for(uint8_t i=NUM_LED_GOCHIZOU_START;i<=NUM_LED_GOCHIZOU_END;i++){
      pixels.setPixelColor(i, pixels.Color(0,0,0));
    }
  }
}

void led_base_pattern_blink_slowly_gochizou(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
  if(inc_dim_start_point_ms == 0 || interval_ms != prev_interval_ms || steps != prev_steps){
    inc_dim_start_point_ms = now_ms;
    prev_interval_ms = interval_ms;
    prev_steps = steps;
  }

  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t r_step = color->r/steps;
  uint8_t g_step = color->g/steps;
  uint8_t b_step = color->b/steps;

  if(is_inc){
    for(uint8_t i=NUM_LED_GOCHIZOU_START;i<=NUM_LED_GOCHIZOU_END;i++){
      //pixels.setPixelColor(i, pixels.Color(r_step*current_step, g_step*current_step, b_step*current_step));
      // 劇中の発光に合わせるため、通常の明滅と異なり、inc時の明るさは一定にする
      pixels.setPixelColor(i, pixels.Color(color->r,color->g,color->b));
    }
  }else{
    for(uint8_t i=NUM_LED_GOCHIZOU_START;i<=NUM_LED_GOCHIZOU_END;i++){
      pixels.setPixelColor(i, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));
    }
  }
  // 銃口側はOFFにする
  pixels.setPixelColor(NUM_LED_MUZZLE, pixels.Color(0,0,0));

  if(now_ms - inc_dim_start_point_ms >= interval_ms){
    is_inc = !is_inc;
    inc_dim_start_point_ms = 0;
  }
}

///////////////

void led_pattern_shoot(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 500){
      led_base_pattern_on_muzzle(&COLOR_ORANGE);
  }else if(500 <= passed_ms && passed_ms <= 1000){
      led_base_pattern_dim_muzzle(&COLOR_ORANGE, now_ms, 500, 10);
  }else if(1000 <= passed_ms){
      led_base_pattern_off();
  }
}

void led_pattern_shooting(unsigned long passed_ms, unsigned long now_ms){
  led_base_pattern_blink_muzzle(&COLOR_ORANGE, now_ms, 50);
}

void led_pattern_set_gochizou(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 500){
      led_base_pattern_on_gochizou_single(&COLOR_PURPLE);
  }else if(500 <= passed_ms && passed_ms <= 1000){
      led_base_pattern_dim_gochizou_single(&COLOR_PURPLE, now_ms, 500, 10);
  }else if(1000 <= passed_ms){
      led_base_pattern_off();
  }
}

void led_pattern_bite(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 400){
      led_base_pattern_on_gochizou(&COLOR_PURPLE);
  }else if(400 <= passed_ms && passed_ms <= 800){
      led_base_pattern_dim_gochizou(&COLOR_PURPLE, now_ms, 400, 10);
  }else if(800 <= passed_ms){
      led_base_pattern_off();
  }
}

void led_pattern_baking(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 400){
      led_base_pattern_on_gochizou(&COLOR_PURPLE);
  }else if(400 <= passed_ms && passed_ms <= 800){
      led_base_pattern_dim_gochizou(&COLOR_PURPLE, now_ms, 400, 10);
  }else if(800 <= passed_ms && passed_ms < 2000){
      led_base_pattern_off();
  }else if(2000 <= passed_ms){
      led_base_pattern_blink_slowly_gochizou(&COLOR_ORANGE, now_ms, 320, 10);
  }
}

void led_pattern_full_blast(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 500){
      led_base_pattern_on_muzzle(&COLOR_ORANGE);
  }else if(500 <= passed_ms && passed_ms <= 1000){
      led_base_pattern_dim_muzzle(&COLOR_ORANGE, now_ms, 500, 10);
  }else if(1000 <= passed_ms && passed_ms < 2500){
      led_base_pattern_dim_muzzle(&COLOR_PURPLE, now_ms, 1500, 20);
  }else if(2500 <= passed_ms && passed_ms < 3000){
      led_base_pattern_off();
  }else if(3000 <= passed_ms && passed_ms < 4000){
      led_base_pattern_dim_muzzle(&COLOR_PURPLE, now_ms, 1000, 20);
  }else if(4000 <= passed_ms && passed_ms < 6000){
      led_base_pattern_on_muzzle(&COLOR_PURPLE);
  }else if(6000 <= passed_ms && passed_ms < 8500){
      led_base_pattern_dim_muzzle(&COLOR_PURPLE, now_ms, 2500, 20);
  }else if(8500 <= passed_ms){
      led_base_pattern_off();
  }
}

void led_pattern_changing(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 400){
      led_base_pattern_on_gochizou(&COLOR_PURPLE);
  }else if(400 <= passed_ms && passed_ms <= 800){
      led_base_pattern_dim_gochizou(&COLOR_PURPLE, now_ms, 400, 10);
  }else if(800 <= passed_ms && passed_ms < 2000){
      led_base_pattern_off();
  }else if(2000 <= passed_ms){
      led_base_pattern_blink_slowly_gochizou(&COLOR_ORANGE, now_ms, 360, 10);
  }
}

void led_pattern_change(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 500){
      led_base_pattern_on_muzzle(&COLOR_ORANGE);
  }else if(500 <= passed_ms && passed_ms <= 1000){
      led_base_pattern_dim_muzzle(&COLOR_ORANGE, now_ms, 500, 10);
  }else if(1000 <= passed_ms && passed_ms < 2000){
      led_base_pattern_off();
  }else if(2000 <= passed_ms && passed_ms < 4000){
      led_base_pattern_inc_gochizou(&COLOR_ORANGE, now_ms, 2000, 20);
  }else if(4000 <= passed_ms && passed_ms < 9500){
      led_base_pattern_on_gochizou(&COLOR_ORANGE);
  }else if(9500 <= passed_ms && passed_ms < 11000){
      led_base_pattern_dim_gochizou(&COLOR_ORANGE, now_ms, 1500, 20);
  }else if(11000 <= passed_ms && passed_ms < 12000){
      led_base_pattern_off();
  }else if(12000 <= passed_ms && passed_ms < 13000){
      led_base_pattern_on_gochizou(&COLOR_PURPLE);
  }else if(13000 <= passed_ms && passed_ms < 14000){
      led_base_pattern_dim_gochizou(&COLOR_PURPLE, now_ms, 1000, 20);
  }else if(14000 <= passed_ms){
      led_base_pattern_off();
  }
}

void led_pattern_burning(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 400){
      led_base_pattern_on_gochizou(&COLOR_PURPLE);
  }else if(400 <= passed_ms && passed_ms <= 800){
      led_base_pattern_dim_gochizou(&COLOR_PURPLE, now_ms, 400, 10);
  }else if(800 <= passed_ms && passed_ms < 2000){
      led_base_pattern_off();
  }else if(2000 <= passed_ms){
      led_base_pattern_blink_slowly_gochizou(&COLOR_ORANGE, now_ms, 150, 10);
  }
}

void led_pattern_full_explosion(unsigned long passed_ms, unsigned long now_ms){
  if(passed_ms < 500){
      led_base_pattern_on_muzzle(&COLOR_ORANGE);
  }else if(500 <= passed_ms && passed_ms <= 1000){
      led_base_pattern_dim_muzzle(&COLOR_ORANGE, now_ms, 500, 10);
  }else if(1000 <= passed_ms && passed_ms < 3300){
      led_base_pattern_dim_muzzle(&COLOR_PURPLE, now_ms, 2300, 20);
  }else if(3300 <= passed_ms && passed_ms < 3600){
      led_base_pattern_off();
  }else if(3600 <= passed_ms && passed_ms < 5500){
      led_base_pattern_dim_muzzle(&COLOR_PURPLE, now_ms, 1900, 20);
  }else if(5500 <= passed_ms && passed_ms < 8000){
      led_base_pattern_on_muzzle(&COLOR_PURPLE);
  }else if(8000 <= passed_ms && passed_ms < 10500){
      led_base_pattern_dim_muzzle(&COLOR_PURPLE, now_ms, 2500, 20);
  }else if(10500 <= passed_ms){
      led_base_pattern_off();
  }
}

void control_led(unsigned long now_ms){

  if(prev_state != state){
    led_pattern_start_point_ms = now_ms;
    inc_dim_start_point_ms= 0;
    led_base_pattern_off();
  }

  if(prev_stage != stage){
    led_pattern_start_point_ms = now_ms;
    inc_dim_start_point_ms= 0;
    led_base_pattern_off();
  }

  unsigned long passed_ms = now_ms - led_pattern_start_point_ms;

  switch(state){
  case STATE_UNSET_OPEN:
    switch(stage){
    case STAGE_SHOOT:
      break;
    case STAGE_SHOOTING:
      led_pattern_shooting(passed_ms, now_ms);
      break;
    case STAGE_INIT:
      if(before_stage == STAGE_SHOOT || before_stage == STAGE_SHOOTING){
        led_pattern_shoot(passed_ms, now_ms);
      }
      break;
    case STAGE_BITE_1:
    case STAGE_BITE_2:
    case STAGE_BITE_3:
    case STAGE_BITE_4:
    default:
      ;
    }
    break;
  case STATE_UNSET_CLOSE:
    switch(stage){
    case STAGE_SHOOT:
      break;
    case STAGE_SHOOTING:
      led_pattern_shooting(passed_ms, now_ms);
      break;
    case STAGE_INIT:
      if(before_stage == STAGE_SHOOT || before_stage == STAGE_SHOOTING){
        led_pattern_shoot(passed_ms, now_ms);
      }
      break;
    case STAGE_BITE_1:
    case STAGE_BITE_2:
    case STAGE_BITE_3:
    case STAGE_BITE_4:
    default:
      ;
    }
    break;
  case STATE_SET_READY:
    switch(stage){
    case STAGE_SHOOT:
      break;
    case STAGE_SHOOTING:
      led_pattern_shooting(passed_ms, now_ms);
      break;
    case STAGE_INIT:
      if(before_stage == STAGE_SHOOT || before_stage == STAGE_SHOOTING || before_stage == STAGE_BITE_1){
        led_pattern_shoot(passed_ms, now_ms);
      }else if(before_stage == STAGE_INIT){
        led_pattern_set_gochizou(passed_ms, now_ms);
      }else if(before_stage == STAGE_BITE_2){
        led_pattern_full_blast(passed_ms, now_ms);
      }
      break;
    case STAGE_BITE_1:
      led_pattern_bite(passed_ms, now_ms);
      break;
    case STAGE_BITE_2:
      led_pattern_baking(passed_ms, now_ms);
      break;
    case STAGE_BITE_3:
    case STAGE_BITE_4:
      led_pattern_changing(passed_ms, now_ms);
      break;
    default:
      ;
    }
    break;
  case STATE_SET_CHANGE:
    switch(stage){
    case STAGE_SHOOT:
      break;
    case STAGE_SHOOTING:
      led_pattern_shooting(passed_ms, now_ms);
      break;
    case STAGE_INIT:
      if(before_stage == STAGE_SHOOT || before_stage == STAGE_SHOOTING || before_stage == STAGE_BITE_1){
        led_pattern_shoot(passed_ms, now_ms);
      }else if(before_stage == STAGE_BITE_2){
        led_pattern_full_blast(passed_ms, now_ms);
      }else if(before_stage == STAGE_BITE_3 || before_stage == STAGE_BITE_4){
        // BITE_3/4からBITE_INITへの遷移は、変身時と超必殺技時の2パターンが存在
        if(is_changed){
          // 超必殺技
          led_pattern_full_explosion(passed_ms, now_ms);
        }else{
          // 変身
          led_pattern_change(passed_ms, now_ms);
         }
      }
      break;
    case STAGE_BITE_1:
      led_pattern_bite(passed_ms, now_ms);
      break;
    case STAGE_BITE_2:
      led_pattern_baking(passed_ms, now_ms);
      break;
    case STAGE_BITE_3:
    case STAGE_BITE_4:
      led_pattern_burning(passed_ms, now_ms);
      break;
    default:
      ;
    }
    break;
  default:
    ;
  }

  pixels.show();
}

 ////////////////////////////////////////////////////////////

#define MOTOR_SPEED_MUZZLE 128

#define PWM_FREQ 25000 // デフォルトは1000Hz? モーターの音を減らすため、可聴範囲外の周波数に設定
#define PWM_DUTY 716 // 1023で100%。

unsigned long vibrate_start_point_ms = 0;
bool is_shot = false;
bool is_critical = false;
bool is_super_critical = false;
#define VIBRATE_STAGE_A 0
#define VIBRATE_STAGE_B 1
#define VIBRATE_STAGE_C 2
#define VIBRATE_STAGE_D 3
uint8_t vibrate_stage = VIBRATE_STAGE_A;

void control_motor(unsigned long now_ms){
  // 通常銃撃
  if((prev_stage == STAGE_SHOOT || prev_stage == STAGE_BITE_1) && stage == STAGE_INIT){
    pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, PWM_DUTY);
    vibrate_start_point_ms = now_ms;
    is_shot = true;
  }
  // 連射
  if(prev_stage == STAGE_SHOOT && stage == STAGE_SHOOTING){
    pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, PWM_DUTY);
  }
  // 連射停止
  if(prev_stage == STAGE_SHOOTING && stage == STAGE_INIT){
    pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, 0);
  }

  // 必殺技
  if(prev_stage == STAGE_BITE_2 && stage == STAGE_INIT){
    pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, PWM_DUTY);
    is_critical = true;
    vibrate_start_point_ms = now_ms;
    vibrate_stage = VIBRATE_STAGE_A;
  }

  // BITE_3/4からBITE_INITへの遷移は、変身時と超必殺技時の2パターンが存在
  if((prev_stage == STAGE_BITE_3 || prev_stage == STAGE_BITE_4) && stage == STAGE_INIT){
    if(is_changed){
      // 超必殺技
      pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, PWM_DUTY);
      is_super_critical = true;
      vibrate_start_point_ms = now_ms;
      vibrate_stage = VIBRATE_STAGE_A;
    }else{
      // 変身
      pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, PWM_DUTY);
      vibrate_start_point_ms = now_ms;
      is_shot = true;
    }
  }

  /////////////// 時間経過処理 ///////////////
  unsigned long passed_ms = now_ms - vibrate_start_point_ms;

  if(is_shot){
    if(passed_ms > 300){
      pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, 0);
      is_shot = false;
    }
  }

  if(is_critical){
    if(vibrate_stage == VIBRATE_STAGE_A && passed_ms > 400){
      pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, 0);
      vibrate_stage = VIBRATE_STAGE_B;
      vibrate_start_point_ms = now_ms;
    }else if(vibrate_stage == VIBRATE_STAGE_B && passed_ms > 2600){
      pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, PWM_DUTY);
      vibrate_stage = VIBRATE_STAGE_C;
      vibrate_start_point_ms = now_ms;
    }else if(vibrate_stage == VIBRATE_STAGE_C && passed_ms > 4200){
      pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, 0);
      vibrate_stage = VIBRATE_STAGE_D;
    }else if(vibrate_stage == VIBRATE_STAGE_D){
      is_critical = false;
    }
  }

  if(is_super_critical){
    if(vibrate_stage == VIBRATE_STAGE_A && passed_ms > 400){
      pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, 0);
      vibrate_stage = VIBRATE_STAGE_B;
      vibrate_start_point_ms = now_ms;
    }else if(vibrate_stage == VIBRATE_STAGE_B && passed_ms > 3200){
      pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, PWM_DUTY);
      vibrate_stage = VIBRATE_STAGE_C;
      vibrate_start_point_ms = now_ms;
    }else if(vibrate_stage == VIBRATE_STAGE_C && passed_ms > 4500){
      pwm(PIN_MOTOR_BARREL_1, PWM_FREQ, 0);
      vibrate_stage = VIBRATE_STAGE_D;
    }else if(vibrate_stage == VIBRATE_STAGE_D){
      is_super_critical = false;
    }
  }
}

////////// メイン処理 ////////////////////////////////////////////////////////////

void setup(){
  Serial.begin(115200);
  pinMode(PIN_SW_TRIGGER,  INPUT_PULLUP);
  pinMode(PIN_SW_GOCHIZOU, INPUT_PULLUP);
  pinMode(PIN_SW_BARREL,   INPUT_PULLUP);
  pinMode(PIN_MOTOR_BARREL_1, OUTPUT);
  pinMode(PIN_MOTOR_BARREL_2, OUTPUT);

  pixels.begin();
  pixels.clear();

  analogWrite(PIN_MOTOR_BARREL_1, 0);
  analogWrite(PIN_MOTOR_BARREL_2, 0);

  // ベイクマグナム側の電源が入っていないと各SWのデフォルトがHIGHにならない。
  // ゴチゾウと銃身はセットされた状態で電源が入る可能性があるので、
  // トリガーの状態でベイクマグナム側の通電チェックを行う。
  // ベイクマグナム側の通電確認ができるまで、loop処理に移行させない。
  while(digitalRead(PIN_SW_TRIGGER) == LOW){
    Serial.println(F("Waiting for Bake Magnum ON."));
    delay(500);
  }

  Serial.println(F("Bake Magnum Ready."));

  if(digitalRead(PIN_SW_GOCHIZOU) == ON){
      change_state(STATE_SET_READY);
      change_stage(STAGE_INIT);
  }else{
    if(digitalRead(PIN_SW_BARREL) == ON){
      change_state(STATE_UNSET_CLOSE);
      change_stage(STAGE_INIT);
    }else{
      change_state(STATE_UNSET_OPEN);
      change_stage(STAGE_INIT);
    }
  }
}

void loop(){
  sw_trigger  = digitalRead(PIN_SW_TRIGGER);
  sw_gochizou = digitalRead(PIN_SW_GOCHIZOU);
  sw_barrel   = digitalRead(PIN_SW_BARREL);

  unsigned long now_ms = millis();

  switch(state){

  case STATE_UNSET_CLOSE:
    switch(stage){
    case STAGE_INIT:
      if(prev_sw_trigger == OFF && sw_trigger == ON){
        change_stage(STAGE_SHOOT);
        shooting_start_point_ms = now_ms;
      }
      break;
    case STAGE_SHOOT:
      if(now_ms - shooting_start_point_ms > TO_SHOOTING_MS){
        change_stage(STAGE_SHOOTING);
      }else{
        if(prev_sw_trigger == ON && sw_trigger == OFF){
          change_stage(STAGE_INIT);
        }
      }
      break;
    case STAGE_SHOOTING:
      if(prev_sw_trigger == ON && sw_trigger == OFF){
        change_stage(STAGE_INIT);
      }
      break;
    default:
      ;
    }

    if(prev_sw_barrel == ON && sw_barrel == OFF){
      change_state(STATE_UNSET_OPEN);
      change_stage(STAGE_INIT);
    }

    break;

  case STATE_UNSET_OPEN:
    switch(stage){
    case STAGE_INIT:
      if(prev_sw_trigger == OFF && sw_trigger == ON){
        change_stage(STAGE_SHOOT);
        shooting_start_point_ms = now_ms;
      }
      break;
    case STAGE_SHOOT:
      if(now_ms - shooting_start_point_ms > TO_SHOOTING_MS){
        change_stage(STAGE_SHOOTING);
      }else{
        if(prev_sw_trigger == ON && sw_trigger == OFF){
          change_stage(STAGE_INIT);
        }
      }
      break;
    case STAGE_SHOOTING:
      if(prev_sw_trigger == ON && sw_trigger == OFF){
        change_stage(STAGE_INIT);
      }
      break;
    default:
      ;
    }

    if(prev_sw_gochizou == OFF && sw_gochizou == ON){
      change_state(STATE_SET_READY);
      change_stage(STAGE_INIT);
      // 念のためここでも変身完了フラグを下ろしておく(基本はゴチゾウを外したとき)
      is_changed = false;
    }

    if(prev_sw_barrel == OFF && sw_barrel == ON){
      change_state(STATE_UNSET_CLOSE);
      change_stage(STAGE_INIT);
    }

    break;

  case STATE_SET_READY:
    switch(stage){
    case STAGE_INIT:
      if(prev_sw_trigger == OFF && sw_trigger == ON){
        change_stage(STAGE_SHOOT);
        shooting_start_point_ms = now_ms;
      }
      if(prev_sw_barrel == OFF && sw_barrel == ON){
        change_stage(STAGE_BITE_1);
      }
      break;
    case STAGE_SHOOT:
      if(now_ms - shooting_start_point_ms > TO_SHOOTING_MS){
        change_stage(STAGE_SHOOTING);
      }else{
        if(prev_sw_trigger == ON && sw_trigger == OFF){
          change_stage(STAGE_INIT);
        }
      }
      break;
    case STAGE_SHOOTING:
      if(prev_sw_trigger == ON && sw_trigger == OFF){
        change_stage(STAGE_INIT);
      }
      break;
    case STAGE_BITE_1:
      if(prev_sw_barrel == OFF && sw_barrel == ON){
        change_stage(STAGE_BITE_2);
      }
      if(prev_sw_trigger == OFF && sw_trigger == ON){
        change_stage(STAGE_INIT);
      }
      break;
    case STAGE_BITE_2:
      if(prev_sw_barrel == OFF && sw_barrel == ON){
        change_stage(STAGE_BITE_3);
      }
      if(prev_sw_trigger == OFF && sw_trigger == ON){
        change_stage(STAGE_INIT);
      }
      break;
    case STAGE_BITE_3:
      if(prev_sw_barrel == OFF && sw_barrel == ON){
        change_stage(STAGE_BITE_4);
      }
      if(prev_sw_trigger == OFF && sw_trigger == ON){
        change_state(STATE_SET_CHANGE);
        change_stage(STAGE_INIT);
      }
      break;
    case STAGE_BITE_4:
      if(prev_sw_barrel == OFF && sw_barrel == ON){
        change_stage(STAGE_BITE_3);
      }
      if(prev_sw_trigger == OFF && sw_trigger == ON){
        change_state(STATE_SET_CHANGE);
        change_stage(STAGE_INIT);
      }
      break;
    default:
      ;
    }

    // どのステージでも、ゴチゾウが外れたらリセットされる
    if(prev_sw_gochizou == ON && sw_gochizou == OFF){
      change_state(STATE_UNSET_OPEN);
      change_stage(STAGE_INIT);
    }

    break;

  case STATE_SET_CHANGE:
    switch(stage){
    case STAGE_INIT:
      if(prev_sw_trigger == OFF && sw_trigger == ON){
        change_stage(STAGE_SHOOT);
        shooting_start_point_ms = now_ms;
        // 変身後の最初の操作で変身完了フラグを立てる
        is_changed = true;
      }
      if(prev_sw_barrel == OFF && sw_barrel == ON){
        change_stage(STAGE_BITE_1);
        // 変身後の最初の操作で変身完了フラグを立てる
        is_changed = true;
      }
      break;
    case STAGE_SHOOT:
      if(now_ms - shooting_start_point_ms > TO_SHOOTING_MS){
        change_stage(STAGE_SHOOTING);
      }else{
        if(prev_sw_trigger == ON && sw_trigger == OFF){
          change_stage(STAGE_INIT);
        }
      }
      break;
    case STAGE_SHOOTING:
      if(prev_sw_trigger == ON && sw_trigger == OFF){
        change_stage(STAGE_INIT);
      }
      break;
    case STAGE_BITE_1:
      if(prev_sw_barrel == OFF && sw_barrel == ON){
        change_stage(STAGE_BITE_2);
      }
      if(prev_sw_trigger == OFF && sw_trigger == ON){
        change_stage(STAGE_INIT);
      }
      break;
    case STAGE_BITE_2:
      if(prev_sw_barrel == OFF && sw_barrel == ON){
        change_stage(STAGE_BITE_3);
      }
      if(prev_sw_trigger == OFF && sw_trigger == ON){
        change_stage(STAGE_INIT);
      }
      break;
    case STAGE_BITE_3:
      if(prev_sw_barrel == OFF && sw_barrel == ON){
        change_stage(STAGE_BITE_4);
      }
      if(prev_sw_trigger == OFF && sw_trigger == ON){
        change_stage(STAGE_INIT);
      }
      break;
    case STAGE_BITE_4:
      if(prev_sw_barrel == OFF && sw_barrel == ON){
        change_stage(STAGE_BITE_3);
      }
      if(prev_sw_trigger == OFF && sw_trigger == ON){
        change_stage(STAGE_INIT);
      }
      break;
    default:
      ;
    }

     // どのステージでも、ゴチゾウが外れたらリセットされる
    if(prev_sw_gochizou == ON && sw_gochizou == OFF){
      change_state(STATE_UNSET_OPEN);
      change_stage(STAGE_INIT);
      // 変身完了フラグを下ろす
      is_changed = false;
    }

    break;

  default:
    ;
  }

  if(stage == STAGE_SHOOTING && now_ms - shooting_start_point_ms > SHOOTING_MAX_MS){
    // ゴチゾウ未セット時に先にベイクマグナムの電源をOFFにすると自動で連射判定になる対策として、連射のMax時間を設ける
    change_stage(STAGE_INIT);
  }

  ///////////////////////////////////////////

  control_motor(now_ms);

  control_led(now_ms);

  ///////////////////////////////////////////

  prev_sw_trigger  = sw_trigger;
  prev_sw_gochizou = sw_gochizou;
  prev_sw_barrel   = sw_barrel;

  prev_state = state;
  prev_stage = stage;

  delay(LOOP_DELAY_MS);
}