ホームチュートリアルプログラム
1-セットアップ 4-サウンド 7-FIFO インベーダー
2-フレームバッファ 5-SRAM 8-割り込み DSEmu
3-キー入力 6-ファイルシステム 9-マイク  
10-拡張回転バックグラウンド

Homebrew任天堂DS開発 Part 3

このチュートリアルシリーズのPart 2ではフレームバッファグラフィックスモードと垂直ブランク割り込みの使用に関して解説しました。

このPart 3ではユーザが任天堂DS上のキー(ボタン)を押したときのハンドリングに関して解説します。

KEYS レジスタ

任天堂DSには、装置の上のキーが押されている時にオフとなるビットを含むハードウェアレジスタがあります。 このレジスタは 'ndslib' では KEYS と名付けられており、メモリアドレス 0x4000130 に位置していて書き込み禁止です。そして、関連キーが押されると、以下のビットはオフ('0'のセット)にされます。

KEYS ビットキー'ndslib' での定義押された時話された時
0AボタンKEY_AClearedSet
1BボタンKEY_BClearedSet
2SelectボタンKEY_SELECTClearedSet
3StartボタンKEY_STARTClearedSet
4十字ボタン 右KEY_RIGHTClearedSet
5十字ボタン 左KEY_LEFTClearedSet
6十字ボタン 上KEY_UPClearedSet
7十字ボタン 下KEY_DOWNClearedSet
8RボタンKEY_RClearedSet
9LボタンKEY_LClearedSet

XKEYS レジスタ

上記レジスタには2つのキーが抜けているのに気付くでしょう。それはゲームボーイアドバンスにはなくて、任天堂DSで新しく追加された 'X' と 'Y' キーです。

これらの2つのキーはメモリアドレス 0x04000136 に位置する別のレジスタ XKEYS から読み出すことができます。残念ながらこのレジスタは ARM7 からしかアクセスできません。それに対して KEYS レジスタは、ARM7 および ARM9 からアクセスできます。

ARM9 からこのレジスタを読み出すために、'ndslib' に含まれるデフォルトの ARM7 テンプレートコードでは垂直ブランク割り込み中に読み込んで、IPC->buttons に値を設定します。IPC とは構造体で ARM7 によって読み込まれた有用なデータを含んでおり、ARM9 からアクセス可能です。ARM7 テンプレートコードの関連セクションを記載します。

void InterruptHandler(void) {
   [...]

   but = XKEYS;

   [...]

   IPC->heartbeat = heartbeat;
   IPC->buttons   = but;
   IPC->touchX    = x;

   [...]
}

XボタンまたはYボタンが押された時、XKEYS レジスタの該当ビットがオフにされます。また、タッチスクリーンでのペンの押下およびスクリーンが閉じられたことを示すビットを含んでいます。

XKEYS ビットキー'ndslib' での定義押された時離された時
0Xボタン(1 << 0)ClearedSet
1Yボタン(1 << 1)ClearedSet
2ペンダウン(1 << 6)ClearedSet
3スクリーン閉鎖(1 << 7)SetCleared

注)'スクリーン閉鎖'は他のビットと意味が異なります。スクリーンが閉じられたときにビットがオンになり、スクリーンが開けられるとビットがオフになります。

キーの読み込み

'ndslib' ライブラリには便利なマクロ READ_KEYS があり、それは KEYS の未使用のビットをマスクし、それの補数を得ます。これで、キーが押されているかどうかは '&' オペレーション間単に取得できます。READ_KEYS マクロを使用したコーディングの例です。

    if(READ_KEYS & KEY_UP)
      --shape_y;

Instead of the less intuitive:

    if(!(KEYS & KEY_UP))
      --shape_y;

7ndslib'には READ_KEYS と同等の XKEYS レジスタに対するマクロが見つかりませんでした。したがって、IPC->buttons から直接値を取得しなければなりません。

    uint16 specialKeysPressed = ~IPC->buttons;

    // Y Key
    if(specialKeysPressed & (1 << 1))
      shape_color = RGB15(7, 7, 7);

    // X Key
    if(specialKeysPressed & (1 << 0))
      shape_color = RGB15(0, 15, 15);

    // Pen Down
    if(specialKeysPressed & (1 << 6))
      shape_color = RGB15(0, 31, 31);

    // Hinge closed
    if(!(specialKeysPressed & (1 << 7)))
      shape_color = RGB15(0, 0, 0);

注)スクリーンが閉じているかのビットの判定は他のビットと異なり '!' (not)が必要です。

キーによる図形の移動

キーも使用のデモとして、チュートリアル2の図形のサンプルをちょっと変更してみます。

図形を自動的に移動させる代わりに、十字ボタンを使用して図形を移動させます 他のボタンは図形の色を変更します。スクリーンを閉じるとそれは見えなくなります。(色を黒にします。というか、閉じたら見えないと思うのですが・・・)スクリーンをオープンにして、別のボタンを押すと、再度図形が表示されます。

色を変更するために、現在の色を保持するグローバル変数を使用します。

static uint16 shape_color = RGB15(31, 0, 0);

そしてこの変数を使用するために描画コードを変更します。

void on_irq() 
{	
  if(IF & IRQ_VBLANK) {
    draw_shape(old_x, old_y, VRAM_A, RGB15(0, 0, 0));
    draw_shape(shape_x, shape_y, VRAM_A, shape_color);

    // VBLANK割り込みをハンドルしたことをDSに伝達
    VBLANK_INTR_WAIT_FLAGS |= IRQ_VBLANK;
    IF |= IRQ_VBLANK;
  }
  else {
    // 他の割り込みを無視
    IF = IF;
  }
}

図形は十字ボタンで移動します。READ_KEYS マクロを使用して、関連したキーのテストを行います。テストは斜めに移動するのを許すために、キーの組み合わせを判定するように、分離した 'if' を使用します。

    uint16 keysPressed = READ_KEYS;

    // 押されたボタンに基づいて、図形を移動
    if(keysPressed & KEY_UP)
      --shape_y;
    if(keysPressed & KEY_DOWN)
      ++shape_y;
    if(keysPressed & KEY_LEFT)
      --shape_x;
    if(keysPressed & KEY_RIGHT)
      ++shape_x;

色を変更するボタンをテストするために IPC->buttons (XKEYSを保持) の値を使用します。しかし、まず直感的なテストができるようにそれの補数を計算します。

    uint16 specialKeysPressed = ~IPC->buttons;

    // 関連キーが押されたら、図形の色を変更
    if(keysPressed & KEY_A)
      shape_color = RGB15(31, 0, 0);

    if(keysPressed & KEY_B)
      shape_color = RGB15(0, 31, 0);

    if(keysPressed & KEY_SELECT)
      shape_color = RGB15(0, 0, 31);

    if(keysPressed & KEY_START)
      shape_color = RGB15(31, 31, 31);

    if(keysPressed & KEY_R)
      shape_color = RGB15(15, 0, 15);

    if(keysPressed & KEY_L)
      shape_color = RGB15(7, 15, 7);

    // Y Key
    if(specialKeysPressed & (1 << 1))
      shape_color = RGB15(7, 7, 7);

    // X Key
    if(specialKeysPressed & (1 << 0))
      shape_color = RGB15(0, 15, 15);

    // Pen Down
    if(specialKeysPressed & (1 << 6))
      shape_color = RGB15(0, 31, 31);

    // Hinge closed
    if(!(specialKeysPressed & (1 << 7)))
      shape_color = RGB15(0, 0, 0);

ビルド

アプリケーションをビルドする手順はチュートリアル2に概説しています。私はデフォルトのarm7_main.cppとARM9コード arm9_main.cppを使用しています。コンパイルコマンドを実行するための簡単な Makefile ファイルを提供します。

完全なソースコードは keys_demo1.zip ファイルで、エミュレータまたは実機で実行できる keys_demo1.ndskeys_demo1.nds.gba ファイルをダウンロードできます。

結論

このチュートリアルでは、どのようにDS上の異なったキー(ボタン)の押下を検出するかを示しました。もちろん使用した手法が唯一の方法ではありません。

この手法では私達が特定の時点で、キー関連レジスタの値を確認する'ポーリング'方式です。これを垂直ブランク割り込み期間内に実行しました。ユーザが非常に速くキーを押して、離す事ができますので、両方ののイベントがポーリングチェックの前または後に起こる可能性があります。今回のプログラムではボタンの押下を通知を見過ごす可能性があります。

これが1秒間に60フレームの割り込み以上に起きている場合は、'割り込み'を使用することによって、それを回避することができます。垂直ブランク割り込みと同様にキーイベントに対する割り込みがあります。私達はキーが押された時にすぐに呼び出される割り込み機能を持つことができます。割り込みチュートリアルは、どのようにこれを処理するかを示してます。また割り込み方式でのいくつかの損失についても概説しています。チュートリアル6では実際のアプリケーションで有効なキーハンドリングの別の手法を提示しています。

いろいろなコメントや提案は歓迎します。 以下の私の連絡先を見てください。