食玩HYPER DETAIL GEARをコンプリートセレクションレベルまで高めてみました【仕上げ編】

 

前回までで「光る!回る!(ソニックウェーブが)唸る!」の基本機能までは動作確認できたので、最後にコンプリートセレクション版の一つの売りである「手をかざすとベルトの出現音がする」という機能を実装して、最終的な形に仕上げていきたいと思います。

この機能については、どうもコンプリートセレクション版では赤外線センサが使われているようなのですが、手持ちにちょうど良い赤外線センサがなかったので、代わりにGroveシステムのジェスチャセンサを使うことにしました。

このセンサはこのときに初めて使ったのですが、プリセットで9種類のジェスチャを認識してくれるので、ちょっとした工作をするにはとても使い勝手が良いです。9種類のジェスチャの中には「近付ける」というジェスチャが含まれているので、これがあれば「ベルトに手をかざす」という動作を認識してくれそうです。

また、食玩HYPER DETAIL GEARにはベルトのサイドのボタン(変身動作を完了させるためのボタン)がないため、普通なら別のボタンを自分で用意してやる必要があるのですが、ジェスチャセンサを使うと、これもうまく解決してくれます。

つまりどういうことかというと、

th_arcle-0012

この完成イメージに対して、ユーザが正面から「ベルトに手を添える動作」を認識させるには、上から手を下ろす、もしくは正面から手を持ってくるという動作(ジェスチャ)をベルト出現動作と判定するようにすれば良さそうです。

そして実際の五代雄介の変身時の手の動きをイメージしてみると、変身ポーズの動きとしては、正面に掲げた手を右に持っていって。。。斜め左下に振り下ろす!で、腰の左にあるはずの変身完了ボタンを押す!という動作になるハズなので、

th_arcle-0013

向かって左、もしくは手前側に手が動く、という動作(ジェスチャ)を変身完了というように判定させれば、サイドのボタンがなくても変身完了という状態を認識することができます。本来は腰に巻くはずのベルトと向かい合っての変身ポーズになるので、ちょっと違和感はあるかもしれませんが、実際にこれで遊ぶには、これが自然な形だと思います。

 

ということで、こちらのリンクからArduino用のジェスチャセンサライブラリを持ってきて、サクッとプロトを作って試してみた結果がこちらです。

 

 

。。。OK!これで必要な機能は全てテストできたので、あとはこれを綺麗にまとめ上げていくだけです。

 

 

。。。が、前にamiiboオルゴールを作ったときもそうでしたが、自分はこの最後の工程が本当にヘタクソです。専用基板の設計とかができないので、プロトで作ったものをそのまま無理やり小さなケースに押し込むことになるのですが、そのケースも「まあ、これぐらいなら何とか入るだろう」ぐらいの超適当な見通しで買ってきてしまうので、何とか収まってくれても中で変な力がかかって半田が外れる、なんてことがザラにあります。

今回もエイヤでこのケースを選んでしまい、なんとか収まってくれましたが、もう少し大きいケースの方がラクだったと思います。

 

以下、簡単な記録です。普通にこの工程だけで丸一日かかってしまって、正直写真撮影どころではありませんでした。

 

th_arcle-0025

まず、ジェスチャセンサの露出用の穴と、ピンヘッダ用の穴を開けます。ジェスチャセンサはそれなりに露出していないとうまく認識してくれないので、テーパーを使ってちょっと大きめの穴にしました。

th_arcle-0026

ここに両足長めのピンヘッダを挿して、

th_arcle-0014

同じように穴あけした台座を通します。

 

次は、フォームチェンジ用のスイッチです。都合良く赤・青・緑・紫のスイッチがあるわけではなかったので、面倒ですが白色のタクトスイッチを買ってきて、自分で色を塗りました。

塗装に関しては本当にド素人なので、 適当にサーフェイサーのスプレーを買ってきてタクトスイッチに吹っかけて、その上からMr.メタリックカラーGXを筆で塗りつけてみました。

th_arcle-0015

こんな感じ。ちなみに使ったカラーは以下の4つです。

 

th_arcle-0016

タクトスイッチの足を伸ばして、正面から開けた1mmの穴に挿してみた状態。この時点で両サイドにスピーカー用の穴も開けています。

th_arcle-0027

裏から見るとこんな感じですね。

 

で、この後、なんやかんやと色々試行錯誤した結果。。。

 

th_arcle-0017

こんな感じになりました。

 

th_arcle-0018

。。。我ながら本当にひどいと思います。実際、完成した後も何回も半田付けし直すことになりました。本当にもう、そろそろ回路基板を作れる技術を身につけた方が良いと思います。

ちなみにスピーカーは2個ついていますが、別にステレオサウンドになっているわけではなく、同じ音を両サイドから流しているだけです。正直1個でも音量的には十分なのですが、やっぱりオリジナルのソニックウェーブがダブルスピーカーだったので、形だけでもそれを踏襲することにしました。

 

th_arcle-0019

中の作りとしてはヒドイものですが、どうにかこうにか中に押し込んでしまえば、なんとか見れるものにはなりました。

th_arcle-0022

裏面に大元の電源をON/OFFするためのスライドスイッチをつけています。

th_arcle-0021

地味に大変だったのが、ジェスチャセンサの露出の位置合わせです。ちょっとでも向きが傾いてしまうと誤作動連発で、しかもケースを閉じる前は大丈夫でもケースを閉じると中がぎっしり過ぎて傾くという有様で、本当にもう、ちゃんと設計しとけよと思いました。

 

まあそれはさておいて、

th_arcle-0023

大変でしたが、なんとか形になりました。良かった良かった。

 

ちなみに、コンプリートセレクション版ではグローイングとアメイジングマイティにも対応しているようなのですが、それはまあ、別にいいかなあと。単純なバリエーションでしかないので、できることがわかっているだけで充分です。

最後にArduinoのソースコードを載せておきます。ちょっと長くてややこしく見えるかもしれませんが、基本はシンプルで、各フォームのバリエーションの分だけ、同じようなコードが増えているだけです。

#include <Adafruit_NeoPixel.h>
#include <SoftwareSerial.h>
#include <DFPlayer_Mini_Mp3.h>
#include <Wire.h>
#include "paj7620.h"

#define NUM_OF_LED  2

SoftwareSerial mySerial(2, 3); // RX, TX
#define MIGHTY_PIN  4
#define DRAGON_PIN  5
#define PEGASUS_PIN 6
#define TITAN_PIN   7
#define RISING_PIN  8
#define CONTROL_PIN 10
#define MOTOR_PIN 11

#define MOTOR_MAX_VAL 70   // 0 to 255 for motor

#define CHANGE_STATE_INIT    0
#define CHANGE_STATE_WAITING 1
#define CHANGE_STATE_CHANGE  2
#define CHANGE_STATE_FINISH  3
uint8_t change_state = CHANGE_STATE_INIT;

#define INIT    0
#define MIGHTY  1
#define DRAGON  2
#define PEGASUS 3
#define TITAN   4
#define RISING  5
uint8_t current_form = MIGHTY;

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUM_OF_LED, CONTROL_PIN, NEO_GRB + NEO_KHZ400);
uint8_t rgb[3];
#define LIGHT_MAX_VAL 128  // 0 to 255 for brightness

void change_color(uint8_t color){
  switch(color){
  case MIGHTY:
    rgb[0] = LIGHT_MAX_VAL;
    rgb[1] = 0;
    rgb[2] = 0;
    break;
  case DRAGON:
    rgb[0] = 0;
    rgb[1] = 0;
    rgb[2] = LIGHT_MAX_VAL;
    break;
  case PEGASUS:
    rgb[0] = 0;
    rgb[1] = LIGHT_MAX_VAL;
    rgb[2] = 0;
    break;
  case TITAN:
    rgb[0] = LIGHT_MAX_VAL;
    rgb[1] = 0;
    rgb[2] = LIGHT_MAX_VAL;
    break;
  case RISING:
    rgb[0] = LIGHT_MAX_VAL;
    rgb[1] = (uint8_t)(rgb[0]*215/255);
    rgb[2] = 0;
    break;
  case INIT:
  default:
    rgb[0] = 0;
    rgb[1] = 0;
    rgb[2] = 0;
  }
}

void select_form(uint8_t form){  
  switch(form){
    case MIGHTY:  mp3_play (2); break;
    case DRAGON:  mp3_play (3); break;
    case PEGASUS: mp3_play (4); break;
    case TITAN:   mp3_play (5); break;
    default:      mp3_play (2);
  }

  change_color(form);
  for(uint8_t i=0;i<NUM_OF_LED;i++){
    pixels.setPixelColor(i, pixels.Color(rgb[0]>>1,rgb[1]>>1,rgb[2]>>1));
  }
  pixels.show();
  delay(800);

  change_color(INIT);
  for(uint8_t i=0;i<NUM_OF_LED;i++){
    pixels.setPixelColor(i, pixels.Color(rgb[0]<<1,rgb[1]<<1,rgb[2]<<1));
  }
  pixels.show();

  current_form = form;
  change_state = CHANGE_STATE_INIT;
}

void come_arcle(uint8_t form){
  mp3_play (1);
  change_color(form);

  for(uint8_t i=20;i>0;i--){
    for(uint8_t j=0;j<NUM_OF_LED;j++){
      pixels.setPixelColor(j, pixels.Color(rgb[0]/i,rgb[1]/i,rgb[2]/i));
    }
    pixels.show();
    delay(85);   
  }

  delay(800);   
  mp3_play (6);
}

void change_mighty(){
  if(digitalRead(RISING_PIN) == HIGH){
     mp3_play (7);
  }else{ // RISING
     mp3_play (12);
  }

  change_color(MIGHTY);
  for(uint8_t i=0;i<NUM_OF_LED;i++){
    pixels.setPixelColor(i, pixels.Color(rgb[0]>>1,rgb[1]>>1,rgb[2]>>1));
  }
  pixels.show();
  analogWrite(MOTOR_PIN, MOTOR_MAX_VAL);
  delay(4000);
  analogWrite(MOTOR_PIN, 0);

  if(digitalRead(RISING_PIN) == LOW){
    change_color(RISING);
  }

  for(uint8_t i=0;i<NUM_OF_LED;i++){
    pixels.setPixelColor(i, pixels.Color(rgb[0],rgb[1],rgb[2]));
  }
  pixels.show();
}

void change_dragon(){
  if(digitalRead(RISING_PIN) == HIGH){
     mp3_play (8);
  }else{ // RISING
     mp3_play (13);
  }

  change_color(DRAGON);
  for(uint8_t i=0;i<NUM_OF_LED;i++){
    pixels.setPixelColor(i, pixels.Color(rgb[0]>>1,rgb[1]>>1,rgb[2]>>1));
  }
  pixels.show();
  analogWrite(MOTOR_PIN, MOTOR_MAX_VAL);
  delay(4200);
  analogWrite(MOTOR_PIN, 0);

  if(digitalRead(RISING_PIN) == LOW){
    change_color(RISING);
  }

  for(uint8_t i=0;i<NUM_OF_LED;i++){
    pixels.setPixelColor(i, pixels.Color(rgb[0],rgb[1],rgb[2]));
  }
  pixels.show();
}

void change_pegasus(){
  if(digitalRead(RISING_PIN) == HIGH){
     mp3_play (9);
  }else{ // RISING
     mp3_play (14);
  }

  change_color(PEGASUS);
  for(uint8_t i=0;i<NUM_OF_LED;i++){
    pixels.setPixelColor(i, pixels.Color(rgb[0]>>1,rgb[1]>>1,rgb[2]>>1));
  }
  pixels.show();
  analogWrite(MOTOR_PIN, MOTOR_MAX_VAL);
  delay(4500);
  analogWrite(MOTOR_PIN, 0);

  if(digitalRead(RISING_PIN) == LOW){
    change_color(RISING);
  }

  for(uint8_t i=0;i<NUM_OF_LED;i++){
    pixels.setPixelColor(i, pixels.Color(rgb[0],rgb[1],rgb[2]));
  }
  pixels.show();
}

void change_titan(){
  if(digitalRead(RISING_PIN) == HIGH){
     mp3_play (10);
  }else{ // RISING
     mp3_play (15);
  }

  change_color(TITAN);
  for(uint8_t i=0;i<NUM_OF_LED;i++){
    pixels.setPixelColor(i, pixels.Color(rgb[0]>>1,rgb[1]>>1,rgb[2]>>1));
  }
  pixels.show();
  analogWrite(MOTOR_PIN, MOTOR_MAX_VAL);
  delay(4100);
  analogWrite(MOTOR_PIN, 0);

  if(digitalRead(RISING_PIN) == LOW){
    change_color(RISING);
  }

  for(uint8_t i=0;i<NUM_OF_LED;i++){
    pixels.setPixelColor(i, pixels.Color(rgb[0],rgb[1],rgb[2]));
  }
  pixels.show();
}

void setup() {  
  pixels.begin();
  pixels.show(); // Initialize all pixels to 'off'

  mySerial.begin (9600);
  mp3_set_serial (mySerial);  //set softwareSerial for DFPlayer-mini mp3 module 
  mp3_set_volume (23);  // 0 - 30

  paj7620Init();

  pinMode(MIGHTY_PIN,  INPUT_PULLUP);
  pinMode(DRAGON_PIN,  INPUT_PULLUP);
  pinMode(PEGASUS_PIN, INPUT_PULLUP);
  pinMode(TITAN_PIN,   INPUT_PULLUP);
  pinMode(RISING_PIN,  INPUT_PULLUP);
  pinMode(MOTOR_PIN,   OUTPUT);

  if(digitalRead(RISING_PIN) == HIGH ){
    rgb[0] = rgb[1] = rgb[2] = LIGHT_MAX_VAL;
  }else{
    mp3_play (11);
    change_color(RISING);
  }

  for(uint8_t i=20;i>0;i--){
    for(uint8_t j=0;j<NUM_OF_LED;j++){
      pixels.setPixelColor(j, pixels.Color(rgb[0]/i,rgb[1]/i,rgb[2]/i));
    }
    pixels.show();
    delay(25);   
  }
  for(uint8_t i=1;i<20;i++){
    for(uint8_t j=0;j<NUM_OF_LED;j++){
      pixels.setPixelColor(j, pixels.Color(rgb[0]/i,rgb[1]/i,rgb[2]/i));
    }
    pixels.show();
    delay(25);   
  }

}

uint8_t last_mighty_state  = HIGH;
uint8_t last_dragon_state  = HIGH;
uint8_t last_pegasus_state = HIGH;
uint8_t last_titan_state   = HIGH;

uint8_t mighty_state  = HIGH;
uint8_t dragon_state  = HIGH;
uint8_t pegasus_state = HIGH;
uint8_t titan_state   = HIGH;

uint8_t gesture = 0;

void loop(){

  paj7620ReadReg(0x43, 1, &gesture);
  if((change_state == CHANGE_STATE_INIT) && (gesture == GES_FORWARD_FLAG || gesture == GES_LEFT_FLAG)){
    change_state = CHANGE_STATE_WAITING;
    come_arcle(current_form);
  }else if((change_state == CHANGE_STATE_WAITING) && (gesture == GES_DOWN_FLAG || gesture == GES_RIGHT_FLAG)){
    change_state = CHANGE_STATE_CHANGE;
    delay(200);
    switch(current_form){
    case MIGHTY:  change_mighty();  break;
    case DRAGON:  change_dragon();  break;
    case PEGASUS: change_pegasus(); break;
    case TITAN:   change_titan();   break;
    default:      change_mighty();
    }
    change_state = CHANGE_STATE_FINISH;
  }

  mighty_state = digitalRead(MIGHTY_PIN);
  if(last_mighty_state == HIGH && mighty_state == LOW){
    select_form(MIGHTY);
  }
  last_mighty_state = mighty_state;

  dragon_state = digitalRead(DRAGON_PIN);
  if(last_dragon_state == HIGH && dragon_state == LOW){
    select_form(DRAGON);
  }
  last_dragon_state = dragon_state;

  pegasus_state = digitalRead(PEGASUS_PIN);
  if(last_pegasus_state == HIGH && pegasus_state == LOW){
    select_form(PEGASUS);
  }
  last_pegasus_state = pegasus_state;

  titan_state = digitalRead(TITAN_PIN);
  if(last_titan_state == HIGH && titan_state == LOW){
    select_form(TITAN);
  }
  last_titan_state = titan_state;

  delay(100);
}

 

以上でHYPER DETAIL GEARの改造はおしまいです。長い間お付き合いいただきありがとうございました。

さて、次は何しよう。。。の前に、本業のお仕事がマジで頑張らないとやばそうですので、工作はちょっと控えないといけないかもしれません。一度やり始めると、工作のことばっかり考えてしまうので。やりたいネタは色々あるんだけどなー。