TFTシールドを装備したことで、昔の8ビットパソコン並みの表示装置が備わったArduino UNOですので、昔遊んだゲームを移植(?)して遊んでみようと思います。
もう30年も昔のこと、「BASICマガジン」という雑誌が有って、時々購読していました。その記事の中に、ユーザが投稿したゲームを掲載するコーナがありました。当時はCD-ROMとかのメディアも付いていなかったし、まして「URLからダウンロードできます」なんてのも無かった時代なので、紙面に掲載されたBASICのプログラムを自分でパソコンに打ち込んで遊んでいました。
そんなゲームの中でいまだに覚えているゲームが有って、久しぶりに遊んでみたいな〜と思いました。
当時の8ビットパソコンで、PC-8001(NEC製)という機体があったのだけど、筆者が持っていたのはFM-7(富士通製)と言う機体でした。両方とも、N-BASICおよびF-BASICという言語(統合実行環境)を採用していて、方言はあったものの同じBASICなので、PC-8001用として投稿されたゲームをFM-7に移植して遊んだのはもちろん、プログラムの作り方を勉強させてもらった作品があったのです。
そのゲームは、BASICの1ページプログラム(※)で、ゼビウス(c)namcoみたいなゲームになっていました。
※1ページプログラム: 当時のPC-8001は、横40桁×縦25行の文字表示だったのだけど、BASICのプログラムを画面に表示した時に、1ページ(40×25文字)で表示が収まるコンパクトなプログラムの事。あれやこれやと詰め込んで、大きなプログラムにするのが「大作」ではなくて、コンパクトなプログラムでもちゃんとゲームが遊べる、と、プログラミングの在り方についても学ばせていただきました。
今回作るのは、このBASICマガジンに掲載されたユーザ投稿作品のオマージュなのですが、そもそも、大元のゼビウスについても、以下よりご覧になっておいてください。
ゼビウスを検索する(別ウインドウが開きます)
※この先、そもそもゼビウスを知らないと、今回作るゲームの意味が解らないと思うので、ゲーム画面やキャラクタなどを見ておいてください。30年前のゲームですが、NINTENDO 3DSなどにも移植された人気の名作なので、ご存知の方も多いはず。
掲載された雑誌(BASICマガジン)がさすがに手元には無くて、作者名も失念してしまい明記できないのが申し訳ありません。当然、「許可取り」とかもできていないので恐縮です。「パクッた?」のではなく、「オマージュ」と言うことでご理解くださいm(1__)m。
昔のアキュムレータマシン(MC6809 @8MHzだのZ80 @4MHz)で、かつ、BASICで動いていたゲームだから、Arduinoにできないはずは無いと思ったのだけど、やってみると結構大変てした。
そりゃそうだよ〜。当時8ビットパソコンのリソース全てを制御できるように作られた統合環境(BASIC)に対して、こっちはTFTの駆動から始まって、文字列の描画ルーチン、音楽(BGM)の再生回路・プログラム、と、全部自前なんだから。
でも、何とか形になったと自負しているので、以下、ご覧ください。
ゲームをデザインして行きます。最終的なプログラム内の変数名で解説してあるので、プログラムを解析する時の参考としてください。
自機の操作は、タッチした方向に動き、タッチしている間移動し続けるインターフェイスとします。
操作するのに画面の真ん中をタッチしてしまうと、肝心なゲーム画面が見えなくなってしまうので、画面の両サイドタッチだけで操作できるデザインにしたいと考えました(下図)。
画面の縦方向を5分割(tft_SIZV/5)して、上から順に、上移動、斜め上移動、横移動、斜め下移動、下移動、と定義します。横方向にも2分割(tft_SIZH/2)して、左移動と右移動としました。こうすることで、画面の左右端に水色で示した領域をタッチすることで操作することが可能で、「ゲーム画面を隠してしまうことが無い」のが利点です。
タッチ領域が左・右に分かれているので、基本的には、左手で左のタッチ、右手で右のタッチ操作をするスタイルとなります。4線抵抗方式タッチパネルの原理上、以下の様な制約が生じます。
4線抵抗方式タッチパネルに関しては、「#10-10 タッチパネル(座標・筆圧)」にて必要な関数を作成済みです。ただ今回は、正確なタッチ座標は不要、筆圧検知も不要等、ゲーム用にチューニング(高速化)して使いました。
ILI9328の描画速度的にはほとんど変わらないので、16ビットカラー(65,536色)を使ったビットマップグラフィックスで作っても良かったのだけど、今回はオリジナルのPC-8001版プログラムに敬意を表して、キャラクタ(文字)ベースのゲームに仕上げます。
と言って、いきなり、「文字'A'が自機、文字'O'が敵機です。障害物は文字列"===="です」などと言う所までレトロな文字ベースにするつもりは無くて、「#11-1 TFTシールドに文字を描画する」の時に作った文字フォントの代わりに、ゲーム用のキャラクタフォントを組み込んで使います(下図)。
フォントが変わるだけで単なる文字なので、「#11-1 TFTシールドに文字を描画する」にて作成したtft_print()関数がそのまま使えます。文字毎に色を変えることもできるので、色付きのゲームにすることが可能です。
※実際には、次節で解説するキャラクタVRAM対応など、今回のゲーム用にチューニングしたtft_print()関数を使っていて、「そのまま」流用できた訳ではありません。
ゲームの説明やスコア表示なども行いたいので、ASCIIコードの空白(' '、0x20)からアクサングラーブ('‘'、0x60)までは、前に作ったフォントをそのまま使います。この範囲には、良く使う記号、数字、アルファベット大文字が含まれています。アルファベット小文字'a'(0x61)から後ろは、ゲーム用のキャラクタフォント(上図の如く)に置き換えて使います。
従ってプログラム内では、自機の表示は文字'a'、敵機の表示は文字'b'みたいなことになっています。プログラムを解析する場合はご注意召されよ。
敵機は障害物なんてお構いなしに自機へ近づいて来るのですが、この時、通り道に障害物が有った場合、一旦は障害物を上書きしてしまいます。通り過ぎた後で、上書きする前の障害物の文字フォントを再生しなければならないのですが、これを実現するためには画面内に配置された文字コードを保持する、キャラクタVRAMという仕組みが必要となります。
tft_print()関数では30桁×40行の表示領域でしたので、ArduinoでキャラクタVRAMを作ろうとすると30桁×40行×1バイト=1,200バイトのデータ領域が必要となってしまいます。RAM 2KバイトしかないArduinoには、とても捻出できません。
そこで、ILI9328のグラフィックRAM(GRAM)の一部をキャラクタVRAMとして借用することにしました。
前の節で検討した様に、画面の左・右端にタッチ操作用領域を持つことにしました。この領域には障害物を出現させず、自機や敵機も侵入できないようにしておくと、この場所にデータ(キャラクタVRAM)を配置することが可能です。
上図の様に、左・右端の一部にごみが表示されたように見えているのが、キャラクタVRAMです。
左・右端にキャラクタVRAM(タッチ領域兼用)を設けたことで、その間に表示可能な文字数は、24桁×40行となりました。……左・右3桁ずつをキャラクタVRAM(タッチ領域兼用)に割り当て。
キャラクタVRAMを読み出すために使用するtft_getpix()関数は以下の通りです。
uint16 tft_getpix (
int ix,
int iy)
{
// get pixel from point (ix, iy)
// return: pixel
uint16 dat[2];
// set point
tft_write (0x20, ix);
tft_write (0x21, iy);
tft_read (0x22, dat,2);
return (dat[1]);
}
実際には、インラインアセンブラを使って高速化して使っています。アセンブラ版はソースコード(ardious.ino)でご確認ください。
電源投入後とゲーム終了後に表示される、トップ画面をデザインしました(下図)。
上のデザインと異なり、背景色は黒にしてしまいましたが、ほぼ、デザイン通りに再現しました。実際のゲーム画面でご確認ください。
ハイスコアはベスト3までを保持しますが、Arduinoをリセットしてしまうと消えてしまいます。「#define GAM_EEP」を用意してあるので、必要に応じてEEPROMのアドレスを指定してください。
#define GAM_EEP 0x300 // high score record address
// ↑デフォルトではコメントされています。
GAM_EEPが#defineされていると、ハイスコアをEEPROMに書き込む様になっています。こうしておくと、リセットしても電源を落としても、不揮発メモリEEPROMにハイスコアが残ります。プログラム先頭付近にコメントしてあるので、デフォルトではEEPROMにデータを書き込みません。
使用する場合は、EEPROMにデータを書き込んでしまいますので、お使いのATmega328で未使用のEEPROMアドレスを指定してください。書き込むデータ量は、指定したアドレス(GAM_EEP)からsizeof (int32)×3=12バイトです。
ゲームですからね……、BGMも欲しいですよね❓
前にplay_mml()関数を作ってあって、Arduinoで音楽を演奏することはできるのですが、音楽を演奏するのに16ビットタイマ(TC1)のPWM出力D9(OC1A)端子が必要となります。この端子、TFTシールドではデータバスと4線抵抗方式タッチパネルの制御端子に割り当たっていて、使用不可です。
そもそもTFTシールドには、ほとんど空き端子は残っていないのですが、5V信号が直結されていて使えないSDカードとのSPIインターフェイス信号4本は別な用途に転用可能です。ATmega328の端子機能表を眺めてみると、転用可能なSPI端子の中にOC2A(D11)、8ビットタイマ(TC2)のPWM出力端子がありました。
play_mml()関数をTC1→TC2へ変更すれば、BGMを演奏できそうです。
play_mml()関数の改定(TC1→TC2変更)については、「#5-8 MMLで音楽を奏でる」をご覧ください。
こんなこともあろうかと、バックグラウンドで演奏できるように改造しておいたのが、今回も大活躍です。ゲームをプレイしながら、BGMを鳴らすことが出来ます。
TFTシールドはArduino UNOに上からぴったりと嵌まっており、空き端子と言えども、引き出すのは至難の業です(↓)。
今回引き出すのが必要なのは、D11(MOSI,OC2A)とGNDの2本です。幸いにも、SPI端子とマルチプレクスされている端子なので、UNOのICSPピンヘッダから引き出すことが出来そうです。ただし、このピンヘッダもTFTシールドの下に隠れていますので、ちょっと工夫が必要です。
上図左上側の様なメスのデュポンワイヤを用意して、ラジオペンチなどを使ってプラスティック部分を割って取り除き、90°折り曲げたものが右上側となります。金属部分がむき出しで、ショートするとまずいので絶縁テープで巻いて使います(上図右下側)。
絶縁テープ(下図)と言うのは、ぱっと見、「茶色いビニルテープ?」と、思うかもしれませんが、違います。
ビニルテープだと、裏面にのりが付いていて物に張り付くのですが、ただの「のり」なので、暫くするとベタベタとして剥がれてしまいます。絶縁テープには「のり」はついておらず、従って絶縁テープには物に張り付く能力はありません。絶縁テープをぎゅっと伸ばして、絶縁テープ同士を密着させるとピタッとくっついて固定することが出来ます。
何十年か前に買ってあった遺物を探し出してきました。ホームセンタとかに売っていると思います。
実際にUNOのICSPピンヘッダからD11(MISO,OC2A)およびGND端子を引き出した様子が下図左側、そこにTFTシールドを装着したところが下図右側です。
ICテストクリップなどをお持ちの方は、それを使うのが楽だと思います。
引き出した端子をサウンド出力回路(下図)に接続すると、イヤホンまたはアンプ内蔵スピーカから音が鳴ります。
個人的には好みではありませんが、パッシブブザーに接続しても、BGMを演奏させることが出来ます。ただ、パッシブブザーの音は甲高くて耳障りなので、お勧めしません。上図の様にサウンド出力回路を繋いだ方が綺麗な音で楽しめます。
完成したプログラムをリンクしておきます。
完成プログラム:ardious.ino
TFTシールドを装着したArduino UNOにコンパイル&書き込んでください。必要に応じてオプションの音声信号を引き出して、サウンド出力回路を接続してください。
シリアルモニタはデバッグ出力用に活かしたままにしてあるのですが、使いません。すべて、TFT画面のタッチだけで操作できます。
実際にプレイした時の動画です(↓)。
Arduinoから引き出したサウンド出力からアンプ内蔵スピーカに接続して、動画撮影時に実際に演奏されていたBGMをそのまま使っています。和音になっていない単音のゲームミュージックなんて、懐かしすぎるでしょう?
バンバン弾を撃って、飛来する敵を破壊するシューティングゲームとか、綺麗なビットマップグラフィックスのキャラクタが動き回ったりとか、凝り始めるとキリのない世界だし、すぐにArduinoの限界を突破してしまうでしょう。8ビットパソコン時代のキャラクタゲームに留め置いたおかげで、そこそこゲームらしいものを作ることが出来た様に思います。
久しぶりに、レトロなゲームで遊びました。単純だけど、これはこれで癖になる楽しさがあります。〜〜ただ単に、昔懐かしさが手伝っているだけかも?当時を知らない今時の方には、「なんだコレ?」って首をかしげたくなるようなゲームかも知れないですね……。でも返って、そういう方にこそ遊んでみて欲しいな〜(1^^)っ。