2016年09月10日

Arduinoベースのゲーム機:Gamebuino、Tiny Arcade/Tiny Screen他

Arduboyのことを調べていたら、他にもArduinoベースのゲーム機が出ているのを知ったのでメモしておきます。

ゲーム機のようなものをまとめてみる - inajob's blog

中でも、GamebuinoはディスプレイはNokia 5110のLCDで解像度は84×48ドットと、Arduboyよりさらに低いですが、色々なゲームが既に作られています。

Games - Gamebuino Wiki

ArduboyでGamebuinoのゲームを流用するためのライブラリも既に作られており、パックマンやインベーダーが実際に移植されていました。

Arduboy meets Gamebuino?! - Arduboy / Development - Community

Gamebuino ライブラリの移植 - 日本語 - Community

余談ですが、Gamebuino用のインベーダーゲームの作者は、今回作ったインベーダーゲームのキャラクタデザインで参考にしたPixel Invadersの作者さんでした。


いろいろなゲーム機の中では、個人的には、96x64ピクセルのカラーOLEDを使ったTinyScreenに魅力を感じますが、$75とやや高価ですね。

TinyScreen Video Game Kit - TinyCircuits

Arduinoベースではありませんが、ESP8266を用いたものもあります。NodeMCU2.2インチTFT LCDを組み合わせたもののようです。
NodeMCUなので、プログラミングはLuaを使うことになります。

WiFiBoy.Org

他には、Pocket C.H.I.P.というさらにパワフルなゲーム機もあります。
こちらはCPU周りはRaspberry Piと同程度でOSもLinuxです。

が、まあここまで強力なものが欲しければ、スマホやタブレットでいいのでは、という気もします。
ラベル:Arduboy
posted by boochow at 16:47| Comment(0) | Arduino | このブログの読者になる | 更新情報をチェックする

2016年09月06日

インベーダーゲームを作ってみる(4)陣地の破壊

今回は、前回触れた「インベーダーゲームで強く印象に残った処理」のその2、「陣地が敵弾にだんだん崩される様子」の処理です。

陣地が敵弾や自機弾で崩れていく処理はどのように行っているのか、YouTubeのビデオを見ながら考えました。
そして、おそらくこうだろう、と推測した処理が以下です。

(1)弾の当たり判定は、弾の移動先のピクセルの有無で判定する。
(2)当たりと判定したら、弾を中心に爆発の画像を描画する。この際、黒いピクセルは描かず、白いピクセルだけを描く(ORで描画)。
(3)次に、爆発の画像を消去する。このときは、先程描いた白いピクセルの部分だけを、黒いピクセルで描く(NOT ANDで描画)。結果、陣地が爆発の画像の形で型抜きしたようになる。
iv08.png

これを繰り返すことで、陣地がだんだん削られていきます。
陣地そのものの処理は、ゲーム開始時に画面に絵として描画するだけです。

今回作ったインベーダーゲームは、自機弾・敵弾ともに上記の処理を行っており、陣地が8×6ピクセルしかない割には、結構雰囲気のある崩れ方を表現できています。

なお、当たり判定を画面上のドットの有無で行うというのは、一般的には誤判定が起こりやすいため、あまり使われません。
今回は陣地への当たり判定に使っていますが、敵弾と自機弾が衝突した場合も、陣地に衝突したと誤判定して弾が消滅します。
実は、敵弾と自機弾の衝突については専用の判定処理は行っておらず、上記の誤判定をそのまま弾同士の衝突処理として用いています。
ラベル:Arduboy
posted by boochow at 23:50| Comment(0) | Arduino | このブログの読者になる | 更新情報をチェックする

2016年09月05日

インベーダーゲームを作ってみる(3)インベーダーの移動処理

かつてマイコン少年だったころ、インベーダーゲームで強く印象に残った処理が2つありました。

(1)グラフィカルな敵が多数動いている。処理が大変そう
(2)陣地が敵弾にだんだん崩される様子がリアル。あらかじめ用意した画像を使っているのではなさそう

今回は、このうち(1)のインベーダーの移動処理を扱います。

インベーダー群は、左右に行進しつつ、インベーダーが画面の端まで行くと、一段下へ移動します。
画面最下端へインベーダーが到達したらゲームオーバーです。

従って、移動処理では

(1)画面左右端に到達したインベーダーが居るかどうかの判定
(2)画面最下端に到達したインベーダーが居るかどうかの判定

を行う必要があります。

また、画面の端まで到達できるのは生きているインベーダーだけなので、これらの判定処理は、全ての(生きている)インベーダーについて行う必要があります。
また、「どんなゲームだったのか」で書いたとおり、インベーダーは一度に一匹しか動きません。

これらのことを踏まえ、今回はインベーダーの移動処理を以下のように実装しました。

(1)インベーダーの座標は、全体を一括して扱う(一匹ごとの座標は持たない)
(2)インベーダーの生死はフラグ(の配列)で判定する(ブロック崩しと同じ)
(3)インベーダーを一匹移動させる都度、画面左右下端への到達をチェックする
(4)インベーダーがすべて移動し終わったときに、行進方向の変更や下降の処理を行うかどうか判定する

なお、(1)のインベーダーの座標は全体の「現在位置」「次に動くべき位置」で表し、個別のインベーダーの位置は「次に動かすインベーダーの番号」で表現します。
図にすると下のようになります。この図ではインベーダーは右から左へ移動しています。
iv07.png

インベーダー全体としては(cur_left, cur_top)から(nxt_left, nxt_top)へ移動するのですが、実際には0番〜(nxt_update -1)番までが移動済みで、nxt_update版〜49番までが現在位置に留まっています。

インベーダーの番号は左下が0番で、右へ進むごとに+1、最後の49番は右上のインベーダーです。
オリジナルのインベーダーゲームの映像をよく見ると、最初に動き出すインベーダーは左下で、そこから右隣が順次移動し、次は下から2列目の左端から動き始めていますので、そのような順で番号を振りました。

インベーダーが端に到達したかどうかは、インベーダーを1匹移動させるごとに、そのときの描画座標でチェックします。
(オリジナルのインベーダーでは、画面の端にドットがあるかどうかで判定していたらしいです。)

コードはGitHubで公開していますが、上の説明に対応する部分を載せておきます。

まずインベーダーを表すデータ構造です。

struct aliens_t {
boolean exist[ALN_NUM];
uint8_t alive; // number of aliens alive
int16_t cur_left;
int16_t cur_top;
int16_t nxt_left;
int16_t nxt_top;
int16_t v; // left move or right move
uint8_t nxt_update; // which invader will be moved at the next tick
uint8_t pose; // index num for iv?_img[]
int16_t bottom; // be updated in aliens_draw
boolean move_down; // down-move flag (updated in aliens_draw)
boolean touch_down; // an alien touched bottom line flag
int8_t status;
uint8_t hit_idx; // which invader is been hit
} g_aliens;


そして、移動ルーチンが以下です。
動かすべきインベーダーが見つかれば(最初のif文)、その番号をnxt_updateに代入して終わりです。
49番まで行っても見つからなければ、生存しているすべてのインベーダーを移動し終えたので、最下端に到達したか(touch_down)の確認、X座標の更新、左右端へ到達した場合はY座標の更新を行います。
void aliens_move(struct aliens_t *a) {
uint8_t u = a->nxt_update;
do {
u++;
} while ((u < ALN_NUM) && (!a->exist[u]));

if (u < ALN_NUM) {
a->nxt_update = u;
}
else { // all aliens have moved
if (a->bottom >= SCRN_BOTTOM)
a->touch_down = true;
for (u = 0; (u < ALN_NUM) && (!a->exist[u]) ; u++);
a->nxt_update = u;
a->pose = (a->pose + 1) % 2;
a->cur_left = a->nxt_left;
a->nxt_left += a->v;
a->cur_top = a->nxt_top;
if (a->move_down) { // this flag had been set in aliens_draw()
a->move_down = false;
a->v = -a->v;
a->nxt_top = a->cur_top + ALN_VMOVE;
a->nxt_left = a->cur_left + a->v;
}
}
}

一匹のインベーダーを描画する処理は以下の通りです。ほとんど3種類を描き分ける処理だけですが、最後のところで左右端への到達判定(move_down)と、インベーダー群の中で最大(最下端)のY座標の保存(bottom)を行っています。
void aliens_draw(struct aliens_t *a) {
if (!a->exist[a->nxt_update])
return;

uint8_t row = A_ROW(a->nxt_update);
uint8_t col = A_COL(a->nxt_update);
int16_t x = a->nxt_left + col * (ALN_W + ALN_HSPACING);
int16_t y = a->nxt_top + row * (ALN_H + ALN_VSPACING);
uint8_t *img;

switch (row) {
case 0: img = (uint8_t *)aln1_img[a->pose]; break;
case 1:
case 2: img = (uint8_t *)aln2_img[a->pose]; break;
case 3:
case 4: img = (uint8_t *)aln3_img[a->pose]; break;
}
DRAW_BITMAP(x, y, img, ALN_W, ALN_H, WHITE);

if (((x <= SCRN_LEFT) && (a->v < 0)) ||
((x >= SCRN_RIGHT - ALN_W) && (a->v > 0)))
a->move_down = true;

a->bottom = max(a->bottom, y + ALN_H);
}
ラベル:Arduboy
posted by boochow at 02:03| Comment(0) | Arduino | このブログの読者になる | 更新情報をチェックする

2016年09月04日

インベーダーゲームを作ってみる(2)Arduboy向けの画面設計

Wikipediaによると、初代のインベーダーゲームは白黒で、画面を縦に使い、解像度は横224×縦260ドットだったそうです。
上下のスペースを得点表示と残機表示に使っています。
iv01.png
(画像はWikipediaより)

Arduboy用の画面は白黒128×64ドットですので、もちろんそのままでは表示できません。
ちなみにゲームボーイの画面は4階調160×144ドットだったそうなので、それよりさらに低スペックです。
2:1という横長のアスペクト比をどう活かすかが難しいところです。

ペイントソフトでドット絵を描きながら、悩んだ末に下図のような画面設計としました。
iv02.png

縦が64ピクセルですので、アスペクト比を4:3とするなら横86ピクセル程度となりますが、わずかに広げて横88ピクセルをゲーム画面に使うことにしました。
残りはスコア表示と残機表示に使っています。

ゲーム画面の中身ですが、インベーダー一匹を横5ドット×縦3ドット、インベーダー同士の隙間を横2ドット、縦3ドットとしました。
従って、インベーダーは横7×縦6ドットの中に一匹ということになります。
ちなみにオリジナルでは、インベーダーは16×16ドットの中に一匹だったようです。

インベーダーは沢山いるほうが本物の雰囲気に近づけるように思いましたので、一匹あたりの表現力は捨てて、インベーダーの数を重視しました。
インベーダーのデザインは、オンラインで公開されている「Pixel Invaders」を参考にしています。(少しだけ変更しましたが。)

Yoda's Video Arcade - Play 8-Bit Retro Game Pixel Invaders - free online retro game | free online game 8-bit style

オリジナルのインベーダーゲームは横11匹×縦5行ですが、一列減らして10匹×5行にしました。
これでも、編隊全体としては横68ドット×縦27ドット必要になります。
iv03.png

インベーダーの移動距離は、横方向は1ドット、縦に下がるときは3ドットとしました。
Wikipediaの画像などを見ると、オリジナルの移動距離は横2ドット、縦8ドットだったようです。
iv04.png


インベーダー以外のキャラクタの移動は、UFOと自機は1ドット、自機弾と敵弾は2ドットです。


敵弾は、オリジナルでは3種類あるということでしたので、3種類×2通りのグラフィックスを作成しました。(ここはちょっとこだわりポイントです。)
幅1ドットだとさすがに表現力に限界があるので、横2ドットにしています。
オリジナルでは、敵弾は横3ドット縦8ドットを4ドットずつ移動させているように(ビデオを見る限りでは)思われます。

また、自機弾も敵弾も陣地に当たると爆発しますので、そのパターンを用意しています。
敵弾の爆発パターンは下図に載せていませんが、2×2ドットの正方形です。
iv05.png


最後にUFOですが、倒したときに点数がUFOに重なって表示されます。
「UFOの得点」はインベーダーゲームの攻略の非常に大きなポイントだったらしいので、点数の表示は再現したいところです。
UFOは7×3ドットにしたので、3桁の数を表現するにはドット数が足りないのですが、かなり無理やり「50・100・150・300」を表現してみました。
iv06.png
ラベル:Arduboy
posted by boochow at 17:09| Comment(0) | Arduino | このブログの読者になる | 更新情報をチェックする

2016年09月03日

インベーダーゲームを作ってみる(1)どんなゲームだったのか

picovaders.png


先日MFT2016で購入したArduboyですが、作ったゲームがブロック崩しの移植だけでは寂しいので、Arduboyをターゲットにしたインベーダーゲームを作ってみました。

もう一応完成していて、コードは以下のGitHubで公開しています。

boochow/picovaders

HTML5用のシミュレータはこちらです。(移動はカーソルキー←→、弾発射はCTRLキー)

picovaders.html


ここでは備忘録を兼ねて、中身の解説を書いておこうと思います。

まず「インベーダーゲームの分析」について情報収集しました。
ゲームそのものについては、Wikipediaや、作者の西角友宏氏のインタビュー記事が詳しいです。

スペースインベーダー・今明かす開発秘話――開発者・西角友宏氏、タイトー・和田洋一社長対談 - 日経トレンディネット

インベーダーゲーム開発秘話! - 週刊アスキー

スペースインベーダー - Wikipedia

Space Invaders - Wikipedia, the free encyclopedia

インベーダー発案

上の記事にもあるように、インベーダーゲームがブロック崩しから発想を得て作られた、というのは有名な話です。

しかし、実際には当時のハードウェアでたくさんのキャラクタを動かすのは大変です。
そのために行われたプログラミング上の工夫が、結果的にインベーダーゲームの独特の画面や、数々の裏技を作り出しているようです。

そのような、インベーダーゲームの実装が垣間見える情報は「2ちゃんねる」が参考になります。

■ 「インベーダー」をトコトン追及したい ■

ここにいろいろ書かれていることの中から、当時のプログラムを知るうえで参考になる情報を引用してみます。

・インベーダは、左下のヤツから1int(1/60秒)に1匹ずつ移動する。
 ゆえに、インベーダーが少なくなると移動が速まる。
・インベーダーの弾は3種類ある。画面上同時に存在するのはこの3つ。
 A.左右非対称にトゲが付いているもの
 B.左右対称にトゲが付いているもの
 C.波形のもの
 弾Aは、砲台に一番近い列から発射される。
 弾B・Cは、あらかじめ決まった列から順次発射される
・UFOは25秒おきに出現する。左右どちらから出るかは、自機のショット
 回数(奇数偶数)で決まる。インベーダーが7匹以下だと出ない。
・UFOの得点は、自機のショット回数で決まる。
 50 50 100 150 100 100 50 300 100 100 100 50 150 100 100 (以降ループ)
・UFOの出現中は、弾Cは発射されない。つまり、この時は同時に2発しか出ない。
・面クリアするごとにインベーダーの初期位置が下がるが、9面をクリア
 すると2面の配置に戻る。
・インベーダの消滅音は毎回微妙に異なる。これは基板上の発振回路のタイミング
 に依存しいるため。(希に2回鳴ることがあるのもこのせい)

インベーダーは、最初に出現したら、まず右方向に動きます。
これを間違えると、名古屋のときに、方向がおかしくなります。

最下段のインベーダーの最初の出現位置は、ビーム砲台から、

 1面 : −10段目
 2面 : −8段目
 3面 : −6段目
 4・5・6面 : −5段目
 7・8・9面 : −4段目

となります。

そうすれば、名古屋の際、4・5・6面は、
方向が逆になるのも再現されます。

敵の弾、相殺できるのとできないのがあるけど何か法則はあるの?

 当たり判定の順番の問題だったような。
 自弾移動ー>当たり判定ー>消滅  が先か
 敵弾移動ー>当たり判定ー>消滅  が先かという。

因みに、当たり判定は描画データをグラフィック画面とANDする事で
判定していますので、1ドット単位で正確です。


インベーダーは、下から上に向かって波打つように動きますが、一回の描画処理が1/60秒で終わるように、一度に1匹ずつ動かしていたのですね。
スタート時のインベーダーは全部で55匹ですから、1秒近くかけて全体を動かしていることになります。

1/60秒という制約があるのは、昔のブラウン管は電子線が画面を毎秒60回、左から右、上から下へスキャンすることによって描画しており、表示内容の変更は下まで行った電子線を上まで戻す間に行っていたからです。
今では、あまり実態のない制約だと思いますが、ゲームでは今でも1/60秒という周期を便利に使っているようです。
Arduboyも、画面更新速度(フレームレート)は最速で1/60秒ごととなっています。

ラベル:Arduboy
posted by boochow at 22:18| Comment(0) | Arduino | このブログの読者になる | 更新情報をチェックする
人気記事