自作キー入力クラスのキーコンフィグ対応について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
Kouichi

自作キー入力クラスのキーコンフィグ対応について

#1

投稿記事 by Kouichi » 14年前

ずいぶんと久しぶりに質問させて頂きます。

以前 「Kou」 で書き込みさせて頂いておりましたが、使用できなかったので 「Kouichi」 に変更させて頂きました。

最近C++の勉強をしながら、シューティングゲームをチョコチョコ作成しています。
環境は、
OS        : Windows7 Home 32ビット版
コンパイラ    : Microsoft Visual C++ 2010 Express
使用ライブラリ : DxLib
使用言語    : C++(微妙にCの文法が混じっている時があります)

以下 【KeyClass.h

コード:

#pragma once

class KeyClass
{
	char cKeyBuf[256];		//Key入力状態格納用変数
	char cKeyInput[256];	//Key入力継続フレーム数格納用変数
	int iPadBuf;			//Pad入力状態格納用変数
	int iPadInput[16];		//Pad入力継続フレーム数格納用変数
	int iPadTable[16];		//Pad入力チェック用配列テーブル

	/* キーコンフィグ用構造体 */
	struct ConfigKey_t
	{
		int up;		//上入力
		int down;	//下入力
		int left;	//左入力
		int right;	//右入力
		int shot;	//ショット
		int bom;	//ボム
		int slow;	//低速
		int start;	//START
		int esc;	//終了
	};
	ConfigKey_t tCfKey;		//Key入力用
	ConfigKey_t tCfPad;		//Pad入力用

	int ReturnInputKey( int hndl );
	int ReturnInputPad( int hndl );
	int ReturnInputState( void );
	int GetInputState( int pad, int key );
	void CheckInputLoop( void );
	void SetInputState( void );

public:
	int up, down;
	int left, right;
	int shot, bom;
	int slow, esc;
	int start;

	KeyClass();	//コンストラクタ

	int GetKeyState( void );
};
以下 【KeyClass.cpp

コード:

#include "DxLib.h"
#include "../include/KeyClass.h"

/* コンストラクタ */
KeyClass::KeyClass()
{
	//Key割り当て
	tCfKey.up    = KEY_INPUT_UP;
	tCfKey.down  = KEY_INPUT_DOWN;
	tCfKey.left  = KEY_INPUT_LEFT;
	tCfKey.right = KEY_INPUT_RIGHT;
	tCfKey.shot  = KEY_INPUT_Z;
	tCfKey.bom   = KEY_INPUT_X;
	tCfKey.slow  = KEY_INPUT_LSHIFT;
	tCfKey.esc   = KEY_INPUT_ESCAPE;

	//PadButton割り当て
	tCfPad.down   =  1;		//PAD_INPUT_DOWN
	tCfPad.left   =  2;		//PAD_INPUT_LEFT
	tCfPad.right  =  3;		//PAD_INPUT_RIGHT
	tCfPad.up     =  4;		//PAD_INPUT_UP
	tCfPad.bom    =  7;		//PAD_INPUT_C
	tCfPad.shot   =  8;		//PAD_INPUT_X
	tCfPad.slow   = 10;		//PAD_INPUT_Z
	tCfPad.start  = 14;		//PAD_INPUT_START

	//Pad用ボタンテーブル初期化
	iPadTable[1]  =    1;	//Down
	iPadTable[2]  =    2;	//Left
	iPadTable[3]  =    4;	//Right
	iPadTable[4]  =    8;	//Up
	iPadTable[7]  =   64;	//Bom
	iPadTable[8]  =  128;	//Shot
	iPadTable[10] =  512;	//Slow
	iPadTable[14] = 8192;	//Start


	for( int i = 0; i < 256; i++ )
	{
		cKeyBuf[i] = 0;
		cKeyInput[i] = 0;
	}
	for( int i = 0; i < 16; i++ )
		iPadInput[i] = 0;
}

/* Key & Pad 入力継続フレームチェック */
void KeyClass::CheckInputLoop( void )
{
	for( int i = 0; i < 256; i++ )
	{
		if( cKeyBuf[i] == 1 )
			cKeyInput[i]++;
		else
			cKeyInput[i] = 0;
	}

	for( int i = 0; i < 16; i++ )
	{
		if( iPadBuf & iPadTable[i] )
			iPadInput[i]++;
		else
			iPadInput[i] = 0;
	}
}

/* Key入力フレーム数取り出し */
int KeyClass::ReturnInputKey( int hndl )
{
	return (int)cKeyInput[hndl];
}

/* Pad入力フレーム数取り出し */
int KeyClass::ReturnInputPad(int hndl)
{
	return iPadInput[hndl];
}

/* Key or Pad 有効入力検索 */
int KeyClass::GetInputState(int pad, int key)
{
	return pad > key ? pad : key;
}

/* 各ボタン入力フレーム数セット */
void KeyClass::SetInputState( void )
{
	up    = GetInputState( ReturnInputPad( tCfPad.up    ), ReturnInputKey( tCfKey.up    ) );
	down  = GetInputState( ReturnInputPad( tCfPad.down  ), ReturnInputKey( tCfKey.down  ) );
	left  = GetInputState( ReturnInputPad( tCfPad.left  ), ReturnInputKey( tCfKey.left  ) );
	right = GetInputState( ReturnInputPad( tCfPad.right ), ReturnInputKey( tCfKey.right ) );
	shot  = GetInputState( ReturnInputPad( tCfPad.shot  ), ReturnInputKey( tCfKey.shot  ) );
	bom   = GetInputState( ReturnInputPad( tCfPad.bom   ), ReturnInputKey( tCfKey.bom   ) );
	slow  = GetInputState( ReturnInputPad( tCfPad.slow  ), ReturnInputKey( tCfKey.slow  ) );
	esc   = cKeyInput[tCfKey.esc];
	start = iPadInput[tCfPad.start];
}

/* キーボード & パッド入力情報取得 */
/* 戻り値 : 成功 =  0              */
/*          失敗 = -1              */
int KeyClass::GetKeyState( void )
{
	if( GetHitKeyStateAll( cKeyBuf ) == -1 ) return -1;	//KeyInput情報取得
	
	iPadBuf = GetJoypadInputState( DX_INPUT_PAD1 );		//JoyPad情報取得

	CheckInputLoop();
	SetInputState();

	return 0;
}
上記のプログラムは、龍神録のキー入力判定の部分を私なりにクラス化してみました。
実際に使用する時は、

コード:

//どこかでKeyClassのオブジェクトを作成
KeyClass* mKey = new KeyClass;

//ループの度にキーの入力状態をチェック
mKey->GetKeyState();

//単純に指定したキーが押されたか?
if( mKey->esc ) return true;

//2フレーム以上押されたか?
if( mKey->shot >= 2 ) return true;
こんな感じで使用します。

前置きが長くなりましたが、質問の内容は、
1.キーコンフィグに対応させたいのですが、キーコーンフィグのプログラムの概念が今ひとつ理解できない。(どんな処理をすれば良いか分からない)
2.現在のプログラムをキーコンフィグに対応させるには、クラスの設計をどう変更すれば良いか。(そもそもこれじゃ無理!・・・とか)
3.現在のプログラムをもう少しスッキリとできないか(ここって無駄だよ・・・とか)

質問が多くなってしまい申し訳ありませんが、どれか1つに付いてでも良いので、回答頂ければありがたいです。
よろしくお願いします。

アバター
h2so5
副管理人
記事: 2212
登録日時: 14年前
住所: 東京
連絡を取る:

Re: 自作キー入力クラスのキーコンフィグ対応について

#2

投稿記事 by h2so5 » 14年前

1,2
例えば、ショットとボムのキーを入れ替えたければ
tCfKey.shot = KEY_INPUT_X;
tCfKey.bom = KEY_INPUT_Z;
のようにします。
どのタイミングで代入するのかは、キーコンフィグの画面をどう実装するかにもよりますが。

3.
KeyClassを動的確保する理由は無いと思いますので、
KeyClass* mKey = new KeyClass;

KeyClass mKey;

また、できればKeyClassはSingletonパターンで実装したほうが良いと思います。

Kouichi

Re: 自作キー入力クラスのキーコンフィグ対応について

#3

投稿記事 by Kouichi » 14年前

h2so5様、回答ありがとうございます。
h2so5 さんが書きました: 1,2
例えば、ショットとボムのキーを入れ替えたければ
tCfKey.shot = KEY_INPUT_X;
tCfKey.bom = KEY_INPUT_Z;
のようにします。
どのタイミングで代入するのかは、キーコンフィグの画面をどう実装するかにもよりますが。
なるほど・・・というか、一応私自身が思っていた感じで良かったようです。
キー情報の入れ替えを考えて、単純に 「何か変数に指定(入力)されたキー情報を格納して、それを各ボタン用の変数に代入すれば・・・」 と思いつくのですが、
逆に単純すぎて、「ホントにこんな単純で良いの?????」・・・ってなってしまったんです。
h2so5 さんが書きました: 3.
KeyClassを動的確保する理由は無いと思いますので、
KeyClass* mKey = new KeyClass;

KeyClass mKey;

また、できればKeyClassはSingletonパターンで実装したほうが良いと思います。
動的確保に関しては、言われてみればそうですよね・・・
Singletonパターンに関しては、よく耳に(目に)するんですが、実際どんなモノなのかまだ勉強していなかったので、サイトを検索して調べてみました。

えっと・・・その結果なんとなく分かった事は、

Singletonパターンとは(大雑把に)
 1.どうせ実体(オブジェクト)を1つしか作る必要がないなら、Singletonパターンを実装して、実体が1つしか無い事を保証(?)する。
 2.実装方法として、まずはコンストラクタをprivateに入れてしまう。
 3.publicにstaticな自分自身のポインタを返す関数を作成して、そのポインタを介してメンバ関数等の呼び出しを行う。

こんな感じでしょうか?
完全に理解したわけではないので、釈然としない部分が多いのですが、
1で、「実体が1つしか無い事を保証する」とありますが、Singletonパターンを使用するのと、プログラム中に実体を1つしか作らない(自分で意識的に)のとでは、実際どんな違いがあるのでしょうか?

アバター
迷彩吹雪
記事: 6
登録日時: 14年前
連絡を取る:

Re: 自作キー入力クラスのキーコンフィグ対応について

#4

投稿記事 by 迷彩吹雪 » 14年前

Kouichi さんが書きました: 1で、「実体が1つしか無い事を保証する」とありますが、Singletonパターンを使用するのと、プログラム中に実体を1つしか作らない(自分で意識的に)のとでは、実際どんな違いがあるのでしょうか?
Singletonパターンを使用すると、そのクラスはグローバル変数の扱いになります(自身を示すポインタがstaticなので)。
すると、プログラム中のどこでも自由にそのクラスインスタンスにアクセスすることができます。
今回のような入力を管理するクラスは、おそらくゲーム中に様々な場所から呼び出されるでしょうから、Singltonであれば簡単に利用できるようになるのです(私もそのように入力管理クラスを作り、使用しています)。

まあ、この特徴は便利な半面多用しすぎてSingletonばかりになり、グローバル変数だらけと変わらないコードになる可能性を生みますが……。

意識的にインスタンスを一つしか作らないとすると、それを示すポインタはグローバル変数として他ファイルでもextern宣言するか、あるいは引数として次々引き渡してゆくしかありません。そして、いつ不用意にdeleteされるかわからない危険性があります。deleteした後にそのインスタンスにアクセスすれば、プログラムは即停止するでしょう。第三者(もしかすると未来の自分)がそのようなコードを書けば、おしまいです。
同様に間違って二つ目のインスタンスを生成してしまった場合でも、一つめのインスタンスで入力を更新して、二つ目で入力状態を判定すれば、期待通りの動作は返って来ないことになります。

……とか何とか言って、私もはっきりとしたことは言えませんが(ぇ

コードを拝見させて頂き、私が気になったことを以下に列挙します。
  • 「mKey->shot==0」と書こうとして「mKey->shot=0」としてしまうとバグの原因になるので、publicにある変数は全てprivateに、入力判定は関数を介して行ってはどうでしょう。
  • キーボードとジョイパッドで別々に機能と識別子のセットが管理されていますが、「機能」の構造体(あるいはクラス)を作り、そこに対応するキーボードとジョイパッドのキー識別子を格納してはどうでしょうか。
► スポイラーを表示
  • キーコンフィグを行う時は、例えば
    設定する機能を選択->設定したいキーを押下->そのキーの識別子を調べ、選択されていた機能のメンバ変数に代入
    という形になるのではないかと思います。
無駄に長文になったことをお詫びします(久々の発言で調子に乗り過ぎた……)。

Kouichi

Re: 自作キー入力クラスのキーコンフィグ対応について

#5

投稿記事 by Kouichi » 14年前

迷彩吹雪様、回答ありがとうございます。

Singletonパターンの説明、すごく良く理解できました。
[tab=30]
  • グローバル変数扱いになるので、利用が簡単になる。
[tab=30]
  • 不必要にバグが発生するのを防止できる。
確かにこれは使わないと損(?)ですね。
乱用は意識して避けないといけませんが・・・・乱用しそう
迷彩吹雪 さんが書きました:
  • 「mKey->shot==0」と書こうとして「mKey->shot=0」としてしまうとバグの原因になるので、publicにある変数は全てprivateに、入力判定は関数を介して行ってはどうでしょう。

コード:

int ReturnShotState( void )//戻り値は入力されたフレーム数
{
  return shot;
}
仮に上記のような関数を作成して、その関数を呼び出して入力の状態を判定すると言う事でしょうか?
確かにこの形なら多少書き間違いがあっても、意味不明の挙動のまま実行される事がなく、コンパイルの時点で怒られますね^^
迷彩吹雪 さんが書きました:
  • キーボードとジョイパッドで別々に機能と識別子のセットが管理されていますが、「機能」の構造体(あるいはクラス)を作り、そこに対応するキーボードとジョイパッドのキー識別子を格納してはどうでしょうか。
これに関しても、サンプルコードまで載せて頂きありがとうございます。
コードを見て何となくは分かる気がするのですが、時間的に頭がボケていてハッキリと理解していません・・・・
頭がスッキリしたらしっかりと見てみます。

Kouichi

Re: 自作キー入力クラスのキーコンフィグ対応について

#6

投稿記事 by Kouichi » 14年前

最後の書き込みから数日経過してしまいました。

また少し仕事が忙しくなり、時間が取れそうもなくなってきたので、落ち着いたら今回の皆さんのアドバイスを参考にして、クラスの修正とキーコンフィグ部分の実装をしようと思います。

アドバイスをしてくださった皆さん、ありがとうございました。
一応解決マークを付けさせて頂きます。

閉鎖

“C言語何でも質問掲示板” へ戻る