- 追加された行はこの色です。
- 削除された行はこの色です。
- doc.17 へ行く。
#author("2023-05-15T12:26:09+09:00;2023-04-17T21:00:09+09:00","","")
#author("2023-05-24T00:43:29+09:00","","")
* ボタン入力の応用 [#mff35492]
ボタン入力を取得すること自体はとても簡単です。ところが実際のゲームに使うとなると、気をつけなくてはいけないことや、テクニックがいくつかあります。たとえば以下のソースコードはやってはいけない例の1つです。これには''チャタリングの問題''が絡んでいます。
int main()
{
for(;;)
{
u16 key = *(volatile u16*)0x4000130;
TRACE("KEY: %x\n", key);
}
}
** チャタリングとは [#ce336b76]
信号線はデジタルでも、ボタンを押す強さそのものは%%ぴよこ%%アナログです。したがって、ボタンを押している/押していないの間の「中間の状態」が必ず存在することになります。その「中間の状態」がどういう波形になるかというと、細かい話を抜きにしておおざっぱに書けばこうです。
OFF ───┐┌┐┌┐┌┐┌┐┌┐
ON └┘└┘└┘└┘└┘└───
人間の指は、ボタンの状態を瞬間的に「ON/OFF」に出来るほど強靭なものではないため、ボタンを押したごく一瞬にも「中間の状態」が発生してしまいます。ウェイト処理を入れずに連続してキーセンスを読んだ場合(大抵はμsec~nsec単位)、この波形を読む事により誤動作を起こすわけです。この現象がチャタリングです。そこでVBLANK期間中(1/60秒間隔)に1回だけ呼び出すことで、その分キーセンスを読みに行く間隔を遅くします。つまり「押した、押さない」という入力は最大30回までの認識になるということです。
人間の指は、ボタンの状態を瞬間的に「ON/OFF」に出来るほど強靭なものではないため、ボタンを押したごく一瞬にも「中間の状態」が発生してしまいます。ウェイト処理を入れずに連続してキーセンスを読んだ場合(大抵はμsec~nsec単位)、この波形を読む事により誤動作を起こすわけです。この現象がチャタリングと呼ばれています。そこでVBLANK期間中(1/60秒間隔)に1回だけ呼び出すことで、その分キーセンスを読みに行く間隔を遅くします。つまり「押した、押さない」という入力は最大30回までの認識になるということです。
int main()
{
IrqInit();
for(;;)
{
VBlankIntrWait();
u16 key = *(volatile u16*)0x4000130;
TRACE("KEY: %x\n", key);
}
}
また「メインループの初めにボタン状態を取得しておき、以後のキー入力チェック処理(自機移動など)では、その取得した値を参照する」ようにします。理由は、「必要になったそのつど入出力を読みに行っていたのでは、連続で読むような構造の場合に、短い間隔で読んでいる事と同じ」になってしまうためです。キーセンスは「必要最低限の間隔で一気に」読む事が重要です。
** さらなる改良 [#yf35a53f]
さらにゲームに特化した場合、モジュール化を進めるのが得策です。以下に自分の使っているライブラリを表します。
- main.c
#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);
}
}
- key.h
#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;
//---------------------------------------------------------------------------
EWRAM_CODE void KeyInit(void);
IWRAM_CODE void KeyExec(void);
IWRAM_CODE u32 KeyGetCnt(void);
IWRAM_CODE u32 KeyGetTrg(void);
IWRAM_CODE u32 KeyGetOff(void);
IWRAM_CODE u32 KeyGetRep(void);
#ifdef __cplusplus
}
#endif
#endif
- key.c
#include "key.h"
//---------------------------------------------------------------------------
ST_KEY Key;
//---------------------------------------------------------------------------
EWRAM_CODE void KeyInit(void)
{
_Memset(&Key, 0x00, sizeof(ST_KEY));
}
//---------------------------------------------------------------------------
// vblank中に1回だけ呼び出します(チャタリング防止)
IWRAM_CODE 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;
}
}
//---------------------------------------------------------------------------
// 現在押されているボタン
IWRAM_CODE u32 KeyGetCnt(void)
{
return Key.cnt;
}
//---------------------------------------------------------------------------
// 押された時のボタン
IWRAM_CODE u32 KeyGetTrg(void)
{
return Key.trg;
}
//---------------------------------------------------------------------------
// 離された時のボタン
IWRAM_CODE u32 KeyGetOff(void)
{
return Key.off;
}
//---------------------------------------------------------------------------
// キーリピートのボタン。十字キーのみ対応
IWRAM_CODE u32 KeyGetRep(void)
{
return Key.rep;
}
** 実行画面 [#hacc9509]
#ref(1.png,nolink)
それぞれのボタンを押したときの特徴です。
|Key.cnt|現在、押されているボタンを格納する。|
|Key.trg|押された時だけのボタンを格納する。押しつづけているボタンは格納しない。|
|Key.off|離された時だけのボタンを格納する。もう一度「離されたボタン」を押さない限り保持する。|
|Key.rep|十字キーのみ対応。押されているボタンをウェイトを挟んで格納する。|
** 出典元 [#p825bd04]
- M-KAIさんの「Witchシューティングゲーム制作記帳」
- 正直日記さん
** 履歴 [#p7782138]
- 2023/04/17
- 2008/07/06