- 追加された行はこの色です。
- 削除された行はこの色です。
#author("2023-04-17T16:02:49+09:00","","")
#freeze
#author("2023-05-24T00:43:29+09:00","","")
* ボタン入力の応用 [#mff35492]
ボタン入力を取得すること自体はとても簡単です。ところが実際のゲームに使うとなると、気をつけなくてはいけないことや、テクニックがいくつかあります。たとえば以下のソースコードはやってはいけない例の1つです。これには''チャタリングの問題''が絡んでいます。~
ボタン入力を取得すること自体はとても簡単です。ところが実際のゲームに使うとなると、気をつけなくてはいけないことや、テクニックがいくつかあります。たとえば以下のソースコードはやってはいけない例の1つです。これには''チャタリングの問題''が絡んでいます。
int main()
{
for(;;)
{
u16 key = *(volatile u16*)0x4000130;
TRACE("KEY: %x\n", key);
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 "intr_arm.h"
#include "bg.h"
#include "irq.arm.h"
#include "key.h"
//---------------------------------------------------------------------------
// key.c
extern ST_KEY Key;
//---------------------------------------------------------------------------
int main()
int main(void)
{
REG_WSCNT = 0x4317;
BgInit();
IrqInit();
KeyInit();
IntrInit();
IntrStart();
char str[32];
char cnt[40];
char trg[40];
char off[40];
char rep[40];
for(;;)
{
SystemCall(5);
VBlankIntrWait();
KeyExec();
_Sprintf(cnt, "cnt: %4x", Key.cnt);
_Sprintf(trg, "trg: %4x", Key.trg);
_Sprintf(off, "off: %4x", Key.off);
_Sprintf(rep, "rep: %4x", Key.rep);
_Sprintf(str, "cnt:[%04x]", KeyGetCnt());
BgAsciiDrawStr(0, 0, str);
BgAsciiDrawStr(0, 0, cnt);
BgAsciiDrawStr(0, 1, trg);
BgAsciiDrawStr(0, 2, off);
BgAsciiDrawStr(0, 3, rep);
_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__
#ifndef KEY_H
#define KEY_H
#ifdef __cplusplus
extern "C" {
#endif
#include "lib/gba.h"
//---------------------------------------------------------------------------
#define KEY_MAX_BTN_CNT 10
#define KEY_REPEAT_WAIT_CNT 32
#define KEY_REPEAT_ENTRY_CNT 3
#define KEY_REPEAT_CNT 10
enum {
SYS_KEY_A = 0x00,
SYS_KEY_B,
SYS_KEY_SL,
SYS_KEY_ST,
SYS_KEY_RI,
SYS_KEY_LE,
SYS_KEY_UP,
SYS_KEY_DO,
SYS_KEY_R,
SYS_KEY_L,
};
//---------------------------------------------------------------------------
typedef struct {
u16 btn[KEY_MAX_BTN_CNT]; // ボタンの判定用
u16 cnt; // 現在のキー
u16 trg; // 押されたキー
u16 off; // 離されたキー
u16 rep; // リピートキー
s16 repCnt; // リピートカウント
u16 cnt; // 現在のキー
u16 trg; // 押されたキー
u16 off; // 離されたキー
u16 rep; // リピートキー
s16 repCnt; // リピートカウント
} ST_KEY;
//---------------------------------------------------------------------------
EWRAM_CODE void KeyInit();
EWRAM_CODE bool KeyChgBtn(u16 b1, u16 b2);
IWRAM_CODE void KeyExec();
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_H_ */
#endif
- key.c
#include "key.h"
//---------------------------------------------------------------------------
ST_KEY Key;
//---------------------------------------------------------------------------
EWRAM_CODE void KeyInit()
EWRAM_CODE void KeyInit(void)
{
_Memset(&Key, 0x00, sizeof(ST_KEY));
Key.btn[SYS_KEY_A] = KEY_A;
Key.btn[SYS_KEY_B] = KEY_B;
Key.btn[SYS_KEY_SL] = KEY_SELECT;
Key.btn[SYS_KEY_ST] = KEY_START;
Key.btn[SYS_KEY_RI] = KEY_RIGHT;
Key.btn[SYS_KEY_LE] = KEY_LEFT;
Key.btn[SYS_KEY_UP] = KEY_UP;
Key.btn[SYS_KEY_DO] = KEY_DOWN;
Key.btn[SYS_KEY_R] = KEY_R;
Key.btn[SYS_KEY_L] = KEY_L;
}
//---------------------------------------------------------------------------
EWRAM_CODE bool KeyChgBtn(u16 b1, u16 b2)
{
if(b1 > KEY_MAX_BTN_CNT)
{
return FALSE;
}
Key.btn[b1] = b2;
return TRUE;
}
//---------------------------------------------------------------------------
// vblank中に1回だけ呼び出します(チャタリング防止)
IWRAM_CODE void KeyExec()
IWRAM_CODE void KeyExec(void)
{
u16 inkey = REG_KEYINPUT;
u16 cnt = 0;
u16 cnt = REG_KEYINPUT;
if(inkey & Key.btn[SYS_KEY_A]) cnt += KEY_A;
if(inkey & Key.btn[SYS_KEY_B]) cnt += KEY_B;
if(inkey & Key.btn[SYS_KEY_SL]) cnt += KEY_SELECT;
if(inkey & Key.btn[SYS_KEY_ST]) cnt += KEY_START;
if(inkey & Key.btn[SYS_KEY_RI]) cnt += KEY_RIGHT;
if(inkey & Key.btn[SYS_KEY_LE]) cnt += KEY_LEFT;
if(inkey & Key.btn[SYS_KEY_UP]) cnt += KEY_UP;
if(inkey & Key.btn[SYS_KEY_DO]) cnt += KEY_DOWN;
if(inkey & Key.btn[SYS_KEY_R]) cnt += KEY_R;
if(inkey & Key.btn[SYS_KEY_L]) cnt += KEY_L;
cnt = ~cnt;
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.cnt & DPAD) || Key.repCnt > KEY_REPEAT_WAIT_CNT)
if(Key.trg & DPAD || Key.repCnt == 0)
{
Key.rep = Key.cnt;
Key.repCnt -= KEY_REPEAT_ENTRY_CNT;
Key.rep = Key.cnt;
Key.repCnt = KEY_REPEAT_CNT;
}
else
{
Key.rep = 0;
}
if(Key.cnt & DPAD)
{
if(Key.repCnt <= KEY_REPEAT_WAIT_CNT) Key.repCnt++;
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)
- 実行画面
#ref(clip_1.png,nolink)
それぞれのボタンを押したときの特徴です。
|Key.cnt|現在、押されているボタンを格納する。|
|Key.trg|押された時だけのボタンを格納する。押しつづけているボタンは格納しない。|
|Key.off|離された時だけのボタンを格納する。もう一度「離されたボタン」を押さない限り保持する。|
|Key.rep|十字キーのみ対応。押されているボタンをウェイトを挟んで格納する。|
*** cnt [#badeb237]
今、押されているボタンを格納する。
*** trg [#i130bc58]
押された時だけ、ボタンを格納する。押しつづけているボタンは格納しない。
*** off [#sa0049c0]
離された時だけ、ボタンを格納する。
*** rep [#ra599a1a]
十字キーのみ対応。repの用途は、たとえばゲームの設定画面があって、メニューの項目がたくさんあったとします。十字キーを押したとき、選択されている項目を1つ移動した後、ウェイトがしばらく入ります(無入力状態)。ウェイト後はcntと短いウェイトが交互に入って移動をするといったことになります。
** 出典元 [#p825bd04]
- M-KAIさんの「Witchシューティングゲーム制作記帳」
- 正直日記さん
** 履歴 [#p7782138]
- 2023/04/17
- 2008/07/06