LinuxでGBAゲーム開発

上記の書籍を参考に、GBA上でミニゲームを作ってみました。ただ、ゲーム作りは手段であり、目的は低位層プログラミングに対する理解を深めることです。

まだ書き途中であり、適宜補足していく予定です。

ダウンロード

バイナリと、一部ソースコードを置きます。参考書籍からほとんど手を加えていないようなソースコードは、載せていません(ライセンスが不明のため)。著者のサイトより、ダウンロードしてください(2007/07/29現在、履歴部分からダウンロードできます)。

前述の事情により、ソースファイルは歯抜けになっています。そのため、あくまで参考程度としてください。このソースファイルからバイナリをビルドすることは、あまり考慮していません。実行してみたい場合は、バイナリ版を落としてください。←ソースもバイナリもまとめて下記アーカイブ内にあります。

スクリーンショット

&attachref(mirmo_dance_ss.jpg,nolink);

メモリマップ

0x0000000BIOS ROM
0x2000000外部RAM
0x4000000I/Oレジスタ
0x5000000パレット
0x6000000VRAM
0x7000000OBJ属性

メモリマップがないと、始まりません。とりあえず、ざっくりと開始アドレスだけ書きましたが、より詳しくはTOBY Soft Wikiをご覧ください。

作成したコードは、外部RAMに置くことになります。このさい、実行時に各オブジェクトを、メモリのどの位置に配置するか設定するのが、リンカスクリプトです。オブジェクトファイルのセクションごとに、配置場所を指定します。

実行ファイルはELF形式となりますが、とりあえず覚えておくべきセクション名は、次のとおりです。

.textメインとなる命令コード
.data初期化済変数
.bss未初期化変数
.rodata*定数

ELF形式(参考:ELFSection)の実行ファイルに対して、下記のようにコマンドを実行すると、利用されているセクションの一覧が見られます。

$ objdump -h a.out

リンカスクリプト

gcc.ls

OUTPUT_ARCH(arm)

SECTIONS {
  .text 0x2000000 : { *(.text) }
  .data   : { *(.data) }
  .bss    : { *(.bss) }
  .rodata : { *(.rodata*) }
}

簡単なリンカスクリプトの例です。開始アドレスを0x2000000と指定すると、あらゆるファイル(ワイルドカード*による)の.textセクションを、順番に配置していきます。その後、.data、.bss、.rodata*セクションと続きます。

スタートアップルーチン

main関数を備えたCソースをコンパイル、アセンブルしたオブジェクトだけでは、実行するのに問題があります。それは、CPUがmain関数のアドレスがわからないためです(先頭にある機械語を順次実行するとうまくいかないのは、何故だろう。何となく想像は付くけど、厳密に答えられない)。ソースコードでは、main関数を先頭に書いたつもりでも、アセンブルした後では、様々な宣言などにより、main関数のアドレスは後ろにずれます。そこで、スタートアップルーチンと呼ばれるアセンブラファイルを用意し、main関数アドレスへのjumpを指定します。そして、そのスタートアップルーチンのオブジェクトファイルを、先頭に配置します。アセンブラで書かれたコードなので、余計なコードが先に入り込むことがありません。では、このスタートアップルーチンが、先頭に配置されることはどうやって保証されるかというと、ビルド時の入力順です。

$ ld-arm -o mirmo_dance.out -T gcc.ls crt0.o mirmo_dance.o \
  `gcc-arm -print-libgcc-file-name`

試しに、crt0.o(スタートアップルーチン)とmirmo_dance.o(main関数を含むオブジェクトファイル)の順序を入替えると、実行開始できない実行ファイルが出来上がるはずです。

以下は、crt.sの例です。

.text
	bl	main

"bl main" で、メイン関数アドレスへ飛びます。

個人的にすっきりしないのは、なぜmainはextern指定なしで使えるの?

main関数の前処理や後処理もできます。具体的には、割込み処理先の指定があります。以下は、crt0.S(拡張子Sが大文字なのは、cpp処理を有効にするため)です。

#define INT_VECTOR	0x03007FFC	// 割り込み処理アドレスの格納先
.extern int_handler

.text
	ldr	r0, =int_handler	// レジスタにユーザー割り込み処理アドレス
	ldr	r1, =INT_VECTOR		// 割り込み処理アドレスの格納先
	str	r0, [ r1 ]		// r1 に r0 の値を格納

	bl	main
loop:
	b	loop			// 無限ループ

参考

基本要素

GBAゲーム開発における、基本的な要素を列挙します。

スクリーン関連

GBAの画像表示モードには、いくつか種類があります。色数が多いモードは、レイヤー化ができないなど、いろいろ制限があります。そこで、タイルモードと呼ばれる、8x8の画像配置による方法を使います。タイル表示というと、昔懐かしいRPGの2D移動フィールドがわかりやすいと思います。もちろん、普通の一枚絵でもタイルモードは使えます。その絵を8x8になるように分割し、それぞれの画像を個別のタイルとして定義すれば良いのです。手間がかかりますが、タイルモードでないと、受ける他の制約が大きいので、タイルモードを利用します。

ここのモジュールは、そのまま使っています。

スプライト関連

背景画像ではなく、アクションゲームのキャラクターのように、ドット単位で動かしたい画像があります。8ドット単位の動きでは、あまりに粗雑すぎます。そこで、スプライトという仕組みがあります。8x8のタイルを組み合わせて、8x16、8x32、64x64などのスプライトと呼ばれる画像オブジェクトを作成できます。このスプライトについて、座標などの属性を指定できます。

ここのモジュールは、そのまま使っています。

用意する画像

画像は、1ドットのRGBを2バイトで保存する形式になっています。そのため、通常の画像形式から、GBA用に変換する必要があります。まず、画像編集ソフトで作成した画像を、256色以下に減色して、RAW形式で保存します。そして、パレットファイルを別途保存します。このとき、パレットは最初の1色が透明色(黒)、2色目が白になるようにしてください。そうしないと、透過部分がおかしくなったり、画像自体が大きく乱れたりします。

RAW形式の画像ファイルは、参考書籍に付属したmaketileプログラムで、Cソースのcharの配列に変換します。また、パレットファイルは、同じく付属のmakepalプログラムで、変換します。

文字表示

そもそもデフォルトでは文字がありません。そのため、フリーのフォントを使い、GBA用の画像データに変換します。各文字は、タイルとして扱います。描画開始位置と、書きたい文字列を指定することで、文字を描画することができるモジュールが、参考書籍にて用意されています。

ここのモジュールは、そのまま使っています。

割り込み

スタートアップルーチンで設定した割り込み処理です。後述するタイマのオーバフローや、キー入力、DMA完了通知など、ハードウェアからの要求によるものがほとんどです。割り込み処理は、メインの処理より優先度の高い別の流れで処理されます。ユーザープログラムでは、専用のハンドラ関数を用意し、そのなかに処理を記述します。割り込み通知を受けるには、あらかじめ割り込みを有効にする設定をする必要があります。マスタと各割り込み個別の設定があります。

タイマー

GBAでは、合計4つのタイマを使うことができます。これらは、連結してより長い時間を計測することもできます。

タイマは、カウンタを指定された初期値から順次加算し、65535もしくは0に達したとき(つまりオーバーフローになったとき)、割り込みが発生します。このオーバーフロー割り込みが発生するまでの、カウンタアップは、1,64,256,1024サイクル(いずれかを設定できる)と決まっており、さらに初期値を設定することで、オーバーフロー割り込みが発生するサイクル数を調整できます。

なお、タイマはサウンド関連のところでも利用するので、予め考慮する必要があります。

キー入力

キー入力は、割り込みで通知されます。キーの状態は、レジスタの値を見て判断します。どのキーが押されたら、割込み通知するか、を設定できます(or条件、and条件も)。

サウンド

サウンドは、画像のときと同じように、WAVEファイルを、GBA用に変換します。変換プログラムは、参考書籍付属のmakegbaを使います。WAVEデータを、タイマ0もしくはタイマ1(いずれかを選択)のオーバーフローごとに、FIFOバッファに送ります。同時に、FIFOバッファに入っていたデータは、PVM回路という音を出す回路に渡されます。つまり、このタイマの間隔が音の標本周波数(サンプリングレート)に合わさることで、滑らかに音を再生できるのです。

DMA

とはいえ、FIFOバッファへのデータ転送をずっと続けるのは、CPUパワーをかなり消費します。そこで、DMA転送を使います。DMA転送を使えば、CPUはデータの転送元と転送先をDMAコントローラーに教えるだけで、良いのです。FIFOバッファの転送を終えるたびに、割り込み通知がくるので、どれくらいデータが転送されたか把握することもできます。必要な量を転送し終えたら、タイマを止めましょう。バッファ中のゴミが流れ込むと、耳障りな音が流れます。


履歴


添付ファイル: filemirmo_dance_gba.lzh 1458件 [詳細] filemirmo_dance_ss.jpg 1021件 [詳細]

トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2008-07-01 (火) 10:57:35