見出し画像

Battle Marine 開発記 Part 2

RaspberryPi Zero 2W で動作する自作ゲーム機「VGS-Zero」のローンチタイトル「Battle Marine」の開発状況を記します。


1月1日〜1月5日(過去分)

今のところ、1月1日から11日連続で日々ツイートを継続中です。

1月6日 メインBGM作曲

そろそろ効果音を入れていきたかったのですが、効果音を入れるためには音楽が必要です。

音楽無しで効果音を入れてしまうと音量バランスが想定外になってしまい、効果音の演出が意図した通りにならないことがよくあるので。

そのため、効果音を入れる時は、仮当てでも良いので音楽を鳴らすのがMUSTです。

普段は過去に創ったゲームのBGMをそのまま仮当てで利用することが多いのですが、その方式だと作曲するのが面倒くさくなってしまい「もうこの曲でいっか」と手抜きしてしまいがちなので、新曲を入れてみました。

曲の方は作曲からVGS化まで1日で終わらせたので少しバランスが悪いかもしれませんね。(2部構成だと単純過ぎるので、イントロ、間奏を入れる程度の軽いリテイクはするかもしれません)

1月7日 自機ショットの実装

弾を撃てるようになるといよいよゲームっぽくなってきます。

ここで割と重要なのが「この状態で遊び尽くす」ことかもしれません。

演出の強化実装(ブラッシュアップ)などはついつい後回しにしてしまいがちですが、後になってからコードを修正するのは結構大変なので、開発を進めずここで一旦立ち止まり、現在の状態のゲームを遊び尽くし、思いついた演出面のフラッシュ・アイディアをカウボーイ・コーディングでどんどん実装していきます。

そのカウボーイ・コーディングが後になってからのコード修正難度を上げていく訳ですが…(都度、リファクタをしてなるべくそうならないように気をつけます)

1月8日 VGS-Zero(本体)のスプライト拡張

VGS-Zeroのスプライトは、FCS80のスプライト機能をそのまま持ってきたのですが、8x8のキャラクタパターンしか表示できないので、16x16のキャラクタを表示したい場合、4枚のスプライトを使う必要があります。

スプライトの総数(OAMレコード数)は256枚ですが、流石にそれではすぐにスプライト不足に陥りそうな気がしたので、一定の条件で複数タイルを1枚のスプライトとして扱えるようにしてみました。

1月9日 当たり判定実装

無事、24x24ピクセル(3x3タイル)の爆破パターンを1枚のスプライトで表示できるようになったので、ショットで敵を撃墜できるようにしてみました。

撃墜時の爆破も内部的には敵と同じテーブルで管理していますが、爆破は敵を巻き込むこと(誘爆)ができ、それをコンボできる形にしようと思っています。

この仕様は、私がかつてリリースした連魂というゲームで採用したコンボ・システムですが、結構気に入っています。

このコンボ・システムの最大のネックは「重い」ということです。

現状、Battle Marineの敵テーブルの最大レコード数は32にしていますが、仮に画面上に16体の敵と16個の爆破オブジェクトがあった場合、誘爆の判定処理に16x16回(256回)の当たり判定処理が必要です。

自機ショット数は最大8個で、その当たり判定処理が8x32回(256回)、自機と敵の当たり判定が32回なので、1フレームあたり544回の当たり判定が必要になります。

昨今のCPUなら全然問題無いのですが、16MHz の Z80 でその処理が捌ききれるのか、やや怪しいところです。

1月10日 敵弾実装 & 当たり判定のH/W化

敵弾(これも内部的には敵と同じオブジェクト)を実装してみたところ、やはりガッツリ処理落ちしてしまいました。

ただ、この処理落ちは「環境依存しない処理落ち」なので、処理落ちが発生することを期待したゲームデザインが可能です。

最近のゲームでは少ないですが、昔のアーケードゲームや家庭用ゲームではこういうゲームデザインが結構あったように思われます。(グラディウス3とか)

一応、インラインアセンブラで組めば処理落ちせずに組むことはできるのですが、もうちょっとベースラインを上げたいところなので、当たり判定処理のハードウェア化などを試みてみました。

Battle Marine の開発はVGS-Zeroのシステムテストを兼ねているので、VGS-Zero本体へ手を入れるのはもちろん、破壊的変更すらガンガン許容してVGS-Zero本体の品質向上を目指しています。

1月11日 三角関数のハードウェア化

上記ツイートで「Z80では一般的に無理だった256 angleの高精度な自機狙い」と言及している点について、何故無理だったのか少し補足しておきます。(私はバリバリの文系なので数学的な部分の解説がしっかり出来ているかやや怪しいですが…)

まず、「自機狙い」をするには2キャラクタ間の角度を求める必要があります。

自機狙いの実装に必要な情報

この角度を求めるには三角関数のatan(アークタンジェント)が必要で、atanはC言語の標準ライブラリ(math.h)にて提供されています。

ただし、atanだと例外パターンなどもあり使い勝手が悪いので、その辺をよしなに計算してくれるatan2という便利な関数もmath.hにあり、以下のような式で2点間((sx, sy)と(dx, dy)の間)の角度(ラジアン値)を求めることができます。

double angle = atan2(sy - dy, sx - dx);
// ※angle(ラジアン値)を元に sin, cos (-1.0 〜 1.0 の範囲) で進行方向を求める

8bit座標系(0, 0)〜(255, 255)の場合、atan2の呼び出しパターン数は65536(256×256)あり、angleのサイズが 1 byte であれば、64KBのテーブルが必要になります。

文章を分かりやすくするために「8bit座標系(0, 0)〜(255, 255)」としていますが、atan2の場合符号付き(signed)にする必要があるので正確には「8bit座標系(-128, -128)〜(127, 127)」です。

細かい補足

Z80でも、バンク切り替えを駆使することで64KBのテーブルを持って参照することも不可能ではありません。

しかし、例えばMSXであれば、そのためだけにメガロムにして64KB(8個のEEPROM!!)をatan2テーブルとしてROMを持たせるか、128KB RAMを前提にして内64KBをatan2テーブルとしてリザーブするなど、非常にコストが掛かる対応が必要になるので不可能ではないにしても現実的ではなかったものと思われます。

もちろん、Z80で作られたゲームでも自機狙いが実装されているものもありますが、それらは基本的に座標系のビット数を減らして粗くすることで、atan2テーブルを小さくしているものと思われます。

  • 7bits (128, 128) = 16,384 bytes (まだキツイ)

  • 6bits (64, 64) = 4,096 bytes (まだ少しキツイがギリギリ大丈夫)

  • 5bits (32, 32) = 1,024 bytes (この辺が実用的だと思われる)

  • 4bits (16, 16) = 256 bytes (割と余裕)

座標系を粗くすると、自機を狙いきれない自機狙いになりますが、4bits座標系でも求まったangle±1の範囲で細かく回転しながら弾を連射することで、ほぼ確実に自機に当てることができるアメリカンスタイル(?)なソリューションもあるかと思われます。(ゲームの難度は凶悪になりますがw)

または、テーブル方式よりも処理コストが掛かると思われますがDDA等の別のアルゴリズムで自機狙いを実装することも出来ます。(恐らくこれが現実的かなと思われます)

VGS-Zeroの本体(64bit ARMv8)では64KBのテーブルは余裕で持てるので、64KBのatan2テーブルをカーネル側に持たせ、I/Oポートでそのテーブルを参照できる機能を実装しました。

この記事が気に入ったらサポートをしてみませんか?