レーザーサイトバレルをつくる 〜 ボトルマンでプログラミング Vol.1 〜

お久しぶりのボトルマンです。ボトルマンをプログラミングで強化するためのシステム を作ってみました。ボトルマンでプログラミング。今回はその第一弾、『レーザーサイトバレル』のご紹介です。

開発経緯

皆さまご存知の通り、ボトルマンは共通の特徴として、身体のどこかにボトルキャップを取り付けられるようになっています。

今のところ大多数は頭頂部、たまに側面、という感じです。ここに、マイコンを仕込んだボトルキャップをセットすれば、ボトルマンを問わず、プログラミングによっていろんな能力を付与できるのでは、というのが今回のアイデアです。

実はこのアイデアは、初代ボトルマンの時代からぼんやり考えていました。ただ、初代のボトルマンはキャップを締め付けて本体に固定する方式だったため、キャップ内にマイコンを収めるスペースを確保するのが難しく、断念していました。結果、『ライト&サウンド ボトルマン』は、キャップではない部品を頭部に埋め込む形での実装になりました。

その後、ボトルマンが『DX』にリニューアルされた際、キャップが締め込み型ではなく外側からロックする形での固定に変更になったことで、キャップ型のケースにマイコンを仕込む余裕ができました。さらに、『DX』の素体が(タイプごとの制限はあるものの)デフォルトで様々なオプションパーツを後付けできる仕様になったため、いろんなセンサやアクチュエータの組込みも容易になりました。

ということで、満を持して(?)の開発スタートになりました。マイコン入りのキャップをコアにしたシステム的な構想を考えていたため、作品が1個だけだとプラットフォームにする意味があまりなさそうだったので、とりあえず2個オプションパーツができたところで公開することにしました。その第一弾が今回紹介する『レーザーサイトバレル』で、第二弾は次回更新で公開予定です。

特徴

 

「ボトルマンDX専用マイコン入りコアキャップ」、以下、略して『DXコアキャップ』と呼ぶことにします。ボトルマンDXの『DX』は『ドリンクロス』の略ですが、ここではビジネス界隈でよく使われる『デジタルトランスフォーメーション』の意味で『DX』を使います。プログラミングを使ったボトルマンのデジタル化、という意味で。どうでも良いこだわりですが。

これと今回セットで使用するのが、『レーザーサイトバレル』です。その名の通りレーザーサイトが搭載されており、非常に狙いをつけやすくなります。

また、このレーザーサイトバレルに合わせて、DXコアキャップの天面部分をボタンパーツに換装しています。これにより、ボトルマンの頭頂部または側面部の押しやすい位置にボタンを用意することができます。

 

ボタンを押すことで、一定時間(今回は約6秒間)、レーザーが射出されます。また、ボタンを押してレーザーが射出開始したときと停止したときには、「ピピッ」と効果音が鳴ります。レーザーの射出開始/停止をユーザにわかりやすくお知らせするためのもので、機能上特に必須というわけでもないのですが、これがあるかないかで遊んでいるときの気持ち良さが全然違います。

レーザーサイトの上下方向は、写真中央の穴の奥にあるネジで微調整可能です。ボトルマンDXは現状、三段階の高さが設定されているので、使用するボトルマンに合わせてレーザーサイトの向きを変えられるようにしておきました。

なお、バレル自体の高さは、厚みの違う電池ボックスのカバーを交換することで対応しています。また、カバー裏にはキャップ射出時の機体のブレを抑制するための滑り止めを取り付けています。

ハードウェア解説

ここからはものづくり面での解説になります。まず、DXコアキャップのケースの設計はこんな感じです。

配線したときにコードを横に流して固定できるように、かつ、天面部分をボタンにするとか電池にするとかのカスタマイズができるように、三層構造にしています。実際、今回のレーザーサイトバレルでは天面部分をボタンパーツに換装しています。

3Dプリンタで出力した結果はこんな感じです。LEDを仕込んだときに中の発光が確認できるよう、PETGのクリアフィラメントを使用しました。

マイコンはSeeed社の展開しているXIAOシリーズが丁度収まるように設計しています。Arduino版(Arm-Cortex-M0+)、ESP32版、RP2040版、nRF52840版を展開してくれているので、好きなマイコンを選んで使用することができます。

私は今のところ、RP2040版(Raspberry Pi Picoと同じ)を使用しています。

電源は、使用するセンサやアクチュエータとセットで外部から供給する形にしています。無理にケース内に収めようとすると、玩具としてはできれば使いたくないリチウムイオンポリマー充電池を使わざるを得なくなるのと、それでも供給できる電力が限られてしまうので、それが拡張パーツの制限になってしまうのは避けたいと考えたためです。

今回のレーザーサイトバレルでは、コンパクトさを重視して、単五電池×2本を電源として採用しました。Maxでも3Vなので、レーザーサイトの明るさも暗めにはなってしまうのですが、子供の玩具である以上、光は弱い方が良いかなとも思っています。

続いて、レーザーサイトバレルの方です。設計はこんな感じです。

ボトルマンDXのバレルの規格に合わせて作るための微調整がなかなかに大変でした。公式のバレルパーツと同様、弾道補正用のバレルを継ぎ足しできるようにしています。

 

続いて、今回使用した電気系の具材のご紹介です。

今回のポイント、レーザーモジュールです。この手の光源は高電圧が必要という勝手なイメージがあったのですが、調べてみたら低電圧(3V)で充分使えるものがありました。

続いて、「ピピッ」という効果音を鳴らすための圧電スピーカーです。上記のリンク先の部品は私が使用したもとは異なるのですが、直径のサイズ感はこれに近いです(もうちょっと薄いものを使用)。複雑な音を鳴らすならMP3音源を再生するためのモジュール(DFPlayer等)と普通のスピーカーが必要になってしまいますが、「ピピッ」ぐらいの効果音ならこれで充分です。

あとは、先でも説明した単五電池2本を入れるためのケースと、DXコアキャップ側に搭載するためのタクトスイッチ、リード線ぐらいです。

回路図はこんな感じです。機能がシンプルなので、配線もシンプルです。

実際の配線はこんな感じです。

ソフトウェア解説

プログラムはMicroPythonで作成しています。いつもは記事の最後に掲載するのですが、今回は短いのでここに全文載せてしまいます。

##########################################################################################################
"""
利用可能なPIN
               USB
26 ... A0/D0         SV(VBUS)
27 ... A1/D1         GND
28 ... A2/D2         3V3
29 ... A3/D3         3 ... MOSI/D10
 6 ... SDA/D4        4 ... MISO/D9
 7 ... SCL/D5        2 ... SCK/D8
 0 ... TX/D6         1 ... RX/CSn/D7
 
以下は割り当て済み;
11 ... NeoPixel (Power 0: OFF, 1: ON)
12 ... NeoPixel (Control)
16 ... 三色LED-G ( 0: 0N, 1: OFF)
17 ... 三色LED-R ( 0: 0N, 1: OFF)
25 ... 三色LED-B ( 0: 0N, 1: OFF)
"""
import utime
from machine import Pin, PWM
from neopixel import NeoPixel

LED_ON_BOARD_ON  = 0
LED_ON_BOARD_OFF = 1

PIN_LED_G = Pin(16, Pin.OUT)
PIN_LED_R = Pin(17, Pin.OUT)
PIN_LED_B = Pin(25, Pin.OUT)
PIN_NP_ON_BOARD_POWER = Pin(11, Pin.OUT)
PIN_NP_ON_BOARD_COLOR = Pin(12, Pin.OUT)

np_on_board = NeoPixel(PIN_NP_ON_BOARD_COLOR, 1)

##########################################################################################################

PIN_SW = Pin(26, Pin.IN, Pin.PULL_UP)
PIN_POINTER = Pin(3, Pin.OUT)
buzzer = PWM(Pin(4))

STATE_INIT      = 0
STATE_READY     = 1
STATE_TARGETING = 2

state = STATE_INIT

LASER_WAIT_MS  =  200
LASER_KEEP_MS  = 5000

state_change_point_ms = 0

SW_ON  = 0 # LOW
SW_OFF = 1 # HIGH
sw = prev_sw = SW_OFF

LASER_OFF = 0
LASER_ON  = 1

NOTE_HIGH = 1760
NOTE_LOW  =  440

def set_state(new_state):
    prev_state = state
    state = new_state

def get_state():
    return state

def buzz(buzzer,frequency,sound_duration,silence_duration):
    # Set duty cycle to a positive value to emit sound from buzzer
    buzzer.duty_u16(int(65536*0.2))
    # Set frequency
    buzzer.freq(frequency)
    # wait for sound duration
    utime.sleep(sound_duration)
    # Set duty cycle to zero to stop sound
    buzzer.duty_u16(int(65536*0))
    # Wait for sound interrumption, if needed 
    utime.sleep(silence_duration)

def sound_on():
    buzz(buzzer,NOTE_HIGH,0.05,0)
    utime.sleep_ms(20)
    buzz(buzzer,NOTE_HIGH,0.05,0)

def sound_off():
    buzz(buzzer,NOTE_LOW,0.05,0)
    utime.sleep_ms(20)
    buzz(buzzer,NOTE_LOW,0.05,0)

##########################################################################################################

# 基板上のRGB LEDを無効化
PIN_LED_R.value(LED_ON_BOARD_OFF)
PIN_LED_G.value(LED_ON_BOARD_OFF)
PIN_LED_B.value(LED_ON_BOARD_OFF)

# 基板上のNeoPixelの有効化
PIN_NP_ON_BOARD_POWER.value(1)

sound_on()

while True:
    sw = PIN_SW.value()
    now = utime.ticks_ms()
        
    if state == STATE_INIT:
        if prev_sw == SW_OFF and sw == SW_ON:
            state = STATE_READY
            state_change_point_ms = now
                        
    elif state == STATE_READY:
        if prev_sw == SW_OFF and sw == SW_ON:
            state_change_point_ms = now        
            
        if now - state_change_point_ms >= LASER_WAIT_MS:
            sound_on()
            PIN_POINTER.value(LASER_ON)
            np_on_board[0] = (0,255,0)
            np_on_board.write()
            state_change_point_ms = now
            state = STATE_TARGETING
            
    elif state == STATE_TARGETING:
        if prev_sw == SW_OFF and sw == SW_ON:
            state = STATE_READY
            state_change_point_ms = now

        if  now - state_change_point_ms >= LASER_KEEP_MS:
            sound_off()
            PIN_POINTER.value(LASER_OFF)
            np_on_board[0] = (0,0,0)
            np_on_board.write()
            state = STATE_INIT

    prev_sw = sw

    utime.sleep_ms(10)

ポイントは、ボタンを押すとレーザーが射出されて、一定時間後に勝手に停止するようにしていることです。ボタンを押すごとに射出の開始/停止を切り替える仕様にもできたのですが、使用電源が単五電池なので出来るだけ無駄なレーザー射出を止めたかったのと、何より、子供が遊ぶものでレーザーが出っ放しになるのは良くないと思いましたので、この仕様にしました。

あと、Raspberry Pi Pico + 圧電スピーカーで音を鳴らす方法については、こちらの記事を参考にさせて頂きました。

おまけ

以上で解説としてはおしまいですが、実は今の形・仕様に至る前段階のプロトタイプが存在します。それがこちらです。

パッと見でわかる特徴として、

  • レーザーサイトの向きが固定
  • 射出口がバレルを継ぎ足しできる構造になっていない

ということがあります。

作り始めたときは、「レーザーで狙いをつければ百発百中だろう」と思っていたのですが、実際作って遊んでみると、まずレーザーが良い感じにボトルターゲットに当たらない。ここでレーザーサイトの向き調整が必要なことに気がついたのですが、レーザーがちゃんと当たっているときでも、これが結構、ターゲットに弾が当たらない。ここに至ってようやく、「狙いをつけること」「弾が真っ直ぐに飛ぶこと」は全く別の話であることに気がつき、弾道補正用バレルを後付け可能な形に修正しました。

また、弾が真っ直ぐ飛ばない理由ですが、これは「ピピッ」という音を鳴らすための仕様にありました。実は当初設計では、ボタンがDXコアキャップ側ではなくバレル側に付いていて、ユーザがキャップを少し押し込むことでレーザーを射出、キャップを発射したら射出停止、という仕様になっていました。個人的にはこの仕様がスマートで気に入っていたのですが、

この仕様を実現するために、ボトルマンとの接合部に取り付けていたボタン。キャップを少し押し込んだときにこのボタンが押されるようになっているのですが、このボタンがリリースされるときの反動が、思った以上に弾道に影響を与えていて、横方向にブレる原因になっていることがわかりました。そのため、ボタンが弾道に影響を与えないようにするため、今のDXコアキャップ側にボタンを設ける仕様になりました。

これまでのライダー玩具系の作品では、一回作り切った後に再度作り直すということはほぼなかったのですが、ボトルマンについてはやってみて初めて気づくことが多く、手間でも作り直した方が良いと判断して、今の形になりました。

まとめ

以上、DXコアキャップ&レーザーサイトバレルのご紹介でした。当初、「ボトルマンでレーザーサイトは流石に反則かなあ…」と思っていたのですが、ボトルマンの先輩とも言えるビーダマンではオフィシャルパーツでもレーザーサイトが出ていたので、おそらくセーフ(?)です。

レーザーサイトをつける改造自体はビーダマンの時代から特に珍しいことではなく、実際ボトルマンでも既製品の取り付けをやっている人は既におられましたが、ボトルマンDX専用のバレルパーツとして作り上げている人は多分まだいなかったんじゃないかなと思います。

ちなみに、「狙ったところに確実に当たるなら遊びにならん」というご意見もあるかもしれません。それはその通りなのですが、「狙ったところに確実に当たる」というのは、シンプルにメチャメチャ気持ち良いです。対戦ではなく一人で黙々遊ぶ人にはとてもオススメな改造だと思います。

次の改造作品は既に出来上がっているので、近いうちに更新予定です。これはこれでまた違う気持ち良さがあるので、ご期待くださいませ。