CのNESのゲーム開発。第4〜6章。 キャラクターを描く

このパートでは、グラフィックスの操作を検討します。背景とキャラクターのスプライトです。

<<<前 次>>>



画像

出所







Vブランクとは何ですか?



PPU(グラフィックプロセッサ)は、テレビに信号を送信するか、プロセッサから情報を受信できますが、同時にはできません。 したがって、送信するのはフレームブランクパルスの周期であるVブランクのみです。







90%の時間、PPUはピクセルをビデオ出力に左から右、上から下に行ごとに送信します。 画面の下部で一時停止が行われ、すべてが再び繰り返されます。 これは毎秒60回発生します。 フレームのレンダリング後の一時停止はV空白です。 これは非常に短い期間です。 背景タイルの2〜4列の更新とスプライトの更新に投資するのが現実的です。 バックグラウンド更新は、スクロールゲームにとって特に重要です。







Vブランクの発症について学ぶには2つの方法があります。 まず、PPUは$ 2002の上位レジスタビットにフラグを設定します。 次に、マスク不能割り込み(NMI)が呼び出され、指定されたアドレス(割り込みベクトル)に移行します。 PPUの操作、音楽のタイミングの制御、スプライトの移動に使用できます。 NMI呼び出しの間隔は1/60秒であり、ゲームの時間をカウントするために使用できます。







次に、30フレームごとに1文字ずつ「HELLO WORLD」を出力するプログラムを作成します。 フレーズ全体が表示された後、画面をクリアし、すべてを円で繰り返します。







フラグメントに注意してください







nmi: inc _NMI_flag inc _Frame_Count
      
      





reset.sファイル内。 これは、グローバル変数にフレームカウンターを実際に実装するマスク不可能な割り込みハンドラコードです。 各フレームはNMI_flagフラグを設定し、30フレームが経過すると(0.5秒)、次の文字のスプライトが表示されます。 すべてのロジックはmain()で実装されます。







lesson2.c
 void Load_Text(void) { if (Text_Position < sizeof(TEXT)){ //    PPU_ADDRESS = 0x21; // Y-  PPU_ADDRESS = 0xca + Text_Position; //X-    PPU_DATA = TEXT[Text_Position]; ++Text_Position; } else { // ,      Text_Position = 0; PPU_ADDRESS = 0x21; PPU_ADDRESS = 0xca; for ( index = 0; index < sizeof(TEXT); ++index ){ PPU_DATA = 0; //          } } } void main (void) { All_Off(); //   Load_Palette(); Reset_Scroll(); All_On(); //   while (1){ //   while (NMI_flag == 0); //  NMI NMI_flag = 0; if (Frame_Count == 30){ //  30  Load_Text(); Reset_Scroll(); Frame_Count = 0; } } } //  NMI  ++NMIflag  ++FrameCount   V-blank
      
      





画像







ここここのソースコード。







理想的には、All_On()を呼び出す前にVブランクを待つ必要があります。そうしないと、1フレーム後に歪んで画面がちらつきます。 この例では、All_On()がプレフィックスの先頭で1回だけ呼び出され、この時点で画面が黒く、歪みが見えないため、この効果は見えません。







リトルカラー



これで、何が起こったかを色付けできます。







NESのパレットに関するいくつかの言葉。 PPUメモリでは、4つのバックグラウンドパレットの場合はアドレス$ 3F00- $ 3F0Fで16バイトが割り当てられ、4つのスプライトパレットの場合はアドレス$ 3F10- $ 3F1Fで16バイトが割り当てられます。 スプライトの最初の色は常に透明としてレンダリングされ、背景の最初の色はデフォルトの背景色としてレンダリングされます。 最初の色に対応するPPUメモリのバイトは、8つのエントリすべてに対してミラー化され、デフォルトの背景色に対応します。 したがって、スプライトごとに3つの一意の色、3つの色、およびタイルの共通の背景を使用できます。







画像







したがって、同時に50色のうち25色を描画できます。







画像







これらの色は、色強調レジスタを介して暗くすることができますが、この機能はコンソールのすべてのクローンと互換性があるわけではありません。 さらに、一部のテレビは屋根を$ 0Dと$ 1Dの色から引き裂いています。 彼らは黒よりも黒くなり、これは緊急事態です。 色$ xFを使用します。







背景の場合、パレットは、サイズが16x16ピクセルの2x2タイルのブロックに対して決定されます。 この内訳にはNes Screen Toolを使用すると便利です。 ほとんどのゲームは、これらのクワッドメタタイルから背景を構築します。







名前の各テーブル(実際には、画面をレンダリングするための空白)では、属性に64バイトが割り当てられています。 たとえば、テーブル0の場合、2000ドルから23FFのPPUアドレスが使用され、属性は23C0から23FFに格納されます。 各属性バイトは、4つのメタタイル、つまり32x32ピクセルの領域を記述します。 したがって、2ビットはメタファイルのパレット番号を選択します。 メタタイルのビットマッチは次のとおりです。







画像

ここでは、1つの文字が1つのタイル、2x2メタタイルの小さな正方形に対応しています。







つまり、右下の象限のパレットを可能な4つの最初のパレットに変更するには、最上位の2つの属性ビットを01:01xxxxxxxに変更する必要があります。







前の例にパレットを追加したので、文字がカラフルになりました。 パレットは別のファイルに移動され、#includeを介してインポートされ、変数は#pragmaを介してメモリのゼロページで定義されます-この機能を示すためです。 ゼロページの方が高速ですが、コンパイラの内部ニーズのために10〜20個の変数が使用されます。 したがって、235個の使用可能なバイトのどこかに依存する必要があります。







 #pragma bss-name(push, "ZEROPAGE") //   #include "PALETTE.c" #include "CODE.c"
      
      





lesson3.c
 const unsigned char PALETTE[]={ 0x11, 0x00, 0x00, 0x31, //  0x00, 0x00, 0x00, 0x15, //  0x00, 0x00, 0x00, 0x27, //  0x00, 0x00, 0x00, 0x1a, //  }; // 011 -   ,  //      const unsigned char Attrib_Table[]={ 0x44, // 0100 0100, 0xbb, // 1011 1011, 0x44, // 0100 0100, 0xbb}; // 1011 1011 }; //   - 2       1616  PPU_ADDRESS = 0x23; PPU_ADDRESS = 0xda; for( index = 0; index < sizeof(Attrib_Table); ++index ){ PPU_DATA = Attrib_Table[index]; } //     PPU
      
      





画像







ここここのソースコード。







次に、属性テーブルアドレスとその画面座標へのバインドに関する便利なチートシートを示します。

画像

画面の幅は256ピクセルで、表の各セルは32x32の正方形に対応しています







スプライト



スプライトは、画面内を移動できる8x8の画像です。 8x16のスプライトを使用するのは難しい方法がありますが、それは行いません。 ほとんどすべてのゲームキャラクターはスプライトで構成されています。 場合によっては、スプライトの数の制限のために背景タイルでレンダリングする必要がありますが、これは一部のゲームの最終ボスに使用されます。

8x8ポイントは非常に小さいため、複数のスプライトからキャラクターを収集する必要があります。2x4の小さなマリオと2x4の大きなマリオです。







画像







制限に留意してください。 サポートされるスプライトは64個までで、画面の1行で8個までです。 制限を超えると、優先度の高いスプライトのみが描画されます。 フレーム間でスプライトの優先度を変更すると、点滅します。 この方法は、ゲームでよく使用されます。







PPUはスプライト情報をOAMテーブルに保存します。 サイズは256バイトです。64個のスプライトごとに4バイトです。 同じ優先度の2つのスプライトが重ね合わされると、小さい番号のスプライトが上に描画されます。 画面の1行に8個以上のスプライトがある場合、数字の小さい8個だけが描画されます。







OAMテーブルには、次の属性が格納されます。







  1. Y座標
  2. 表示するスプライトインデックス
  3. 属性:パレット、優先度、鏡像
  4. X座標


座標は左上隅でカウントされます。 Xは$ 00から$ F8まで、Yは$ 00から$ EEまでです。 スプライトを画面の下または右に部分的に隠すことができますが、左と上の境界線は不可侵です。 この例では、初期化コードは以下のスプライトを非表示にします、Y = $ F8。

OAMテーブルの詳細。







これはすべてメモリにどのように保存されますか:

画像

確かに、ここではスプライトテーブルはRAMに700ドルで保存され、例では200ドルを使用しています。







OAMテーブルへの書き込みはメモリレジスタを介して実装されます。2003年にスプライトのアドレスを書き込み、2004年にそのデータを書き込む必要があります。 DMAを使用するより便利で高速な方法があるため、これはめったに使用されません。 レジスタ$ 4014を介してアクティブ化されます。アドレス$ 4014に$ xxを書き込み、範囲$ xx00〜$ xxFFから256バイト自体がOAMに注がれます。 これは、Vブランク期間中に行う必要があります。







この例では、4つのスプライトのメタスプライトを作成し、アニメーションを追加します。







スプライトは予想される座標の1ポイント下に描画されることに注意してください。 投稿の上部にあるマリオの写真では、彼が1ピクセル床に行ったことがわかります。







lesson4.c
 #pragma bss-name(push, "ZEROPAGE") unsigned char NMI_flag; unsigned char Frame_Count; unsigned char index; unsigned char index4; unsigned char X1; unsigned char Y1; unsigned char move; unsigned char move_count; #pragma bss-name(push, "OAM") unsigned char SPRITES[256]; // OAM    $200-$2FF,     cfg const unsigned char PALETTE[]={ 0x19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x19, 0x37, 0x24, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; const unsigned char MetaSprite_Y[] = {0, 0, 8, 8}; //  Y- const unsigned char MetaSprite_Tile[] = {0, 1, 0x10, 0x11}; //   const unsigned char MetaSprite_Attrib[] = {0, 0, 0, 0}; // :     const unsigned char MetaSprite_X[] = {0, 8, 0, 8}; //  X- //  4 ,    -   void every_frame(void) { OAM_ADDRESS = 0; OAM_DMA = 2; //     OAM  DMA PPU_CTRL = 0x90; //   NMI  PPU_MASK = 0x1e; SCROLL = 0; SCROLL = 0; // ,    } //      void update_Sprites (void) { index4 = 0; for (index = 0; index < 4; ++index ){ SPRITES[index4] = MetaSprite_Y[index] + Y1; //    ++index4; SPRITES[index4] = MetaSprite_Tile[index]; //   ++index4; SPRITES[index4] = MetaSprite_Attrib[index]; //    ++index4; SPRITES[index4] = MetaSprite_X[index] + X1; //    ++index4; } } //  ,   void main (void) { All_Off(); //   X1 = 0x7f; //    Y1 = 0x77; //     Load_Palette(); Reset_Scroll(); All_On(); //   while (1){ //   while (NMI_flag == 0); //    NMI NMI_flag = 0; every_frame(); //      v-blank if (move == 0) ++X1; if (move == 1) ++Y1; if (move == 2) --X1; if (move == 3) --Y1; ++move_count; if (move_count == 20){ //  20  move_count = 0; ++move; } if (move == 4) move=0; update_Sprites(); } }
      
      





四角の中を移動するときにキャラクターが回転するわずかに改善されたバージョンがあります。







フラグメントlesson4b.c
 const unsigned char MetaSprite_Tile[] = { //  2, 3, 0x12, 0x13, // right 0, 1, 0x10, 0x11, // down 6, 7, 0x16, 0x17, // left 4, 5, 0x14, 0x15}; // up void update_Sprites (void) { move4 = move << 2; //    4 index4 = 0; for (index = 0; index < 4; ++index ){ SPRITES[index4] = MetaSprite_Y[index] + Y1; //    ++index4; SPRITES[index4] = MetaSprite_Tile[index + move4]; //   ++index4; SPRITES[index4] = MetaSprite_Attrib[index]; //    ++index4; SPRITES[index4] = MetaSprite_X[index] + X1; //    ++index4; } }
      
      





画像







ここここのソースコード。








All Articles