30年以上も昔の話ですが、当時、父親が買った電卓機能付き腕時計に「デジタルインベーダー」が入っていて、夢中になって遊んでいました。最近、カシオの電卓にデジタルインベーダーが復刻されたという記事を見まして、懐かしさから作ってみることにしました。
ルールについてはググればいろいろと出てくるのですが、地元で展示する機会があったので子どもたちにも遊んでもらいやすいようアレンジを加えて下記のようにしました。最近のゲームはすぐに何万点とか何百万点とかになってしまうので、逆に1桁単位で増えていくのも面白いかと思って少ない点数になるようにしました。
・遠い方から6点、5点、4点、3点、2点、1点
・UFO「n」は、どこで当てても10点
・自機は1機(オリジナルは3機だったはず)
・50点とるごとに速くなる
20181010234040.jpg
マイコンは、以前購入した Arduino UNO R3(の互換機)を使いました。正式な Arduino は Amazon でも3,240円しますが、互換機は1個500円くらいなので気軽に使うことができ、配線を間違えて壊してしまったとしてもダメージは少ないです。ただし不良品が混ざっている可能性があるので、予備も合わせて注文しておいたほうが良いかもしれません。
IMG_4546.jpg
表示部分は MAX7219 というICチップで制御されたLEDチューブを使用しました。これも廉価品で、Amazonで2個600円程度で購入しました。念のため2セット買っておきました。今回の「デジタルインベーダー」では2個使っていて、1個目がハイスコアとスコア表示、2個目がゲーム画面になります。8桁表示が可能なもので、ゲーム画面は照準選択1桁、自機残数1桁、敵が攻めてくるエリア6桁、という構成になります。
IMG_4548.jpg
MAX7219 でググったところ、LedControlというライブラリを使用するのが良さそうです。最も参考にさせていただいた記事は下記です。ありがとうございました。
Arduino+MAX7219で8桁7セグLEDを簡単に扱う – Qiita
この記事で紹介されている LEDControl のリンク先は下記のとおりです。
LedControl
ライブラリ概要
GitHub – wayoda/LedControl
LEDチューブを使うには LedControl のオブジェクトを作成してピン番号を割り当てます。

LedControl lc1=LedControl(12, 11, 10, 1);

12 → DataIn
11 → CLK
10 → CS/Load
5V → VCC
GND → GND
それぞれのピンとピンを接続します。4つ目の引数の1はディジーチェーンされているユニットの個数で、今回はディジーチェーンしていないので1を指定しています。(サンプルでは8が指定されていますが、起動時に接続されているユニット数をチェックして正しい数値で動作するので常に8を指定していても良いみたいです)
今回の「デジタルインベーダー」ではLEDチューブを2個使うので、ディジーチェーンで接続して16桁のチューブとして使用することもできるのですが、物理的な配置の都合でそれぞれ別々の制御とすることにしました。2個めの LedControl には、続きの 9, 8, 7 ピンを使用することにしました。

LedControl lc1=LedControl(12, 11, 10, 1);
LedControl lc2=LedControl(9, 8, 7, 1);

配線については基本的にジャンパ線を使用しました。メインのLED表示制御は、Arduinoの12,11,10をLED2に、9,8,7をLED1に接続、それぞれVCCとGNDを5VとGNDに接続します。ゲームに使用するタクトスイッチをブレッドボードに取り付け、3つのスイッチをD4,D3,D2に接続しました。展示するとき周囲の明るさによって見えにくくなりそうだったので、可変抵抗を入れてArduinoのアナログポートで値を読み取り、LedControl を介してLEDの明るさを調節できるようにしました。
LEDチューブの接続には落とし穴があり、配線自体は簡単だったのですが表示がおかしくなってしまう事態が繰り返し発生してしまい悩みました。突然表示が消えてLEDの最大輝度で全セグメントが点灯し続ける(テストモードになってしまったと思われます)、またはメチャクチャな場所のLEDが点灯し文字にならなくなってしまうという状況でした。
MAX7219のデータシートにはリップルノイズ対策として10μFの電解コンデンサと0.1μFのセラミックコンデンサを入れると記述があるという情報があったので入れてみましたが、変わりませんでした。LEDチューブの不良やArduino自体の不良まで疑ったのですが、LEDチューブやArduinoを交換してみても現象はおさまらず何日もたってしまいました。
LEDチューブをよく見てみると、信号を入力するのとは反対側(写真では右側)にディジーチェーンで接続するための信号線が出ていてVCCとGNDがあります。ここから次のユニットへ電気が渡されていくのだろうと考えていたのですが、ふと、このVCCとGNDにも5VとGNDを接続してみたところ、おかしな表示がピタリとおさまりました。
IMG_4549-2.jpg
ゲームを操作するためのタクトスイッチはカラフルなカバーがついたものを使用しました。いちおうここでは、青がスタート、緑色が照準、赤が発射、という役割にしています。
IMG_4549.jpg
音の再生はD13ポートからtone()関数を使用して出力します。たまたまD13が空いていたのでここにピエゾ式の圧電スピーカーを接続しました。ピコピコという音がチープなゲームにマッチしていて良い感じです。展示会対応として音量を調整できるよう可変抵抗を直列に接続しました。
20181010234044.jpg
次にプログラムです。まずは LedControl を使用するための設定をします。[スケッチ]→[ライブラリをインクルード]→[ライブラリを管理]と選択するとライブラリマネージャ画面が表示されますので、「検索をフィルタ」のところに ledcontrol を入力して LedControl を探します。見つかったら LedControl の欄をクリックすると[インストール]ボタンが表示されますので、クリックします。これで LedControl が使えるようになります。
ソースコードでは先頭部分にヘッダファイルのインクルードを書きます。

#include "LedControl.h"

そうしたら、void setup() の中で初期化します。電源投入時はシャットダウンモード(データは保持しているがLEDを点灯させないモード)なので、まずは lc1.shutdown(0, false); としてシャットダウンモードを解除します。lc1.setIntensity(0, 8); はユニットの明るさを8(中間くらい)にします。lc1.clearDisplay(0); でLEDの表示内容をクリアします。

lc1.shutdown(0,false);
lc2.shutdown(0,false);
lc1.setIntensity(0, 8);
lc2.setIntensity(0, 8);
lc1.clearDisplay(0, 8);
lc2.clearDisplay(0, 8);

ハイスコア等の数字を表示するとき右詰めにして桁揃えさせた状態で表示したかったので、桁に対応する数値で除算して余りを取得し、桁ごとに表示するようにしました。具体的には下記のようなコードになりました。9999以上の数字が表示できないようになっていますが、実際ゲームで遊んでみると、どんなに頑張っても200点ちょっとまでしかいかない感じでしたで、これだけあれば大丈夫だと思います。

void disp_myscore() {
  int dvalue = myscore;
  if(dvalue > 9999) dvalue = 9999;
  lc2.setDigit(0, 0, (dvalue % 10), false);
  if(dvalue >= 10){
    lc2.setDigit(0, 1, (dvalue / 10) % 10, false);
  }else{
    lc2.setRow(0, 1, LED_EMPTY);
  }
  if(dvalue >= 100){
    lc2.setDigit(0, 2, (dvalue / 100) % 10, false);
  }else{
    lc2.setRow(0, 2, LED_EMPTY);
  }
  if(dvalue >= 1000){
    lc2.setDigit(0, 3, (dvalue / 1000) % 10, false);
  }else{
    lc2.setRow(0, 3, LED_EMPTY);
  }
}

タクトスイッチ検出は digitalRead(PinNo) を使用しますが、INPUT_PULLUP を指定してプルアップ抵抗の実装を省略しています。1回押すごとに1回発射するという動きにするために下記のような関数を作成して1回押されたことを検出しています。
数値として指定している200の部分は、実際に動かす環境によって適切な値が変わってくるので適宜調整してください。数値が小さいとチャタリング(1回押したのに何度も反応する)ぽくなったり、数値が大きいと押されたことが検出されなくなったりします。

boolean pushed(int pin) {
  long gauge = 0;
  while (!digitalRead(pin)) gauge++;
  if(gauge > 200) return true;
  return false;
}

ゲーム自体の作りとしては、敵が攻めてくるエリアを enemy[0]~enemy[5] までの配列として確保し、普通の敵は 0~9 の数字をそのまま格納、なにもない空間は -1、UFOは -2 としました。Timer2 を使用して一定時間ごとに1桁ずつ左にずれるようにし、新しく出現する敵は乱数で選択するようにしました。オリジナルでは、普通の敵が10個出現するとUFOが出てくるようになっているようなのですが、今回はUFOも乱数で選択されて出てくるようになっています。
自機の照準選択状態を変数として保持し、ボタンが押されたときに配列内の数字と照合して一致するものがあれば消して配列を詰めます。場所によって点数を判定してスコアに加算し、UFOの場合は特別な音を出すようにします。
ゲームオーバーの判定は敵が攻めてくるエリアの左端に数字が入っている状態になったかどうかで判定します。LEDを点滅させてゲーム終了時の簡単な音楽を流し、自分のスコアがハイスコアを超えていたら更新します。ゲームオーバー時の曲といえば葬送行進曲、ということで葬送行進曲をアレンジして入れてあります。
メイキングおよび動作状況がわかる動画を YouTube にアップしました。
Arduino でデジタルインベーダーを作ってみた【電子工作】

カテゴリー: ロボット/IoT

4件のコメント

BIANCHI · 2019年5月23日 07:54

はじめまして。
私も同じような8桁のを買ってNanoに3つ、つなげたのですが表示が不安定で悩んでいました。
こちらの記事を見つけて右側のVCCとGNDも繋いだところ安定しました。
とても参考になりました。

はるこち · 2019年5月23日 11:52

BIANCHIさま
コメントありがとうございます。お役に立てたようで、私としても光栄です。
ドキュメント等のエビデンスがなく、試しにやってみたらうまく行ったというレベルなので、試作品の範囲でしたら大丈夫かもしれませんね。

    BIANCHI · 2019年5月25日 09:20

    Nanoのピンの節約のため、試しにデイジーチェーンでも接続してみましたが、やはり3つ全ての右側のVCCとGNDにも接続しないとダメでした。
    とりあえず上手くいっているので良しとします。

Saeki · 2020年4月29日 23:50

私も表示が点かなかったり不安定になって嫌になってたのですがこの記事で解消できスッキリしました。ありがとうございます。

コメントを残す

アバタープレースホルダー

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください