「ファミコンソフトの作り方」の編集履歴(バックアップ)一覧に戻る

ファミコンソフトの作り方 - (2010/12/15 (水) 00:01:26) の編集履歴(バックアップ)



「hello kikakubu」の作り方

1.まずcc65をダウンロードする。
http://www.cc65.org/
Download->available for download.の

2.全て展開し、中身をC:\cc65に移動する。
  • cc65というフォルダは自分で作る。
  • 「フォルダの上書きの確認」が表示されたら「はい」を選ぶ。

3.メモ帳に以下の内容を書き、test.cという名前でデスクトップに保存。
#include <conio.h>
int main (void)
{
    clrscr();
    cprintf("hello kikakubu");
    while(1)
    {
    }
    return 0;
}
 

4.メモ帳に以下の内容を書き、sakusei.batという名前でデスクトップに保存。
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
 

5.コマンドプロンプトを開く。
スタートメニューのすべてのプログラム-アクセサリ-コマンドプロンプトを選ぶ。

6.cd デスクトップと入力し、Enterを押す。

7.sakusei testと入力し、Enterを押す。

8.test.nesが作成されるので、ファミコンエミュレータで開く。


コントローラチェックソフトの作り方

1.メモ帳に以下の内容を書き、test.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);
        }
    }
}
 

2.以降は「hello kikakubu」と同じ手順でtest.nesを作る。


BGMの鳴らし方

  • 演奏用ライブラリ(後述)を使う。
  • 15puzzleのサウンドモジュールを使う。
  • セミコロンCのドライバを使う。
  • mck関連のツールを使って作成されるアセンブリソースを流用する。

アセンブリソースの作成手順
  • 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が鳴る(はず)。
    (ppmck/sounddrv.hのsound_initとsound_driver_startを呼ぶ?)


ファミコン用ライブラリの作り方


ライブラリ使用例

  • 雛形をダウンロードして、実際に動作を確認することができます。
  • ライブラリを作成する必要があります。手順はこちらをご覧下さい。
  • cc65とcygwin(とmakeコマンド)をインストールする必要があります。
    • cc65のインストール方法はこちらを参考にして下さい。
    • cygwin(とmakeコマンド)のインストール方法はググって下さい。
  • 作成手順は次の通りです。
    • 雛形のsample.cをメモ帳で開いて、例に示した内容に書き換えて上書き保存します。
    • 指定があれば.cfg・.asmの内容も書き換えて上書き保存します。
    • makerom.batをダブルクリックします。
      パスが異なる場合はmakerom.batをメモ帳で開いて編集して下さい。
    • sample.nesが作成されます。
    • sample.nesをファミコンエミュレータで開きます。

コントローラによるスプライトの操作とBGのスクロール

#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);
    }
}
 

マイクの確認

CheckPad();
if ( ControlOther(MIC_USE) ) { /* マイク使用中 */ }
 

背景の多重スクロール(ラスタスクロール)

  • 0x00にセットしたスプライト(0爆弾)の描画のタイミングで、画面のスクロール速度を変えています。
  • 0爆弾に使用する画像には何らかの絵が描かれている必要があります。
#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として設定したアドレスへのポインタです。
#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の例
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)が毎回実行されます。
#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にしておきましょう。(チラつき防止、本来逆にしなければならない筈
  • NMIProc内でif (bgx % 8 == ?) {}としていくつかに分けて処理しているのは負荷分担のためです。
    まとめて処理すると表示が追いつかずに背景がきちんと出力されません。
  • 初期化の処理と画面切り替えのタイミングの見直しが必要かも。
#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にしておきましょう。(チラつき防止、本来逆にしなければならない筈
  • その他は縦スクロールと同じです。
#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・効果音

  • 短形波・三角波・ノイズの設定と再生を行います。
  • STARTを押しながらSELECTでタイトルに戻ります。
  • 各項目で左右を押すと値が増減します。
  • SHUHASUはSELECT・A・Bいずれかのボタンを押しながら左右を押すと増分値が変わります。
  • SHUHASUには以下の内容を設定します。
    • 矩形波:1790000/ (周波数 * 32 - 1) ・・・「ラ」(440hz)を鳴らそうと思えば127になります
    • 三角波:1790000/ (周波数 * 64 - 1) ・・・多分。
  • STARTを押すと音が鳴り、フラグの内容が16進数で表示されます。
    ゲームを開発する際にフラグとshuhasu、timeの値を指定すれば同じ音を鳴らすことができます。
#include <kikakubu.h>
#include <kanade.h>
 
typedef enum {
    menuTITLE    = 0,
    menuSQUARE   = 1,
    menuTRIANGLE = 2,
    menuNOISE    = 3,
} emenu;
 
emenu menu;
char init;
 
void ClearScreen()
{
    unsigned char i,pos[2];
    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);
}
 
void DrawNumeric(char x, char y, int val)
{
    unsigned int wrk;
    char str[8];
    signed char i;
 
    for (i=7;i>=0;i--) {
        wrk = val % 10;
        str[i] = 0x30 + wrk;
        val /= 10;
    }
 
    DrawString(x,y,str,8);
}
 
void Square()
{
    char flag1,flag2;
    static char top   =0;
    static char duty  =0,counter=0,onkyo =0,vol =0;
    static char henka =0,sokudo =0,houhou=0,hani=0;
    static char time  =0;
    static unsigned int shuhasu=0;
 
    if (! init) {
        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 );
    }
 
    DrawNumeric(19, 3, duty   );
    DrawNumeric(19, 5, counter);
    DrawNumeric(19, 7, onkyo  );
    DrawNumeric(19, 9, vol    );
    DrawNumeric(19,11, henka  );
    DrawNumeric(19,13, sokudo );
    DrawNumeric(19,15, houhou );
    DrawNumeric(19,17, hani   );
    DrawNumeric(19,19, shuhasu);
    DrawNumeric(19,21, time   );
 
}
 
void Triangle()
{
    static char top = 0;
    static char counter=0;
    static char length =0,time   =0;
    static unsigned int  shuhasu=0;
    char flag;
 
    if (! init) {
        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 );
    }
 
    DrawNumeric(19, 3, counter);
    DrawNumeric(19, 5, length );
    DrawNumeric(19, 7, time   );
    DrawNumeric(19, 9, shuhasu);
 
}
 
void Noise()
{
    static char top   =0;
    static char counter=0;
    static char onkyo =0,volume =0,ransu=0,rate=0,time=0;
    char flag;
 
    if (! init) {
        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 );
    }
 
    DrawNumeric(19, 3, counter);
    DrawNumeric(19, 5, onkyo  );
    DrawNumeric(19, 7, volume );
    DrawNumeric(19, 9, ransu  );
    DrawNumeric(19,11, rate   );
    DrawNumeric(19,13, time   );
 
}
 
void Title()
{
    static char top=0;
 
    if (! init) {
        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);
 
    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, 0x20, 0x00, 0x20, 0x11,
                   0x0f, 0x00, 0x30, 0x36, 0x0f, 0x06, 0x15, 0x30 };
    const char palettesp[] = { 0x0f, 0x21, 0x30, 0x30, 0x11, 0x30, 0x26, 0x1f,
                   0x11, 0x21, 0x26, 0x1f, 0x11, 0x26, 0x15, 0x1f };
 
    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){}
 

注意事項

無限ループ

  • 以下の処理は無限ループになります。
signed char i;
char j;
j = 0;
for (i=10; i >= j; i--) {}
 

  • 回避するには、評価式に使用する変数もsignedにする必要があります。
signed char i,j;
j = 0;
for (i=10; i >= j; i--) {}
 

描画について

  • 背景の描画などを行った後は、スクロール値を設定する必要があります。
    ($2006や$2007にアクセスすると、スクロール値が書き換わるため。)
VBlank();
SetBackground(0x20,0x00,(char *)bg,10)
SetScroll( 0, 0 );
 
  • VBlank中やNMI割り込み中に行う処理の優先順位はDMA→BG描画(一部)→その他の処理(パッドのチェックなど)の順。
  • VBlank中を確認して描画処理を行っても、VBlank終了間際だと画面がちらつく場合があります。
    その場合はVBlankを2回チェックしてから描画処理を行いましょう。

計算について

  • サブルーチンの引数に割り算などをそのまま渡すと落ちます。
    例)SetScroll(10 / 3,0)
    一旦変数に入れるなどしてからサブルーチンを呼び出しましょう。

参考資料