「ファミコンソフトの作り方」の編集履歴(バックアップ)一覧はこちら

ファミコンソフトの作り方」(2012/03/06 (火) 21:47:43) の最新版変更点

追加された行は緑色になります。

削除された行は赤色になります。

#contents() *&spanid(hello){「hello kikakubu」の作り方} &bold(){1.まずcc65をダウンロードする。} http://www.cc65.org/ Download&bold(){->}available for download.の -[[cc65-win32-2.13.2-1.zip>ftp://ftp.musoftware.de/pub/uz/cc65/cc65-win32-2.13.2-1.zip]] -[[cc65-nes-2.13.2-1.zip>ftp://ftp.musoftware.de/pub/uz/cc65/cc65-nes-2.13.2-1.zip]] -[[cc65-atmos-2.13.2-1.zip>ftp://ftp.musoftware.de/pub/uz/cc65/cc65-atmos-2.13.2-1.zip]] &bold(){2.全て展開し、中身をC:\cc65に移動する。} -cc65というフォルダは自分で作る。 -「フォルダの上書きの確認」が表示されたら「はい」を選ぶ。 &bold(){3.メモ帳に以下の内容を書き、test.cという名前でデスクトップに保存。} #highlight(c){{{ #include <conio.h> int main (void) { clrscr(); cprintf("hello kikakubu"); while(1) { } return 0; } }}} &bold(){4.メモ帳に以下の内容を書き、sakusei.batという名前でデスクトップに保存。} #highlight(dos){{{ PATH c:\cc65\bin;%PATH% set CA65_INC=c:\cc65\include set CC65_INC=c:\cc65\include set LD65_CFG=c:\cc65\cfg set LD65_LIB=c:\cc65\lib set LD65_OBJ=c:\cc65\obj set CC65_HOME=c:\cc65 cc65 -t nes %1.c ca65 -t nes %1.s ld65 -t nes %1.o nes.lib atmos.lib -o %1.nes }}} &bold(){5.コマンドプロンプトを開く。} スタートメニューのすべてのプログラム-アクセサリ-コマンドプロンプトを選ぶ。 &bold(){6.cd デスクトップと入力し、Enterを押す。} &bold(){7.sakusei testと入力し、Enterを押す。} &bold(){8.test.nesが作成されるので、ファミコンエミュレータで開く。} *コントローラチェックソフトの作り方 &bold(){1.メモ帳に以下の内容を書き、test.cという名前でデスクトップに保存。} #highlight(c){{{ #include <conio.h> #define JOYPAD (char*)0x4016 #define BTN_A 0x80 #define BTN_B 0x40 #define BTN_SELECT 0x20 #define BTN_START 0x10 #define BTN_UP 0x08 #define BTN_DOWN 0x04 #define BTN_LEFT 0x02 #define BTN_RIGHT 0x01 unsigned char padinfo[2][2]; //player:0=1P , 1=2P void check_pad(unsigned char player) { unsigned char i; *JOYPAD = 1; *JOYPAD = 0; padinfo[player][1] = padinfo[player][0]; padinfo[player][0]= 0; for(i=0; i<8; i++) { padinfo[player][0] <<= 1; padinfo[player][0] += (*(JOYPAD + player) & 0x01); } } //ボタン押しっぱなし char btndown(unsigned char player,unsigned char btn) { if(padinfo[player][0] & btn) { return 1; } else { return 0; } } //ボタン押す char btnpush(unsigned char player,unsigned char btn) { if((padinfo[player][0] & btn) && ! (padinfo[player][1] & btn)) { return 1; } else { return 0; } } void main(void) { clrscr(); cprintf("CHECK START\r\n"); while(1) { check_pad(0); if ( btndown(0 , BTN_A ) ) { cprintf("A," ); } if ( btndown(0 , BTN_B ) ) { cprintf("B," ); } if ( btnpush(0 , BTN_SELECT) ) { cprintf("Select,"); } if ( btnpush(0 , BTN_START ) ) { cprintf("Start," ); } if ( btnpush(0 , BTN_UP ) ) { cprintf("Up," ); } if ( btnpush(0 , BTN_DOWN ) ) { cprintf("Down," ); } if ( btnpush(0 , BTN_LEFT ) ) { cprintf("Left," ); } if ( btnpush(0 , BTN_RIGHT ) ) { cprintf("Right," ); } if (wherey() > 27) { clrscr(); gotoxy(0, 0); } } } }}} &bold(){2.以降は「hello kikakubu」と同じ手順でtest.nesを作る。} *BGMの鳴らし方 -BGM・効果音出力ライブラリと使用例(後述)を使う。 -15puzzleのサウンドモジュールを使う。 -セミコロンCのドライバを使う。 -mck関連のツールを使って作成されるアセンブリソースを流用する。 &bold(){アセンブリソースの作成手順} -mck hogehogeよりppmckをダウンロードする。 -mck wikiを参考にmmlを作成する。 -ppmckのmck/songsフォルダに移動。 -00startcmd.batをダブルクリック。 -コマンドプロンプトが表示されるので、mknes <mmlのファイル名>を入力する。(拡張子は不要) -???.nesとdefine.inc、effect.h、???.hが出力される。 -nes_include/ppmck.asmのマネをしてこれらをC言語から実行すればBGMが鳴る(はず)。&br()(ppmck/sounddrv.hのsound_initとsound_driver_startを呼ぶ?) *&spanid(library){ファミコン用ライブラリの作り方} -ライブラリを介して処理すると開発効率が上がるかもしれません。が、処理速度は落ちます。 -ライブラリは仕様を調べつつ作成している関係上、バージョンアップ後に前版との互換性がなくなる可能性があります。 --[[コントローラ確認・BG&スプライト出力ライブラリ>ファミコンソフトの作り方/コントローラ確認・BG&スプライト出力]] --[[BGM・効果音出力ライブラリ>ファミコンソフトの作り方/BGM・効果音出力]] --[[ゲーム共通処理ライブラリ>ファミコンソフトの作り方/ゲーム共通処理]] *ライブラリ使用例 -雛形&ref(hina.zip)をダウンロードして、実際に動作を確認することができます。&br()(BGM・効果音のみ雛形2&ref(hina2.zip)を使用。) -ライブラリを作成する必要があります。手順は[[こちら>#library]]をご覧下さい。 -cc65とcygwin(とmakeコマンド)をインストールする必要があります。 --cc65のインストール方法は[[こちら>#hello]]を参考にして下さい。 --cygwin(とmakeコマンド)のインストール方法はググって下さい。 -作成手順は次の通りです。 --雛形のsample.cをメモ帳で開いて、例に示した内容に書き換えて上書き保存します。 --指定があれば.cfg・.asmの内容も書き換えて上書き保存します。 --makerom.batをダブルクリックします。&br()パスが異なる場合はmakerom.batをメモ帳で開いて編集して下さい。 --sample.nesが作成されます。 --sample.nesをファミコンエミュレータで開きます。 **&spanid(controller){コントローラによるスプライトの操作とBGのスクロール} #highlight(c){{{ #include <kikakubu.h> //NMI割り込み void NMIProc(void) { } // メイン処理 void NesMain() { unsigned char x=50,y=50,bgy=0; const char palettebg[] = { 0x0f, 0x11, 0x21, 0x30, 0x0f, 0x11, 0x21, 0x30, 0x0f, 0x11, 0x21, 0x30, 0x0f, 0x11, 0x21, 0x30 }; const char palettesp[] = { 0x0f, 0x00, 0x10, 0x21, 0x0f, 0x0f, 0x10, 0x21, 0x0f, 0x09, 0x19, 0x21, 0x0f, 0x15, 0x27, 0x30 }; VBlank(); InitPPU(); // パレット設定 SetPalette((char *)palettebg,0); SetPalette((char *)palettesp,1); // 背景設定 SetBackground(0x21,0xc9,"SAMPLE",6); // スクロール設定 SetScroll( 0, 0); SetPPU(0x08,0x1e); while (1) { VBlank(); // スプライト設定 SetSprite(1,x,y,1,0); CheckPad(); if ( ButtonDown(0, BTN_UP ) ) { y--; } if ( ButtonDown(0, BTN_DOWN ) ) { y++; } if ( ButtonDown(0, BTN_LEFT ) ) { x--; } if ( ButtonDown(0, BTN_RIGHT) ) { x++; } bgy++; if (bgy == 240) { bgy = 0; } SetScroll( 0, bgy); } } }}} **マイクの確認 #highlight(c){{{ CheckPad(); if ( ControlOther(MIC_USE) ) { /* マイク使用中 */ } }}} **背景の多重スクロール(ラスタスクロール) -0x00にセットしたスプライト(0爆弾)の描画のタイミングで、画面のスクロール速度を変えています。 -0爆弾に使用する画像には何らかの絵が描かれている必要があります。 #highlight(c){{{ #include <kikakubu.h> typedef void (*func)(); func functhis; void NMIProc(void){} void DrawBG() { unsigned char i,pos1,pos2; unsigned int pos; pos = 0x2000; for (i = 0; i < 15; i++) { pos1 = (pos & 0xff00) >> 8; pos2 = pos & 0x00ff; FillBackground(pos1,pos2,1,32); pos += 0x20; } FillBackground(0x20,0x88,2,1); FillBackground(0x20,0xd8,2,1); FillBackground(0x21,0x12,2,1); } void Scroll() { static unsigned char i,j; static unsigned char spx[] = {0,0,0,0}; static unsigned char bgx=0,blank=0; VBlank(); SetScroll( 0, 0); //0爆弾設置 SetSprite(1,0,118,0,0); SetSprite(0,spx[0] , 50,2,0); SetSprite(0,spx[1] ,124,1,1); SetSprite(0,spx[2] ,154,1,2); SetSprite(0,spx[3] ,184,1,3); //0爆弾 ZeroSprite(); bgx++; SetScroll( bgx , 0); for (i = 2; i < 4; i++){ for (j = 0; j < 150; j++){} SetScroll( bgx * i , 0); } blank++; if (blank % 6 == 0) { spx[0]++; } if (blank % 5 == 0) { spx[2]++; } if (blank % 3 == 0) { spx[3]++; } if (blank > 20) { spx[1]++; blank = 0; } } // メイン処理 void NesMain() { const char bgpalette[] = { 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20 }; const char sppalette[] = { 0x0f, 0x0a, 0x37, 0x20, 0x0f, 0x0a, 0x25, 0x20, 0x0f, 0x0a, 0x11, 0x20, 0x0f, 0x0a, 0x2a, 0x20 }; VBlank(); InitPPU(); // パレット設定 SetPalette((char *)bgpalette ,0); SetPalette((char *)sppalette, 1); DrawBG(); SetPPU(0x08,0x1e); functhis = Scroll; while (1) { (functhis)(); } } }}} **DMA転送 -DMA転送により、まとめてスプライトの設定ができます。 -(char *)0x0700は.cfgでDMAAREAとして設定したアドレスへのポインタです。 #highlight(c){{{ #include <kikakubu.h> // メイン処理 void NesMain() { unsigned char i,j,first; static unsigned char cnt=0; const char bgpalette[] = { 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20 }; const char sppalette[] = { 0x0f, 0x0a, 0x37, 0x20, 0x0f, 0x0a, 0x25, 0x20, 0x0f, 0x0a, 0x11, 0x20, 0x0f, 0x0a, 0x2a, 0x20 }; VBlank(); InitPPU(); SetPalette((char *)bgpalette ,0); SetPalette((char *)sppalette, 1); SetPPU(0x08,0x1e); VBlank(); first = 1; for (i=0;i<6;i++) { for (j=0;j<5;j++) { SetDMA((char *)0x0700,first,i * 20,j * 10,i,0); first = 0; } } SendDMA(0x7); while (1); } //NMI割り込み void NMIProc(void) { } }}} -.cfgの例 #highlight(c){{{ RAM: start = $0400, size = $0400, type = rw, define = yes; ↓ RAM: start = $0400, size = $0300, type = rw, define = yes; DMAAREA: start = $0700, size = $0100, type = rw, define = yes; }}} **NMI割り込み -while文の中でVBlank待ちをしなくても、.asmで宣言した処理(NMIProc)が毎回実行されます。 #highlight(c){{{ #include <kikakubu.h> // メイン処理 void NesMain() { const char bgpalette[] = { 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20 }; const char sppalette[] = { 0x0f, 0x0a, 0x37, 0x20, 0x0f, 0x0a, 0x25, 0x20, 0x0f, 0x0a, 0x11, 0x20, 0x0f, 0x0a, 0x2a, 0x20 }; VBlank(); InitPPU(); SetPalette((char *)bgpalette ,0); SetPalette((char *)sppalette, 1); //NMI割り込みの設定(flag1の7bitを1に) SetPPU(0x80,0x1e); while (1); } //NMI割り込み void NMIProc(void) { static unsigned char x = 50; x++; SetSprite(1,x,100,1,0); } }}} **縦スクロール(要調整) -裏で新しい背景を描きながら画面スクロールします。 -.asmを$01=Vertical Mirrorにしておきましょう。(チラつき防止、&color(red){本来逆にしなければならない筈}) -NMIProc内でif (bgx % 8 == ?) {}としていくつかに分けて処理しているのは負荷分担のためです。&br()まとめて処理すると表示が追いつかずに背景がきちんと出力されません。 -初期化の処理と画面切り替えのタイミングの見直しが必要かも。 #highlight(c){{{ #include <kikakubu.h> //NMI割り込み void NMIProc(void) { static unsigned char bgy=239,no=2,mode=0,wno,scr=0; unsigned char pos[2],y; if (bgy % 8 == 3) { if (mode) { scr = 0; } else { scr = 1; } y = bgy / 8; } if (bgy % 8 == 4) { wno = no; } if (bgy % 8 == 5) { GetBackgroundAddress(scr, 0, y, pos); FillBackground(*(pos + 0),*(pos + 1) ,wno,10); } if (bgy % 8 == 6) { GetBackgroundAddress(scr,10, y, pos); FillBackground(*(pos + 0),*(pos + 1) ,wno,10); } if (bgy % 8 == 7) { GetBackgroundAddress(scr,20, y, pos); FillBackground(*(pos + 0),*(pos + 1) ,wno,12); } if (bgy==3) { no++; mode^=1; if (mode) { SetPPU(0x8a,0x1e); } else { SetPPU(0x88,0x1e); } bgy = 239; SetScroll(0,bgy); } else { SetScroll(0,bgy); bgy--; } if (no > 9) { no=1; } } // メイン処理 void NesMain() { unsigned char i,pos[2]; const char bgpalette[] = { 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20 }; const char sppalette[] = { 0x0f, 0x30, 0x37, 0x20, 0x0f, 0x0a, 0x25, 0x20, 0x0f, 0x0a, 0x11, 0x20, 0x0f, 0x0a, 0x2a, 0x20 }; VBlank(); InitPPU(); // パレット設定 SetPalette((char *)bgpalette ,0); SetPalette((char *)sppalette, 1); for (i = 0; i < 30; i++) { GetBackgroundAddress(0, 0, i, pos); FillBackground(*(pos + 0), *(pos + 1), 1, 32); } SetPPU(0x88,0x1e); VBlank(); while (1); } }}} **横スクロール(要調整) -裏で新しい背景を描きながら画面スクロールします。 -SetPPUにてflag1の2bit目を1(32bitインクリメント)にして背景を縦一列に出力しています。 -.asmを$00=Horizontal Mirrorにしておきましょう。(チラつき防止、&color(red){本来逆にしなければならない筈}) -その他は縦スクロールと同じです。 #highlight(c){{{ #include <kikakubu.h> //NMI割り込み void NMIProc(void) { static unsigned char bgx=0,no=2,mode=0,wno,scr=0; unsigned char pos[2],x; if (bgx % 8 == 3) { if (mode) { scr = 0; } else { scr = 2; } x = bgx / 8; } if (bgx % 8 == 4 && bgx < 247) { wno = no; } if (bgx % 8 == 5) { GetBackgroundAddress(scr, x, 0, pos); FillBackground(*(pos + 0),*(pos + 1) ,wno,10); } if (bgx % 8 == 6) { GetBackgroundAddress(scr, x,10, pos); FillBackground(*(pos + 0),*(pos + 1) ,wno,10); } if (bgx % 8 == 7) { GetBackgroundAddress(scr, x,20, pos); FillBackground(*(pos + 0),*(pos + 1) ,wno,10); } SetScroll(bgx,0); bgx++; if (bgx==247) { no++; mode^=1; if (mode) { SetPPU(0x8d,0x1e); } else { SetPPU(0x8c,0x1e); } } if (no > 9) { no=1; } } // メイン処理 void NesMain() { unsigned char i,pos[2]; const char bgpalette[] = { 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20 }; const char sppalette[] = { 0x0f, 0x0a, 0x37, 0x20, 0x0f, 0x0a, 0x25, 0x20, 0x0f, 0x0a, 0x11, 0x20, 0x0f, 0x0a, 0x2a, 0x20 }; VBlank(); InitPPU(); // パレット設定 SetPalette((char *)bgpalette ,0); SetPalette((char *)sppalette, 1); for (i = 0; i < 30; i++) { GetBackgroundAddress(0, 0, i, pos); FillBackground(*(pos + 0), *(pos + 1), 1, 32); } SetPPU(0x8c,0x1e); while (1); } }}} **BGM・効果音 -&color(red){雛形2}を使用します。 -短形波・三角波・ノイズの設定と再生を行います。 -STARTを押しながらSELECTでタイトルに戻ります。 -各項目で左右を押すと値が増減します。 -SHUHASUはSELECT・A・Bいずれかのボタンを押しながら左右を押すと増分値が変わります。 -SHUHASUには以下の内容を設定します。 --矩形波:1790000/ (周波数 * 32 - 1) ・・・「ラ」(440hz)を鳴らそうと思えば127になります --三角波:1790000/ (周波数 * 64 - 1) ・・・多分。 -STARTを押すと音が鳴り、フラグの内容が16進数で表示されます。&br()ゲームを開発する際にフラグとshuhasu、timeの値を指定すれば同じ音を鳴らすことができます。 #highlight(c){{{ #include <kikakubu.h> #include <kanade.h> #define DMA (char*)0x0700 typedef enum { menuTITLE = 0, menuSQUARE = 1, menuTRIANGLE = 2, menuNOISE = 3, } emenu; emenu menu; char init; void ClearScreen() { unsigned char i,first,pos[2]; first = 1; for (i = 0; i < 64; i++) { SetDMA( DMA,first, 0, 240, 0, 0); first = 0; } for (i = 0; i < 30; i++) { GetBackgroundAddress(0, 0, i, pos); VBlank(); FillBackground(*(pos + 0),*(pos + 1),0,32); SetScroll( 0, 0 ); } } void DrawString(char x, char y,char *str,char len) { char pos[2]; GetBackgroundAddress(0, x, y, pos); VBlank(); SetBackground(*(pos + 0), *(pos + 1), str, len); SetScroll( 0, 0 ); } void DrawHexdecimal(char x, char y,char val) { char ret1, ret2, str[1]; ret2 = val % 16; ret1 = (val - ret2) / 16; if (ret1 < 10) { str[0] = ret1 + 0x30; } else { str[0] = ret1 + 0x37; } DrawString(x , y, str, 1); if (ret2 < 10) { str[0] = ret2 + 0x30; } else { str[0] = ret2 + 0x37; } DrawString(x + 1, y, str, 1); } void DrawArrow(char y) { //SetSprite(1, 30, (y * 2 + 3) * 8, 0x40, 0); SetDMA( DMA,1, 30, (y * 2 + 3) * 8 - 1, 0x40, 0); } void DrawNumMax(char x, char y, char val, char max) { char wrk; wrk = val % 10; SetDMA( DMA,0, (x + 0) * 8, y * 8, (val - wrk) / 10 + 0x30, 0); SetDMA( DMA,0, (x + 1) * 8, y * 8, wrk + 0x30 , 0); SetDMA( DMA,0, (x + 2) * 8, y * 8, 0x5c , 0); wrk = max % 10; SetDMA( DMA,0, (x + 3) * 8, y * 8, (max - wrk) / 10 + 0x30, 0); SetDMA( DMA,0, (x + 4) * 8, y * 8, wrk + 0x30 , 0); } void DrawNumLen(char x, char y, char val, char max) { unsigned char wrk; signed char i; for (i=2; i>=0; i--) { wrk = val % 10; SetDMA( DMA,0, (x + i) * 8, y * 8, 0x30 + wrk, 0); val /= 10; } SetDMA( DMA,0, (x + 3) * 8, y * 8, 0x5c , 0); for (i=2; i>=0; i--) { wrk = max % 10; SetDMA( DMA,0, (x + i + 4) * 8, y * 8, 0x30 + wrk, 0); max /= 10; } } void DrawNumber(char x, char y, int val) { unsigned int wrk; signed char i; for (i=6; i>=0; i--) { wrk = val % 10; SetDMA( DMA,0, (x + i) * 8, y * 8, 0x30 + wrk, 0); val /= 10; } } void Square() { char flag1,flag2; static char top ; static char duty ,counter,onkyo ,vol ; static char henka ,sokudo ,houhou,hani; static char time ; static unsigned int shuhasu; if (! init) { top = 0; duty = 0; counter = 0; onkyo = 0; vol = 0; henka = 0; sokudo = 0; houhou = 0; hani = 0; time = 0; shuhasu = 0; ClearScreen(); DrawString( 5, 3,"DUTY" , 4); DrawString( 5, 5,"COUNTER" , 7); DrawString( 5, 7,"ONKYO" , 5); DrawString( 5, 9,"VOLUME" , 6); DrawString( 5,11,"HENKA" , 5); DrawString( 5,13,"SOKUDO" , 6); DrawString( 5,15,"HOUHOU" , 6); DrawString( 5,17,"HANI" , 4); DrawString( 5,19,"SHUHASU" , 7); DrawString( 5,21,"TIME" , 4); DrawString( 7,26,"PUSH START" ,10); DrawString(17,26," TO PLAY" , 9); init = 1; } if ( ButtonDown(0, BTN_UP ) && top > 0) { top--; } if ( ButtonDown(0, BTN_DOWN) && top < 9) { top++; } DrawArrow(top); switch (top) { case 0: if ( ButtonDown(0, BTN_RIGHT ) && duty < 3 ) { duty++; } if ( ButtonDown(0, BTN_LEFT ) && duty > 0 ) { duty--; } break; case 1: if ( ButtonDown(0, BTN_RIGHT ) ) { counter = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { counter = 0; } break; case 2: if ( ButtonDown(0, BTN_RIGHT ) ) { onkyo = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { onkyo = 0; } break; case 3: if ( ButtonDown(0, BTN_RIGHT ) && vol < 15 ) { vol++; } if ( ButtonDown(0, BTN_LEFT ) && vol > 0 ) { vol--; } break; case 4: if ( ButtonDown(0, BTN_RIGHT ) ) { henka = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { henka = 0; } break; case 5: if ( ButtonDown(0, BTN_RIGHT ) && sokudo < 7 ) { sokudo++; } if ( ButtonDown(0, BTN_LEFT ) && sokudo > 0 ) { sokudo--; } break; case 6: if ( ButtonDown(0, BTN_RIGHT ) ) { houhou = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { houhou = 0; } break; case 7: if ( ButtonDown(0, BTN_RIGHT ) && hani < 7 ) { hani++; } if ( ButtonDown(0, BTN_LEFT ) && hani > 0 ) { hani--; } break; case 8: if ( ButtonDown(0, BTN_SELECT) && ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=1000; } else if ( ButtonDown(0, BTN_SELECT) && ButtonDown(0, BTN_LEFT ) && shuhasu >= 1000 ) { shuhasu-=1000; } else if ( ButtonDown(0, BTN_B ) && ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=100; } else if ( ButtonDown(0, BTN_B ) && ButtonDown(0, BTN_LEFT ) && shuhasu >= 100 ) { shuhasu-=100; } else if ( ButtonDown(0, BTN_A ) && ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=10; } else if ( ButtonDown(0, BTN_A ) && ButtonDown(0, BTN_LEFT ) && shuhasu >= 10 ) { shuhasu-=10; } else if ( ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=1; } else if ( ButtonDown(0, BTN_LEFT ) && shuhasu >= 1 ) { shuhasu-=1; } break; case 9: if ( ButtonDown(0, BTN_RIGHT ) && time < 63 ) { time++; } if ( ButtonDown(0, BTN_LEFT ) && time > 0 ) { time--; } break; } if ( ButtonPush(0, BTN_START) ) { flag1 = duty << 6; flag1 |= counter << 5; flag1 |= onkyo << 4; flag1 |= vol; flag2 = henka << 7; flag2 |= sokudo << 4; flag2 |= houhou << 3; flag2 |= hani; SetChannel(0x00); SetSquare(0,flag1,flag2); SetChannel(0x01); PlaySquare(0,shuhasu,time); DrawHexdecimal(12, 23, flag1); DrawHexdecimal(18, 23, flag2 ); } DrawNumMax(19, 3, duty , 3); DrawNumMax(19, 5, counter, 1); DrawNumMax(19, 7, onkyo , 1); DrawNumMax(19, 9, vol ,15); DrawNumMax(19,11, henka , 1); DrawNumMax(19,13, sokudo , 7); DrawNumMax(19,15, houhou , 1); DrawNumMax(19,17, hani , 7); DrawNumber(19,19, shuhasu ); DrawNumMax(19,21, time ,63); VBlank(); SendDMA(7); } void Triangle() { static char top; static char counter; static char length ,time; static unsigned int shuhasu; char flag; if (! init) { top = 0; counter = 0; length = 0; time = 0; shuhasu = 0; ClearScreen(); DrawString( 5, 3,"COUNTER" , 7); DrawString( 5, 5,"LENGTH" , 6); DrawString( 5, 7,"TIME" , 4); DrawString( 5, 9,"SHUHASU" , 7); DrawString( 7,26,"PUSH START" ,10); DrawString(17,26," TO PLAY" , 9); init = 1; } if ( ButtonDown(0, BTN_UP ) && top > 0) { top--; } if ( ButtonDown(0, BTN_DOWN) && top < 3) { top++; } DrawArrow(top); switch (top) { case 0: if ( ButtonDown(0, BTN_RIGHT ) ) { counter = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { counter = 0; } break; case 1: if ( ButtonDown(0, BTN_RIGHT ) && length < 127 ) { length++; } if ( ButtonDown(0, BTN_LEFT ) && length > 0 ) { length--; } break; case 2: if ( ButtonDown(0, BTN_RIGHT ) && time < 31 ) { time++; } if ( ButtonDown(0, BTN_LEFT ) && time > 0 ) { time--; } break; case 3: if ( ButtonDown(0, BTN_SELECT) && ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=1000; } else if ( ButtonDown(0, BTN_SELECT) && ButtonDown(0, BTN_LEFT ) && shuhasu >= 1000 ) { shuhasu-=1000; } else if ( ButtonDown(0, BTN_B ) && ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=100; } else if ( ButtonDown(0, BTN_B ) && ButtonDown(0, BTN_LEFT ) && shuhasu >= 100 ) { shuhasu-=100; } else if ( ButtonDown(0, BTN_A ) && ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=10; } else if ( ButtonDown(0, BTN_A ) && ButtonDown(0, BTN_LEFT ) && shuhasu >= 10 ) { shuhasu-=10; } else if ( ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=1; } else if ( ButtonDown(0, BTN_LEFT ) && shuhasu >= 1 ) { shuhasu-=1; } break; } if ( ButtonPush(0, BTN_START) ) { flag = counter << 7; flag |= length; SetChannel(0x00); SetTriangle(flag); SetChannel(0x04); PlayTriangle(shuhasu,time); DrawHexdecimal(15, 23, flag ); } DrawNumMax(19, 3, counter, 1); DrawNumLen(19, 5, length,127); DrawNumMax(19, 7, time ,31); DrawNumber(19, 9, shuhasu ); VBlank(); SendDMA(7); } void Noise() { static char top; static char counter; static char onkyo, volume, ransu, rate, time; char flag; if (! init) { top = 0; counter = 0; onkyo = 0; volume = 0; ransu = 0; rate = 0; time = 0; ClearScreen(); DrawString( 5, 3,"COUNTER" , 7); DrawString( 5, 5,"ONKYO" , 5); DrawString( 5, 7,"VOLUME" , 6); DrawString( 5, 9,"RANSU" , 5); DrawString( 5,11,"RATE" , 4); DrawString( 5,13,"TIME" , 4); DrawString( 7,26,"PUSH START" ,10); DrawString(17,26," TO PLAY" , 9); init = 1; } if ( ButtonDown(0, BTN_UP ) && top > 0) { top--; } if ( ButtonDown(0, BTN_DOWN) && top < 5) { top++; } DrawArrow(top); switch (top) { case 0: if ( ButtonDown(0, BTN_RIGHT ) ) { counter = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { counter = 0; } break; case 1: if ( ButtonDown(0, BTN_RIGHT ) ) { onkyo = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { onkyo = 0; } break; case 2: if ( ButtonDown(0, BTN_RIGHT ) && volume < 15 ) { volume++; } if ( ButtonDown(0, BTN_LEFT ) && volume > 0 ) { volume--; } break; case 3: if ( ButtonDown(0, BTN_RIGHT ) ) { ransu = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { ransu = 0; } break; case 4: if ( ButtonDown(0, BTN_RIGHT ) && rate < 15 ) { rate++; } if ( ButtonDown(0, BTN_LEFT ) && rate > 0 ) { rate--; } break; case 5: if ( ButtonDown(0, BTN_RIGHT ) && time < 31 ) { time++; } if ( ButtonDown(0, BTN_LEFT ) && time > 0 ) { time--; } break; } if ( ButtonPush(0, BTN_START) ) { flag = counter << 5; flag = onkyo << 4; flag |= volume; SetChannel(0x00); SetNoise(flag); DrawHexdecimal(12, 23, flag ); flag = ransu << 7; flag |= rate; SetChannel(0x08); PlayNoise(flag,time); DrawHexdecimal(18, 23, flag ); } DrawNumMax(19, 3, counter, 1); DrawNumMax(19, 5, onkyo , 1); DrawNumMax(19, 7, volume ,15); DrawNumMax(19, 9, ransu , 1); DrawNumMax(19,11, rate ,15); DrawNumMax(19,13, time ,31); VBlank(); SendDMA(7); } void Title() { static char top; if (! init) { top = 0; ClearScreen(); SetChannel(0x00); DrawString( 5, 3,"SQUARE" , 6); DrawString( 5, 5,"TRIANGLE" , 8); DrawString( 5, 7,"NOISE" , 5); init = 1; } if ( ButtonPush(0, BTN_UP ) && top > 0) { top--; } if ( ButtonPush(0, BTN_DOWN) && top < 2) { top++; } DrawArrow(top); VBlank(); SendDMA(7); if ( ButtonPush(0, BTN_A ) || ButtonPush(0, BTN_START ) ) { init = 0; switch (top) { case 0: menu = menuSQUARE; break; case 1: menu = menuTRIANGLE; break; case 2: menu = menuNOISE; break; } } } // メイン処理 void NesMain() { const char palettebg[] = { 0x0f, 0x21, 0x30, 0x30, 0x0f, 0x21, 0x30, 0x30, 0x0f, 0x21, 0x30, 0x30, 0x0f, 0x21, 0x30, 0x30 }; const char palettesp[] = { 0x0f, 0x21, 0x30, 0x30, 0x0f, 0x21, 0x30, 0x30, 0x0f, 0x21, 0x30, 0x30, 0x0f, 0x21, 0x30, 0x30 }; VBlank(); InitPPU(); SetPalette((char *)palettebg,0); SetPalette((char *)palettesp,1); SetPPU(0x08,0x1e); menu = menuTITLE; init = 0; while (1) { CheckPad(); if ( ButtonDown(0, BTN_START) && ButtonPush(0, BTN_SELECT) ) { init = 0; menu = menuTITLE; } switch (menu) { case menuTITLE: Title(); break; case menuSQUARE: Square(); break; case menuTRIANGLE: Triangle(); break; case menuNOISE: Noise(); break; } } } //NMI割り込み void NMIProc(void){} }}} *注意事項 **実機での動作について -Nestopia・NnnesterJ・VirtuaNESの3つで動作が確認できれば実機でも動く可能性が高いでしょう。 -変数宣言時の初期値設定は効果がありません。 -背景のみならず、スプライトの表示もVBlank中に済ませる必要があります。 **無限ループ -以下の処理は無限ループになります。 #highlight(c){{{ signed char i; char j; j = 0; for (i=10; i >= j; i--) {} }}} -回避するには、評価式に使用する変数もsignedにする必要があります。 #highlight(c){{{ signed char i,j; j = 0; for (i=10; i >= j; i--) {} }}} **描画について -背景の描画などを行った後は、スクロール値を設定する必要があります。&br()($2006や$2007にアクセスすると、スクロール値が書き換わるため。) #highlight(c){{{ VBlank(); SetBackground(0x20,0x00,(char *)bg,10) SetScroll( 0, 0 ); }}} -VBlank中やNMI割り込み中に行う処理の優先順位はDMA→BG描画(一部)→その他の処理(パッドのチェックなど)の順。 -VBlank中を確認して描画処理を行っても、VBlank終了間際だと画面がちらつく場合があります。&br()その場合はVBlankを2回チェックしてから描画処理を行いましょう。 **計算について -サブルーチンの引数に割り算などをそのまま渡すと落ちます。&br()例)SetScroll(10 / 3,0)&br()一旦変数に入れるなどしてからサブルーチンを呼び出しましょう。 *参考資料 -[[ギコ猫でもわかるファミコンプログラミング>http://gikofami.fc2web.com/]] -[[ブルジョアソフトウェア研究所>http://hp.vector.co.jp/authors/VA042397/]] -[[オレスタルビーイング00>http://www.geocities.jp/tkoztkoz/]] -[[D-Soft>http://web.archive.org/web/20070521083612/http://aqube.kir.jp/dsoft/]]&br()消えないうちにご覧下さい -[[すずめ愛好会>http://web.archive.org/web/20021209174225/vsync.org/ns/index.html]]&br()消えないうちにご覧下さい -[[simple 15puzzle for nes ver 0.1>http://www37.atwiki.jp/kikakubu4/?cmd=upload&act=open&pageid=62&file=15puzzle0.1.zip]]&br()有志作成の15パズル -[[わいわいの巣>http://www.geocities.jp/yy_6502/]]&br()グラフィックエディタ -[[NES Hack Factory>http://www.geocities.jp/kz_s6502/]]&br()ネームテーブルエディタ・PCM for NES -[[mck hogehoge (仮)>http://takamatsu.cool.ne.jp/dutycycle/]]&br()サウンドツール -[[MCK Wiki>http://wikiwiki.jp/mck/]]&br()サウンドツール -[[NesDev>http://nesdev.parodius.com/]]&br()海外のNES開発資料 -[[ワンチップマイコンでゲームを作ろう。>http://www.geocities.jp/r8ctiny/]]&br()RP2C02にファミコンの資料があります -[[pgate1@crystal>http://crystal.freespace.jp/pgate1/]]&br()PPUについての説明があります。 -[[魂の道具箱>http://taka-p.homeip.net/dtm/tools/index.html]]&br()ファミコンに音声合成で喋らせるツール(ただしディスクシステム用) -[[セミコロンC>http://offgao.no-ip.org/]]&br()プログラムのページにファミコン用サウンドドライバ -[[98-026 Nintendo>http://bobrost.com/nes/]]&br()nbasic - [[ファミコンのプログラム@2ch>http://www37.atwiki.jp/kikakubu4/archive/20101108/1710b326237ab726e93d7f7b8ef76a8a]] - [[ファミコンのプログラム2@2ch>http://www37.atwiki.jp/kikakubu4/archive/20101108/5fed20b34cd47def355ded525ef1ceb7]] -[[ファミコンのプログラム現行スレ@2ch>http://find.2ch.net/?TYPE=TITLE&COUNT=10&STR=%A5%D5%A5%A1%A5%DF%A5%B3%A5%F3%A4%CE%A5%D7%A5%ED%A5%B0%A5%E9%A5%E0+board%3A%A5%B2%C0%BD%BA%EE%B5%BB%BD%D1]]
書くスペースが手狭になったのでお引越しします。 引越し先はこちら *cc65@wiki *http://www34.atwiki.jp/cc65/ #co(){{{{{ #contents() *&spanid(hello){「hello kikakubu」の作り方} &bold(){1.まずcc65をダウンロードする。} http://www.cc65.org/ Download&bold(){->}available for download.の -[[cc65-win32-2.13.2-1.zip>ftp://ftp.musoftware.de/pub/uz/cc65/cc65-win32-2.13.2-1.zip]] -[[cc65-nes-2.13.2-1.zip>ftp://ftp.musoftware.de/pub/uz/cc65/cc65-nes-2.13.2-1.zip]] -[[cc65-atmos-2.13.2-1.zip>ftp://ftp.musoftware.de/pub/uz/cc65/cc65-atmos-2.13.2-1.zip]] &bold(){2.全て展開し、中身をC:\cc65に移動する。} -cc65というフォルダは自分で作る。 -「フォルダの上書きの確認」が表示されたら「はい」を選ぶ。 &bold(){3.メモ帳に以下の内容を書き、test.cという名前でデスクトップに保存。} #highlight(c){{{ #include <conio.h> int main (void) { clrscr(); cprintf("hello kikakubu"); while(1) { } return 0; } }}} &bold(){4.メモ帳に以下の内容を書き、sakusei.batという名前でデスクトップに保存。} #highlight(dos){{{ PATH c:\cc65\bin;%PATH% set CA65_INC=c:\cc65\include set CC65_INC=c:\cc65\include set LD65_CFG=c:\cc65\cfg set LD65_LIB=c:\cc65\lib set LD65_OBJ=c:\cc65\obj set CC65_HOME=c:\cc65 cc65 -t nes %1.c ca65 -t nes %1.s ld65 -t nes %1.o nes.lib atmos.lib -o %1.nes }}} &bold(){5.コマンドプロンプトを開く。} スタートメニューのすべてのプログラム-アクセサリ-コマンドプロンプトを選ぶ。 &bold(){6.cd デスクトップと入力し、Enterを押す。} &bold(){7.sakusei testと入力し、Enterを押す。} &bold(){8.test.nesが作成されるので、ファミコンエミュレータで開く。} *コントローラチェックソフトの作り方 &bold(){1.メモ帳に以下の内容を書き、test.cという名前でデスクトップに保存。} #highlight(c){{{ #include <conio.h> #define JOYPAD (char*)0x4016 #define BTN_A 0x80 #define BTN_B 0x40 #define BTN_SELECT 0x20 #define BTN_START 0x10 #define BTN_UP 0x08 #define BTN_DOWN 0x04 #define BTN_LEFT 0x02 #define BTN_RIGHT 0x01 unsigned char padinfo[2][2]; //player:0=1P , 1=2P void check_pad(unsigned char player) { unsigned char i; *JOYPAD = 1; *JOYPAD = 0; padinfo[player][1] = padinfo[player][0]; padinfo[player][0]= 0; for(i=0; i<8; i++) { padinfo[player][0] <<= 1; padinfo[player][0] += (*(JOYPAD + player) & 0x01); } } //ボタン押しっぱなし char btndown(unsigned char player,unsigned char btn) { if(padinfo[player][0] & btn) { return 1; } else { return 0; } } //ボタン押す char btnpush(unsigned char player,unsigned char btn) { if((padinfo[player][0] & btn) && ! (padinfo[player][1] & btn)) { return 1; } else { return 0; } } void main(void) { clrscr(); cprintf("CHECK START\r\n"); while(1) { check_pad(0); if ( btndown(0 , BTN_A ) ) { cprintf("A," ); } if ( btndown(0 , BTN_B ) ) { cprintf("B," ); } if ( btnpush(0 , BTN_SELECT) ) { cprintf("Select,"); } if ( btnpush(0 , BTN_START ) ) { cprintf("Start," ); } if ( btnpush(0 , BTN_UP ) ) { cprintf("Up," ); } if ( btnpush(0 , BTN_DOWN ) ) { cprintf("Down," ); } if ( btnpush(0 , BTN_LEFT ) ) { cprintf("Left," ); } if ( btnpush(0 , BTN_RIGHT ) ) { cprintf("Right," ); } if (wherey() > 27) { clrscr(); gotoxy(0, 0); } } } }}} &bold(){2.以降は「hello kikakubu」と同じ手順でtest.nesを作る。} *BGMの鳴らし方 -BGM・効果音出力ライブラリと使用例(後述)を使う。 -15puzzleのサウンドモジュールを使う。 -セミコロンCのドライバを使う。 -mck関連のツールを使って作成されるアセンブリソースを流用する。 &bold(){アセンブリソースの作成手順} -mck hogehogeよりppmckをダウンロードする。 -mck wikiを参考にmmlを作成する。 -ppmckのmck/songsフォルダに移動。 -00startcmd.batをダブルクリック。 -コマンドプロンプトが表示されるので、mknes <mmlのファイル名>を入力する。(拡張子は不要) -???.nesとdefine.inc、effect.h、???.hが出力される。 -nes_include/ppmck.asmのマネをしてこれらをC言語から実行すればBGMが鳴る(はず)。&br()(ppmck/sounddrv.hのsound_initとsound_driver_startを呼ぶ?) *&spanid(library){ファミコン用ライブラリの作り方} -ライブラリを介して処理すると開発効率が上がるかもしれません。が、処理速度は落ちます。 -ライブラリは仕様を調べつつ作成している関係上、バージョンアップ後に前版との互換性がなくなる可能性があります。 --[[コントローラ確認・BG&スプライト出力ライブラリ>ファミコンソフトの作り方/コントローラ確認・BG&スプライト出力]] --[[BGM・効果音出力ライブラリ>ファミコンソフトの作り方/BGM・効果音出力]] --[[ゲーム共通処理ライブラリ>ファミコンソフトの作り方/ゲーム共通処理]] *ライブラリ使用例 -雛形&ref(hina.zip)をダウンロードして、実際に動作を確認することができます。&br()(BGM・効果音のみ雛形2&ref(hina2.zip)を使用。) -ライブラリを作成する必要があります。手順は[[こちら>#library]]をご覧下さい。 -cc65とcygwin(とmakeコマンド)をインストールする必要があります。 --cc65のインストール方法は[[こちら>#hello]]を参考にして下さい。 --cygwin(とmakeコマンド)のインストール方法はググって下さい。 -作成手順は次の通りです。 --雛形のsample.cをメモ帳で開いて、例に示した内容に書き換えて上書き保存します。 --指定があれば.cfg・.asmの内容も書き換えて上書き保存します。 --makerom.batをダブルクリックします。&br()パスが異なる場合はmakerom.batをメモ帳で開いて編集して下さい。 --sample.nesが作成されます。 --sample.nesをファミコンエミュレータで開きます。 **&spanid(controller){コントローラによるスプライトの操作とBGのスクロール} #highlight(c){{{ #include <kikakubu.h> //NMI割り込み void NMIProc(void) { } // メイン処理 void NesMain() { unsigned char x=50,y=50,bgy=0; const char palettebg[] = { 0x0f, 0x11, 0x21, 0x30, 0x0f, 0x11, 0x21, 0x30, 0x0f, 0x11, 0x21, 0x30, 0x0f, 0x11, 0x21, 0x30 }; const char palettesp[] = { 0x0f, 0x00, 0x10, 0x21, 0x0f, 0x0f, 0x10, 0x21, 0x0f, 0x09, 0x19, 0x21, 0x0f, 0x15, 0x27, 0x30 }; VBlank(); InitPPU(); // パレット設定 SetPalette((char *)palettebg,0); SetPalette((char *)palettesp,1); // 背景設定 SetBackground(0x21,0xc9,"SAMPLE",6); // スクロール設定 SetScroll( 0, 0); SetPPU(0x08,0x1e); while (1) { VBlank(); // スプライト設定 SetSprite(1,x,y,1,0); CheckPad(); if ( ButtonDown(0, BTN_UP ) ) { y--; } if ( ButtonDown(0, BTN_DOWN ) ) { y++; } if ( ButtonDown(0, BTN_LEFT ) ) { x--; } if ( ButtonDown(0, BTN_RIGHT) ) { x++; } bgy++; if (bgy == 240) { bgy = 0; } SetScroll( 0, bgy); } } }}} **マイクの確認 #highlight(c){{{ CheckPad(); if ( ControlOther(MIC_USE) ) { /* マイク使用中 */ } }}} **背景の多重スクロール(ラスタスクロール) -0x00にセットしたスプライト(0爆弾)の描画のタイミングで、画面のスクロール速度を変えています。 -0爆弾に使用する画像には何らかの絵が描かれている必要があります。 #highlight(c){{{ #include <kikakubu.h> typedef void (*func)(); func functhis; void NMIProc(void){} void DrawBG() { unsigned char i,pos1,pos2; unsigned int pos; pos = 0x2000; for (i = 0; i < 15; i++) { pos1 = (pos & 0xff00) >> 8; pos2 = pos & 0x00ff; FillBackground(pos1,pos2,1,32); pos += 0x20; } FillBackground(0x20,0x88,2,1); FillBackground(0x20,0xd8,2,1); FillBackground(0x21,0x12,2,1); } void Scroll() { static unsigned char i,j; static unsigned char spx[] = {0,0,0,0}; static unsigned char bgx=0,blank=0; VBlank(); SetScroll( 0, 0); //0爆弾設置 SetSprite(1,0,118,0,0); SetSprite(0,spx[0] , 50,2,0); SetSprite(0,spx[1] ,124,1,1); SetSprite(0,spx[2] ,154,1,2); SetSprite(0,spx[3] ,184,1,3); //0爆弾 ZeroSprite(); bgx++; SetScroll( bgx , 0); for (i = 2; i < 4; i++){ for (j = 0; j < 150; j++){} SetScroll( bgx * i , 0); } blank++; if (blank % 6 == 0) { spx[0]++; } if (blank % 5 == 0) { spx[2]++; } if (blank % 3 == 0) { spx[3]++; } if (blank > 20) { spx[1]++; blank = 0; } } // メイン処理 void NesMain() { const char bgpalette[] = { 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20 }; const char sppalette[] = { 0x0f, 0x0a, 0x37, 0x20, 0x0f, 0x0a, 0x25, 0x20, 0x0f, 0x0a, 0x11, 0x20, 0x0f, 0x0a, 0x2a, 0x20 }; VBlank(); InitPPU(); // パレット設定 SetPalette((char *)bgpalette ,0); SetPalette((char *)sppalette, 1); DrawBG(); SetPPU(0x08,0x1e); functhis = Scroll; while (1) { (functhis)(); } } }}} **DMA転送 -DMA転送により、まとめてスプライトの設定ができます。 -(char *)0x0700は.cfgでDMAAREAとして設定したアドレスへのポインタです。 #highlight(c){{{ #include <kikakubu.h> // メイン処理 void NesMain() { unsigned char i,j,first; static unsigned char cnt=0; const char bgpalette[] = { 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20 }; const char sppalette[] = { 0x0f, 0x0a, 0x37, 0x20, 0x0f, 0x0a, 0x25, 0x20, 0x0f, 0x0a, 0x11, 0x20, 0x0f, 0x0a, 0x2a, 0x20 }; VBlank(); InitPPU(); SetPalette((char *)bgpalette ,0); SetPalette((char *)sppalette, 1); SetPPU(0x08,0x1e); VBlank(); first = 1; for (i=0;i<6;i++) { for (j=0;j<5;j++) { SetDMA((char *)0x0700,first,i * 20,j * 10,i,0); first = 0; } } SendDMA(0x7); while (1); } //NMI割り込み void NMIProc(void) { } }}} -.cfgの例 #highlight(c){{{ RAM: start = $0400, size = $0400, type = rw, define = yes; ↓ RAM: start = $0400, size = $0300, type = rw, define = yes; DMAAREA: start = $0700, size = $0100, type = rw, define = yes; }}} **NMI割り込み -while文の中でVBlank待ちをしなくても、.asmで宣言した処理(NMIProc)が毎回実行されます。 #highlight(c){{{ #include <kikakubu.h> // メイン処理 void NesMain() { const char bgpalette[] = { 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20 }; const char sppalette[] = { 0x0f, 0x0a, 0x37, 0x20, 0x0f, 0x0a, 0x25, 0x20, 0x0f, 0x0a, 0x11, 0x20, 0x0f, 0x0a, 0x2a, 0x20 }; VBlank(); InitPPU(); SetPalette((char *)bgpalette ,0); SetPalette((char *)sppalette, 1); //NMI割り込みの設定(flag1の7bitを1に) SetPPU(0x80,0x1e); while (1); } //NMI割り込み void NMIProc(void) { static unsigned char x = 50; x++; SetSprite(1,x,100,1,0); } }}} **縦スクロール(要調整) -裏で新しい背景を描きながら画面スクロールします。 -.asmを$01=Vertical Mirrorにしておきましょう。(チラつき防止、&color(red){本来逆にしなければならない筈}) -NMIProc内でif (bgx % 8 == ?) {}としていくつかに分けて処理しているのは負荷分担のためです。&br()まとめて処理すると表示が追いつかずに背景がきちんと出力されません。 -初期化の処理と画面切り替えのタイミングの見直しが必要かも。 #highlight(c){{{ #include <kikakubu.h> //NMI割り込み void NMIProc(void) { static unsigned char bgy=239,no=2,mode=0,wno,scr=0; unsigned char pos[2],y; if (bgy % 8 == 3) { if (mode) { scr = 0; } else { scr = 1; } y = bgy / 8; } if (bgy % 8 == 4) { wno = no; } if (bgy % 8 == 5) { GetBackgroundAddress(scr, 0, y, pos); FillBackground(*(pos + 0),*(pos + 1) ,wno,10); } if (bgy % 8 == 6) { GetBackgroundAddress(scr,10, y, pos); FillBackground(*(pos + 0),*(pos + 1) ,wno,10); } if (bgy % 8 == 7) { GetBackgroundAddress(scr,20, y, pos); FillBackground(*(pos + 0),*(pos + 1) ,wno,12); } if (bgy==3) { no++; mode^=1; if (mode) { SetPPU(0x8a,0x1e); } else { SetPPU(0x88,0x1e); } bgy = 239; SetScroll(0,bgy); } else { SetScroll(0,bgy); bgy--; } if (no > 9) { no=1; } } // メイン処理 void NesMain() { unsigned char i,pos[2]; const char bgpalette[] = { 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20 }; const char sppalette[] = { 0x0f, 0x30, 0x37, 0x20, 0x0f, 0x0a, 0x25, 0x20, 0x0f, 0x0a, 0x11, 0x20, 0x0f, 0x0a, 0x2a, 0x20 }; VBlank(); InitPPU(); // パレット設定 SetPalette((char *)bgpalette ,0); SetPalette((char *)sppalette, 1); for (i = 0; i < 30; i++) { GetBackgroundAddress(0, 0, i, pos); FillBackground(*(pos + 0), *(pos + 1), 1, 32); } SetPPU(0x88,0x1e); VBlank(); while (1); } }}} **横スクロール(要調整) -裏で新しい背景を描きながら画面スクロールします。 -SetPPUにてflag1の2bit目を1(32bitインクリメント)にして背景を縦一列に出力しています。 -.asmを$00=Horizontal Mirrorにしておきましょう。(チラつき防止、&color(red){本来逆にしなければならない筈}) -その他は縦スクロールと同じです。 #highlight(c){{{ #include <kikakubu.h> //NMI割り込み void NMIProc(void) { static unsigned char bgx=0,no=2,mode=0,wno,scr=0; unsigned char pos[2],x; if (bgx % 8 == 3) { if (mode) { scr = 0; } else { scr = 2; } x = bgx / 8; } if (bgx % 8 == 4 && bgx < 247) { wno = no; } if (bgx % 8 == 5) { GetBackgroundAddress(scr, x, 0, pos); FillBackground(*(pos + 0),*(pos + 1) ,wno,10); } if (bgx % 8 == 6) { GetBackgroundAddress(scr, x,10, pos); FillBackground(*(pos + 0),*(pos + 1) ,wno,10); } if (bgx % 8 == 7) { GetBackgroundAddress(scr, x,20, pos); FillBackground(*(pos + 0),*(pos + 1) ,wno,10); } SetScroll(bgx,0); bgx++; if (bgx==247) { no++; mode^=1; if (mode) { SetPPU(0x8d,0x1e); } else { SetPPU(0x8c,0x1e); } } if (no > 9) { no=1; } } // メイン処理 void NesMain() { unsigned char i,pos[2]; const char bgpalette[] = { 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20, 0x0f, 0x21, 0x11, 0x20 }; const char sppalette[] = { 0x0f, 0x0a, 0x37, 0x20, 0x0f, 0x0a, 0x25, 0x20, 0x0f, 0x0a, 0x11, 0x20, 0x0f, 0x0a, 0x2a, 0x20 }; VBlank(); InitPPU(); // パレット設定 SetPalette((char *)bgpalette ,0); SetPalette((char *)sppalette, 1); for (i = 0; i < 30; i++) { GetBackgroundAddress(0, 0, i, pos); FillBackground(*(pos + 0), *(pos + 1), 1, 32); } SetPPU(0x8c,0x1e); while (1); } }}} **BGM・効果音 -&color(red){雛形2}を使用します。 -短形波・三角波・ノイズの設定と再生を行います。 -STARTを押しながらSELECTでタイトルに戻ります。 -各項目で左右を押すと値が増減します。 -SHUHASUはSELECT・A・Bいずれかのボタンを押しながら左右を押すと増分値が変わります。 -SHUHASUには以下の内容を設定します。 --矩形波:1790000/ (周波数 * 32 - 1) ・・・「ラ」(440hz)を鳴らそうと思えば127になります --三角波:1790000/ (周波数 * 64 - 1) ・・・多分。 -STARTを押すと音が鳴り、フラグの内容が16進数で表示されます。&br()ゲームを開発する際にフラグとshuhasu、timeの値を指定すれば同じ音を鳴らすことができます。 #highlight(c){{{ #include <kikakubu.h> #include <kanade.h> #define DMA (char*)0x0700 typedef enum { menuTITLE = 0, menuSQUARE = 1, menuTRIANGLE = 2, menuNOISE = 3, } emenu; emenu menu; char init; void ClearScreen() { unsigned char i,first,pos[2]; first = 1; for (i = 0; i < 64; i++) { SetDMA( DMA,first, 0, 240, 0, 0); first = 0; } for (i = 0; i < 30; i++) { GetBackgroundAddress(0, 0, i, pos); VBlank(); FillBackground(*(pos + 0),*(pos + 1),0,32); SetScroll( 0, 0 ); } } void DrawString(char x, char y,char *str,char len) { char pos[2]; GetBackgroundAddress(0, x, y, pos); VBlank(); SetBackground(*(pos + 0), *(pos + 1), str, len); SetScroll( 0, 0 ); } void DrawHexdecimal(char x, char y,char val) { char ret1, ret2, str[1]; ret2 = val % 16; ret1 = (val - ret2) / 16; if (ret1 < 10) { str[0] = ret1 + 0x30; } else { str[0] = ret1 + 0x37; } DrawString(x , y, str, 1); if (ret2 < 10) { str[0] = ret2 + 0x30; } else { str[0] = ret2 + 0x37; } DrawString(x + 1, y, str, 1); } void DrawArrow(char y) { //SetSprite(1, 30, (y * 2 + 3) * 8, 0x40, 0); SetDMA( DMA,1, 30, (y * 2 + 3) * 8 - 1, 0x40, 0); } void DrawNumMax(char x, char y, char val, char max) { char wrk; wrk = val % 10; SetDMA( DMA,0, (x + 0) * 8, y * 8, (val - wrk) / 10 + 0x30, 0); SetDMA( DMA,0, (x + 1) * 8, y * 8, wrk + 0x30 , 0); SetDMA( DMA,0, (x + 2) * 8, y * 8, 0x5c , 0); wrk = max % 10; SetDMA( DMA,0, (x + 3) * 8, y * 8, (max - wrk) / 10 + 0x30, 0); SetDMA( DMA,0, (x + 4) * 8, y * 8, wrk + 0x30 , 0); } void DrawNumLen(char x, char y, char val, char max) { unsigned char wrk; signed char i; for (i=2; i>=0; i--) { wrk = val % 10; SetDMA( DMA,0, (x + i) * 8, y * 8, 0x30 + wrk, 0); val /= 10; } SetDMA( DMA,0, (x + 3) * 8, y * 8, 0x5c , 0); for (i=2; i>=0; i--) { wrk = max % 10; SetDMA( DMA,0, (x + i + 4) * 8, y * 8, 0x30 + wrk, 0); max /= 10; } } void DrawNumber(char x, char y, int val) { unsigned int wrk; signed char i; for (i=6; i>=0; i--) { wrk = val % 10; SetDMA( DMA,0, (x + i) * 8, y * 8, 0x30 + wrk, 0); val /= 10; } } void Square() { char flag1,flag2; static char top ; static char duty ,counter,onkyo ,vol ; static char henka ,sokudo ,houhou,hani; static char time ; static unsigned int shuhasu; if (! init) { top = 0; duty = 0; counter = 0; onkyo = 0; vol = 0; henka = 0; sokudo = 0; houhou = 0; hani = 0; time = 0; shuhasu = 0; ClearScreen(); DrawString( 5, 3,"DUTY" , 4); DrawString( 5, 5,"COUNTER" , 7); DrawString( 5, 7,"ONKYO" , 5); DrawString( 5, 9,"VOLUME" , 6); DrawString( 5,11,"HENKA" , 5); DrawString( 5,13,"SOKUDO" , 6); DrawString( 5,15,"HOUHOU" , 6); DrawString( 5,17,"HANI" , 4); DrawString( 5,19,"SHUHASU" , 7); DrawString( 5,21,"TIME" , 4); DrawString( 7,26,"PUSH START" ,10); DrawString(17,26," TO PLAY" , 9); init = 1; } if ( ButtonDown(0, BTN_UP ) && top > 0) { top--; } if ( ButtonDown(0, BTN_DOWN) && top < 9) { top++; } DrawArrow(top); switch (top) { case 0: if ( ButtonDown(0, BTN_RIGHT ) && duty < 3 ) { duty++; } if ( ButtonDown(0, BTN_LEFT ) && duty > 0 ) { duty--; } break; case 1: if ( ButtonDown(0, BTN_RIGHT ) ) { counter = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { counter = 0; } break; case 2: if ( ButtonDown(0, BTN_RIGHT ) ) { onkyo = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { onkyo = 0; } break; case 3: if ( ButtonDown(0, BTN_RIGHT ) && vol < 15 ) { vol++; } if ( ButtonDown(0, BTN_LEFT ) && vol > 0 ) { vol--; } break; case 4: if ( ButtonDown(0, BTN_RIGHT ) ) { henka = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { henka = 0; } break; case 5: if ( ButtonDown(0, BTN_RIGHT ) && sokudo < 7 ) { sokudo++; } if ( ButtonDown(0, BTN_LEFT ) && sokudo > 0 ) { sokudo--; } break; case 6: if ( ButtonDown(0, BTN_RIGHT ) ) { houhou = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { houhou = 0; } break; case 7: if ( ButtonDown(0, BTN_RIGHT ) && hani < 7 ) { hani++; } if ( ButtonDown(0, BTN_LEFT ) && hani > 0 ) { hani--; } break; case 8: if ( ButtonDown(0, BTN_SELECT) && ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=1000; } else if ( ButtonDown(0, BTN_SELECT) && ButtonDown(0, BTN_LEFT ) && shuhasu >= 1000 ) { shuhasu-=1000; } else if ( ButtonDown(0, BTN_B ) && ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=100; } else if ( ButtonDown(0, BTN_B ) && ButtonDown(0, BTN_LEFT ) && shuhasu >= 100 ) { shuhasu-=100; } else if ( ButtonDown(0, BTN_A ) && ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=10; } else if ( ButtonDown(0, BTN_A ) && ButtonDown(0, BTN_LEFT ) && shuhasu >= 10 ) { shuhasu-=10; } else if ( ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=1; } else if ( ButtonDown(0, BTN_LEFT ) && shuhasu >= 1 ) { shuhasu-=1; } break; case 9: if ( ButtonDown(0, BTN_RIGHT ) && time < 63 ) { time++; } if ( ButtonDown(0, BTN_LEFT ) && time > 0 ) { time--; } break; } if ( ButtonPush(0, BTN_START) ) { flag1 = duty << 6; flag1 |= counter << 5; flag1 |= onkyo << 4; flag1 |= vol; flag2 = henka << 7; flag2 |= sokudo << 4; flag2 |= houhou << 3; flag2 |= hani; SetChannel(0x00); SetSquare(0,flag1,flag2); SetChannel(0x01); PlaySquare(0,shuhasu,time); DrawHexdecimal(12, 23, flag1); DrawHexdecimal(18, 23, flag2 ); } DrawNumMax(19, 3, duty , 3); DrawNumMax(19, 5, counter, 1); DrawNumMax(19, 7, onkyo , 1); DrawNumMax(19, 9, vol ,15); DrawNumMax(19,11, henka , 1); DrawNumMax(19,13, sokudo , 7); DrawNumMax(19,15, houhou , 1); DrawNumMax(19,17, hani , 7); DrawNumber(19,19, shuhasu ); DrawNumMax(19,21, time ,63); VBlank(); SendDMA(7); } void Triangle() { static char top; static char counter; static char length ,time; static unsigned int shuhasu; char flag; if (! init) { top = 0; counter = 0; length = 0; time = 0; shuhasu = 0; ClearScreen(); DrawString( 5, 3,"COUNTER" , 7); DrawString( 5, 5,"LENGTH" , 6); DrawString( 5, 7,"TIME" , 4); DrawString( 5, 9,"SHUHASU" , 7); DrawString( 7,26,"PUSH START" ,10); DrawString(17,26," TO PLAY" , 9); init = 1; } if ( ButtonDown(0, BTN_UP ) && top > 0) { top--; } if ( ButtonDown(0, BTN_DOWN) && top < 3) { top++; } DrawArrow(top); switch (top) { case 0: if ( ButtonDown(0, BTN_RIGHT ) ) { counter = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { counter = 0; } break; case 1: if ( ButtonDown(0, BTN_RIGHT ) && length < 127 ) { length++; } if ( ButtonDown(0, BTN_LEFT ) && length > 0 ) { length--; } break; case 2: if ( ButtonDown(0, BTN_RIGHT ) && time < 31 ) { time++; } if ( ButtonDown(0, BTN_LEFT ) && time > 0 ) { time--; } break; case 3: if ( ButtonDown(0, BTN_SELECT) && ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=1000; } else if ( ButtonDown(0, BTN_SELECT) && ButtonDown(0, BTN_LEFT ) && shuhasu >= 1000 ) { shuhasu-=1000; } else if ( ButtonDown(0, BTN_B ) && ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=100; } else if ( ButtonDown(0, BTN_B ) && ButtonDown(0, BTN_LEFT ) && shuhasu >= 100 ) { shuhasu-=100; } else if ( ButtonDown(0, BTN_A ) && ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=10; } else if ( ButtonDown(0, BTN_A ) && ButtonDown(0, BTN_LEFT ) && shuhasu >= 10 ) { shuhasu-=10; } else if ( ButtonDown(0, BTN_RIGHT ) ) { shuhasu+=1; } else if ( ButtonDown(0, BTN_LEFT ) && shuhasu >= 1 ) { shuhasu-=1; } break; } if ( ButtonPush(0, BTN_START) ) { flag = counter << 7; flag |= length; SetChannel(0x00); SetTriangle(flag); SetChannel(0x04); PlayTriangle(shuhasu,time); DrawHexdecimal(15, 23, flag ); } DrawNumMax(19, 3, counter, 1); DrawNumLen(19, 5, length,127); DrawNumMax(19, 7, time ,31); DrawNumber(19, 9, shuhasu ); VBlank(); SendDMA(7); } void Noise() { static char top; static char counter; static char onkyo, volume, ransu, rate, time; char flag; if (! init) { top = 0; counter = 0; onkyo = 0; volume = 0; ransu = 0; rate = 0; time = 0; ClearScreen(); DrawString( 5, 3,"COUNTER" , 7); DrawString( 5, 5,"ONKYO" , 5); DrawString( 5, 7,"VOLUME" , 6); DrawString( 5, 9,"RANSU" , 5); DrawString( 5,11,"RATE" , 4); DrawString( 5,13,"TIME" , 4); DrawString( 7,26,"PUSH START" ,10); DrawString(17,26," TO PLAY" , 9); init = 1; } if ( ButtonDown(0, BTN_UP ) && top > 0) { top--; } if ( ButtonDown(0, BTN_DOWN) && top < 5) { top++; } DrawArrow(top); switch (top) { case 0: if ( ButtonDown(0, BTN_RIGHT ) ) { counter = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { counter = 0; } break; case 1: if ( ButtonDown(0, BTN_RIGHT ) ) { onkyo = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { onkyo = 0; } break; case 2: if ( ButtonDown(0, BTN_RIGHT ) && volume < 15 ) { volume++; } if ( ButtonDown(0, BTN_LEFT ) && volume > 0 ) { volume--; } break; case 3: if ( ButtonDown(0, BTN_RIGHT ) ) { ransu = 1; } if ( ButtonDown(0, BTN_LEFT ) ) { ransu = 0; } break; case 4: if ( ButtonDown(0, BTN_RIGHT ) && rate < 15 ) { rate++; } if ( ButtonDown(0, BTN_LEFT ) && rate > 0 ) { rate--; } break; case 5: if ( ButtonDown(0, BTN_RIGHT ) && time < 31 ) { time++; } if ( ButtonDown(0, BTN_LEFT ) && time > 0 ) { time--; } break; } if ( ButtonPush(0, BTN_START) ) { flag = counter << 5; flag = onkyo << 4; flag |= volume; SetChannel(0x00); SetNoise(flag); DrawHexdecimal(12, 23, flag ); flag = ransu << 7; flag |= rate; SetChannel(0x08); PlayNoise(flag,time); DrawHexdecimal(18, 23, flag ); } DrawNumMax(19, 3, counter, 1); DrawNumMax(19, 5, onkyo , 1); DrawNumMax(19, 7, volume ,15); DrawNumMax(19, 9, ransu , 1); DrawNumMax(19,11, rate ,15); DrawNumMax(19,13, time ,31); VBlank(); SendDMA(7); } void Title() { static char top; if (! init) { top = 0; ClearScreen(); SetChannel(0x00); DrawString( 5, 3,"SQUARE" , 6); DrawString( 5, 5,"TRIANGLE" , 8); DrawString( 5, 7,"NOISE" , 5); init = 1; } if ( ButtonPush(0, BTN_UP ) && top > 0) { top--; } if ( ButtonPush(0, BTN_DOWN) && top < 2) { top++; } DrawArrow(top); VBlank(); SendDMA(7); if ( ButtonPush(0, BTN_A ) || ButtonPush(0, BTN_START ) ) { init = 0; switch (top) { case 0: menu = menuSQUARE; break; case 1: menu = menuTRIANGLE; break; case 2: menu = menuNOISE; break; } } } // メイン処理 void NesMain() { const char palettebg[] = { 0x0f, 0x21, 0x30, 0x30, 0x0f, 0x21, 0x30, 0x30, 0x0f, 0x21, 0x30, 0x30, 0x0f, 0x21, 0x30, 0x30 }; const char palettesp[] = { 0x0f, 0x21, 0x30, 0x30, 0x0f, 0x21, 0x30, 0x30, 0x0f, 0x21, 0x30, 0x30, 0x0f, 0x21, 0x30, 0x30 }; VBlank(); InitPPU(); SetPalette((char *)palettebg,0); SetPalette((char *)palettesp,1); SetPPU(0x08,0x1e); menu = menuTITLE; init = 0; while (1) { CheckPad(); if ( ButtonDown(0, BTN_START) && ButtonPush(0, BTN_SELECT) ) { init = 0; menu = menuTITLE; } switch (menu) { case menuTITLE: Title(); break; case menuSQUARE: Square(); break; case menuTRIANGLE: Triangle(); break; case menuNOISE: Noise(); break; } } } //NMI割り込み void NMIProc(void){} }}} *注意事項 **実機での動作について -Nestopia・NnnesterJ・VirtuaNESの3つで動作が確認できれば実機でも動く可能性が高いでしょう。 -変数宣言時の初期値設定は効果がありません。 -背景のみならず、スプライトの表示もVBlank中に済ませる必要があります。 **無限ループ -以下の処理は無限ループになります。 #highlight(c){{{ signed char i; char j; j = 0; for (i=10; i >= j; i--) {} }}} -回避するには、評価式に使用する変数もsignedにする必要があります。 #highlight(c){{{ signed char i,j; j = 0; for (i=10; i >= j; i--) {} }}} **描画について -背景の描画などを行った後は、スクロール値を設定する必要があります。&br()($2006や$2007にアクセスすると、スクロール値が書き換わるため。) #highlight(c){{{ VBlank(); SetBackground(0x20,0x00,(char *)bg,10) SetScroll( 0, 0 ); }}} -VBlank中やNMI割り込み中に行う処理の優先順位はDMA→BG描画(一部)→その他の処理(パッドのチェックなど)の順。 -VBlank中を確認して描画処理を行っても、VBlank終了間際だと画面がちらつく場合があります。&br()その場合はVBlankを2回チェックしてから描画処理を行いましょう。 **計算について -サブルーチンの引数に割り算などをそのまま渡すと落ちます。&br()例)SetScroll(10 / 3,0)&br()一旦変数に入れるなどしてからサブルーチンを呼び出しましょう。 *参考資料 -[[ギコ猫でもわかるファミコンプログラミング>http://gikofami.fc2web.com/]] -[[ブルジョアソフトウェア研究所>http://hp.vector.co.jp/authors/VA042397/]] -[[オレスタルビーイング00>http://www.geocities.jp/tkoztkoz/]] -[[D-Soft>http://web.archive.org/web/20070521083612/http://aqube.kir.jp/dsoft/]]&br()消えないうちにご覧下さい -[[すずめ愛好会>http://web.archive.org/web/20021209174225/vsync.org/ns/index.html]]&br()消えないうちにご覧下さい -[[simple 15puzzle for nes ver 0.1>http://www37.atwiki.jp/kikakubu4/?cmd=upload&act=open&pageid=62&file=15puzzle0.1.zip]]&br()有志作成の15パズル -[[わいわいの巣>http://www.geocities.jp/yy_6502/]]&br()グラフィックエディタ -[[NES Hack Factory>http://www.geocities.jp/kz_s6502/]]&br()ネームテーブルエディタ・PCM for NES -[[mck hogehoge (仮)>http://takamatsu.cool.ne.jp/dutycycle/]]&br()サウンドツール -[[MCK Wiki>http://wikiwiki.jp/mck/]]&br()サウンドツール -[[NesDev>http://nesdev.parodius.com/]]&br()海外のNES開発資料 -[[ワンチップマイコンでゲームを作ろう。>http://www.geocities.jp/r8ctiny/]]&br()RP2C02にファミコンの資料があります -[[pgate1@crystal>http://crystal.freespace.jp/pgate1/]]&br()PPUについての説明があります。 -[[魂の道具箱>http://taka-p.homeip.net/dtm/tools/index.html]]&br()ファミコンに音声合成で喋らせるツール(ただしディスクシステム用) -[[セミコロンC>http://offgao.no-ip.org/]]&br()プログラムのページにファミコン用サウンドドライバ -[[98-026 Nintendo>http://bobrost.com/nes/]]&br()nbasic - [[ファミコンのプログラム@2ch>http://www37.atwiki.jp/kikakubu4/archive/20101108/1710b326237ab726e93d7f7b8ef76a8a]] - [[ファミコンのプログラム2@2ch>http://www37.atwiki.jp/kikakubu4/archive/20101108/5fed20b34cd47def355ded525ef1ceb7]] -[[ファミコンのプログラム現行スレ@2ch>http://find.2ch.net/?TYPE=TITLE&COUNT=10&STR=%A5%D5%A5%A1%A5%DF%A5%B3%A5%F3%A4%CE%A5%D7%A5%ED%A5%B0%A5%E9%A5%E0+board%3A%A5%B2%C0%BD%BA%EE%B5%BB%BD%D1]] }}}}}

表示オプション

横に並べて表示:
変化行の前後のみ表示: