ニンテンドーDSのプログラミング。 シンプルなゲーム

この記事では、タイルグラフィック、割り込み、タッチスクリーン、キーボードの操作を検討します。 これに基づいて、私たちは子供の頃から「タグ」という有名なゲームをすべての人に書きます。
まず、DSビデオコントローラーの動作を詳細に調べます。

ビデオコントローラーの初期化


ほとんどすべてのビデオモードは、画面への出力を整理するために「マルチレイヤー」構造を使用します。つまり、同時に最大4つのプラン(背景)を表示できます。 どの用語を使用するのが良いかよくわかりません。「計画」-「背景」があるとします。

合計で6種類の背景があります。



既に述べたように、両方のニンテンドーDSグラフィックコアは共有ビデオメモリ(656KB)を使用します。 さまざまなサイズと目的の9つの銀行に分かれており、AからIまでのラテン文字で名前が付けられています。完全なリストを次に示します。 ビデオコントローラがこれらのバンクを使用できるようにするには、それらを0x06000000で始まるアドレス空間の特別な領域にマップ(「マップ」)する必要があります。
ビデオメモリの構成とさまざまな銀行の目的の詳細については、 こちらをご覧ください

ゲームでは、ビデオコントローラーのゼログラフィックモード(MODE_0_2D)を使用します。このモードには4つのタイルプランがあります。 下の画面(デフォルトでは追加のコア)では、ゲームの1つが実際に発生し(チップを移動)、もう1つはスプラッシュ画面の表示に適用されます。 上部画面(メインコア)は、テキスト情報を表示するためにのみ使用されます。
videoSetMode(MODE_0_2D | DISPLAY_BG0_ACTIVE); //
videoSetModeSub(MODE_0_2D | DISPLAY_BG1_ACTIVE | DISPLAY_BG0_ACTIVE); //

* This source code was highlighted with Source Code Highlighter .


タイルプランモードでのビデオメモリの構成についてもう少し詳しく説明します。 このモードでの画面上の画像は、いわゆるタイルカードに基づいて形成されます。このタイルカードには、画像内の8x8ピクセルの正方形に表示されるタイルの数が書き込まれます。 タイル自体は別のメモリ領域に保存されます。 ビデオコントローラーはどのアドレスからマップを表示し、タイルはコントロールレジスタ(CR)によって決定されます。 8つのプラン(メインコアに4つ、セカンダリに4つ)のそれぞれにレジスタがあります。 この例では、SUB_BG0_CR、SUB_BG1_CRおよびBG0_CRの3つを初期化する必要があります-使用される各プランに1つです。
ちょっとしたトリックがあります。 実際には、制御レジスタは16ビットであり、その中にカードアドレスとタイルのアドレスおよびその他のパラメーターの両方を格納する必要があります。 これに関連して、5ビットがアドレスに割り当てられます。 したがって、タイルは16Kのオフセットで32個のベースアドレスに保存でき、カードは2Kの位置で32個のアドレスに保存できます。
1つのメモリバンクに格納されているという事実にもかかわらず、次の図があります。

下の画面には2枚のタイルカードが必要です。 それらはベースアドレス0と1に配置されます。また、タイル自体の2つのセットが必要です。 タイルのゼロベースアドレスは、カードの使用済みメモリと交差するため、使用しません。 ベースアドレス1から、タイルタイルを配置します。 これらは36 KBを占有するため、ベースアドレス2、3、および4も使用しません。 次に、アドレス5から、スタートアップスクリーンセーバー用のタイルのセットを配置します。
トップ画面では、ベースアドレスが0のタイルカードを使用し、ロシア語フォントが配置されるタイルは、アドレス1から配置します。テキストの制御レジスタは、コンソールの初期化中にlibNDSを設定します。
次に、16個のカラータイル(BG_COLOR16)を使用するように制御レジスタを初期化します。
int tile_base = 1;
int map_base = 0;
int tile_base_s = 5;
int map_base_s = 1;
int char_base = 1;
int scr_base = 0;
REG_BG0CNT_SUB = BG_COLOR_16 | BG_TILE_BASE(tile_base_s) | BG_MAP_BASE(map_base_s); //
REG_BG1CNT_SUB = BG_COLOR_16 | BG_TILE_BASE(tile_base) | BG_MAP_BASE(map_base); //

* This source code was highlighted with Source Code Highlighter .

実際、他のベースアドレスにタイルとカードを配置すると、かなりの量のビデオメモリ(32KB)を節約できます。 ただし、この場合、このような最適化は必要ありません。 空きメモリが十分すぎる。

次に、ベースアドレス番号を絶対的なビデオメモリアドレスに変換して、直接操作できるようにします。
u16* sub_tile = (u16*)BG_TILE_RAM_SUB(tile_base);
u16* sub_map = (u16*)BG_MAP_RAM_SUB(map_base);
u16* sub_tile0 = (u16*)BG_TILE_RAM_SUB(tile_base_s);
u16* sub_map0 = (u16*)BG_MAP_RAM_SUB(map_base_s);
u16* tile_char = (u16*)BG_TILE_RAM(char_base);
u16* map_char = (u16*)BG_MAP_RAM(scr_base);

* This source code was highlighted with Source Code Highlighter .


次に、タイルのデータをビデオメモリにコピーします。
memcpy(( void *)sub_tile, (u8*)tilesTiles, 192*192/2); //
for (i=0; i < 16; ++i)
BG_PALETTE_SUB[i] = tilesPal[i]; //
memcpy(( void *)sub_tile0, (u8*)startTiles, 256*192/2); //
for (i=0; i < 16; ++i)
BG_PALETTE_SUB[i+16] = startPal[i]; //

* This source code was highlighted with Source Code Highlighter .


そして、すぐに下の画面のゼロプランにスプラッシュスクリーンタイルを入力します。
for (i=0; i< 24*32; i++) //
sub_map0[i] = (u16)(i)|0x1000;

* This source code was highlighted with Source Code Highlighter .

タイル番号以外のタイルマップの各単語には、パレットと軸に沿った反射に関する情報が含まれています。 最初のパレットを使用するために、マップの各要素に対して12ビットを1に設定します。
タイルマップのビットマップ要素をペイントすると、次のように表示されます。
ビット1514131211109876543210
予定パレットVetrik.otr。ホライズ。タイル番号

libNDSコンソールを初期化します。
PrintConsole *console = consoleInit(NULL, 0, BgType_Text4bpp, BgSize_T_256x256, scr_base, char_base, true , true );
ConsoleFont font;
font.gfx = (u16*)pa_text2Tiles; //
font.pal = (u16*)pa_text2Pal; //
font.numChars = 256; //
font.numColors = pa_text2PalLen/2;
font.bpp = 4;
font.asciiOffset = 0;
font.convertSingleColor = false ;
consoleSetFont(console, &font);

* This source code was highlighted with Source Code Highlighter .

プログラムでは、PAlibエンコードCP1251用にClusterMによって作成されたフォントを使用します。 残念ながら、現在のバージョンのライブラリでは、Unicodeに切り替えようとしたときに、ASCIIの上半分の文字を出力するサポートが壊れていたため、ロシア語のテキストなしで行う必要があります。 もちろん、文字コードをタイルマップに書き込むことで直接表示できます。

すべてのタイルは、 gritプログラムを使用してBMPから変換することにより作成されます。

キーボードとタッチスクリーン


libNDSを使用せずにキーの状態を読み取るには、ARM9プロセッサレジスタだけでなく、ARM7も使用する必要があります。 幸いなことに、ライブラリの作成者はこの事実を無視するように誘われます。 scanKeys()関数を使用して、クリック情報の状態を更新するだけです。 また、押されたキーを決定するため、またはタッチスクリーンを押すためのkeysHeld()。 正確に押されるものは、関数によって返される値のビットに従って決定されます。
キー定義仮面
ビット
関連する入力
KEY_A1 << 0ボタンA
KEY_B1 << 1ボタンB
KEY_SELECT1 << 2選択ボタン
KEY_START1 << 3スタートボタン
KEY_RIGHT1 << 4右ボタン
KEY_LEFT1 << 5左ボタン
KEY_UP1 << 6上ボタン
KEY_DOWN1 << 7ボタンダウン
KEY_R1 << 8Rボタン
KEY_L1 << 9Lボタン
KEY_X1 << 10Xボタン
KEY_Y1 << 11Yボタン
KEY_TOUCH1 << 12タッチスクリーン
KEY_LID1 << 13カバーを閉じました


したがって、ループでそれを行うだけです。
scanKeys();
held = keysHeld();

* This source code was highlighted with Source Code Highlighter .

そして、保持された変数で必要なアクションを実行するという事実に応じて。
KEY_TOUCHビットが設定されている場合、タッチスクリーンが検出され、touchRead関数を使用してスタイラスの座標を読み取ることができます。 touchPosition構造体を返します。この構造体では、スタイラスが指すピクセルの座標を含むpxおよびpyフィールドに関心があります。
if (held&KEY_TOUCH){ //
touchRead(&touchXY);
...
}

* This source code was highlighted with Source Code Highlighter .


中断


ユーザーと対話するほとんどのプログラムの通常の動作(プログラムも例外ではありません)には、時間間隔の制御が必要です。これは通常、タイマーからの割り込みによって提供されます。 割り込みを処理するための3つのレジスタがあります。
住所大きさ説明
REG_IME0x0400020816ビットメイン割り込み許可レジスタ
REG_IE0x0400021032ビット割り込み許可レジスタ
REG_IF0x0400021432ビット割り込みフラグレジスタ

割り込みマスタイネーブルレジスタは、すべての割り込みハンドラをオンおよびオフにする機能を提供します。
割り込み有効化レジスタを使用すると、個々の割り込みを有効または無効にすることができます。 レジスタの各ビットは、特定の割り込みを担当します。
ビットlibndsの名前説明
0IRQ_VBLANK垂直逆ビーム
1IRQ_HBLANK水平戻りビーム
2IRQ_YTRIGGERREG_VCOUNT行のスキャン
3IRQ_TIMER0タイマーが作動しました0
4IRQ_TIMER1タイマー1が機能しました
5IRQ_TIMER2タイマー2が作動した
6IRQ_TIMER3タイマー3トリガー
7IRQ_NETWORK
8IRQ_DMA0DMA 0
9IRQ_DMA1DMA 1
10IRQ_DMA2DMA 2
11IRQ_DMA3DMA 3
12IRQ_KEYSキーを押した
13IRQ_CARTGBAカートリッジを取り外しました
16ARM7 IPC割り込みがトリガーされました
17入力FIFOは空ではありません
18出力FIFOは空ではありません
19IRQ_CARDDSカードのデータが完了しました
20IRQ_CARD_LINEDSカード割り込み3
21GFX FIFO割り込み

割り込みフラグレジスタは、割り込みが発生するとハードウェアによって設定されます。 割り込みビットマスクが含まれています。

割り込みを直接処理することはありませんが、通常どおり、libndsのサービスを使用します。
まず、「垂直リバースビーム」に沿って割り込みハンドラーを設定します。 この割り込みは、画面のレンダリングが完了すると呼び出されます。 画像のちらつきや裂けを防ぐために、この割り込みのハンドラーに画像を出力します。
void IRQ_vblank( void ){ //
... ...
}
...
irqSet(IRQ_VBLANK, IRQ_vblank); // .

* This source code was highlighted with Source Code Highlighter .

次に、タイマーの1つを目的の周波数に設定し、割り込みハンドラーを設定します。 libNDSライブラリは、これらの目的に非常に便利なtimerStart関数を提供します。 タイマーと割り込みを完全に初期化するには、必要な分周器、周波数、割り込みハンドラーへのポインターを使用してこの関数を呼び出すだけで十分です。
void timer0_function( void ){
... ...
}
...
timerStart(0, ClockDivider_256, TIMER_FREQ_256(1000), timer0_function); // 256 1000

* This source code was highlighted with Source Code Highlighter .


最後に、libndsライブラリによって提供される別の関数、swiWaitForVBlankを検討します。 垂直後方割り込みが発生するまでARM9プロセッサを停止します。

上記のすべてを使用して、簡単なゲームをすでに作成できます。 ここでは、ゲームの「タグ」のソースコードを取得できます。 ここに実行可能ファイルがあります。
スクリーンショット:


Source: https://habr.com/ru/post/J102679/


All Articles