Rust で GBA 開発のとっかかり
![](/2024/05/12/gba-dev-rust/featured_hube58fe58fbfe99bf4633f2d66e622f54_34230_720x2500_fit_q75_h2_lanczos_3.webp)
背景
新しい言語を学ぶキッカケとして、 2015 年頃にほんの少しだけ触っていた GameBoy Advance の開発を Rust で出来ないかと検討していた。
当時 libgba を利用してつくったものは以下。これらを Rust で書き直すことが目標。
jumpingdroid
![図1: ドロイド君が跳ねたり銀の林檎に乗ったりする](/2024/05/12/gba-dev-rust/2024-05-11_20-44-18_jumpingdroid_mb-1_hub6daa238d3b535a717cc341e05e214bb_1891_accbc4438332e65e741b6328bf771fcc.webp)
単純な背景と、複数スプライトの配置を試してみたもの。
キャラクターを動かして何かに乗せる ( ように見せる ) 、というだけのことをやってみた。
jumpingdroid2
![図2: ドロイド君が右へ右へと進んでいく。ボタン押下で色も変わる。](/2024/05/12/gba-dev-rust/2024-05-11_20-57-33_jumpingdroid2_mb-1_hu3aca1eab22b41537ede8b5a64014df0e_2477_eebe7ea2367de1648f30493229575fce.webp)
1 画面に収まらないステージを横にスクロールしながら進んでいくだけのもの。
画面外で次々に進行方向の背景を描画していくことで、キャラクターが進んでいるように見せる、ということをやってみた。
GBA 開発について
GBA の開発は普通のコンピュータ上で行なうようなものとは大分様相が異なる。
OS なんてものはなく、「見渡す限りビットだらけ」である。
There is no operating system, no messing with drivers and hardware incompatibilities; it’s bits as far as the eye can see.
メモリの番地 0x06000000
からが背景画像のデータ領域で、 0x06010000
からはキャラクターのスプライト用のデータ、
個々のスプライトは 0x07000000
から始まる領域で管理される、
というように、 ある特定のメモリ領域のビットを操作したらそれが画面に反映される というのが基本的な仕組み。
![図3: ドロイド君の左下部分の画像が `0x06010040` にある様子](/2024/05/12/gba-dev-rust/2024-05-11_21-43-09_screenshot_hu77976386f2ce1304899a3b16fd0b939d_27489_81c3476c20cf6032f6154c41c2691354.webp)
0x06010040
にある様子jumpingdroid では BG (背景画像)とスプライトの基本的な機能を利用している。
BG
GBA では最大 4 枚の背景画像を利用でき、256×256 ピクセルや 256×512 ピクセルなどのサイズに設定出来る。
![図4: 256×256 ピクセルの BG](/2024/05/12/gba-dev-rust/2024-05-11_22-06-15_screenshot_hu2e0510542be8f47466e358dda1e1fa88_38835_e0399b3ac50ae2dcd87ec0a6bee74439.webp)
GBA の画面は 240×160 ピクセルなので、上記の内実際に表示されるのは中央部分のみ。 スクロール表示の実現の為には、この非表示部分と表示オフセットを上手く利用することが必要になる。
スプライト
スプライトは小さな画像オブジェクトで、個別に移動させたり反転させたりといった操作が可能。 GBA では 8×8 から 64×64 ピクセルで、最大 128 個のスプライトを扱える。
![図5: `0x07000000` に格納されているドロイド君のスプライトデータ。参照先タイルは `0x06010180` にある。](/2024/05/12/gba-dev-rust/2024-05-11_22-15-29_screenshot_hu5585815a96b62dedcd671b9ad9d0700c_50084_5847800b3e5afb49ed4cfe5b56da7a1d.webp)
0x07000000
に格納されているドロイド君のスプライトデータ。参照先タイルは 0x06010180
にある。高速紙芝居
無限ループの中で 各ループ内で画面がどういう状態になるかをひたすら更新していく 、というのが基本的なイメージ。
各オブジェクトの状態とプレイヤーによるボタン入力の状態に応じて高速で画面を書き換えていくと動いているように見えるという、単純な仕組み。
単純ゆえに、例えばただキャラクターを動かすといったことでも、全てゼロから定義する必要がある。
移動や拡大・回転といった基本的な機能は提供されているが、それらをどう利用するかは完全に開発者次第。
agb での開発
今回利用する agb というライブラリでは、 GBA でのゲーム開発に必要な機能をかなり抽象化して扱い易くしてくれている。
スプライトは Aseprite というピクセルアート・アニメーション作成ツールのフォーマットをそのまま利用出来る ( Aseprite は購入するか、自身でビルドする必要がある )。
![図6: Aseprite の編集画面](/2024/05/12/gba-dev-rust/2024-05-11_23-00-28_screenshot_hueffae2e4a303003b105aa3deca7d25fe_19939_3c262d5e25f496368714c8bd4986287d.webp)
const GRAPHICS: &Graphics = agb::include_aseprite!("gfx/sprites.aseprite");
const TAG_MAP: &TagMap = GRAPHICS.tags();
const IDLE: &Tag = TAG_MAP.get("Idle");
const WALKING: &Tag = TAG_MAP.get("Walking");
const JUMPING: &Tag = TAG_MAP.get("Jumping");
BG データは png 等もそのまま扱える。
agb::include_background_gfx!(tiles,
"ff00ff", // 透過色
bg => "gfx/bg.png");
これらはコンパイル時に GBA での利用に適した形に変換されているらしい。
プログラム全体の構造をざっくり書くと以下のようになる:
#[agb::entry]
fn main(mut gba: agb::Gba) -> ! {
// 初期化
let object = gba.display.object.get_managed();
let mut droid_object = object.object_sprite(IDLE.sprite(0));
let mut bg0 = gfx.background(
Priority::P0,
RegularBackgroundSize::Background32x32,
TileFormat::FourBpp
);
// ...
loop {
// フレーム毎に色々計算
// ...
// BG のオフセット更新
bg0.set_scroll_pos((bg_offset, 0));
// BG のタイル状態更新
bg0.set_tile(vram, (bgx, bgy), tileset, tiles::bg.tile_settings[tile_id]);
bg0.commit(&mut vram);
droid_object
// スプライトの位置更新
.set_position((dx, dy))
// スプライト画像更新
.set_sprite(object.sprite(sprite_for_char(ch)));;
object.commit();
}
}
実際のコードは以下:
成果物
mGBA で jumpingdroidr2 を実行している様子が以下。
BG1 のオフセットとタイルが更新される様子を併せて収録している。 ドロイド君の進行方向にステージを書き足していっている様子が観察出来る。
終わりに
普段の開発とは全く異なる考え方が必要になるため面白い。
今回は GBA ソフトの移植を目的とした為に、Rust 自体の学習は必要最低限しかやらなかった。
とっかかりは出来たので、今後細く長くやっていきたい。
agb のリポジトリには例も豊富に提供されており、とても参考になる。