光る!遊べる!エアリアル!をつくる

あけましておめでとうございます。本年も宜しくお願い致します。今回は自分としては初となるガンプラ改造チャレンジです。1/100 FULL MECHANICS ガンダムエアリアルを電飾発光改造してみました。

開発経緯

自分の玩具改造の基本スタンスは「他の人がやったことはやらない」です。そのため、エアリアルの電飾改造なんか何番煎じか知れないぐらいなのですが、これについては例え何百番煎じになってもやらずにはいられませんでした。というのも、やはり『機動戦士ガンダム 水星の魔女』が良過ぎたからとしか言いようがありません。テレビ番組を夢中で観るなんてことは、ここ数年全然なかったのですが、『水星の魔女』は本当に毎週ワクワクしながら観ていました。

そんなわけで、主役機・ガンダムエアリアルを発光改造させたいと私が思うのはほぼ必然(?)だったのですが、ガンプラ界隈、本当にバケモノみたいなスキルをお持ちの方がたくさんたくさんおられまして。。。「HG (1/144) だと小さくて改造しんどそうだから1/100のガンプラ出てからやってみよ〜」とかのんびり構えながらX(twitter)とか観てたら、HGで完璧に発光改造してる人とかがゴロゴロ見つかりました。そのため、ガンプラに関しては本当に初心者の私が今更やったところで、特に目新しさもクオリティもあったものではないのですが、それはそれとして、やっぱり発光エアリアルは自分の手で実現したい!という思いが強く、何とかオリジナリティを加えて差別化する形でトライしてみることにしました。コンセプトは「光る!遊べる!エアリアル!」です。

特徴

ボタンやマイコンを追加したガンプラのアクションベースにエアリアルをセットして使用します。電力はマイコンにUSBケーブルを挿す形で給電します。エアリアルの内部にバッテリーを入れてスタンドアロン化されている猛者の方もおられるのですが、その場合はどうしても超小型のバッテリーを使用する必要があります。自分の場合は、バッテリーの残量を気にしながら遊ぶよりは、光るエアリアルをずっと眺めている方が良いなと思いましたので、有線で給電する形にしました。

中央のダイヤルで発光をコントロールすることができます。

少し上げると、起動のイメージでエアリアルの目が発光します。

そこからさらに上げると、シェルユニットが徐々に赤色に発光していきます。パーメットスコア2~4あたりをイメージしています。

さらに上げると、青白い発光になります。パーメットスコア5~6あたりのイメージです。

さらにさらに上げると、完全に白く発光します。パーメットスコア7~8あたりのイメージ。このあたりは劇中ではエアリアル改修型になってから到達していますが、元のエアリアルでも理屈は同じだろう、と想定しています。

それを超えると、虹色の発光が現れます。パーメットスコア9~10あたりのイメージ。劇中ではキャリバーンが到達した領域ですが、これも、エアリアルで到達していたならのイメージになります。

 

ダイヤルに加えて、4つのアクションボタンを用意しています。左から順に、一つ目はビームライフルボタン。

押すと、ビームライフルの銃口が発光すると共に、銃撃音が鳴ります。

二つ目は、ビームサーベルボタン。

押すと、ビームサーベルが発光します。できるだけ刀身全体が発光するように頑張ってみました。

三つ目は、ビットステイヴボタン。

サークルを取り付けてからボタンを押すと、ビットステイヴをイメージした光が走ります。ボタンを押すことでビットの数が増えています。劇中設定に従い、最大11個まで増やせます。

最後はBGMボタン。押すたびに再生→停止→次曲を再生、を繰り返す形になっていて、OP/EDの4曲(TVサイズ)に、個人的に劇中で印象的だったBGM 6曲を加えた計10曲を聴けるようにしました。

収録曲は以下になります。

  1. 祝福 -Anime Edit-
  2. Asticassia
  3. Get Ready for the Duel
  4. Fix Release
  5. The Witch From Mercury
  6. 君よ 気高くあれ -TV Size-
  7. GUND-ARM Inc. (Karaoke Ver.)
  8. slash (Anime Ver.)
  9. AERIAL REBUILD
  10. Red:birthmark -TV Size-

音源はこちらのサントラになります。

ハードウェア解説

ここからいつも通りハードとソフトに分けて解説していきますが、今回、ソフト的な困難さはほぼなく、九割方はハードウェアの大変さです。なので、ほぼほぼハードウェアの解説に終始します。

とりあえず、まずはいつも通りの基本具材の紹介から。

素体です。自分のスキルレベルでは1/144サイズのHGに電飾を組み込むのは不可能と判断しましたので、1/100サイズのコイツを待っていました。出た直後は入手困難でしたが、今では結構どこでも買える印象です(2024/1時点)。

特徴で述べたとおり、今回はエアリアルをベースに半固定する形にしています。電力をベースから給電するのが主目的ですが、ベースがあることで、電飾LED以外の部品を全部エアリアルの外に出すことができます。これにより、改造の難易度を下げる他、さらにボタン類の追加で様々なアクションやBGM再生みたいな付加価値を加えることができるようになりました。

私が使用したのはクリアカラーですが、ブラックもあります。

今回使用したマイコンです。サイズと性能のバランスが良いので、最近はこればっかり使用しています。

定番のMP3プレイヤーです。マイクロSDカードとスピーカーも忘れずに。スピーカーはこちらを使用しました。

エアリアルの中に組み込んでいくフルカラーLEDです。こちら、テープ状になっているのを一つずつ切り取って使用しています。

このタイプのLEDは、サイズとしては大きくなるので部品を内部に組み込みづらいというデメリットがある一方、配線はわかりやすい(←ひたすら直列に繋いでいけば良い)というメリットがあります。モジュール単体のものは、サイズは小さいので内部にたくさん入れられますが、4つ足になるので配線に工夫が要ります。

今回は電飾初チャレンジということで、配線のしやすさを優先してこのモジュールにしました。もう少し発光粒度を細かくしたい方は、モジュール単体版にチャレンジすると良いと思います。

ビットステイヴの発光に使用するLEDです。LEDをたくさん繋げて円環にするのが面倒だったので、お金の力で解決しました。結構高い…

こちらはビームサーベルに使用。基本的には130mmを使用していますが、トライアルでは300mmを使用したりもしました。

ビームライフルの銃口の発光用に使用しました。ビームサーベルに比べるとちょっと発光が地味になってしまってので、先のフィラメントLEDをビームエフェクトのように使用する方法もあったかもしれません。

エアリアルの内部に導線を張り巡らせていくことになるので、普通の被膜付きのリード線だと太過ぎてとても無理です。ということで、これを使用しました。実はポリウレタン銅線を使用するのは今回が初めてだったので、よくわからずこれを購入しましたが、もうちょい柔らかいものがあれば、そっちの方が良かったかもしれません。あるのかどうかもよくわかっていませんが。

パーメットスコアを上げてシェルユニットの発光を変化させるのに使用。今回は使用しませんでしたが、押しボタンの機能と、赤色発光機能も持っています。これに合うツマミも別で購入しました。

各種アクション用のボタンに使用。こちらも、今回は使用しませんでしたが、発光機能があります。

ケーブルまとめたりビームサーベルを作るときに地味に活躍しました。

 

あとはピンソケットとかの細かい部品ですが、ピンヘッダ・ピンソケットは、場所によって1.27mmタイプと2.54mmタイプを使い分けています。最終的に、右手の平部分のみ1.27mm、他は2.54mmタイプになりました。

とりあえず電子系の部品としては以上です。他に必要なものは、順次記事の中で紹介していきます。

 

さて、エアリアルを発光改造させるにあたって必ず越えなければいけない壁は、「シェルユニット内部のクリアパーツ化」です。

フルメカニクスでは発光なしでシェルユニットの煌めきを再現できるように、メッキパーツが入っています。このメッキを剥がせば無色(白)にならないかなとも思ったのですが、ちゃんとやろうとするとそれなりに大変そうなのと、剥がした結果も赤色だったりすると、結局パーメットスコアが低い状態(赤色発光)しか再現できないので、素直にクリア素材でパーツを複製することにしました。

最初は真面目にシリコンで型を作ってクリアのレジンを流し込んでみたりもしていたのですが、シリコンのモナカの隙間からレジンが漏れ出たり、空気穴をうまく作れくて大きな気泡の欠けができたりでなかなか上手くいかず。。。結局最後までうまくいきませんでしたが、ギリギリセーフぐらいのところまではなんとかいったので、以下ではそのやり方で説明します。

使用したのは、プラモデル界隈では有名(?)な『おゆまる』です。

お湯で温まると良い感じに柔らかくなるので、柔らかいうちに複製したい部品を押し当てれば簡単に型がとれて部品の複製に使える、というものです。部品を完全な形で複製しようと思うと、通常のシリコン複製と同様にモナカ(タイヤキ?)の如く挟み込む形にしなくてはいけないので結局難しくなるのですが、完全でなくても表面だけある程度綺麗に複製できればOK、とかなら、シリコンよりおゆまるの方が断然ラクだと思います。今回はこれでシェルユニットの内部パーツを複製しました。

そんな便利なおゆまるですが、ちょっと面倒なのは、基本的にはお湯で温めるものである、というところです。そりゃそうなんですが、毎回お湯を沸かしたり後で水気をとったりというのは非常に面倒です。ということで、何かもっとラクに扱える方法はないかと色々WebやX(twitter)で情報を漁って試してみた結果、最終的に『ヘアアイロン+クッキングシート』に辿り着きました。先人の知恵は素晴らしい。

実際に購入したのは以下になります。

なるべく幅広で、温度設定がわかりやすいものをチョイス。これにクッキングシートを間に入れておゆまるを挟むと、おゆまるがお湯不要であっという間にふにゃふにゃになります。ただ、その分めちゃめちゃ熱くなっているので、火傷しないようにくれぐれもご注意ください

柔らかくなったおゆまるにパーツを押し当てて、十分に冷えてからパーツを外すと型ができるので、あとはそこにクリアのレジンを流して固めれば、少なくとも片面は綺麗な複製ができます。ただ、あくまで片面だけなので、それで問題ないかは用途に依ります。

自分は最終的には、こんな感じで疑似モナカというか、とりあえずパーツの片面埋めて冷えたおゆまるに、さらに別のおゆまるを被せるという無茶苦茶なことをしました。で、空気穴開けるのも面倒だったので、とりあえず片方のおゆまるの型にレジンをいっぱい入れて、もう片方のおゆまる型を上から被せてレジンを溢れさせる、という、セオリーガン無視のやり方をしました。こうすると当然、細部の精度はメチャクチャになりますが、おおまかな形までは複製できます。

で、エアリアルの場合、この複製パーツの上にさらにクリアブラックの外装パーツを被せるので、多少粗があっても遠目には問題ない。ということで、難所の「シェルユニット内部のクリアパーツ化」は、これで強引に切り抜けました。凄腕の人なら、シリコン型でメチャメチャ精度良く複製したり、あるいは3Dモデリングで一から部品設計して3Dプリントする、ということもあるのでしょうけれど、自分には無理でした。

あ、レジンは100均で売っていたものを適当に使いました。基本的にクリアで、目の部分だけはクリアグリーンにしています。私は皮膚が弱いせいか下手にレジンを使うと肌が荒れてしまうので、換気&ゴム手袋必須です。

 

で、その次に乗り越えなければいけないのが、当たり前ですが「LEDを中にどう仕込んでいくか」というところで。。。。これはもう本当に、どうすれば良いか全然わからなかったです。凄腕の人の制作動画を見ると、メチャメチャ小さいフルカラーLEDをメチャメチャ細かく配線して中に何個も仕込まれたりしているのですが、あれはもう自分には無理です。無理。

なので、もうLEDは「各シェルに一つ」と割り切ることにしました。で、配線も、ややこしいことは考えるずに、とにかく直列に繋ぐ。そうなると導線の数は増えてしまう(←一つのフルカラーLEDを仕込むと、INで3本、OUTで3本の線が生まれる)けど、配線としてはまだわかりやすい。

ということで、アクションベースからの電力供給をエアリアルのお尻ぐらいから行うことにして、あとはここからどう一筆書きでエアリアルの中を通してLEDを配線していくか。。。穴あけ等をミスったら取り返しがつかなくなるところもあるので、とにかく頭の中で慎重にシミュレーションしながら、少しずつ少しずつ進めていきました。3〜4ヶ月ぐらい。しんどすぎて制作過程の写真を撮ることもほぼできていなかったのですが、かろうじて撮っていた写真を参考に載せておきます。

いやマジで辛かったです。二度とやりたくない。。。けど、最後まで組み上がって点灯させたときはメチャメチャ感動しました。

あと、上の写真でも見えていますが、「どうせならビームライフルとビームサーベルも光らせたいよねー」と欲を出したせいで、エアリアルの右手だけ、武器に電力を通すための線を更に2本通す羽目になりました。アクションベースから武器に有線で電力を供給する手もあったかもしれませんが、それは流石にカッコ悪いかなと思いましたので、ここは頑張ってエアリアルの手から電力を供給する形に拘ってみました。

ちょっと見にくいですが、手のひらはこんなふうにソケットになっていまして、

ここに、武器を持った手についたピンヘッダを挿して電力を供給しています。

ビームサーベルの方は、更に分解するとこんな感じ。初めはプラモのビームサーベルの持ち手を何とか加工して作れないかなと思っていましたが、今の自分の腕だとそれは無理そうだったので、なんとなくビームサーベルの柄っぽいものを3Dプリンタで自作して、それに配線することにしました。なので、近くで見るとディティールはメチャメチャです。ただ、刀身全体が発光するようにすれば、ほぼほぼ注目はそちらに行くはずなので、ここは多少雑でもいいだろう、と割り切りました。

ビームの刀身部分は、こんな感じで130mmのフィラメントLEDを、これまた3Dプリンタで作った「サーベルっぽい形状のもの」で挟み込んでいます。フィラメントLEDの片側(上部)からポリウレタン銅線を真っ直ぐ綺麗に下ろしてくるのがちょっと難しかったので、フィラメントLEDとポリウレタン銅線をまとめて3mmのクリアの熱収集チューブで固めてから挟み込みました。

ちなみに、熱収縮チューブを収縮させたいときは、普通のドライヤー使ったりハンダゴテの側面を押し当てたりと色々やり方はあると思いますが、個人的にはエンボスヒーターがあると捗るかなと思います。

なお、このやり方で刀身部分を作ると、先端の方が黒っぽくなってしまうので、少し大型化しますが、300mmフィラメントLEDを折り曲げて作るタイプのものも試作はしました。

少しサイズをミスったのでそこまで刀身が明るくなりませんでしたが、もう少しサイズをきちんと合わせればちゃんと光ったかなと思います。作り直す気力がなかったという。

 

一方で、ビームライフルの方は、シンプルに銃口を青色LEDで光らせているだけです。ビームサーベルと比べると、ちょっと地味になってしまったかもしれません。

 

あとは、ビットステイヴ。これは、長めのCOBテープLEDを円環の中に豪快に入れ込むことで表現することにしました。

円環は3Dプリンタで3分割して作成。保持パーツには、アクションベースに接続できるように3mm穴を開けています。

ビットステイブの表現方法は色々考えられて、例えば白色のテープにエフェクト模様をプリントしてブラックライトで発光させる、というやり方もあったりするのですが、同じことするのもなんだかな、と思いましたので、もう「LEDの発光=ビット」と割り切ってしまって、自分でボタン操作で発光(ビット)を増やしたりできる方が玩具としては楽しいかなと思いましたので、このやり方で実装してみました。電力はアクションベースの方から供給しています。

 

エアリアル周りはそんな感じで、あとはアクションベースの方です。マイコンは奥の方の押しやって、操作パネル(?)を前面に持ってきています。

ボタンはこんな感じで前に4つ。それぞれ3Dプリンタで作った簡易パーツにはめ込んだ上で、アクションベースに元から空いている穴に取り付けてます。配線もこの穴を通して、台座の下を通って後部のマイコンに繋げています。

パーメットスコアのダイヤル調整部分は、MP3プレーヤー&スピーカーとまとめて一つのケースに入れています。FULL MECHANICS エアリアル付属のデカールの余りを適当に貼り付けてみました。

マイコンはこちら。配線としては以下のようになります。

なお、プラモとしてのエアリアルの組み立てにはほぼ工夫はなく、素組みにせいぜいスミ入れしているぐらいです。初心者なので。

 

ソフトウェア解説

残るはプログラムですが、正直ここはこれといった工夫はなく、ほぼほぼ自分がこれまで書いてきたプログラムの寄せ集めです。ダイヤルを捻る(=パーメットスコアを上げ下げする)ときに、シェルユニットの発光色ができるだけシームレスに変化するよう心がけた、ぐらいです。ということで、今回半年ぐらいかかりましたが、プログラム部分にかかった時間はせいぜい2週間〜1ヶ月ぐらいのもので、残るは全部ハード部分に費やしました。プログラムの全文は本記事の最後に掲載します。

まとめ

以上、『光る!遊べる!エアリアル!』のご紹介でした。今回はもう本当に、洒落にならないぐらい時間がかかってしまいました。ガンプラガチ勢の方々は本当にすごい。。。

ガンプラ改造として見れば、正直、色々粗があると思います。バリ取りとかも十分じゃないし、塗装なんかもしていません。ただ、玩具としてはかなり満足できるものになったかなと思います。これが、自分が欲しかったエアリアルです。

時間はかかりましたが、その分、クリアパーツの複製とかポリウレタン銅線を使った配線とか、自分としては新たな武器を手に入れることができたので、今後の創作の幅がまた広がって良かったかなと思います。

 

次は再びライダーの玩具改造に戻る予定です。多分、『ガッチャード』関係か、『音角 Ver.3.0』のどっちか。

 

ソースコード全文

最後に全文紹介です。正直あんまり綺麗じゃないです。動けばいいんだ動けばぐらいな感じです。

#define PIN_LED_BODY      0
#define PIN_LED_BIT       1
#define PIN_LED_WEAPON    2
#define PIN_PERMET_SCORE  3
#define PIN_SW_SHELL      4
#define PIN_SW_WP_RIFLE   5
#define PIN_SW_WP_SABER   6
#define PIN_SW_WP_BIT     7
#define PIN_SW_BGM        8
#define PIN_MP3_RX        9
#define PIN_MP3_TX       10

#define ON  LOW
#define OFF HIGH

#define SHELL_STATE_A 0
#define SHELL_STATE_B 1

uint8_t shell_state = SHELL_STATE_A;

uint16_t permet_score_raw = 0;

uint8_t sw_shell    = OFF;
uint8_t sw_wp_rifle = OFF;
uint8_t sw_wp_saber = OFF;
uint8_t sw_bit      = OFF;
uint8_t sw_bgm      = OFF;

uint8_t prev_sw_shell    = OFF;
uint8_t prev_sw_wp_rifle = OFF;
uint8_t prev_sw_wp_saber = OFF;
uint8_t prev_sw_bit      = OFF;
uint8_t prev_sw_bgm      = OFF;

#define WEAPON_STATE_INIT           0
#define WEAPON_STATE_RIFLE_READY    1
#define WEAPON_STATE_RIFLE_ON       2
#define WEAPON_STATE_RIFLE_SHOOTING 3
#define WEAPON_STATE_SABER_READY    4
#define WEAPON_STATE_SABER_ON       5
#define WEAPON_STATE_SABER_OFF      6
uint8_t weapon_state      = WEAPON_STATE_INIT;
uint8_t prev_weapon_state = WEAPON_STATE_INIT;

#define RIFLE_ON_DELAY_MS  150
#define RIFLE_ON_MS        100
#define RIFLE_SHOOTING_MS 4000
#define SABER_ON_DELAY_MS  150
unsigned long weapon_state_change_point_ms = 0;

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

#include <DFPlayerMini_Fast.h>
#include <SoftwareSerial.h>
SoftwareSerial ss_mp3_player(PIN_MP3_RX, PIN_MP3_TX);

DFPlayerMini_Fast mp3_player;

#define NUM_BGMS 10
#define ADV_RIFLE 11
#define ADV_SABER 12

#define VOLUME 15 // 0〜30

uint8_t bgm_number = 0;

void control_sound_effect(uint16_t sound_num){
  if(mp3_player.isPlaying()){
    mp3_player.playAdvertisement(sound_num);
  }else{
    mp3_player.playFromMP3Folder(sound_num);
  }
}

void control_bgm(){
  if(mp3_player.isPlaying()){
    mp3_player.pause();
  }else{
    if(bgm_number < NUM_BGMS){
      bgm_number++;
    }else{
      bgm_number = 1;
    }
    mp3_player.playFromMP3Folder(bgm_number);
    Serial.print(F("Play BGM: "));
    Serial.println(bgm_number);
  }
}

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

#include <Adafruit_NeoPixel.h>
#define N_LED_BODY   9
#define N_LED_BIT  166

#define LED_LEG_R      0
#define LED_LEG_L      1
#define LED_CHEST_R    2
#define LED_CHEST_L    3
#define LED_BACK       4
#define LED_SHOULDER_R 5
#define LED_SHOULDER_L 6
#define LED_HEAD       7
#define LED_EYE        8

#define LED_BRIGHTNESS_MAX      80 // 0〜255
#define RAINBOW_BRIGHTNESS_MAX 160 // 0〜255
#define RAINBOW_SATURATION_MAX  80 // 0〜255 値が小さいほど色がぼんやりする
#define RAINBOW_SPEED_INIT     128
#define RAINBOW_SPEED_MAX      512 // 基本的に256の倍数で設定

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

struct color_rgb COLOR_BLUE = {0,0,128};
struct color_rgb color_eye  = {0,0,0};
struct color_rgb color_body = {0,0,0};

uint8_t rainbow_brightness = 0;
uint8_t rainbow_saturation = 0;
uint16_t rainbow_step = 0;
uint16_t rainbow_speed = RAINBOW_SPEED_INIT;

int8_t led_index_body = 0;

#define N_BITS_MAX 11
uint8_t num_bits = 0;
int16_t base_bit_index = 0;

int prev_interval_ms = 0;
uint8_t prev_steps = 0;
unsigned long prev_action_point_ms = 0;
boolean is_inc = true;
unsigned long inc_dim_start_point_ms = 0;

// 本来はNEO_RGB指定が正しいはずだが、Color(r,g,b)の色指定でなぜかRとGが入れ替わってしまうため、NEO_GRB指定にしている
Adafruit_NeoPixel pixels_body = Adafruit_NeoPixel(N_LED_BODY, PIN_LED_BODY, NEO_GRB);
Adafruit_NeoPixel pixels_bit  = Adafruit_NeoPixel(N_LED_BIT,  PIN_LED_BIT,  NEO_GRB);

void set_body_led_color(uint8_t r, uint8_t g, uint8_t b){
  color_body.r = r;
  color_body.g = g;
  color_body.b = b;
}

void set_aerial_led_color(uint16_t raw){
  // パーメットスコアの考え方:
  // スコア1    (  0 <= permet_score_raw <  103)    ... 目の発光のみ
  // スコア2〜4 (103 <= permet_score_raw <  412) ... 目の発光+シェルユニット赤色発光
  // スコア5    (412 <= permet_score_raw <  515) ... 目の発光+シェルユニット発光移行期(赤→青)
  // スコア6    (515 <= permet_score_raw <  618) ... 目の発光+シェルユニット青色発光
  // スコア7    (618 <= permet_score_raw <  721) ... 目の発光+シェルユニット発光移行期(青→白)
  // スコア8    (721 <= permet_score_raw <  824) ... 目の発光+シェルユニット白色発光
  // スコア9    (824 <= permet_score_raw <  927) ... 目の発光+シェルユニット発光移行期(白→虹)
  // スコア10   (927 <= permet_score_raw < 1024) ... 目の発光+シェルユニット虹色発光

  if(raw < 103){ // スコア1
    color_eye.g = LED_BRIGHTNESS_MAX * raw / 102; // LED_BRIGHTNESS_MAX / 102 * raw だと0になるので注意
    set_body_led_color(0,0,0);
    rainbow_brightness = 0;
  }else if(raw < 412){ // スコア2〜4
    color_eye.g = LED_BRIGHTNESS_MAX;
    set_body_led_color(LED_BRIGHTNESS_MAX * (raw - 103) / 309,0,0);
    rainbow_brightness = 0;
  }else if(raw < 515){ // スコア5
    color_eye.g = LED_BRIGHTNESS_MAX;
    set_body_led_color(
      LED_BRIGHTNESS_MAX - LED_BRIGHTNESS_MAX * (raw - 412) / 103,
      LED_BRIGHTNESS_MAX * (raw - 412) / 103,
      LED_BRIGHTNESS_MAX * (raw - 412) / 103
     );
    rainbow_brightness = 0;
  }else if(raw < 618){ // スコア6
    color_eye.g = LED_BRIGHTNESS_MAX;
    set_body_led_color(0,LED_BRIGHTNESS_MAX,LED_BRIGHTNESS_MAX);
    rainbow_brightness = 0;
  }else if(raw < 721){ // スコア7
    color_eye.g = LED_BRIGHTNESS_MAX;
    set_body_led_color(LED_BRIGHTNESS_MAX * (raw - 618) / 103,LED_BRIGHTNESS_MAX,LED_BRIGHTNESS_MAX);
    rainbow_brightness = 0;
  }else if(raw < 824){ // スコア8
    color_eye.g = LED_BRIGHTNESS_MAX;
    set_body_led_color(LED_BRIGHTNESS_MAX, LED_BRIGHTNESS_MAX, LED_BRIGHTNESS_MAX);
    rainbow_brightness = 0;
  }else if(raw < 927){ // スコア9
    color_eye.g = LED_BRIGHTNESS_MAX;
    rainbow_brightness = RAINBOW_BRIGHTNESS_MAX;
    rainbow_saturation = RAINBOW_SATURATION_MAX * (raw - 824) / 103;
    rainbow_speed = RAINBOW_SPEED_INIT + (RAINBOW_SPEED_MAX - RAINBOW_SPEED_INIT) * (raw - 824) / 103;
  }else if(raw < 1024){ // スコア10
    color_eye.g = LED_BRIGHTNESS_MAX;
    rainbow_brightness = RAINBOW_BRIGHTNESS_MAX;
    rainbow_saturation = RAINBOW_SATURATION_MAX;
    rainbow_speed = RAINBOW_SPEED_MAX;
  }
}

void led_base_pattern_body_off(){
  for(uint8_t i=0;i<N_LED_BODY;i++){
    pixels_body.setPixelColor(i, pixels_body.Color(0,0,0));
  }
}

void led_base_pattern_body_on(struct color_rgb *color){
  for(uint8_t i=0;i<N_LED_BODY-1;i++){
    pixels_body.setPixelColor(i, pixels_body.Color(color->r,color->g,color->b));
  }
  pixels_body.setPixelColor(LED_EYE, pixels_body.Color(0,color_eye.g,0));
}

void led_base_pattern_body_blink_slowly(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=0;i<N_LED_BODY-1;i++){
      pixels_body.setPixelColor(i, pixels_body.Color(r_step*current_step, g_step*current_step, b_step*current_step));
    }
  }else{
    for(uint8_t i=0;i<N_LED_BODY-1;i++){
      pixels_body.setPixelColor(i, pixels_body.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));
    }
  }
  pixels_body.setPixelColor(LED_EYE, pixels_body.Color(0,color_eye.g,0));

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

void led_base_battern_body_rainbow(){
  for(uint8_t i=0;i<N_LED_BODY-1;i++){
    int pixelHue = rainbow_step + (i * 65536L / (N_LED_BODY - 1));
    pixels_body.setPixelColor(i, pixels_body.gamma32(pixels_body.ColorHSV(pixelHue, rainbow_saturation, rainbow_brightness)));
  }
  pixels_body.setPixelColor(LED_EYE, pixels_body.Color(0,color_eye.g,0));

  rainbow_step += rainbow_speed;
  if (rainbow_step == 65536) {
    rainbow_step = 0;
  }
}

void led_base_pattern_bit_off(){
  pixels_bit.clear();
}

void led_base_pattern_bit_circle(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t v){
  if(now_ms - prev_action_point_ms >= interval_ms){
    if(num_bits > 0){
      // 現在点灯しているLEDを消す
      pixels_bit.clear();

      // indexを進める
      base_bit_index = base_bit_index + v;
      if(base_bit_index >= N_LED_BIT){
        base_bit_index = base_bit_index - N_LED_BIT;
      }

      // 指定ビット数で点灯させる
      uint8_t led_interval = N_LED_BIT / num_bits;

      for(uint8_t i=0;i<num_bits;i++){
        uint16_t temp_bit_index = base_bit_index + led_interval * i;
        if(temp_bit_index >= N_LED_BIT){
          temp_bit_index = temp_bit_index - N_LED_BIT;
        }
        pixels_bit.setPixelColor(temp_bit_index, pixels_bit.Color(color->r, color->g, color->b));
      }
    }else{
      pixels_bit.clear();
    }
    prev_action_point_ms = now_ms;
  }
}

#define LED_WEAPON_BRIGHTNESS_MAX 255 // 0〜255
unsigned long led_weapon_pattern_start_point_ms = 0;
int prev_weapon_interval_ms = 0;
uint8_t prev_weapon_steps = 0;
unsigned long weapon_inc_dim_start_point_ms = 0;

void led_base_pattern_weapon_off(){
  analogWrite(PIN_LED_WEAPON, 0);
}

void led_base_pattern_weapon_on(){
  analogWrite(PIN_LED_WEAPON, LED_WEAPON_BRIGHTNESS_MAX);
}

void led_base_pattern_weapon_inc(unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(interval_ms != prev_weapon_interval_ms || steps != prev_weapon_steps){
    weapon_inc_dim_start_point_ms = now_ms;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - weapon_inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t l_step = LED_WEAPON_BRIGHTNESS_MAX/steps;
  analogWrite(PIN_LED_WEAPON, l_step*current_step);

  prev_weapon_interval_ms = interval_ms;
  prev_weapon_steps = steps;
}

void led_base_pattern_weapon_dim(unsigned long now_ms, int interval_ms, uint8_t steps){
  // いずれかの条件が変化していたら、処理をリセットする
  if(interval_ms != prev_weapon_interval_ms || steps != prev_weapon_steps){
    weapon_inc_dim_start_point_ms = now_ms;
  }
  int ms_per_step = interval_ms / steps;
  int current_step = (now_ms - weapon_inc_dim_start_point_ms) / ms_per_step;
  if(current_step > steps){
    current_step = steps;
  }
  uint8_t l_step = LED_WEAPON_BRIGHTNESS_MAX/steps;
  analogWrite(PIN_LED_WEAPON, l_step*(steps-current_step));

  prev_weapon_interval_ms = interval_ms;
  prev_weapon_steps = steps;
}

void control_led(unsigned long now_ms){

  if(rainbow_brightness == 0){
    switch(shell_state){
    case SHELL_STATE_A:
      led_base_pattern_body_on(&color_body);
      break;
    case SHELL_STATE_B:
      led_base_pattern_body_blink_slowly(&color_body, now_ms, 1000, 20);
      break;
    default:
      ;
    }
  }else{
    led_base_battern_body_rainbow();
  }

  if(prev_weapon_state != weapon_state){
    if(weapon_state != WEAPON_STATE_RIFLE_SHOOTING){
      led_weapon_pattern_start_point_ms = now_ms;
      prev_weapon_interval_ms = 0;
    }
  }

  unsigned long passed_ms = now_ms - led_weapon_pattern_start_point_ms;

  switch(weapon_state){
  case WEAPON_STATE_RIFLE_ON:
  case WEAPON_STATE_RIFLE_SHOOTING:
      if(passed_ms <= 1000){led_base_pattern_weapon_on();}
      else if(1000 < passed_ms && passed_ms <= 2000){led_base_pattern_weapon_dim(now_ms, 1000, 20);}
      else{led_base_pattern_weapon_off();}
      break;
  case WEAPON_STATE_SABER_ON:
      if(passed_ms <= 100){led_base_pattern_weapon_inc(now_ms, 100, 10);}
      else{led_base_pattern_weapon_on();}
      break;
  case WEAPON_STATE_SABER_OFF:
      if(passed_ms <= 60){led_base_pattern_weapon_dim(now_ms, 60, 5);}
      else{led_base_pattern_weapon_off();}
      break;
  default:
      led_base_pattern_weapon_off();
  }

  led_base_pattern_bit_circle(&COLOR_BLUE, now_ms, 20, 3);

  pixels_body.show();
  pixels_bit.show();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(void){
  Serial.begin(115200);
  pinMode(PIN_LED_WEAPON, OUTPUT);
  pinMode(PIN_PERMET_SCORE, INPUT);
  pinMode(PIN_SW_SHELL, INPUT_PULLUP);
  pinMode(PIN_SW_WP_RIFLE, INPUT_PULLUP);
  pinMode(PIN_SW_WP_SABER, INPUT_PULLUP);
  pinMode(PIN_SW_WP_BIT, INPUT_PULLUP);
  pinMode(PIN_SW_BGM, INPUT_PULLUP);

  // MP3プレイヤーセットアップ
  ss_mp3_player.begin(9600);
  if(!mp3_player.begin(ss_mp3_player)) {
    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("dfplayer online."));
  mp3_player.volume(VOLUME);

  // フルカラーLED初期化
  pixels_body.begin();
  pixels_body.clear();
  pixels_bit.begin();
  pixels_bit.clear();

  led_base_pattern_body_off();
  led_base_pattern_bit_off();

  pixels_body.show();
  pixels_bit.show();

  analogWrite(PIN_LED_WEAPON, 0);
}

void loop(){
  permet_score_raw = analogRead(PIN_PERMET_SCORE);
  //Serial.println(permet_score_raw);
  sw_shell    = digitalRead(PIN_SW_SHELL);
  sw_wp_rifle = digitalRead(PIN_SW_WP_RIFLE);
  sw_wp_saber = digitalRead(PIN_SW_WP_SABER);
  sw_bit   = digitalRead(PIN_SW_WP_BIT);
  sw_bgm   = digitalRead(PIN_SW_BGM);  

  unsigned long now_ms = millis();

  if(prev_sw_shell == OFF && sw_shell == ON){
    switch(shell_state){
    case SHELL_STATE_A: shell_state = SHELL_STATE_B; break;
    case SHELL_STATE_B: shell_state = SHELL_STATE_A; break;
    default:
      ;
    }
  }

  if(prev_sw_wp_rifle == OFF && sw_wp_rifle == ON){
    control_sound_effect(ADV_RIFLE);
    Serial.println(F("Rifle ON."));
    weapon_state = WEAPON_STATE_RIFLE_READY;
    weapon_state_change_point_ms = now_ms;
  }

  if(prev_sw_wp_saber == OFF && sw_wp_saber == ON){
    if(weapon_state == WEAPON_STATE_SABER_ON){
      Serial.println(F("Saber OFF."));
      weapon_state = WEAPON_STATE_SABER_OFF;
    }else{
      control_sound_effect(ADV_SABER);
      Serial.println(F("Saber ON."));
      weapon_state = WEAPON_STATE_SABER_READY;
      weapon_state_change_point_ms = now_ms;
    }
  }

  if(prev_sw_bit == OFF && sw_bit == ON){
    Serial.println(F("Bit ON."));
    num_bits++;
    if(num_bits > N_BITS_MAX){
      num_bits = 0;
    }
  }

  if(prev_sw_bgm == OFF && sw_bgm == ON){
    control_bgm();
  }

  // 時間処理でWEAPON_STATEを変更
  if(weapon_state == WEAPON_STATE_RIFLE_READY && now_ms - weapon_state_change_point_ms >= RIFLE_ON_DELAY_MS){
    weapon_state = WEAPON_STATE_RIFLE_ON;
    weapon_state_change_point_ms = now_ms;
  }else if(weapon_state == WEAPON_STATE_RIFLE_ON && now_ms - weapon_state_change_point_ms >= RIFLE_ON_MS){
    weapon_state = WEAPON_STATE_RIFLE_SHOOTING;
    weapon_state_change_point_ms = now_ms;
  }else if(weapon_state == WEAPON_STATE_RIFLE_SHOOTING && now_ms - weapon_state_change_point_ms >= RIFLE_SHOOTING_MS){
    weapon_state = WEAPON_STATE_INIT;
  }else if(weapon_state == WEAPON_STATE_SABER_READY && now_ms - weapon_state_change_point_ms >= SABER_ON_DELAY_MS){
    weapon_state = WEAPON_STATE_SABER_ON;
  }

  // エアリアル本体のLEDの色指定
  set_aerial_led_color(permet_score_raw);
  // 発光制御
  control_led(now_ms);

  prev_sw_shell    = sw_shell;
  prev_sw_wp_rifle = sw_wp_rifle;
  prev_sw_wp_saber = sw_wp_saber;
  prev_sw_bit = sw_bit;
  prev_sw_bgm = sw_bgm;

  prev_weapon_state = weapon_state;

  delay(10);
}