kriwです。この記事はtraPAdventCalendar201624日目の記事です。
今回は多くの人が遊んだことがあるであろうGBA(ゲームボーイアドバンス)でプログラミングして実機で動かすところまで軽く紹介したいと思います。
前日はスーパーファミコンに関する記事が出たかと思いますが、今回はGBAです。
開発言語もアセンブラからCになり、より高級になりました。
環境構築
devkitPro
devkitProという開発ツールをインストールします。GBAのみならず, DS, 3DSなどの開発ツールも入っているみたいです。
macもしくはLinuxの人 はこちらからスクリプトをダウンロードして実行してください。
windowsの方はこちらからdevkitProUpdater-1.6.0.exeをダウンロードすれば多分いけます。
あと、Makefileからmakeできるようにしておいてください。
エミュレータ
VisualBoyAdvanceというエミュレータが一番良いらしい。私はVBA-MというVisualBoyAdvanceの派生版を使用しました。こっちだと動くけどVisualBoyAdvanceを使うと動かないものもあったので、VBA-Mを推します。
ここから自分のOSに合ったエミュレータをダウンロードしましょう
動かしてみる
開発環境が手に入ったので、エミュレータ上でサンプルプログラムを実行してみましょう。
先ほどインストールしたdevkiProからdevkitPro/examples/gba/graphics/SimpleBGScrollディレクトリへ移動します。
そこでmakeをします。
makeによりSimpleBGScroll_mb.gbaというファイルができます。
ビルドしたプログラムをエミュレータ上で実行すると以下の画面が表示されました。
GBAについて
このサイトが非常に参考になります。詳しく勉強したい方はどうぞ。
便利なライブラリが存在しているので簡単なプログラムを書くだけならGBAの仕様を詳しく知る必要はありません。
ここではざっくりと重要そうな概念をあげていきます。
スクリーンサイズ
160x240 pixelです。
FPS
GBAは60fpsで動作します。
VBlank
描画と描画の間のことで、GBAの場合、160行(縦)の間に68行分のVBlankがあり、その間に描画処理を終えておく必要があります。
RGB
GBAではR,G,Bがそれぞれ0 ~ 32までしかありません。
bitmaps, tiled
背景の描画方式です。bitmapsはピクセルを指定して描画するのに対して、tiledはタイルを貼り付ける要領で描画します。
ほとんどの商用ゲームはtiledを使用しており、処理速度の観点から、3Dゲームなどを作るとき以外はbitmapsを使うことはあまりありません。
描画やキー入力のメモリ番地なども細かく指定はありますが、今回は割愛します。
ライフゲームを書いてみる
細かい話は置いておいて、実際にプログラムを書いてみました。
ここからtonclibを用いたサンプルコードがダウンロードできます。 (クリックするとダウンロードが始まります。)
ファイルをダウンロードしたら解凍 -> tonc-code/basic/m3_demo/ にあるファイル全てをdevkitPro/examples/gba/template/source/にコピーして、devkitPro/examples/gba/template ディレクトリからtemplate.cを削除して、m3_demo.cを以下のコードに書き換えたらmakeします。
#include "toolbox.h"
#define SCREEN_W 240
#define SCREEN_H 240
#define W 60
#define H 60
#define CELL_W (SCREEN_W / W)
#define CELL_H (SCREEN_H / H)
int dxy[] = {-1,0,1};
void draw_at(int x, int y, int cell){
if(cell){
m3_rect(CELL_W * x, CELL_H * y, CELL_W * (x+1), CELL_H * (y+1), CLR_LIME);
}else{
m3_rect(CELL_W * x, CELL_H * y, CELL_W * (x+1), CELL_H * (y+1), CLR_BLACK);
}
}
void update_all(int field[2][H][W], int *now){
int i,j,k,l;
int next = ((*now) + 1) % 2;
for(i = 0; i < H; i++){
for(j = 0; j < W; j++){
int sum = 0;
for(k = 0; k < 3; k++){
for(l = 0; l < 3; l++){
//周期境界
int nx = (j + dxy[k] + W) % W;
int ny = (i + dxy[l] + H) % H;
sum += field[*now][ny][nx];
}
}
sum -= field[*now][i][j];
if(field[*now][i][j] == 1){
if(sum == 2 || sum == 3)
field[next][i][j] = 1;
else
field[next][i][j] = 0;
}else if(sum == 3){
field[next][i][j] = 1;
}else{
field[next][i][j] = 0;
}
if(field[*now][i][j] != field[next][i][j]){
draw_at(j, i, field[next][i][j]);
}
}
}
*now = next;
}
void draw_field(int field[2][H][W], int *now){
int i,j;
m3_fill(RGB15(32, 32, 32));
for(i = 0; i < H; i++){
for(j = 0; j < W; j++){
if(field[*now][i][j])
m3_rect(CELL_W * j, CELL_H * i, CELL_W * (j+1), CELL_H * (i+1), CLR_LIME);
}
}
}
void init_field(int field[2][H][W], int now){
field[now][10][10] = 1;
field[now][10][11] = 1;
field[now][10][12] = 1;
field[now][11][11] = 1;
field[now][9][10] = 1;
}
int main()
{
REG_DISPCNT= DCNT_MODE3 | DCNT_BG2;
// Fill screen with gray color
m3_fill(RGB15(12, 12, 14));
int now = 0;
int field[2][H][W] = {0};
init_field(field, now);
draw_field(field, now);
while(1){
vid_vsync();//60fpsに調整
update_all(field, now);
};
return 0;
}
ライフゲームができました。フィールドが縦にはみ出していますがそこ以外は何も気になりません。
解説
GBA特有の処理は
REG_DISPCNT= DCNT_MODE3 | DCNT_BG2;
くらいです。mode3(pixelで描画する)でBG2を選択しています。
次に、ライフゲームに関するコードの解説をします。
#include "toolbox.h"
描画に必要なものがたくさん詰まっています。今回はこの中からm3_rectを使用しました。
void draw_at(int x, int y, int cell)
指定した座標にcell(1なら緑、0なら黒)を描画します。
void update_all(int field[2][H][W], int *now)
フィールドを渡して更新&描画をします。描画は高速化のため、前の世代と後の世代の差分だけ描画するようにしています。
void draw_field(int field[2][H][W], int *now)
現在の世代のフィールドを愚直に描画します
void init_field(int field[2][H][W], int now)
セルの初期配置を決定します。
これらの関数をmain関数で実行しています。
実機で動かしてみる
せっかくGBAのプログラムができたので実機で動かしてみましょう。
ブートケーブルというものによって作成したプログラムをロードさせることができます。
(↓↓↓昔はブートケーブルが同梱されたGBAプログラミングの本が売っていました。)
ですが、この本は入手困難なので、
ブートケーブルを自作してみましょう。
こちらに書いてあったものを丸パクリします。 http://akkera102.sakura.ne.jp/gbadev/index.php?doc.16
mbed LPC1768を使った上記のサイトのやり方を、私がわかる範囲で説明しようと思います。
mbedが初めてでもできるはずなので安心してください。
mbedとは
wikipediaでは
mbed(エンベッド)はARM社[1]のプロトタイピング用ワンボードマイコンおよびそのデバイスのプログラミング環境を指す。
要するに、mbedがあれば簡単にマイコンとかのプログラミングができるということです。
必要なもの
- mbed LPC1768 価格: 7000円くらい
- GBAの通信ケーブル
- GBA/ GBA SP
- ジャンパーコード(メス-メス) * 5
何をすれば良いのか
SPI通信というものをmbed-GBA間で行い mbedからGBAへROMを転送するとゲームが動くようになります。
ワンカートリッジプレイでカセット1つで通信ケーブルをつなぐとみんなで対戦できるゲームもあります。その要領でmbedから通信ケーブル経由でROMを送信してあげるとGBA上でROMを動かすことができます。
SPI通信って?
シリアル通信の一種(1bitずつ逐次データを送るやつ)で複数のデバイスと通信できます。
通信はMaster(mbed)とSlave(GBA)に分かれてMaster(mbed)が通信を制御します。
mbed - GBA をつなぐケーブルを作る。
通信ケーブルを用意します。
用意したケーブルを切断して、外側のゴムを剥いでやると、銅線に包まれた5つのコードが現れるはずです。
それぞれのコードはSPI通信において
青 -> GND
赤 -> MOSI
橙 -> MISO
緑 -> SCLK
茶 -> ????
の役割を果たします。茶はよくわかりませんでした。
それぞれコードから銅線を取り出してジャンパーに突っ込んで、コードが抜けないようにテープで固定してあげます。
テスターで断線していないか念のため確認しましょう。
これでmbedとGBAをつなぐケーブルが完成です。
mbedにSPI通信をするプログラムを書く
https://developer.mbed.org/users/kek/code/gba_multiboot/のmain.cppをダウンロードしてビルドしたらできます。
ビルド方法は、mbedをpcに接続してmbedを開くとリンクのファイルがあるので開いて、メールアドレスを登録 すると、mbedに入っているリンク先のページでブラウザ上のIDEが開けるのでそこでビルドしましょう。
ビルドが成功すると完成したバイナリファイルのダウンロードが始まります。
そのファイルをmbedのファイルシステムにドラッグ&ドロップします。
あと、mbedとPCをUSBケーブルで接続すると勝手に起動してくれます。
mbedにGBAのROMを入れる
mbed上にはLocalFileSystemというものがあり、mbed上のファイルをプログラム上で読み込むことができます。
起動したいROM(*.gba)をさっきと同じようにドラッグ&ドロップして入れてあげます。
さっきビルドしたmbedのプログラムではvolt4.gbaを読み込むことになっているので、起動したいROMの名前をvolt4.gbaにします。
配線
mbedのp5,p6,p7のピンにMOSI(赤), MISO(橙), SCLK(緑), 茶色をp8にさしてあげます。
GND(青)はmbedの左上のGNDに接続してあげます。
ピンの位置は写真にあるあたり
起動する
いよいよ起動です。
↓の流れに沿ってやるとできました。
p8ピンを抜く -> spの電源をoff -> mbedのリセットボタンを押下 -> spの電源をon -> p8ピンを元に戻す -> 起動
起動しました。
↓画面
ライフゲーム動いた pic.twitter.com/fH0YknJAMk
— xss (@kotarou777775) December 22, 2016
まとめ
GBAは過去にプログラミング本が出ているので日本語の情報も豊富で簡単に挑戦することができます。
おかげで私も実機で試すところまでできました。今までmbedとか使ったことがなかったのでとてもいい経験になったと思います。皆さんも低レイヤーな世界への入門として実機でGBAのゲームを走らせてはいかがでしょうか。