#contents

* LinuxでGBAゲーム開発 [#ic9b4010]

- [[Linuxから目覚めるぼくらのゲームボーイ!:http://www.amazon.co.jp/Linux%E3%81%8B%E3%82%89%E7%9B%AE%E8%A6%9A%E3%82%81%E3%82%8B%E3%81%BC%E3%81%8F%E3%82%89%E3%81%AE%E3%82%B2%E3%83%BC%E3%83%A0%E3%83%9C%E3%83%BC%E3%82%A4%EF%BC%81-%E8%A5%BF%E7%94%B0-%E4%BA%99/dp/479732564X]]

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

&color(#DD0000){''まだ書き途中であり、適宜補足していく予定です。''};

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

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

- &attachref(mirmo_dance_gba.lzh);
- [[参考書籍「Linuxから目覚めるぼくらのゲームボーイ!」著者のサイト:http://www.skyfree.org/jpn/index.html]]

*** スクリーンショット [#pe3afdcd]
&attachref(mirmo_dance_ss.jpg,nolink);

** メモリマップ [#d26be9db]
|0x0000000|BIOS ROM|
|0x2000000|外部RAM|
|0x4000000|I/Oレジスタ|
|0x5000000|パレット|
|0x6000000|VRAM|
|0x7000000|OBJ属性|

メモリマップがないと、始まりません。とりあえず、ざっくりと開始アドレスだけ書きましたが、より詳しくは[[TOBY Soft Wiki:http://tobysoft.net/wiki/index.php?GBA%2FGBATEK%2FMemory%20Map]]をご覧ください。

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

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

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

ELF形式(参考:[[ELFSection:http://meraman.dip.jp/index.php?ELFSection]])の実行ファイルに対して、下記のようにコマンドを実行すると、利用されているセクションの一覧が見られます。
 $ objdump -h a.out

** リンカスクリプト [#c9fac3e8]
gcc.ls
 OUTPUT_ARCH(arm)
 
 SECTIONS {
   .text 0x2000000 : { *(.text) }
   .data   : { *(.data) }
   .bss    : { *(.bss) }
   .rodata : { *(.rodata*) }
 }

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

- [[GNU LD リンカの使い方:http://www.sra.co.jp/wingnut/ld/ld-ja_3.html]]

** スタートアップルーチン [#jcc0d28c]
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" で、メイン関数アドレスへ飛びます。

&color(#FF0000){個人的にすっきりしないのは、なぜmainはextern指定なしで使えるの?};
&color(#DD0000){''個人的にすっきりしないのは、なぜ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			// 無限ループ

*** 参考 [#caa4823e]
- [[「組み込み」ならではの基礎知識――スタートアップ・ルーチンからハードウェアまで:http://www.kumikomi.net/article/explanation/2003/10kumi/07.html]]

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

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

&color(#FF0000){ここのモジュールは、そのまま使っています。};
&color(#DD0000){''ここのモジュールは、そのまま使っています。''};

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

&color(#FF0000){ここのモジュールは、そのまま使っています。};
&color(#DD0000){''ここのモジュールは、そのまま使っています。''};

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

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

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

&color(#FF0000){ここのモジュールは、そのまま使っています。};
&color(#DD0000){''ここのモジュールは、そのまま使っています。''};

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

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

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

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

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

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

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

----
** 履歴 [#ie34a210]
- 2007/07/29 bokupi 新規作成

トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS