ボタン入力を取得すること自体はとても簡単です。ところが実際のゲームに使うとなると、気をつけなくてはいけないことや、テクニックがいくつかあります。たとえば以下のソースコードはやってはいけない例の1つです。これにはチャタリングの問題が絡んでいます。
int main()
{
for(;;)
{
u16 key = *(volatile u16*)0x4000130;
TRACE("KEY: %x\n", key);
}
}
信号線はデジタルでも、ボタンを押す強さそのものはぴよこアナログです。したがって、ボタンを押している/押していないの間の「中間の状態」が必ず存在することになります。その「中間の状態」がどういう波形になるかというと、細かい話を抜きにしておおざっぱに書けばこうです。
OFF ───┐┌┐┌┐┌┐┌┐┌┐ ON └┘└┘└┘└┘└┘└───
人間の指は、ボタンの状態を瞬間的に「ON/OFF」に出来るほど強靭なものではないため、ボタンを押したごく一瞬にも「中間の状態」が発生してしまいます。ウェイト処理を入れずに連続してキーセンスを読んだ場合(大抵はμsec~nsec単位)、この波形を読む事により誤動作を起こすわけです。この現象がチャタリングと呼ばれています。そこでVBLANK期間中(1/60秒間隔)に1回だけ呼び出すことで、その分キーセンスを読みに行く間隔を遅くします。つまり「押した、押さない」という入力は最大30回までの認識になるということです。
int main()
{
IrqInit();
for(;;)
{
VBlankIntrWait();
u16 key = *(volatile u16*)0x4000130;
TRACE("KEY: %x\n", key);
}
}
また「メインループの初めにボタン状態を取得しておき、以後のキー入力チェック処理(自機移動など)では、その取得した値を参照する」ようにします。理由は、「必要になったそのつど入出力を読みに行っていたのでは、連続で読むような構造の場合に、短い間隔で読んでいる事と同じ」になってしまうためです。キーセンスは「必要最低限の間隔で一気に」読む事が重要です。
さらにゲームに特化した場合、モジュール化を進めるのが得策です。以下に自分の使っているライブラリを表します。
#include "lib/gba.h"
#include "bg.h"
#include "irq.arm.h"
#include "key.h"
//---------------------------------------------------------------------------
int main(void)
{
REG_WSCNT = 0x4317;
BgInit();
IrqInit();
KeyInit();
char str[32];
for(;;)
{
VBlankIntrWait();
KeyExec();
_Sprintf(str, "cnt:[%04x]", KeyGetCnt());
BgAsciiDrawStr(0, 0, str);
_Sprintf(str, "trg:[%04x]", KeyGetTrg());
BgAsciiDrawStr(0, 1, str);
_Sprintf(str, "off:[%04x]", KeyGetOff());
BgAsciiDrawStr(0, 2, str);
_Sprintf(str, "rep:[%04x]", KeyGetRep());
BgAsciiDrawStr(0, 3, str);
}
}#ifndef KEY_H
#define KEY_H
#ifdef __cplusplus
extern "C" {
#endif
#include "lib/gba.h"
//---------------------------------------------------------------------------
#define KEY_REPEAT_CNT 10
//---------------------------------------------------------------------------
typedef struct {
u16 cnt; // 現在のキー
u16 trg; // 押されたキー
u16 off; // 離されたキー
u16 rep; // リピートキー
s16 repCnt; // リピートカウント
} ST_KEY;
//---------------------------------------------------------------------------
void KeyInit(void);
void KeyExec(void);
u32 KeyGetCnt(void);
u32 KeyGetTrg(void);
u32 KeyGetOff(void);
u32 KeyGetRep(void);
#ifdef __cplusplus
}
#endif
#endif#include "key.h"
//---------------------------------------------------------------------------
ST_KEY Key;
//---------------------------------------------------------------------------
void KeyInit(void)
{
_Memset(&Key, 0x00, sizeof(ST_KEY));
}
//---------------------------------------------------------------------------
// vblank中に1回だけ呼び出します(チャタリング防止)
void KeyExec(void)
{
u16 cnt = REG_KEYINPUT;
cnt = ~cnt & 0x3ff;
Key.trg = (Key.trg ^ cnt) & ~Key.cnt;
Key.off = Key.off ^ (~cnt & Key.cnt);
Key.cnt = cnt;
// キーリピート
if(Key.trg & DPAD || Key.repCnt == 0)
{
Key.rep = Key.cnt;
Key.repCnt = KEY_REPEAT_CNT;
}
else
{
Key.rep = 0;
}
if(Key.cnt & DPAD)
{
if(Key.repCnt != 0) Key.repCnt--;
}
else
{
Key.repCnt = 0;
}
}
//---------------------------------------------------------------------------
// 現在押されているボタン
u32 KeyGetCnt(void)
{
return Key.cnt;
}
//---------------------------------------------------------------------------
// 押された時のボタン
u32 KeyGetTrg(void)
{
return Key.trg;
}
//---------------------------------------------------------------------------
// 離された時のボタン
u32 KeyGetOff(void)
{
return Key.off;
}
//---------------------------------------------------------------------------
// キーリピートのボタン。十字キーのみ対応
u32 KeyGetRep(void)
{
return Key.rep;
}
それぞれのボタンを押したときの特徴です。
| Key.cnt | 現在、押されているボタンを格納する。 |
| Key.trg | 押された時だけのボタンを格納する。押しつづけているボタンは格納しない。 |
| Key.off | 離された時だけのボタンを格納する。もう一度「離されたボタン」を押さない限り保持する。 |
| Key.rep | 十字キーのみ対応。押されているボタンをウェイトを挟んで格納する。 |