ゲームを作ろうと思い立ったはいいものの、何から始めればいいんだろう…。cocos2d-xでスマホゲームを作る?Unityで3Dバリバリのゲームを作る?いまいちパッとしませんね。そうです、なんだか複雑すぎるのです。
そんなあなたにピッタリなのがファミコンのゲームです。もう30年前のハードですから、きっと単純ですって。たぶん。だって8ビットですよ。
環境構築
今回はOS Xで開発をすることにします。まずはアセンブラ、コンパイラを準備します。
アセンブラはnesasmを使います。オブログ — なあ藤村くんファミコンプログラミングをやろうじゃないか[1]を参考にmakeしてください。
コンパイラはcc65を使います。なぜコンパイラも準備するかというと、まだできる限りアセンブラを書きたくないからです。簡単なほうがいいですよね :)
% brew install cc65
とりあえず動くものを書いてみる
ビルド手順はライブラリ使用例 - cc65 @ wiki - アットウィキを参考にしてください。
まずはBGにチェック模様を描いてみます。
#define REGIST_PPU_CTRL1 (char *)0x2000 #define REGIST_PPU_CTRL2 (char *)0x2001 #define REGIST_PPU_STS (char *)0x2002 #define REGIST_SPR_ADR (char *)0x2003 #define REGIST_SPR_GRA (char *)0x2004 #define REGIST_SCROLL (char *)0x2005 #define REGIST_ADR (char *)0x2006 #define REGIST_GRA (char *)0x2007 #define REGIST_DMA (char *)0x4014 void NesMain(void) { int i, j; byte v_stat, h_stat; const char palettebg[] = { 0x0f, 0x11, 0x21, 0x30, 0x0f, 0x11, 0x21, 0x30, 0x0f, 0x11, 0x21, 0x30, 0x0f, 0x11, 0x21, 0x30 }; while (!(*REGIST_PPU_STS & 0x80)); *REGIST_PPU_CTRL1 = 0x00; *REGIST_PPU_CTRL2 = 0x00; *REGIST_ADR = 0x3f; *REGIST_ADR = 0x00; for (i = 0; i < 16; i++) { *REGIST_GRA = *(palettebg + i); } *REGIST_ADR = 0x20; *REGIST_ADR = 0x0; v_stat = h_stat = 1; for (i = 0; i < 30; i++) { for (j = 0; j < 32; j += 4) { *REGIST_GRA = h_stat ? 0x01 : 0x03; *REGIST_GRA = h_stat ? 0x01 : 0x03; *REGIST_GRA = h_stat ? 0x01 : 0x03; *REGIST_GRA = h_stat ? 0x01 : 0x03; h_stat = h_stat ? 0 : 1; } if (i % 4 == 0) { h_stat = h_stat ? 0 : 1; } } *REGIST_PPU_CTRL1 = 0x0; *REGIST_PPU_CTRL2 = 0x1e; while (1); } void NMIProc(void) { }
ファミコンでBGを描画するにはまずネームテーブルに書き込む必要があります。0x2000-0x2007がPPUのI/Oレジスタです。PPUメモリデータ(0x2007)にパターンテーブル番号を書き込むことでネームテーブルに書き込むことができます。*1
ファミコンの画面は1枚あたり8x8のタイルパターンが32x30個あります。*2 4x4の四角を配置しましたが、縦横の比率が異なるため、上下に余りがあります。この画面を複製してスクロールさせる場合、4x4の四角ができない部分ができてしまいます。もう少し工夫が必要みたいです。
次は画面をスクロールさせてみます。
void NesMain(void) { int i, j; byte v_stat, h_stat; byte bgx = 0, bgy = 0; const char palettebg[] = { 0x0f, 0x11, 0x21, 0x30, 0x0f, 0x11, 0x21, 0x30, 0x0f, 0x11, 0x21, 0x30, 0x0f, 0x11, 0x21, 0x30 }; while (!(*REGIST_PPU_STS & 0x80)); *REGIST_PPU_CTRL1 = 0x00; *REGIST_PPU_CTRL2 = 0x00; *REGIST_ADR = 0x3f; *REGIST_ADR = 0x00; for (i = 0; i < 16; i++) { *REGIST_GRA = *(palettebg + i); } *REGIST_ADR = 0x20; *REGIST_ADR = 0x0; v_stat = h_stat = 1; for (i = 0; i < 30; i++) { for (j = 0; j < 32; j += 4) { *REGIST_GRA = h_stat ? 0x01 : 0x03; *REGIST_GRA = h_stat ? 0x01 : 0x03; *REGIST_GRA = h_stat ? 0x01 : 0x03; *REGIST_GRA = h_stat ? 0x01 : 0x03; h_stat = h_stat ? 0 : 1; } if (i % 4 == 0) { h_stat = h_stat ? 0 : 1; } } *REGIST_ADR = 0x24; *REGIST_ADR = 0x0; v_stat = h_stat = 1; for (i = 0; i < 30; i++) { for (j = 0; j < 32; j += 4) { *REGIST_GRA = h_stat ? 0x01 : 0x03; *REGIST_GRA = h_stat ? 0x01 : 0x03; *REGIST_GRA = h_stat ? 0x01 : 0x03; *REGIST_GRA = h_stat ? 0x01 : 0x03; h_stat = h_stat ? 0 : 1; } if (i % 4 == 0) { h_stat = h_stat ? 0 : 1; } } *REGIST_PPU_CTRL1 = 0x0; *REGIST_PPU_CTRL2 = 0x1e; while (1) { while (!(*REGIST_PPU_STS & 0x80)); *REGIST_SCROLL = bgx; *REGIST_SCROLL = bgy; *REGIST_DMA = 0x07; bgx++; if (bgx == 255) bgx = 0; bgy++; if (bgy == 240) bgy = 0; } }
最後にスプライトを表示させて完成です。ちゃんと円を描くようにしてみました。
書き殴ったものをここにおいておきます。
GitHub - akiym/nes-circle: exapmle #1
ところでまだコントローラーを握っていませんね。まだまだ長い道のりになりそうです…。