ページ 1 / 1
キー入力クラスの設計
Posted: 2014年9月13日(土) 09:31
by Rittai_3D
C++でのリプレイの実装の続きとなっております。
続きの場合、どう書いて良いかわからないのでテンプレートを使用させて頂きます。
[1] 質問文
[1.1] 自分が今行いたい事は何か
キー入力クラスやゲームパッドクラスの設計をしたい。詳しくは、上のURLを参照して下さい。
[1.2] どのように取り組んだか(プログラムコードがある場合記載)
前のトピックにいくつかコードがあります。全て記載するとかなり長くなるので記載しません。載せろ、との場合でしたら記載致します。
[1.3] どのようなエラーやトラブルで困っているか(エラーメッセージが解る場合は記載)
良い設計がわからない。
[1.4] 今何がわからないのか、知りたいのか
適切なキー入力クラス、ゲームパッドクラスなどの設計についてです。
[2] 環境
[2.1] OS : Windows, Linux等々
Windows 7 Home Premium 64bit
[2.2] コンパイラ名 : VC++ 2008EE, Borand C++, gcc等々
Visual Studio Express 2012 for Windows Desktop
[3] その他
・どの程度C言語を理解しているか
C++ですが、一年程度勉強しております。理解度等は前のトピックのコードを見て下さればわかると思います。
・ライブラリを使っている場合は何を使っているか
Dxライブラリを使用しています。
なにか、不備がありましたらご指摘お願いします。
Re: キー入力クラスの設計
Posted: 2014年9月13日(土) 19:21
by ISLe()
いったんDXライブラリの実装から離れて考えるべきだと思います。
前トピックの最後に書いた見なしの部分ですけど、
例えばひとつのキーボードを複数のキーボードが重なったものと見なせるように設計する
使うキーが異なれば、物理的に複数の装置からの入力と見なす
使うキーが同じなら、物理的にすべての装置に入力されたと見なす
逆にキーボードとゲームパッドをひとつの入力装置と見なせるように設計する
現実のキーボードとスクリーンキーボードをひとつの装置と見なすのも同じ
といったふうのがわたしの考える装置に対する抽象化です。
まったく逆のことを同時にやろうとしているわけですが、入力クラスとコントローラークラスそれぞれの役割を抽象化し組み合わせることで無理のない実装になります。
Re: キー入力クラスの設計
Posted: 2014年9月13日(土) 19:24
by へにっくす
まずは設計をしっかり固めましょう
プレイする人数は1人固定なのか複数人対応するのか。
それぞれのプレイヤーで使用できる入力装置の種類は。それは一つだけか、複数の入力装置が必要なのか。どのように制御するのか。
これをまとめてみては?
・・・と書いてたらISLeさんとかぶってしまった
でも投稿しちゃうぜッ 笑
Re: キー入力クラスの設計
Posted: 2014年9月14日(日) 10:48
by Rittai_3D
ISLe()さん、へにっくすさん
返信ありがとうございます
>ISLe()さん
例えばひとつのキーボードを複数のキーボードが重なったものと見なせるように設計する
使うキーが異なれば、物理的に複数の装置からの入力と見なす
使うキーが同じなら、物理的にすべての装置に入力されたと見なす
逆にキーボードとゲームパッドをひとつの入力装置と見なせるように設計する
現実のキーボードとスクリーンキーボードをひとつの装置と見なすのも同じ
といったふうのがわたしの考える装置に対する抽象化です。
まだよくわかっていないです。
>キーボードとゲームパッドをひとつの入力装置と見なせるように設計する
は、ゲームパッドをキーボードのキーが増えただけ、と解釈しましたが、
>ひとつのキーボードを複数のキーボードが重なったものと見なせるように
がまったく想像できません。
>へにっくすさん
プレイする人数は1人固定なのか複数人対応するのか。
それぞれのプレイヤーで使用できる入力装置の種類は。それは一つだけか、複数の入力装置が必要なのか。どのように制御するのか。
これをまとめてみては?
考えてみましたが、分からない部分があります。
プレイ人数 : 一人
対応する入力装置 : キーボードとゲームパッドの二つ(各一つずつ。複数のゲームパッド、キーボードには対応しない。)
ここまでは思いつきましたが、
>どのように制御するのか
がよくわかりません。押されているかどうかを返す、入力フレームを返す、など、ボタンを押されているかどうかで判断するという解釈であっていますか?それとも、もっとべつのことでしょうか?
Re: キー入力クラスの設計
Posted: 2014年9月15日(月) 18:15
by ISLe()
マルチインスタンスはオブジェクト指向の根本を成すものなんですよね。
3D_3Dさんが1人で両手を使って1つのキーボードを使っている
3D_3Dさんが2人いて右手と左手それぞれの担当に分かれて1つのキーボードを使っている
3D_3Dさんが2人いて右手用のキーボードと左手用のキーボードに分かれてそれぞれ使っている
というのはぜんぶ結果としては同じことなんですよ。
2人いる3D_3Dさんや2つあるキーボードをそれぞれ別のものとして扱うことができれば、そのままいくつも増やしていけます。
想像できますか?
キーボードやゲームパッドを上から見てませんか?
3D_3Dさんが2人いるとした場合、3D_3Dさんが2人の3D_3Dさんを動かすのではないですよ。
2人の3D_3Dさんが途中で入れ替わっても、入れ替わってるところが見えなければ、外からは分からないのです。
そして2人の3D_3Dさんがものすごい速さで入れ替わっていたら、それは重なっていることになります。
キーボードの多重化は、参考にしたというJavaのコードで既にやっていることなんですよね。
前トピックに書きましたけど、それをコントローラークラスの中で実装してしまっているのが良くなかったんですよね。
あの時点ではコントローラークラスの汎用化にまで意識が行ってませんでした。
キーリスナーもコントローラークラスではなく、入力クラスそれぞれで登録するのが本来正しいし。
Re: キー入力クラスの設計
Posted: 2014年9月15日(月) 22:27
by へにっくす
3D_3D さんが書きました:>どのように制御するのか
がよくわかりません。押されているかどうかを返す、入力フレームを返す、など、ボタンを押されているかどうかで判断するという解釈であっていますか?
ほらそこが入力装置にしか意識いってない証拠。それは入力クラスで行えばよいことだと思います。
私が言ってるのは、ゲームの進行のために必要な操作、つまり
スタートボタン/プレイヤー1のボタン/プレイヤー2のボタン・・・などの割り当てをどうするかを言ってます。
前のトピックの(No.28)でゲームパッドとキーボードを自動的に切り替えると記述してましたがそれはコントローラーで行うことではありません。入力クラス内の処理で行うことだと思います。
コントローラーはその入力クラスから、知りたいボタンの状態をただ読みとるだけ。
ISLeさんも書かれていますが、複数の入力装置だろうが、一つの入力装置だろうが一人だけそのゲームに参加しているのであれば、結果としてゲームの進行を行っている人は一人です。
そこのところを区別する必要があります。
Re: キー入力クラスの設計
Posted: 2014年9月16日(火) 18:43
by Rittai_3D
ISLe()さん、へにっくすさん
返信ありがとうございます。
>ISLe()さん
3D_3Dさんが1人で両手を使って1つのキーボードを使っている
3D_3Dさんが2人いて右手と左手それぞれの担当に分かれて1つのキーボードを使っている
3D_3Dさんが2人いて右手用のキーボードと左手用のキーボードに分かれてそれぞれ使っている
というのはぜんぶ結果としては同じことなんですよ。
2人いる3D_3Dさんや2つあるキーボードをそれぞれ別のものとして扱うことができれば、そのままいくつも増やしていけます。
想像できますか?
キーボードやゲームパッドを上から見てませんか?
なんとなくですがイメージできました。
うまく言葉にできないので完璧には理解できていないと思いますが、若干は理解できました。
キーボードの多重化は、参考にしたというJavaのコードで既にやっていることなんですよね。
前トピックに書きましたけど、それをコントローラークラスの中で実装してしまっているのが良くなかったんですよね。
あの時点ではコントローラークラスの汎用化にまで意識が行ってませんでした。
キーリスナーもコントローラークラスではなく、入力クラスそれぞれで登録するのが本来正しいし。
なるほど。Update()の padstates |= ki.getKeyStates() の部分は、キーコードは知らなくてもよいですね。
キーリスナーとは、Javaのコードのコントローラークラスのコンストラクタ内で行っていることですか?
>へにっくすさん
ほらそこが入力装置にしか意識いってない証拠。それは入力クラスで行えばよいことだと思います。
私が言ってるのは、ゲームの進行のために必要な操作、つまり
スタートボタン/プレイヤー1のボタン/プレイヤー2のボタン・・・などの割り当てをどうするかを言ってます。
前のトピックの(No.28)でゲームパッドとキーボードを自動的に切り替えると記述してましたがそれはコントローラーで行うことではありません。入力クラス内の処理で行うことだと思います。
コントローラーはその入力クラスから、知りたいボタンの状態をただ読みとるだけ。
こちらも、なんとなくですが理解できました。ここまで真剣にコントローラクラスやキー入力クラスを設計したことがないので四苦八苦していますが、完成出来るように頑張ります。
コードが書けましたら、また載せる予定なのでご指導よろしくお願いします。
Re: キー入力クラスの設計
Posted: 2014年9月21日(日) 11:37
by Rittai_3D
ここ最近とても忙しく、コードを書く時間が全く取れずに返信が遅れてしまいました。
忙しいなりに色々考えてみましたが、やはり理解が出来ていないようで、空き時間などを利用してコードを書いてみましたが、ISLe()さんのおっしゃっていたDxライブラリのラッパーにしか見れません。諦めたわけではないですが、正直、思いつく気がしません。
これからさらに忙しくなり、返信が更に遅れると思います。なんとしてでも完成はさせます。
Re: キー入力クラスの設計
Posted: 2014年9月23日(火) 10:57
by Rittai_3D
C++で上手く書けないのでC言語で一度書いてみました。C言語といってもC++の機能を若干使用しています。
今までのコードをすべて忘れて書きました。こんな感じでしょうか?
コード:
#include <DxLib.h>
using namespace std;
#define DBG_COMMENT { DrawFormatString( 0, 0, GetColor( 255, 255, 255 ), "ぬわああああああ!" ); }
#define DBG_PRINT(f) { DrawFormatString( 0, 16, GetColor( 255, 255, 255 ), "dbgData : %d", ( int )f ); }
enum eKeyType
{
eUP, eDOWN, eLEFT, eRIGHT,
eSHOT, eBOM, eSLOW,
};
// anonymous
namespace
{
const static int csKeyNum = 256;
};
const static int csDefaultKey[] =
{
KEY_INPUT_UP, KEY_INPUT_DOWN, KEY_INPUT_LEFT, KEY_INPUT_RIGHT,
KEY_INPUT_Z, KEY_INPUT_X, KEY_INPUT_LSHIFT,
};
const static eKeyType csKeyList[] =
{
eUP , eDOWN, eLEFT, eRIGHT,
eSHOT, eBOM , eSLOW,
};
int GetPadState()
{
int state = 0;
int loopNum = ( sizeof( csKeyList ) / sizeof( csKeyList[0] ) );
char tmpKey[ csKeyNum ];
GetHitKeyStateAll( tmpKey );
for( int i=0 ; i<csKeyNum ; i++ ) {
if( tmpKey[i] != 0 ) {
for( int j=0 ; j<loopNum ; j++ ) {
if( i == csDefaultKey[j] ) {
state |= ( 1 << csKeyList[j] );
}
}
}
}
return state;
}
// テストコード
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow )
{
ChangeWindowMode( TRUE ), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK );
int padState = 0;
while( !ScreenFlip() && !ProcessMessage() && !ClearDrawScreen() && !clsDx() ) {
padState = GetPadState();
if( ( padState & ( 1 << csKeyList[ eSHOT ] ) ) != 0 ) {
DBG_COMMENT;
DBG_PRINT( 0 );
}
}
DxLib_End();
return 0;
}
Re: キー入力クラスの設計
Posted: 2014年9月23日(火) 18:38
by へにっくす
いったんクラスを取っ払いましたね。
若干C++使ってますと言われても、
もともとDxLib.hをインクルードすると、C言語ではコンパイルできないので
許容範囲ですw
ざっと見ましたが、
いちいちビットシフトしてるのが気に入りませんね。
また、WinMainでGetPadState関数で使用する配列csKeyListを持ってきてるのも変です。
完全に隠れてませんよ。
WinMain側(ゲームメイン側)からは、eKeyTypeのみで判定したいのですよね?
ちょっと修正させてもらいました。どうでしょう?
コード:
#include <DxLib.h>
using namespace std;
#define DBG_COMMENT { DrawFormatString( 0, 0, GetColor( 255, 255, 255 ), "ぬわああああああ!" ); }
#define DBG_PRINT(f) { DrawFormatString( 0, 16, GetColor( 255, 255, 255 ), "dbgData : %d", ( int )f ); }
enum eKeyType
{
eUP = 1 << 0,
eDOWN = 1 << 1,
eLEFT = 1 << 2,
eRIGHT = 1 << 3,
eSHOT = 1 << 4,
eBOM = 1 << 5,
eSLOW = 1 << 6,
};
// anonymous
namespace
{
const static int csKeyNum = 256;
};
const static int csDefaultKey[] =
{
KEY_INPUT_UP, KEY_INPUT_DOWN, KEY_INPUT_LEFT, KEY_INPUT_RIGHT,
KEY_INPUT_Z, KEY_INPUT_X, KEY_INPUT_LSHIFT,
};
const static eKeyType csKeyList[] =
{
eUP , eDOWN, eLEFT, eRIGHT,
eSHOT, eBOM , eSLOW,
};
int GetPadState()
{
int state = 0;
int loopNum = ( sizeof( csKeyList ) / sizeof( csKeyList[0] ) );
char tmpKey[ csKeyNum ];
GetHitKeyStateAll( tmpKey );
for( int i=0 ; i<csKeyNum ; i++ ) {
if( tmpKey[i] != 0 ) {
for( int j=0 ; j<loopNum ; j++ ) {
if( i == csDefaultKey[j] ) {
state |= csKeyList[j];
}
}
}
}
return state;
}
// テストコード
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow )
{
ChangeWindowMode( TRUE ), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK );
int padState = 0;
while( !ScreenFlip() && !ProcessMessage() && !ClearDrawScreen() && !clsDx() ) {
padState = GetPadState();
if( ( padState & eSHOT ) != 0 ) {
DBG_COMMENT;
DBG_PRINT( 0 );
}
}
DxLib_End();
return 0;
}
Re: キー入力クラスの設計
Posted: 2014年9月23日(火) 19:41
by Rittai_3D
>へにっくすさん
返信ありがとうございます。
へにっくす さんが書きました:
いちいちビットシフトしてるのが気に入りませんね。
また、WinMainでGetPadState関数で使用する配列csKeyListを持ってきてるのも変です。
完全に隠れてませんよ。
WinMain側(ゲームメイン側)からは、eKeyTypeのみで判定したいのですよね?
ビットシフトがなくなってだいぶ読みやすくなりました。enumってこんな風にも書けるのか、と思いましたが、よく考えたら代入しているものは2の累乗になるから代入出来るのはあたりまえですよね。
配列や関数は、わたしの変な癖で、一旦main.cppに全て書いてからファイル分けをしていくスタイルをとっていることが原因です。
ちゃんと頭の中では「これは外部からアクセスさせない」とか「これは公開する」など、一通りわかっています。メインからは見えていても使わないようにしているつもりです。ファイル分割をちゃんとすればいい話ですが…
Re: キー入力クラスの設計
Posted: 2014年9月23日(火) 20:20
by へにっくす
3D_3D さんが書きました:配列や関数は、わたしの変な癖で、一旦main.cppに全て書いてからファイル分けをしていくスタイルをとっていることが原因です。
ちゃんと頭の中では「これは外部からアクセスさせない」とか「これは公開する」など、一通りわかっています。メインからは見えていても使わないようにしているつもりです。ファイル分割をちゃんとすればいい話ですが…
隠すべきcsKeyListをWinMainで使ってたところをみても
ちょっと怪しい印象がぬぐえませんが・・・(^^;
クラス化、がんばってください。
Re: キー入力クラスの設計
Posted: 2014年9月23日(火) 21:17
by Rittai_3D
へにっくす さんが書きました:隠すべきcsKeyListをWinMainで使ってたところをみても
ちょっと怪しい印象がぬぐえませんが・・・(^^;
クラス化、がんばってください。
csKeyListってWinMainで使ってますか?
コード中の
>if( ( padState & eSHOT ) != 0 )
のeSHOTは eKeyType::eSHOT の意味で使用していたのですが。
#追記
GetPadState関数で更新処理と取得処理をまとめて書いてしまいました。すいません。
C++版は修正してありますので明日以降に投下します。
Re: キー入力クラスの設計
Posted: 2014年9月23日(火) 22:44
by へにっくす
3D_3D さんが書きました:csKeyListってWinMainで使ってますか?
コード中の
>if( ( padState & eSHOT ) != 0 )
のeSHOTは eKeyType::eSHOT の意味で使用していたのですが。
No.9のあなたが掲示したコードの61行目から64行目は以下の通りです。
コード:
if( ( padState & ( 1 << csKeyList[ eSHOT ] ) ) != 0 ) {
DBG_COMMENT;
DBG_PRINT( 0 );
}
WinMain関数内のif の条件内にcsKeyList、使ってますよね?
No.10の私のコードと混同しないでください。(怒)
- これでもcsKeyListを使ってなかったと言えるのか。
自分の書いたコードを確認もしないで
理解していたと書いていても、信用できないですよ?
そのことを言ってます。ちゃんと確認してください。
Re: キー入力クラスの設計
Posted: 2014年9月23日(火) 22:57
by Rittai_3D
すいません、混同してました。
ファイル分割して書き直した際に、ついでに書き直してたのを忘れてました。
Re: キー入力クラスの設計
Posted: 2014年9月24日(水) 15:35
by usao
オフトピック
3D_3D さんが書きました:
配列や関数は、わたしの変な癖で、一旦main.cppに全て書いてからファイル分けをしていくスタイルをとっていることが原因です。
ちゃんと頭の中では「これは外部からアクセスさせない」とか「これは公開する」など、一通りわかっています。メインからは見えていても使わないようにしているつもりです。ファイル分割をちゃんとすればいい話ですが…
こんな感じにして間違いを抑止…とか考えたけど,最初からファイル分ければいいのに感がすごい…
コード:
//[main.cpp]
namespace Sub //あとで別のところに分けて書く予定のもの
{
const int A = 800;
const int B = 100;
void G( int a ){ std::cout << a << std::endl; }
void F(){ G( A + B ); }
}
//main()側が使って良い予定のを
//「Sub::」を付けずに書けるようにするための記述
inline void F(){ return Sub::F(); }
const int &B = Sub::B;
//
int main( int argc, char **argv )
{//※この中では「Sub::XXX」のように書かないというルール
F();
std::cout << B+50 << std::endl;
G(25); //←これは怒られる
return 0;
}
Re: キー入力クラスの設計
Posted: 2014年9月24日(水) 18:34
by Rittai_3D
コードの確認を怠り、投稿したコードと書きなおしたコードを混同してしまい申し訳ありませんでした。
とりあえずクラス化したものです。ゲームパッドクラス、コントローラークラスも実装してみました。
コントローラークラスのGetState関数ではゲームパッドかキーボードのどちらかの入力か関係なく返すようにしました。
スマートポインタなど色々使用してますが、これは、C++の他の機能の勉強も兼ねているからで、とくに理由はありません。
以下、コードです。
► スポイラーを表示
main.cpp
コード:
#include <DxLib.h>
#include <memory>
#include "Controller.h"
using namespace std;
#define DBG_COMMENT { DrawFormatString( 0, 0, GetColor( 255, 255, 255 ), "ぬわああああああ!" ); }
#define DBG_PRINT(x,y,f) { DrawFormatString( x, y, GetColor( 255, 255, 255 ), "dbgData : %d", ( int )f ); }
// テストコード
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow )
{
ChangeWindowMode( TRUE ), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK );
{
std::shared_ptr< CController > pCtrl;
pCtrl = std::make_shared< CController >();
while( !ScreenFlip() && !ProcessMessage() && !ClearDrawScreen() && !clsDx() ) {
pCtrl->Proc();
int padState = pCtrl->GetState();
if( ( padState & eSHOT ) != 0 ) {
DBG_COMMENT;
}
}
}
DxLib_End();
return 0;
}
Controller.h
コード:
#pragma once
#include <memory>
#include <list>
#include "KeyInput.h"
#include "GamepadInput.h"
#include "BaseInput.h"
class CController
{
typedef std::shared_ptr< CBaseInput > InputPtr;
std::list< InputPtr > m_List;
public:
CController();
~CController();
bool Proc();
int GetState() const;
};
Controller.cpp
コード:
#include "Controller.h"
#include "KeyInput.h"
#include "GamepadInput.h"
#include "BaseInput.h"
CController::CController()
{
m_List.push_back( std::make_shared< CKeyboardInput >() );
m_List.push_back( std::make_shared< CGamepadInput >() );
}
CController::~CController()
{
m_List.clear();
}
bool CController::Proc()
{
for( auto& it : m_List ) {
it->Proc();
}
return true;
}
int CController::GetState() const
{
int nRet = 0;
for( auto& it : m_List ) {
nRet |= it->GetState();
}
return nRet;
}
KeyInput.h
コード:
#pragma once
#include "BaseInput.h"
class CKeyboardInput : public CBaseInput
{
public:
CKeyboardInput();
~CKeyboardInput();
bool Proc() override;
};
KeyInput.cpp
コード:
#include "KeyInput.h"
#include <DxLib.h>
const static int csKeyNum = 256;
const static int csDefaultKey[] =
{
KEY_INPUT_UP, KEY_INPUT_DOWN, KEY_INPUT_LEFT, KEY_INPUT_RIGHT,
KEY_INPUT_Z, KEY_INPUT_X, KEY_INPUT_LSHIFT,
};
CKeyboardInput::CKeyboardInput()
{
}
CKeyboardInput::~CKeyboardInput()
{
}
bool CKeyboardInput::Proc()
{
m_nState = 0;
int loopNum = ( sizeof( csKeyList ) / sizeof( csKeyList[0] ) );
static char tmpKey[ csKeyNum ];
GetHitKeyStateAll( tmpKey );
for( int i=0 ; i<csKeyNum ; i++ ) {
if( tmpKey[i] != 0 ) {
for( int j=0 ; j<loopNum ; j++ ) {
if( i == csDefaultKey[j] ) {
m_nState |= csKeyList[j];
}
}
}
}
return true;
}
GamepadInput.h
コード:
#pragma once
#include "BaseInput.h"
class CGamepadInput : public CBaseInput
{
int m_nPadInput;
public:
CGamepadInput();
~CGamepadInput();
bool Proc() override;
};
GamepadInput.cpp
コード:
#include "GamepadInput.h"
#include <DxLib.h>
const static int csPadKeyNum = 28;
const static int csPadKeyList[] =
{
PAD_INPUT_UP, PAD_INPUT_DOWN, PAD_INPUT_LEFT, PAD_INPUT_RIGHT,
PAD_INPUT_1 , PAD_INPUT_2 , PAD_INPUT_11 ,
};
CGamepadInput::CGamepadInput() : m_nPadInput( 0 )
{
m_nPadInput = GetJoypadInputState( DX_INPUT_PAD1 );
SetJoypadInputToKeyInput( DX_INPUT_PAD1, PAD_INPUT_11, KEY_INPUT_LSHIFT );
}
CGamepadInput::~CGamepadInput()
{
}
/*
* 頭の中で考えたことをそのままコードにしました。
* リファクタリングも何もしていません。そのうち綺麗にします。
*/
bool CGamepadInput::Proc()
{
m_nState = 0;
m_nPadInput = GetJoypadInputState( DX_INPUT_PAD1 );
int loopNum = ( sizeof( csKeyList ) / sizeof( csKeyList[0] ) );
static int tmpPad[ csPadKeyNum ];
/* パッドの入力フレームを計算 */
int mask1 = 1;
for( int i=0 ; i<csPadKeyNum ; i++ ) {
if( ( m_nPadInput & mask1 ) != 0 ) {
tmpPad[i]++;
} else {
tmpPad[i] = 0;
}
mask1 <<= 1;
}
/*
* ゲームで使用するキーが押されたかどうかのチェック
* 押されていたらフラグを立てる
*/
int mask2 = 1;
for( int i=0 ; i<csPadKeyNum ; i++ ) {
if( tmpPad[i] != 0 ) {
for( int j=0 ; j<loopNum ; j++ ) {
if( mask2 == csPadKeyList[j] ) {
m_nState |= csKeyList[j];
}
mask2 <<= 1;
}
}
}
return true;
}
BaseInput.h
コード:
#pragma once
enum eKeyType
{
eUP = ( 1 << 0 ),
eDOWN = ( 1 << 1 ),
eLEFT = ( 1 << 2 ),
eRIGHT = ( 1 << 3 ),
eSHOT = ( 1 << 4 ),
eBOM = ( 1 << 5 ),
eSLOW = ( 1 << 6 ),
};
const eKeyType csKeyList[] =
{
eKeyType::eUP , eKeyType::eDOWN, eKeyType::eLEFT, eKeyType::eRIGHT,
eKeyType::eSHOT, eKeyType::eBOM , eKeyType::eSLOW,
};
class CBaseInput
{
protected:
int m_nState;
public:
CBaseInput() : m_nState( 0 ) {}
virtual ~CBaseInput(){}
virtual bool Proc() = 0;
inline int GetState() const { return m_nState; };
};
Re: キー入力クラスの設計
Posted: 2014年9月24日(水) 19:11
by ISLe()
参考にされてるJavaのコードで、入力情報をビットにパックしているのは、4方向デジタル入力を前提にしているからなんです…
デジタル入力しか扱わない、という前提で話が進んでいるという認識で良いのでしょうか。
トピックのタイトルに『設計』とあるのに実装が主体で、前提となる仕様策定がぜんぜん進んでいない気がするのです。
あと、いまさらかもしれませんが、コントローラークラスというのは、MVCアーキテクチャでいうところのコントローラーです。
入力装置としてのゲームコントローラーではありません。
Javaのサンプルコードを投稿したトピックの趣旨がそれなんですけどね。
リプレイの話題から派生しているので、プレイヤーキャラのコントローラーで話題は進んでいますが、画面にメニューが出ているとき操作するためのメニューコントローラーとか操作対象(MVCアーキテクチャでいうところのビュー)に応じて特殊化したコントローラーが必要になることでしょう。
リプレイのトピックでもコントローラーの派生あたりの話は出ましたが、どのように受け取られているのでしょうかね。
Re: キー入力クラスの設計
Posted: 2014年9月25日(木) 22:16
by Rittai_3D
>ISLe()さん
返信ありがとうございます。
ISLe() さんが書きました:参考にされてるJavaのコードで、入力情報をビットにパックしているのは、4方向デジタル入力を前提にしているからなんです…
デジタル入力しか扱わない、という前提で話が進んでいるという認識で良いのでしょうか。
はい、そうです。
ISLe() さんが書きました:
トピックのタイトルに『設計』とあるのに実装が主体で、前提となる仕様策定がぜんぜん進んでいない気がするのです。
わたしが 設計=実装 と考えてしまっていたことが原因です。
仕様もよく考えておらず、キーボードとゲームパッドからの入力を受け付ければいいや程度のことしか考えていませんでした。
ISLe() さんが書きました:リプレイの話題から派生しているので、プレイヤーキャラのコントローラーで話題は進んでいますが、画面にメニューが出ているとき操作するためのメニューコントローラーとか操作対象(MVCアーキテクチャでいうところのビュー)に応じて特殊化したコントローラーが必要になることでしょう。
リプレイのトピックでもコントローラーの派生あたりの話は出ましたが、どのように受け取られているのでしょうかね。
メニューが出ている状態のことを全く考えずに、ゲーム内でのプレイヤーのコントローラーだけ考えていましたが、よくよく考えるとメニューやそのほかの部分でも必要になりますね。深く考えていませんでした。
リプレイのトピックでも、派生はゲーム内の話だけで、他は無関係だと思っていましたし、考えてもいませんでした。
Re: キー入力クラスの設計
Posted: 2014年9月27日(土) 21:56
by ISLe()
コントローラーはアプリケーションに依存しますから、コントローラーだけで設計を決めるということ自体が難しいのですよね。
アプリケーションフレームワークというアプリケーション全体の骨組みを固定する形まで持っていけば可能ですが。
インプットクラスは、GetJoypadInputState関数の戻り値に合わせたものでも、XInput準拠のものでも、単独で作ることはできます。
だけどそれを使うときにはそのまま使えないのは同じことで。
GetJoypadInputState関数をそのまま使うよりかはどんなメリットがあるかという点を考えないといけませんね。
実際に使っているところを想像してください。
そして想像したものから制作に必要な情報を引き出してください。
どれだけ具体的に想像できるかがキモです。
Re: キー入力クラスの設計
Posted: 2014年10月04日(土) 23:24
by Rittai_3D
ISLe() さん、返信ありがとうございます。このごろ忙しく、返信が遅れてしまいました。すいません。
コントローラーはアプリケーションに依存しますから、コントローラーだけで設計を決めるということ自体が難しいのですよね。
アプリケーションフレームワークというアプリケーション全体の骨組みを固定する形まで持っていけば可能ですが。
フレームワークですか。興味があるので一生のうちに一度は作ってみたいと思っています。
関係のない質問ですが、フレームワークとやらはライブラリとは違うのでしょうか?
根本的に違うのか、途中までは同じような用途で、使用するタイミングが違うのか、そのあたりの認識があいまいです。
インプットクラスは、GetJoypadInputState関数の戻り値に合わせたものでも、XInput準拠のものでも、単独で作ることはできます。
だけどそれを使うときにはそのまま使えないのは同じことで。
GetJoypadInputState関数をそのまま使うよりかはどんなメリットがあるかという点を考えないといけませんね。
わたしの語彙力がないため、伝わりにくいかもしれませんが、DxライブラリやXInput?などを意識しないで書けるので、使用するライブラリ等に変更があっても、インプットクラスを変更するだけで済む、でしょうか。
Re: キー入力クラスの設計
Posted: 2014年10月06日(月) 23:41
by ISLe()
フレームワークというのは、コールバックをメインに、あらかじめ決められた手順で使うようにデザインされたライブラリのことです。
手順だけを指していう場合もあります。
アプリケーションの起動から終了まで扱うものだけを指すように言われますが、それに限ったものではありません。
「意識しないで書ける」という表現には具体性が無いですね。
語彙力がないというのは免罪符になりません。
具体的なメリットを挙げることがいま必要なのですから。
これはもはや企画…設計以前の問題です。
どうしてそれをやらなければいけないのか、という話です。
他のライブラリを使うことになったら、DXライブラリのラッパ関数を作るだけで元からあるコードをまったく変更しなくて済むかもしれません。
使用するライブラリを変更できるというメリットはそんなに重要でしょうか。
これまで提示されたコードがDXライブラリ以外でも使えるという保証もないですし。
Re: キー入力クラスの設計
Posted: 2014年10月18日(土) 17:26
by Rittai_3D
ISLe()さん。返信ありがとうございます。
何度も返信が遅れてしまい、申し訳ありません。これからも返信が遅れてしまうと思います。ご容赦ください。
「意識しないで書ける」という表現には具体性が無いですね。
語彙力がないというのは免罪符になりません。
具体的なメリットを挙げることがいま必要なのですから。
これはもはや企画…設計以前の問題です。
どうしてそれをやらなければいけないのか、という話です。
他のライブラリを使うことになったら、DXライブラリのラッパ関数を作るだけで元からあるコードをまったく変更しなくて済むかもしれません。
使用するライブラリを変更できるというメリットはそんなに重要でしょうか。
これまで提示されたコードがDXライブラリ以外でも使えるという保証もないですし。
わたしは、
「キーコンフィグで割り当てるキーを変更しても、実際にプレーたときやリプレイを見るとき、キーの割り振りを知らなくてよい」
がメリットだと思います。直接キーの入力状態を取得してしまうと、キーコンフィグを実装したときに面倒なことになると考えました。
リプレイを自分なりに作ってみて、直接キーコードをいじると面倒なことになることを実感しました。
また、新しくコードを書いてみました。
キー入力クラスと、入力されたキーコードを仮想キーコードに変換するクラス、そしてコントローラクラスです。
このコードで言う CKeyInput クラス、 CConvKeyCode/CConvPadCode クラス、 CController クラスです。
変換クラスを作った理由は、CKeyInputクラスで VirtualKeyCode.h で定義した仮想キーコードを使用したくなかっただけです。
問題点などがありましたら教えてください。
► スポイラーを表示
main.cpp
コード:
#include <DxLib.h>
#include <memory>
#include "Controller.h"
#include "VirtualKeyCode.h"
using namespace std;
#if 1
# define DBG_COMMENT { DrawFormatString( 0, 0, GetColor( 255, 255, 255 ), "ぬわああああああ!" ); }
# define DBG_PRINT(x,y,f) { DrawFormatString( x, y, GetColor( 255, 255, 255 ), "dbgData : %d", ( int )f ); }
#else
# define DBG_COMMENT ( void )0
# define DBG_PRINT(x,y,f) ( void )0
#endif
// テストコード
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow )
{
ChangeWindowMode( TRUE ), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK );
{
std::shared_ptr< CController > pCtrl( new CController );
int state = 0;
while( !ScreenFlip() && !ProcessMessage() && !ClearDrawScreen() && !clsDx() ) {
pCtrl->Proc();
state = pCtrl->GetState();
if( ( state & BUTTON_A ) != 0 ) {
DBG_COMMENT;
}
}
}
DxLib_End();
return 0;
}
Controller.h
コード:
#pragma once
#include "ConvertKeyCode.h"
#include "ConvertPadCode.h"
class CController
{
KeyConvPtr m_pKey;
PadConvPtr m_pPad;
int m_State;
public:
CController();
~CController();
bool Proc();
inline int GetState() const { return m_State; }
};
Controller.cpp
コード:
#include "Controller.h"
#include <DxLib.h>
#include "ConvertKeyCode.h"
#include "ConvertPadCode.h"
CController::CController()
{
m_pKey = std::make_shared< CConvKeyCode >();
m_pPad = std::make_shared< CConvPadCode >();
}
CController::~CController()
{
}
bool CController::Proc()
{
m_pKey->Proc();
m_pPad->Proc();
m_State = 0;
m_State |= m_pKey->GetState();
m_State |= m_pPad->GetState();
return true;
}
ConvertKeyCode.h
コード:
#pragma once
#include "KeyInput.h"
struct KeyTable_t
{
int virtualCode;
int keyCode;
};
class CConvKeyCode
{
int m_InputState;
KeyInputPtr m_pKey;
public:
CConvKeyCode();
~CConvKeyCode();
bool Proc();
inline int GetState() const { return m_InputState; }
};
#include <memory>
typedef std::shared_ptr< CConvKeyCode > KeyConvPtr;
ConvertKeyCode.cpp
コード:
#include "ConvertKeyCode.h"
#include "VirtualKeyCode.h"
#include "KeyInput.h"
#include <DxLib.h>
const static struct KeyTable_t csKeyTable[] =
{
{ BUTTON_A, KEY_INPUT_Z },
{ BUTTON_B, KEY_INPUT_X },
{ BUTTON_C, KEY_INPUT_LSHIFT },
{ BUTTON_D, KEY_INPUT_ESCAPE },
{ BUTTON_E, KEY_INPUT_RETURN },
{ BUTTON_UP , KEY_INPUT_UP },
{ BUTTON_DOWN , KEY_INPUT_DOWN },
{ BUTTON_LEFT , KEY_INPUT_LEFT },
{ BUTTON_RIGHT , KEY_INPUT_RIGHT },
};
CConvKeyCode::CConvKeyCode() : m_InputState( 0 )
{
m_pKey = std::make_shared< CKeyInput >();
}
CConvKeyCode::~CConvKeyCode()
{
}
bool CConvKeyCode::Proc()
{
m_pKey->Proc();
m_InputState = 0; // 入力状態を戻す
for( int i=0 ; i<( sizeof( csKeyTable ) / sizeof( csKeyTable[0] ) ) ; i++ ) {
if( m_pKey->IsPressing( csKeyTable[i].keyCode ) ) {
m_InputState |= csKeyTable[i].virtualCode;
}
}
return true;
}
ConvertPadCode
コード:
#pragma once
#include "PadInput.h"
struct PadTable_t
{
int virtualCode;
int padCode;
};
class CConvPadCode
{
int m_InputState;
PadInputPtr m_pPad;
public:
CConvPadCode();
~CConvPadCode();
bool Proc();
inline int GetState() const { return m_InputState; }
};
#include <memory>
typedef std::shared_ptr< CConvPadCode > PadConvPtr;
ConvertPadCode.cpp
コード:
#include <DxLib.h>
#include "PadInput.h"
#include "VirtualKeyCode.h"
#include "ConvertPadCode.h"
const static struct PadTable_t csPadTable[] =
{
// virtualCode, padCode
{ BUTTON_A, PAD_INPUT_9 }, // OK/SHOT
{ BUTTON_B, PAD_INPUT_8 }, // CANCEL/BOMB
{ BUTTON_C, PAD_INPUT_8 }, // SHOW
{ BUTTON_D, PAD_INPUT_13 }, // PAUSE
{ BUTTON_E, PAD_INPUT_9 }, // OK
{ BUTTON_UP , PAD_INPUT_UP },
{ BUTTON_DOWN , PAD_INPUT_DOWN },
{ BUTTON_LEFT , PAD_INPUT_LEFT },
{ BUTTON_RIGHT , PAD_INPUT_RIGHT },
};
CConvPadCode::CConvPadCode() : m_InputState( 0 )
{
m_pPad = std::make_shared< CGamepadInput >();
}
CConvPadCode::~CConvPadCode()
{
}
bool CConvPadCode::Proc()
{
m_pPad->Proc();
m_InputState = 0; // 入力状態を戻す
for( int i=0 ; i<( sizeof( csPadTable ) / sizeof( csPadTable[0] ) ) ; i++ ) {
if( m_pPad->IsPressing( csPadTable[i].padCode ) ) {
m_InputState |= csPadTable[i].virtualCode;
}
}
return true;
}
KeyInput.h
コード:
#pragma once
class CKeyInput
{
public:
CKeyInput();
~CKeyInput();
bool Proc();
bool IsPressing( int KeyCode );
};
#include <memory>
typedef std::shared_ptr< CKeyInput > KeyInputPtr;
KeyInput.cpp
コード:
#include "KeyInput.h"
#include <DxLib.h>
#include <assert.h>
const static int csKeyNum = 256;
static char KeyBuff[ csKeyNum ];
CKeyInput::CKeyInput()
{
memset( KeyBuff, 0, sizeof( KeyBuff ) );
}
CKeyInput::~CKeyInput()
{
}
bool CKeyInput::Proc()
{
GetHitKeyStateAll( KeyBuff );
return true;
}
bool CKeyInput::IsPressing( int KeyCode )
{
assert( -1 < KeyCode && KeyCode < csKeyNum );
if( KeyBuff[ KeyCode ] ) return true;
return false;
}
PadInput.h
コード:
#pragma once
class CGamepadInput
{
public:
CGamepadInput();
~CGamepadInput();
bool Proc();
bool IsPressing( int PadCode );
};
#include <memory>
typedef std::shared_ptr< CGamepadInput > PadInputPtr;
PadInput.cpp
コード:
#include <DxLib.h>
#include "PadInput.h"
#include <assert.h>
const static int csPadButtonMax = 28;
static bool PressingFlag[ csPadButtonMax ];
CGamepadInput::CGamepadInput()
{
for( int i=0 ; i<csPadButtonMax ; i++ ) {
PressingFlag[i] = false;
}
}
CGamepadInput::~CGamepadInput()
{
}
bool CGamepadInput::Proc()
{
int padState = GetJoypadInputState( DX_INPUT_PAD1 );
for( int i=0 ; i<csPadButtonMax ; i++ ) {
if( ( padState & ( 1 << i ) ) != 0 ) {
PressingFlag[i] = true;
} else {
PressingFlag[i] = false;
}
}
return true;
}
bool CGamepadInput::IsPressing( int PadCode )
{
// assert( !( ( -1 < PadCode ) && ( PadCode < csPadButtonMax ) ) );
return PressingFlag[ ( 1 >> PadCode ) ];
}
VirtualKeyCode.h
コード:
#pragma once
/*
* 仮想キーコード
*/
const int BUTTON_NONE = 0;
const int BUTTON_A = ( 1 << 0 );
const int BUTTON_B = ( 1 << 1 );
const int BUTTON_C = ( 1 << 2 );
const int BUTTON_D = ( 1 << 3 );
const int BUTTON_E = ( 1 << 4 );
const int BUTTON_UP = ( 1 << 5 );
const int BUTTON_DOWN = ( 1 << 6 );
const int BUTTON_LEFT = ( 1 << 7 );
const int BUTTON_RIGHT = ( 1 << 8 );
const int VirtualKeyCodeList[] =
{
BUTTON_NONE ,
BUTTON_A ,
BUTTON_B ,
BUTTON_C ,
BUTTON_D ,
BUTTON_E ,
BUTTON_UP ,
BUTTON_DOWN ,
BUTTON_LEFT ,
BUTTON_RIGHT ,
};
const unsigned BUTTON_NUM = ( sizeof( VirtualKeyCodeList ) / sizeof( VirtualKeyCodeList[0] ) );
Re: キー入力クラスの設計
Posted: 2014年10月18日(土) 23:55
by ISLe()
以前、同じインターフェースを持つオブジェクトをパイプで繋ぐように結び付けることで変換等を実装できるという話をしましたよね。
というかそもそもそのあたりの話からこのトピックに繋がっているのではなかったでしたっけ。
コントローラークラスが変換クラスに依存しているので、入力クラスに対する変換クラスが常に必要ですよね。
これはどうして必須なのでしょうか。
CKeyInputはKEY_INPUT_*を、CGamepadInputはPAD_INPUT_*を直接指定するようになってます。
仮想キーコードを使いたくないというだけでCKeyInput/CGamepadInput各単体での存在意義がほとんどありません。
CKeyInput/CGamepadInputを無くして、CConvKeyCode/CConvPadCodeに統合すべきかと。
というわけで堂々巡りですね。
まずきちんとゴールを設定してください。
ゴールがないまま走っていたら永遠に終わらないですよ。
キーコンフィグやリプレイで影響を受けない・与えないというのではなくて、どれだけ既存のコードに手を入れずにそれらを実装できるとか、ここでは逆に実装時のメリットをあげて欲しいところです。
あと、ひとつのキーボードで複数プレイヤー対応とか、このトピックで新たに出た課題はまだ後回しということで良いのでしょうか。
Re: キー入力クラスの設計
Posted: 2014年10月25日(土) 17:40
by Rittai_3D
ISLe()さん、返信ありがとうございます。
以前、同じインターフェースを持つオブジェクトをパイプで繋ぐように結び付けることで変換等を実装できるという話をしましたよね。
というかそもそもそのあたりの話からこのトピックに繋がっているのではなかったでしたっけ。
コントローラークラスが変換クラスに依存しているので、入力クラスに対する変換クラスが常に必要ですよね。
これはどうして必須なのでしょうか。
CKeyInput/CGamepadInput は直接キーコードのみを扱い、
CConvKeyCode/CConvPadCode は仮想キーコードと直接キーコードの両方を扱い、
CController は仮想キーコードのみを扱う
というのでしっかり分けておこうと考えましたが、よく考えたら必要ないですよね。
直接キーコードと仮想キーコードを混ぜたものを CKeyInput で扱いたくないと思いましたが、結局混ぜてしまうので要らないですね。
まずきちんとゴールを設定してください。
ゴールがないまま走っていたら永遠に終わらないですよ。
どこをゴールにしてよいかわかりませんが、後で機能を追加しても、機能専用のコードを書かなくてよいようなものをゴールにして頑張ります。
あと、ひとつのキーボードで複数プレイヤー対応とか、このトピックで新たに出た課題はまだ後回しということで良いのでしょうか。
はい。なかなかこちらに時間が回せないので、後回しになります。
CConvKeyCode/CConvPadCode と CKeyInput/CGamepadInput を統合してコードを書いてみました。
問題点などがありましたら教えてください。
► スポイラーを表示
main.cpp
コード:
#include <DxLib.h>
#include <memory>
#include "Controller.h"
#include "VirtualKeyCode.h"
using namespace std;
#if 1
# define DBG_COMMENT { DrawFormatString( 0, 0, GetColor( 255, 255, 255 ), "ぬわああああああ!" ); }
# define DBG_PRINT(x,y,f) { DrawFormatString( x, y, GetColor( 255, 255, 255 ), "dbgData : %d", ( int )f ); }
#else
# define DBG_COMMENT ( void )0
# define DBG_PRINT(x,y,f) ( void )0
#endif
// テストコード
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow )
{
ChangeWindowMode( TRUE ), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK );
{
std::shared_ptr< CController > pCtrl( new CController );
int state = 0;
int frame = 0;
while( !ScreenFlip() && !ProcessMessage() && !ClearDrawScreen() && !clsDx() ) {
pCtrl->Proc();
state = pCtrl->GetState(); // ステート表示用
if( pCtrl->GetState( BUTTON_A ) != 0 ) {
DBG_COMMENT;
}
DBG_PRINT( 0, 16, state );
}
}
DxLib_End();
return 0;
}
BaseInput.h
コード:
#pragma once
class CBaseInput
{
protected:
int m_State;
public:
CBaseInput() : m_State( 0 ) {}
virtual ~CBaseInput() {}
virtual bool Proc() = 0;
inline int GetState() const { return m_State; }
};
KeyInput.h
コード:
#pragma once
struct KeyTable_t
{
int keyCode;
int virtualCode;
};
#include "BaseInput.h"
#include <vector>
class CKeyInput : public CBaseInput
{
std::vector< KeyTable_t > m_Table;
private:
inline bool IsInput( int keyCode );
public:
CKeyInput();
~CKeyInput();
virtual bool Proc() override;
inline void SetKey( int keyCode, int virtualCode );
};
#include <memory>
typedef std::shared_ptr< CKeyInput > KeyInputPtr;
KeyInput.cpp
コード:
#include "KeyInput.h"
#include "VirtualKeyCode.h"
#include <DxLib.h>
#include <assert.h>
const static int csKeyboardNum = 256;
static char KeyFrame[ csKeyboardNum ];
const static KeyTable_t csTable[] =
{
{ KEY_INPUT_Z , BUTTON_A },
{ KEY_INPUT_RETURN , BUTTON_B },
{ KEY_INPUT_X , BUTTON_C },
{ KEY_INPUT_ESCAPE , BUTTON_D },
{ KEY_INPUT_UP , BUTTON_UP },
{ KEY_INPUT_DOWN , BUTTON_DOWN },
{ KEY_INPUT_LEFT , BUTTON_LEFT },
{ KEY_INPUT_RIGHT , BUTTON_RIGHT },
};
const static size_t csTableSize = sizeof( csTable ) / sizeof( csTable[0] );
bool CKeyInput::IsInput( int keyCode )
{
assert( -1 < keyCode );
assert( keyCode < csKeyboardNum );
return KeyFrame[ keyCode ] != 0;
}
CKeyInput::CKeyInput() : m_Table()
{
memset( KeyFrame, 0, sizeof( KeyFrame ) );
for( int i=0 ; i<csTableSize ; i++ ) {
SetKey( csTable[i].keyCode, csTable[i].virtualCode );
}
}
CKeyInput::~CKeyInput()
{
}
void CKeyInput::SetKey( int keyCode, int virtualCode )
{
KeyTable_t tmp = { keyCode, virtualCode };
m_Table.push_back( tmp );
}
bool CKeyInput::Proc()
{
m_State = 0;
GetHitKeyStateAll( KeyFrame );
for( unsigned i=0 ; i<m_Table.size() ; i++ ) {
if( IsInput( m_Table[i].keyCode ) ) {
m_State |= m_Table.at( i ).virtualCode;
}
}
return true;
}
Controller.h
コード:
#pragma once
#include <vector>
#include "KeyInput.h"
class CController
{
int m_State;
std::vector< KeyInputPtr > m_List;
public:
CController();
~CController();
bool Proc();
const inline int GetState() const { return m_State; }
const inline int GetState( int virtualCode ) const { return m_State & virtualCode; }
};
Controller.cpp
コード:
#include "Controller.h"
#include "KeyInput.h"
#include <DxLib.h>
#include <memory>
CController::CController() : m_State( 0 ), m_List()
{
m_List.push_back( std::make_shared< CKeyInput >() );
}
CController::~CController()
{
}
bool CController::Proc()
{
m_State = 0;
for( auto& it : m_List ) {
it->Proc();
}
for( auto& it : m_List ) {
m_State |= it->GetState();
}
// DrawFormatString( 0, 64, GetColor( 255, 255, 255 ), "CALLED : %s()", __FUNCTION__ );
return true;
}
Re: キー入力クラスの設計
Posted: 2014年10月25日(土) 22:48
by ISLe()
3D_3D さんが書きました:どこをゴールにしてよいかわかりませんが、後で機能を追加しても、機能専用のコードを書かなくてよいようなものをゴールにして頑張ります。
どうしたら「後で機能を追加しても、機能専用のコードを書かなくてよ」くなるかを考えて組み立てるのが設計というものですが。
試行錯誤と行き当たりばったりは違いますよ。
3D_3D さんが書きました:CConvKeyCode/CConvPadCode と CKeyInput/CGamepadInput を統合してコードを書いてみました。
問題点などがありましたら教えてください。
No.17あたりとそっくりですね。
むしろゲームパッド対応のコードがなくなった分後退してますかね。
まさに時間が巻き戻ったような感じです。
ここは無限ループの二周目といったところでしょうか。
指摘内容は一周目と同じです。
このトピックを最初から読み直してみてください。
Re: キー入力クラスの設計
Posted: 2014年10月27日(月) 22:46
by Rittai_3D
ゲームの仕様を以下のようにまとめました。
コード:
プレイヤー人数:1人
入力装置の種類:キーボード、ゲームパッド(各一つだけ対応。複数は対応しない)
(現時点で)ゲームの進行に必要なキー(プレイヤーの操作のみ):
ショット、ボム、スロー、方向キー(4つ)
現時点では、ゲームパッドのアナログ入力は対応しない。
今後、対応するとすれば、特定の値以上倒していたら入力があったとみなす。
特定の値はまだ未定。
どうしたら「後で機能を追加しても、機能専用のコードを書かなくてよ」くなるかを考えて組み立てるのが設計というものですが。
試行錯誤と行き当たりばったりは違いますよ。
そうでした・・・。
何を以てゴールにしようか悩んでいます。このトピックに入る前のトピックのリプレイは、もともとC++でリプレイは実装できるのか試してみたいと思い、サンプルとして書き始めたものでした。
特にゴールを決めていなかったので今痛い目を見ています。
以下はソースコードです。
http://dixq.net/forum/viewtopic.php?t=8433#p69002のISLeさんの返信を元に書いてみました。
いったんコントローラクラスを抽象化しました。キー入力クラスとゲームパッドクラスはそれを継承して、とあるキーが押されたら
仮想コントローラーの入力状態を変化させるようにしました。
また、メインで、押されたという証拠の文字の表示に変化をつけたくてキー入力で動くプレイヤーを追加しました。
► スポイラーを表示
main.cpp
コード:
#include <DxLib.h>
#include <memory>
#include "VirtualKeyCode.h"
#include "Player.h"
#include "Controller.h"
using namespace std;
#if 1
# define DBG_COMMENT { DrawFormatString( 0, 0, GetColor( 255, 255, 255 ), "ぬわああああああ!" ); }
# define DBG_PRINT(x,y,f) { DrawFormatString( x, y, GetColor( 255, 255, 255 ), "dbgData : %d", ( int )f ); }
#else
# define DBG_COMMENT ( void )0
# define DBG_PRINT(x,y,f) ( void )0
#endif
// テストコード
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow )
{
ChangeWindowMode( TRUE ), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK );
{
auto pCtrl = make_shared< CController >();
auto pPlayer = make_shared< CPlayer >( pCtrl.get() );
while( !ScreenFlip() && !ProcessMessage() && !ClearDrawScreen() && !clsDx() ) {
pCtrl->Proc();
pPlayer->Proc();
pPlayer->Draw();
DBG_PRINT( 0, 0, pCtrl->GetState() );
}
}
DxLib_End();
return 0;
}
Controller.h
コード:
#pragma once
#include "VirtualController.h"
#include "GamepadInput.h"
#include "KeyInput.h"
#include <vector>
#include <memory>
class CController : public CVirtualController
{
typedef std::shared_ptr< CVirtualController > InputPtr;
int m_State;
std::vector< InputPtr > m_List;
public:
CController();
~CController();
virtual bool Proc() override;
inline virtual const int& GetState() const override { return m_State; }
};
Controller.cpp
コード:
#include "Controller.h"
#include "GamepadInput.h"
#include "KeyInput.h"
#include <memory>
#include <vector>
CController::CController()
{
m_List.clear();
m_List.push_back( std::make_shared< CKeyboardInput >() );
m_List.push_back( std::make_shared< CGamepadInput >() );
}
CController::~CController()
{
}
bool CController::Proc()
{
m_State = 0;
for( auto& it : m_List ) {
it->Proc();
}
for( auto& it : m_List ) {
m_State |= it->GetState();
}
return true;
}
KeyInput.h
コード:
#pragma once
#include "VirtualController.h"
class CKeyboardInput : public CVirtualController
{
public:
CKeyboardInput();
~CKeyboardInput();
virtual bool Proc() override;
};
KeyInput.cpp
コード:
#include <DxLib.h>
#include "KeyInput.h"
#include "VirtualKeyCode.h"
static char InputInfo[ 256 ];
struct KeyTable_t
{
int keyCode;
int virtualCode;
};
const static struct KeyTable_t csKeyTable[] =
{
{ KEY_INPUT_UP , BUTTON_UP },
{ KEY_INPUT_DOWN , BUTTON_DOWN },
{ KEY_INPUT_LEFT , BUTTON_LEFT },
{ KEY_INPUT_RIGHT , BUTTON_RIGHT },
{ KEY_INPUT_Z , BUTTON_A },
{ KEY_INPUT_RETURN, BUTTON_A },
{ KEY_INPUT_X , BUTTON_B },
{ KEY_INPUT_ESCAPE, BUTTON_B },
{ KEY_INPUT_LSHIFT, BUTTON_C },
};
const static size_t csTableSize = sizeof( csKeyTable ) / sizeof( csKeyTable[0] );
CKeyboardInput::CKeyboardInput()
{
memset( InputInfo, 0, sizeof( InputInfo ) );
}
CKeyboardInput::~CKeyboardInput()
{
}
bool CKeyboardInput::Proc()
{
m_InputState = 0;
GetHitKeyStateAll( InputInfo );
for( int i=0 ; i<csTableSize ; i++ ) {
if( InputInfo[ csKeyTable[i].keyCode ] ) {
m_InputState |= csKeyTable[i].virtualCode;
}
}
return true;
}
GamepadInput.h
コード:
#pragma once
#include "VirtualController.h"
class CGamepadInput : public CVirtualController
{
public:
CGamepadInput();
~CGamepadInput();
virtual bool Proc() override;
};
GamepadInput.cpp
コード:
#include "GamepadInput.h"
#include "VirtualKeyCode.h"
#include <DxLib.h>
const static int csPadKeyNum = 28;
static bool InputFlag[ csPadKeyNum ];
/*
* shot : 9
* slow : 8
* bomb : 11
* down : 0
* up : 3
* left : 1
* right: 2
*/
struct PadTable_t
{
int padKeyVal; // 上の対応表
int virtualCode;
};
const static PadTable_t csPadTable[] =
{
{ 3, BUTTON_UP },
{ 0, BUTTON_DOWN },
{ 1, BUTTON_LEFT },
{ 2, BUTTON_RIGHT },
{ 9, BUTTON_A },
{ 11, BUTTON_B },
{ 8, BUTTON_C },
};
const static size_t csTableSize = sizeof( csPadTable ) / sizeof( csPadTable[0] );
CGamepadInput::CGamepadInput()
{
memset( InputFlag, 0, sizeof( InputFlag ) );
}
CGamepadInput::~CGamepadInput()
{
}
bool CGamepadInput::Proc()
{
int joyPad = GetJoypadInputState( DX_INPUT_PAD1 );
int mask = 1;
m_InputState = 0;
for( int i=0 ; i<csPadKeyNum ; i++ ) {
if( joyPad & mask ) {
InputFlag[i] = true;
} else {
InputFlag[i] = false;
}
mask <<= 1;
}
for( int i=0 ; i<csTableSize ; i++ ) {
if( InputFlag[ csPadTable[i].padKeyVal ] ) {
m_InputState |= csPadTable[i].virtualCode;
}
}
return true;
}
VirtualController.h
コード:
#pragma once
class CVirtualController
{
protected:
int m_InputState;
public:
CVirtualController() : m_InputState( 0 ) {}
virtual ~CVirtualController() {}
virtual bool Proc() = 0;
inline virtual const int& GetState() const { return m_InputState; }
};
VirtualKeyCode.h
コード:
#pragma once
/*
* 仮想キーコード
*/
const int BUTTON_NONE = 0;
const int BUTTON_A = ( 1 << 0 );
const int BUTTON_B = ( 1 << 1 );
const int BUTTON_C = ( 1 << 2 );
const int BUTTON_D = ( 1 << 3 );
const int BUTTON_E = ( 1 << 4 );
const int BUTTON_UP = ( 1 << 5 );
const int BUTTON_DOWN = ( 1 << 6 );
const int BUTTON_LEFT = ( 1 << 7 );
const int BUTTON_RIGHT = ( 1 << 8 );
const int VirtualKeyCodeList[] =
{
BUTTON_NONE,
BUTTON_A,
BUTTON_B,
BUTTON_C,
BUTTON_D,
BUTTON_E,
BUTTON_UP,
BUTTON_DOWN,
BUTTON_LEFT,
BUTTON_RIGHT,
};
const size_t BUTTON_NUM = ( sizeof( VirtualKeyCodeList ) / sizeof( VirtualKeyCodeList[0] ) );
Player.h
コード:
#pragma once
#include "VirtualController.h"
struct Vector_t
{
double x, y;
};
class CPlayer
{
CVirtualController* m_pCtrl;
Vector_t m_Pos;
private:
void Move();
public:
CPlayer();
CPlayer( CVirtualController* ctrl );
~CPlayer();
bool Proc();
void Draw();
};
Player.cpp
コード:
#include "Player.h"
#include "VirtualKeyCode.h"
#include <DxLib.h>
CPlayer::CPlayer() :
m_pCtrl( nullptr )
{
m_Pos.x = 480/2;
m_Pos.y = 640/2;
}
CPlayer::CPlayer( CVirtualController* ctrl ) :
m_pCtrl( ctrl )
{
m_Pos.x = 640/2;
m_Pos.y = 480/2;
}
CPlayer::~CPlayer()
{
}
/*
* *********** メモ ***********
*
* BUTTON_UP, BUTTON_DOWN, ... : 方向キー
* BUTTON_A : ショットキー
* BUTTON_B : ボムキー
* BUTTON_C : 低速キー
*
* ****************************
*/
#define SQRT2 1.41421356
const static double MoveSpd = 2.0;
void CPlayer::Move()
{
int padstate = m_pCtrl->GetState();
double x = 0.0, y = 0.0;
if( ( padstate & BUTTON_UP ) != 0 ) { y = -MoveSpd; }
if( ( padstate & BUTTON_DOWN ) != 0 ) { y = +MoveSpd; }
if( ( padstate & BUTTON_LEFT ) != 0 ) { x = -MoveSpd; }
if( ( padstate & BUTTON_RIGHT ) != 0 ) { x = +MoveSpd; }
if( x && y ) {
x /= SQRT2;
y /= SQRT2;
}
if( ( padstate & BUTTON_C ) != 0 ) {
x /= 2.0;
y /= 2.0;
}
m_Pos.x += x, m_Pos.y += y;
if( m_Pos.x < 0 ) { m_Pos.x = 0; }
if( 640 < m_Pos.x ) { m_Pos.x = 640; }
if( m_Pos.y < 0 ) { m_Pos.y = 0; }
if( 480 < m_Pos.y ) { m_Pos.x = 480; }
}
bool CPlayer::Proc()
{
Move();
return true;
}
void CPlayer::Draw()
{
DrawCircle( ( int )m_Pos.x, ( int )m_Pos.y, 4, GetColor( 255, 255, 255 ) );
}
Re: キー入力クラスの設計
Posted: 2014年10月28日(火) 10:36
by Rittai_3D
CKeyboardInput/CGamepadInput 内の Proc() でキーテーブルを直接操作していましたが、なんとなく嫌だったので若干修正しました。
また、CController 内で typedef していたのですが、これもなんとなく嫌なので修正しました。
CVirtualController.h で typedef するようにしました。
目に付いた個人的に嫌なところは修正しました。また、これ以外の場所は変化なしです。
► スポイラーを表示
CGamepadInput.h
コード:
#pragma once
#include "VirtualController.h"
#include <vector>
struct PadTable_t
{
int padKeyVal;
int virtualCode;
};
class CGamepadInput : public CVirtualController
{
std::vector< PadTable_t > m_Pad;
private:
void SetKey( int padCode, int virtualCode );
public:
CGamepadInput();
~CGamepadInput();
virtual bool Proc() override;
};
GamepadInput.cpp
コード:
#include "GamepadInput.h"
#include "VirtualKeyCode.h"
#include <DxLib.h>
const static int csPadKeyNum = 28;
static bool InputFlag[ csPadKeyNum ];
/*
* shot : 9
* slow : 8
* bomb : 11
* down : 0
* up : 3
* left : 1
* right: 2
*/
const static PadTable_t csPadTable[] =
{
{ 3, BUTTON_UP },
{ 0, BUTTON_DOWN },
{ 1, BUTTON_LEFT },
{ 2, BUTTON_RIGHT },
{ 9, BUTTON_A },
{ 11, BUTTON_B },
{ 8, BUTTON_C },
};
const static size_t csTableSize = sizeof( csPadTable ) / sizeof( csPadTable[0] );
CGamepadInput::CGamepadInput()
{
memset( InputFlag, 0, sizeof( InputFlag ) );
for( int i=0 ; i<csTableSize ; i++ ) {
SetKey( csPadTable[i].padKeyVal, csPadTable[i].virtualCode );
}
}
CGamepadInput::~CGamepadInput()
{
m_Pad.clear();
}
void CGamepadInput::SetKey( int padCode, int virtualCode )
{
PadTable_t tmp = { padCode, virtualCode };
m_Pad.push_back( tmp );
}
bool CGamepadInput::Proc()
{
int joyPad = GetJoypadInputState( DX_INPUT_PAD1 );
int mask = 1;
m_InputState = 0;
for( int i=0 ; i<csPadKeyNum ; i++ ) {
if( joyPad & mask ) {
InputFlag[i] = true;
} else {
InputFlag[i] = false;
}
mask <<= 1;
}
for( auto& it : m_Pad ) {
if( InputFlag[ it.padKeyVal ] ) {
m_InputState |= it.virtualCode;
}
}
return true;
}
KeyInput.h
コード:
#pragma once
#include "VirtualController.h"
#include <vector>
struct KeyTable_t
{
int keyCode;
int virtualCode;
};
class CKeyboardInput : public CVirtualController
{
std::vector< KeyTable_t > m_Key;
private:
void SetKey( int keyCode, int virtualCode );
public:
CKeyboardInput();
~CKeyboardInput();
virtual bool Proc() override;
};
KeyInput.cpp
コード:
#include <DxLib.h>
#include "KeyInput.h"
#include "VirtualKeyCode.h"
#include <assert.h>
static char InputInfo[ 256 ];
const static struct KeyTable_t csKeyTable[] =
{
{ KEY_INPUT_UP , BUTTON_UP },
{ KEY_INPUT_DOWN , BUTTON_DOWN },
{ KEY_INPUT_LEFT , BUTTON_LEFT },
{ KEY_INPUT_RIGHT , BUTTON_RIGHT },
{ KEY_INPUT_Z , BUTTON_A }, // (ゲーム内では区別するが)両方"決定"に関するボタンである。
{ KEY_INPUT_RETURN, BUTTON_A }, // よって、"決定"に関するボタンが押されたら、区別しないでフラグを立てる。
{ KEY_INPUT_X , BUTTON_B },
{ KEY_INPUT_ESCAPE, BUTTON_B },
{ KEY_INPUT_LSHIFT, BUTTON_C },
};
const static size_t csTableSize = sizeof( csKeyTable ) / sizeof( csKeyTable[0] );
CKeyboardInput::CKeyboardInput()
{
memset( InputInfo, 0, sizeof( InputInfo ) );
for( int i=0 ; i<csTableSize ; i++ ) {
SetKey( csKeyTable[i].keyCode, csKeyTable[i].virtualCode );
}
}
CKeyboardInput::~CKeyboardInput()
{
m_Key.clear();
}
void CKeyboardInput::SetKey( int keyCode, int virtualCode )
{
KeyTable_t tmp = { keyCode, virtualCode };
m_Key.push_back( tmp );
}
bool CKeyboardInput::Proc()
{
m_InputState = 0;
GetHitKeyStateAll( InputInfo );
for( auto& it : m_Key ) {
if( InputInfo[ it.keyCode ] ) {
m_InputState |= it.virtualCode;
}
}
return true;
}
Re: キー入力クラスの設計
Posted: 2014年10月28日(火) 19:26
by ISLe()
コントローラークラスとインプットクラスを分ける意味は、以前説明したと思うのですが。
Re: キー入力クラスの設計
Posted: 2014年10月30日(木) 21:32
by Rittai_3D
コントローラークラスはキー入力とゲームパッドクラスの入力を総括するクラスと考えています。
コントローラークラスとキー入力クラスは分ける必要がないのでしょうか。完全に混乱しています。
キー入力クラスは押されているキーコードから自分で定義した仮想キーコードの入力に変換しているつもりです。
GamepadInput.cppの
コード:
for( auto& it : m_Pad ) {
if( InputFlag[ it.padKeyVal ] ) {
m_InputState |= it.virtualCode;
}
}
の部分が変換になると思っていますが、違うのでしょうか?
以前に貼った
http://dixq.net/forum/viewtopic.php?t=8433#p69002のISLeさんの
理想コントローラークラスを継承したクラスでキーボード入力から変換するような実装にすれば、キーボード以外の入力装置に差し替えるのも簡単です。
を参考に書いたのが最後に載せたコードです。そのコードで言う CVirtualController が引用した部分での理想コントローラークラスです。
CVirtualController を継承した CKeyboardInput (キーボードからの入力)と CGamepadInput (ゲームパッドからの入力)の入力状態を総括するのが CController です。
また、リプレイのトピックで
キークラスやゲームパッドクラスは、ネイティブな入力を仮想ステータスに変換します。
という部分を
「理想ゲームパッドクラスを継承したキークラスやゲームパッドクラス内で、直接の入力があれば、そのキーコードに対応した仮想キーコードが押されたとする」
と考え、投稿したコードになりましたが、何がどうだめなのか分かりません。わたしの解釈違いでしょうか?
また、メニュー画面、ポーズ画面、ゲーム画面、・・・などの画面ごとのコントローラーはすべて統一してしまおうと考えています。
たとえば、ゲーム画面ではショットに対応するキーはメニュー画面では決定キーにする。
同様に、ゲーム画面ではボムに対応するキーはメニュー画面ではキャンセルキーにする。・・・などです。
Re: キー入力クラスの設計
Posted: 2014年10月31日(金) 18:44
by ISLe()
入力装置を総括したいなら、入力装置の範疇でするべきでしょう。
コントローラーを巻き込んではいけません。
インプットクラスがコントローラークラスに通知するのは、仮想であっても物理的なキーやボタンが押されたという情報です。
コントローラークラスがアプリケーションに通知するのは、アプリケーションで定義されるイベントやシグナルです。
右へ移動するためのキーやボタンが押された、というのと、プレイヤーを右へ動かす要求がされた、というのは異なるものです。
コントローラーは、あくまでもアプリケーション(内のオブジェクト)をコントロールするのが主目的です。
例えばAIが制御するコントローラーであれば外部装置の入力を必要としません。
コントローラークラスもインプットクラスもCVirtualControllerを継承するようにしてしまいました。
継承というのはis aですから、コントローラーもインプットもCVirtualControllerだということです。
これは設計として分かれていないですよね。
現在のコントローラークラスの実装が入力情報をスルーしているだけのものだから同じものとしてしまっているのではないでしょうか。
そして将来的にも実装で仕様の違いを何とかしよう・何とかできると思っているのではないでしょうか。
それが実装でモノを考えているということなのです。
理想コントローラークラスと書いたとき、コントローラーとはゲームコントローラー、入力装置のことを想定してました。
ゲームパッドではなくゲームコントローラーという呼称のほうが自分の中ではメジャーなもので、気を付けてはいるつもりなのですが、用語の意味が場所によって異なっていることがあるのは申し訳ないです。
Re: キー入力クラスの設計
Posted: 2014年10月31日(金) 23:29
by ISLe()
過去のトピックから読み直してみたのですが、わたしがコントローラークラスに対して総括とか理想とかいう単語を出していますね。
ですが、その単語だけに集約されて、さらに過去のトピックと混ざって、別のモノに置き換わってしまっている印象です。
わたしの投稿には、上記の単語だけでなく、具体的な説明もあるのですが、そちらはまったく意識に残っていないようです。
3D_3Dさんに限らず、日本語で説明したことがまったく見向きもされないというのを、以前からしばしば感じていましたが、わたしの文章はわたしが思っている以上にヒドいのかもしれません。
Re: キー入力クラスの設計
Posted: 2014年11月01日(土) 13:32
by Tatu
前のトピック「C++でのリプレイの実装」に書かれている
http://dixq.net/forum/viewtopic.php?f=3&t=15513
インプット、コントローラー、リプレイについて書いてみました。
インプット(キー入力クラス・ゲームパッドクラス)
使用されるかどうかを問わず、全てのキー、ボタンの情報(No:16の「ネイティブな入力」)を管理(No:18)
ネイティブな入力からステータスへの変換を行う(No:16)
キーコンフィグの影響を受ける(No:16)
変換されたステータスを渡す機能を持つ(No:16)
ステータスの定義はコントローラーで行われている(No:18)
コントローラー
ユーザー操作のステータスを渡す機能を持つ(No:4)
インプットから受け取ったステータスの変換・合成を行う(No:16、No:30)
リプレイ(No:4)
コントローラーを継承
ユーザー操作のステータスを渡す機能については
記録時は元のコントローラーのステータスを取得・記録し、元のステータスをそのまま要求元に返す
再生時は記録を読み出し要求元に返す
というようにする
Re: キー入力クラスの設計
Posted: 2014年11月01日(土) 17:29
by Rittai_3D
ISLe()さん、Tatuさん。返信ありがとうございます。
>ISLe()さん
入力装置を総括したいなら、入力装置の範疇でするべきでしょう。
コントローラーを巻き込んではいけません。
インプットクラスがコントローラークラスに通知するのは、仮想であっても物理的なキーやボタンが押されたという情報です。
コントローラークラスがアプリケーションに通知するのは、アプリケーションで定義されるイベントやシグナルです。
右へ移動するためのキーやボタンが押された、というのと、プレイヤーを右へ動かす要求がされた、というのは異なるものです。
コントローラーは、あくまでもアプリケーション(内のオブジェクト)をコントロールするのが主目的です。
例えばAIが制御するコントローラーであれば外部装置の入力を必要としません。
ひょっとして、わたしは今まで勘違いをしていたかもしれませんし、これも勘違いかもしれませんが、コントローラークラス(ゲームパッドではない方)はインターフェースクラスでしょうか?
だとすると、わたしは「コントローラークラスは2つの入力装置からの入力をひとつの入力装置からの入力とみなして、お互いの入力を統合したステータスを返す」ようなものだと思っていました。違うのならば考えなおしてみます。
過去のトピックから読み直してみたのですが、わたしがコントローラークラスに対して総括とか理想とかいう単語を出していますね。
ですが、その単語だけに集約されて、さらに過去のトピックと混ざって、別のモノに置き換わってしまっている印象です。
わたしの投稿には、上記の単語だけでなく、具体的な説明もあるのですが、そちらはまったく意識に残っていないようです。
このトピック内でコントローラーがどのコントローラーを指すのか分からなくなる時があります。
複数の意味を持つコントローラーのせいなのでしょうね。
>Tatuさん
インプット(キー入力クラス・ゲームパッドクラス)
使用されるかどうかを問わず、全てのキー、ボタンの情報(No:16の「ネイティブな入力」)を管理(No:18)
ネイティブな入力からステータスへの変換を行う(No:16)
キーコンフィグの影響を受ける(No:16)
変換されたステータスを渡す機能を持つ(No:16)
ステータスの定義はコントローラーで行われている(No:18)
コントローラー
ユーザー操作のステータスを渡す機能を持つ(No:4)
インプットから受け取ったステータスの変換・合成を行う(No:16、No:30)
リプレイ(No:4)
コントローラーを継承
ユーザー操作のステータスを渡す機能については
記録時は元のコントローラーのステータスを取得・記録し、元のステータスをそのまま要求元に返す
再生時は記録を読み出し要求元に返す
というようにする
前トピックの入力周りの要点をまとめてくださりありがとうございます。
今後このトピックを参考にするであろう方々が、いちいち前トピックに飛ばなくても大まかな流れがつかめると思います。
オフトピック
これで、わたしも混乱せずに流れの把握ができることでしょうw
Re: キー入力クラスの設計
Posted: 2014年11月01日(土) 19:05
by ISLe()
このトピックは、良い設計をしたい、という主旨ではなかったのでしょうか。
Tatuさんの書いたように、3D_3Dさん自身がまとめることをずっと期待していたのですが。
インターフェースかどうかは重要なのでしょうか。
まとめも大事ですが、前のスレの話の流れもよく見てください。
入力とコントローラーを分離する、という話題の提供から始まっているのです。
入力とコントローラーを分離する、というのは大前提なのです。
いまは入力装置のコントローラーのことは、ゲームコントローラーと書くようにしています。
Re: キー入力クラスの設計
Posted: 2014年11月01日(土) 22:22
by Rittai_3D
ISLe()さん、返信ありがとうございます。
このトピックは、良い設計をしたい、という主旨ではなかったのでしょうか。
Tatuさんの書いたように、3D_3Dさん自身がまとめることをずっと期待していたのですが。
あせりすぎていて、すべきことをしていませんでした。
あせり、更に混乱すると何をすべきなのか分からなくなる悪い癖です。もう少し落ち着いてするべきことを見てみます。
インターフェースかどうかは重要なのでしょうか。
まとめも大事ですが、前のスレの話の流れもよく見てください。
入力とコントローラーを分離する、という話題の提供から始まっているのです。
入力とコントローラーを分離する、というのは大前提なのです。
インターフェースというのは抽象化するという意味で言ったので深い意味はありません。
またコードを書いてみました。以前は、キー入力クラスとゲームパッドクラスが仮想のコントローラークラスを継承していましたが、 CKeyboardInput / CGamepadInput では入力装置基底クラスを継承し、入力をまとめてみました。 CInputDevice は仮想コントローラークラスを継承し、複数の入力装置からの入力をひとつの入力装置からの入力とみなすようにしました。
そして、キー入力で動くプレイヤーのサンプルと適当に動くAIのようなものを作ってみました。
考え方はこれであっているでしょうか。
► スポイラーを表示
コード:
#include <DxLib.h>
#include <memory>
#include "PlayerSample.h"
#include "AISample.h"
#include "InputDeviceMgr.h"
using namespace std;
#if 1
# define DBG_COMMENT { DrawFormatString( 0, 0, GetColor( 255, 255, 255 ), "ぬわああああああ!" ); }
# define DBG_PRINT(x,y,f) { DrawFormatString( x, y, GetColor( 255, 255, 255 ), "dbgData : %d", ( int )f ); }
#else
# define DBG_COMMENT ( void )0
# define DBG_PRINT(x,y,f) ( void )0
#endif
// テストコード
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow )
{
ChangeWindowMode( TRUE ), DxLib_Init(), SetDrawScreen( DX_SCREEN_BACK );
{
//auto pInput = make_shared< CInputDeviceMgr >();
auto pInput = make_shared< CAISample >();
auto pPlayer = make_shared< CPlayer >( pInput.get() );
while( !ScreenFlip() && !ProcessMessage() && !ClearDrawScreen() && !clsDx() ) {
pInput->StateProc();
pPlayer->Proc();
pPlayer->Draw();
}
}
DxLib_End();
return 0;
}
コード:
#pragma once
/*
* 仮想キーコード
*/
const int BUTTON_NONE = 0;
const int BUTTON_A = ( 1 << 0 );
const int BUTTON_B = ( 1 << 1 );
const int BUTTON_C = ( 1 << 2 );
const int BUTTON_D = ( 1 << 3 );
const int BUTTON_E = ( 1 << 4 );
const int BUTTON_UP = ( 1 << 5 );
const int BUTTON_DOWN = ( 1 << 6 );
const int BUTTON_LEFT = ( 1 << 7 );
const int BUTTON_RIGHT = ( 1 << 8 );
const int VirtualKeyCodeList[] =
{
BUTTON_NONE,
BUTTON_A,
BUTTON_B,
BUTTON_C,
BUTTON_D,
BUTTON_E,
BUTTON_UP,
BUTTON_DOWN,
BUTTON_LEFT,
BUTTON_RIGHT,
};
const size_t BUTTON_NUM = ( sizeof( VirtualKeyCodeList ) / sizeof( VirtualKeyCodeList[0] ) );
コード:
#pragma once
class IController
{
protected:
int m_State;
public:
IController() {}
virtual ~IController() {}
// 入力状態の更新
virtual void StateProc()
{
m_State = 0;
}
inline const int& GetState() const { return m_State; }
};
コード:
#pragma once
/*
* 入力装置からの入力の基底クラス
*/
class IBaseInputDevice
{
protected:
int m_InputState;
public:
IBaseInputDevice() : m_InputState( 0 ) {}
virtual ~IBaseInputDevice() {}
// 入力装置からの入力を処理する
virtual void InputProc()
{
m_InputState = 0;
}
inline const int& GetInputState() const { return m_InputState; }
};
#include <memory>
typedef std::shared_ptr< IBaseInputDevice > BaseDevicePtr;
コード:
#pragma once
#include "BaseInputDevice.h"
/*
* プレイヤーが動かすサンプル(キーボード)
*/
struct KeyTable_t
{
int keyCode;
int virtualCode;
};
#include <list>
class CKeyboardInput : public IBaseInputDevice
{
std::list< KeyTable_t > m_KeyList;
public:
CKeyboardInput();
~CKeyboardInput();
inline void SetKey( int keyCode, int virtualCode );
virtual void InputProc() override;
};
コード:
#include "KeyboardInput.h"
#include <DxLib.h>
#include "VirtualKeyCode.h"
const static struct KeyTable_t csKeyTable[] =
{
{ KEY_INPUT_UP , BUTTON_UP },
{ KEY_INPUT_DOWN , BUTTON_DOWN },
{ KEY_INPUT_LEFT , BUTTON_LEFT },
{ KEY_INPUT_RIGHT , BUTTON_RIGHT },
{ KEY_INPUT_Z , BUTTON_A },
{ KEY_INPUT_RETURN, BUTTON_A },
{ KEY_INPUT_X , BUTTON_B },
{ KEY_INPUT_ESCAPE, BUTTON_B },
{ KEY_INPUT_LSHIFT, BUTTON_C },
};
static char gKeyBuf[ 256 ];
CKeyboardInput::CKeyboardInput() : m_KeyList()
{
memset( gKeyBuf, 0, sizeof( gKeyBuf ) );
for( auto& i : csKeyTable ) {
SetKey( i.keyCode, i.virtualCode );
}
}
CKeyboardInput::~CKeyboardInput()
{
m_KeyList.clear();
}
void CKeyboardInput::SetKey( int keyCode, int virtualCode )
{
KeyTable_t tmp = { keyCode, virtualCode };
m_KeyList.push_back( tmp );
}
void CKeyboardInput::InputProc()
{
IBaseInputDevice::InputProc();
GetHitKeyStateAll( gKeyBuf );
for( auto& it : m_KeyList ) {
if( gKeyBuf[ it.keyCode ] ) {
m_InputState |= it.virtualCode;
}
}
}
コード:
#pragma once
#include "BaseInputDevice.h"
/*
* プレイヤーが動かすサンプル(ゲームパッド)
*/
struct PadTable_t
{
int padKeyVal;
int virtualCode;
};
#include <list>
class CGamepadInput : public IBaseInputDevice
{
std::list< PadTable_t > m_PadBtnList;
public:
CGamepadInput();
~CGamepadInput();
inline void SetButton( int padButtonVal, int virtualCode );
virtual void InputProc() override;
};
コード:
#include "GameapadInput.h"
#include <DxLib.h>
#include "VirtualKeyCode.h"
const static int csPadKeyNum = 28;
static bool InputFlag[ csPadKeyNum ];
/*
* shot : 9
* slow : 8
* bomb : 11
* down : 0
* up : 3
* left : 1
* right: 2
*/
const static PadTable_t csPadTable[] =
{
{ 3, BUTTON_UP },
{ 0, BUTTON_DOWN },
{ 1, BUTTON_LEFT },
{ 2, BUTTON_RIGHT },
{ 9, BUTTON_A },
{ 11, BUTTON_B },
{ 8, BUTTON_C },
};
CGamepadInput::CGamepadInput() : m_PadBtnList()
{
memset( InputFlag, 0, sizeof( InputFlag ) );
for( auto i : csPadTable ) {
SetButton( i.padKeyVal, i.virtualCode );
}
}
CGamepadInput::~CGamepadInput()
{
}
void CGamepadInput::SetButton( int padButtonVal, int virtualCode )
{
PadTable_t tmp = { padButtonVal, virtualCode };
m_PadBtnList.push_back( tmp );
}
void CGamepadInput::InputProc()
{
IBaseInputDevice::InputProc();
int joyPad = GetJoypadInputState( DX_INPUT_PAD1 );
for( int i=0 ; i<csPadKeyNum ; i++ ) {
if( joyPad & ( 1 << i ) ) {
InputFlag[i] = true;
} else {
InputFlag[i] = false;
}
}
for( auto& it : m_PadBtnList ) {
if( InputFlag[ it.padKeyVal ] ) {
m_InputState |= it.virtualCode;
}
}
}
コード:
#pragma once
/*
* 複数の入力装置からの入力を一つの入力装置からの入力だと
* みなすクラス
*/
#include "BaseController.h"
#include "BaseInputDevice.h"
#include <list>
class CInputDeviceMgr : public IController
{
std::list< BaseDevicePtr > m_DeviceList;
public:
CInputDeviceMgr();
~CInputDeviceMgr();
virtual void StateProc() override;
inline void Insert( BaseDevicePtr pDevice );
};
コード:
#include "InputDeviceMgr.h"
#include "KeyboardInput.h"
#include "GameapadInput.h"
#include <memory>
CInputDeviceMgr::CInputDeviceMgr() : m_DeviceList()
{
Insert( std::make_shared< CKeyboardInput >() );
Insert( std::make_shared< CGamepadInput >() );
}
CInputDeviceMgr::~CInputDeviceMgr()
{
}
void CInputDeviceMgr::StateProc()
{
IController::StateProc();
for( auto& it : m_DeviceList ) {
it->InputProc();
m_State |= it->GetInputState();
}
}
void CInputDeviceMgr::Insert( BaseDevicePtr pDevice )
{
m_DeviceList.push_back( pDevice );
}
コード:
#pragma once
/*
* プレイヤーサンプル
*/
#include "BaseController.h"
struct Pos_t
{
double x, y;
Pos_t() : x( 0.0 ), y( 0.0 ) {}
};
class CPlayer
{
IController* m_pCtrl;
Pos_t m_Pos;
public:
CPlayer();
CPlayer( IController* pCtrl );
~CPlayer();
bool Proc();
void Draw();
};
コード:
#include "PlayerSample.h"
#include "VirtualKeyCode.h"
#include <DxLib.h>
const static double Sqrt2 = 1.41421356;
const static double MoveSpd = 2.0;
CPlayer::CPlayer() : m_pCtrl( nullptr )
{
m_Pos.x = 640.0 / 2.0;
m_Pos.y = 480.0 / 2.0;
}
CPlayer::CPlayer( IController* pCtrl ) : m_pCtrl( pCtrl )
{
m_Pos.x = 640.0 / 2.0;
m_Pos.y = 480.0 / 2.0;
}
CPlayer::~CPlayer()
{
}
bool CPlayer::Proc()
{
int padstate = m_pCtrl->GetState();
double x = 0.0, y = 0.0;
if( ( padstate & BUTTON_UP ) != 0 ) { y = -MoveSpd; }
if( ( padstate & BUTTON_DOWN ) != 0 ) { y = +MoveSpd; }
if( ( padstate & BUTTON_LEFT ) != 0 ) { x = -MoveSpd; }
if( ( padstate & BUTTON_RIGHT ) != 0 ) { x = +MoveSpd; }
if( x && y ) {
x /= Sqrt2;
y /= Sqrt2;
}
if( ( padstate & BUTTON_C ) != 0 ) {
x /= 2.0;
y /= 2.0;
}
m_Pos.x += x, m_Pos.y += y;
if( m_Pos.x < 0 ) { m_Pos.x = 0; }
if( 640 < m_Pos.x ) { m_Pos.x = 640; }
if( m_Pos.y < 0 ) { m_Pos.y = 0; }
if( 480 < m_Pos.y ) { m_Pos.x = 480; }
return true;
}
void CPlayer::Draw()
{
DrawCircle( ( int )m_Pos.x, ( int )m_Pos.y, 4, GetColor( 255, 255, 255 ) );
}
コード:
#pragma once
#include "BaseController.h"
/*
* プレイヤー以外が動かすサンプル
*/
class CAISample : public IController
{
int m_Cnt;
public:
CAISample();
~CAISample();
virtual void StateProc() override;
};
コード:
#include "AISample.h"
#include "VirtualKeyCode.h"
CAISample::CAISample() : m_Cnt( 0 )
{
}
CAISample::~CAISample()
{
}
void CAISample::StateProc()
{
// 適当なサンプル.
// キーボードを触っていなくてもプレイヤー(アプリケーション内のオブジェクトの方)が動く
// 基底クラスの関数呼び出す
IController::StateProc();
if( m_Cnt < 60 ) {
m_State |= BUTTON_DOWN;
m_State |= BUTTON_LEFT;
}
if( m_Cnt > 60 ) {
m_State |= BUTTON_UP;
}
if( m_Cnt > 60 + 120 ) {
m_State |= BUTTON_RIGHT;
}
if( m_Cnt > 60 + 120 + 10 ) {
m_State = 0;
}
m_Cnt++;
}
Re: キー入力クラスの設計
Posted: 2014年11月01日(土) 23:56
by ISLe()
CInputDeviceMgrは複数の入力装置からの入力をひとつにまとめてもなお入力装置だと思いますが。
もはや3D_3Dさんが何をしたいのか分かりません。
あっているかと尋ねられても答えようがありません。
ここでDeviceという単語を使うのは個人的にはお勧めしません。
「入力装置」としたのが良くなかったでしたかね。単に「入力」としたほうが良かったか。
キーボードに複数の十字キーを割り当てるような場合、それぞれの入力装置クラスでいちいちハードウェアのキーの状態を取得してチェックするのは効率が悪いので、現実においての物理的なハードウェアは一箇所で管理したほうが良い。
デバイスドライバの仕様で排他的にしかアクセスできないような場合もありますし。
Deviceという単語はそういうハードウェアリソースにアクセスする部分で使うのが良いと思いますが。
Re: キー入力クラスの設計
Posted: 2014年11月03日(月) 10:49
by Rittai_3D
分からない点は、
・キーボード入力クラスやゲームパッド入力クラスで自分で定義した仮想キーコードとDxLib.hで定義されているキーコードの対応表を作ることは良いのか悪いのか。(ゲームパッドで使用している数字は、龍神録プログラミングの館の「キーコンフィグに対応させてみよう」で使用しているような数字です。)
・コントローラークラスで、以前貼ったコードで言う CInputDeviceMgr のインスタンスを作成して良いのか。(以前、コントローラークラスでキー入力クラスのインスタンス化はよくないと言っていた気がしましたので。)
よくないならどこでインスタンス化すれば良いのか。
・キーボード入力クラス、ゲームパッド入力クラスの InputProc() で入力状態の更新をしつつ、自分で定義した仮想キーコードに変換してよいのか。
現在のわたしが疑問に思っている点です。
わたし自身何がしたいかよくわからなくなっています。すこし落ち着いて何がしたいのかよく考えてみます。
ここでDeviceという単語を使うのは個人的にはお勧めしません。
「入力装置」としたのが良くなかったでしたかね。単に「入力」としたほうが良かったか。
キーボードに複数の十字キーを割り当てるような場合、それぞれの入力装置クラスでいちいちハードウェアのキーの状態を取得してチェックするのは効率が悪いので、現実においての物理的なハードウェアは一箇所で管理したほうが良い。
デバイスドライバの仕様で排他的にしかアクセスできないような場合もありますし。
Deviceという単語はそういうハードウェアリソースにアクセスする部分で使うのが良いと思いますが。
なるほど。入力よりは入力装置としたほうが分かりやすいだろう、と考えたことがあだになってしまいました。気をつけます。
Re: キー入力クラスの設計
Posted: 2014年11月04日(火) 18:30
by usao
なんというか コード提示が長い ですよね.
皆まで書く必要も無いのではないでしょうか?
例えば,CKeyboardInput で言えば,コードっぽく書くにしても
コード:
class CKeyboardInput : public IBaseInputDevice
{
public:
virtual void InputProc()
{
//キーボードの押下状態を調べて,現在のキーコンフィグ状態に基づき
//仮想キー押下状態情報を生成して メンバ変数 m_InputState に設定する
//※ここの実装詳細まではいらないのでは?※
}
virtual int GetInputState() const { return InputState; }
private:
int m_InputState;
};
くらいの表記さえあれば,話は可能なように思うのですが,どうなのでしょう?
オフトピック
「設計」ではなく「実装」側の話かと思いますが,
IBaseInputDeviceクラスが
int m_InputState; というメンバ変数を抱えているのは違うかな,という気がします.
Re: キー入力クラスの設計
Posted: 2014年11月04日(火) 18:49
by ISLe()
3D_3D さんが書きました:・キーボード入力クラスやゲームパッド入力クラスで自分で定義した仮想キーコードとDxLib.hで定義されているキーコードの対応表を作ることは良いのか悪いのか。(ゲームパッドで使用している数字は、龍神録プログラミングの館の「キーコンフィグに対応させてみよう」で使用しているような数字です。)
入力クラスで完結することなので、好きなように実装すれば良いと思います。
ただし入力モジュール間の共通化等考慮してください。
3D_3D さんが書きました:・コントローラークラスで、以前貼ったコードで言う CInputDeviceMgr のインスタンスを作成して良いのか。(以前、コントローラークラスでキー入力クラスのインスタンス化はよくないと言っていた気がしましたので。)
よくないならどこでインスタンス化すれば良いのか。
それをすると、コントローラーが入力の実装に依存するので、コントローラーと入力の分離という大前提が崩れます。
既に説明したことですが、将来的に、現実に接続される装置と複数プレイヤーへの対応などのマネージメントが必要になります。
それはコントローラーの役割ではないので、外部からコントローラーモジュールと入力モジュールのアタッチやデタッチができるように考慮しておくべきです。
きちんと分離できていれば新たに考慮することはないとも言えます。
3D_3D さんが書きました:・キーボード入力クラス、ゲームパッド入力クラスの InputProc() で入力状態の更新をしつつ、自分で定義した仮想キーコードに変換してよいのか。
コントローラーに対して必要な出力ができれば、実装はどうでも良いです。
動作に癖が出る可能性は否定できませんが。
わたしには、どうでもいいことで悩んでいるようにしか見えません。
きちんと設計して仕様を決めれば、自ずと決まることばかりではないでしょうか。
前のトピックから、良い設計ということで、たくさんの要件を出してきました。
直前に提示されたコードの問題点を指摘しているだけではないのです。
すべてを満たす方法を考えてください。
ソースコードのコメントに、そのコードを書く意図を書きましょう。
そうすれば堂々巡りになることは少なくなるのではないでしょうか。
Re: キー入力クラスの設計
Posted: 2014年11月05日(水) 11:28
by usao
読み間違えているのかもしれませんが, BUTTON_UP とかは「仮想キー」というやつですよね?
CPlayer::Proc() 内でこの BUTTON_UP とかを調べている,というのが,なんか話が分からない感じです.
簡単な(動作種類が少ない)例として左右移動とジャンプだけができるゲームを考えると,
ゲームシーンにおいてプレイヤキャラクタに要求されるのは
{左に行け,右に行け,ジャンプしろ}の3種(の組み合わせ).
であれば,例えば,
コード:
enum PLAYER_ACTION_REQUEST_FLAGS
{
GO_LEFT=0x01, GO_RIGHT=0x02, JUMP=0x04
};
void CPlayer::Proc( unsigned int ActionRequestFlags ) //引数は↑のenumのフラグの組み合わせ.(上位がControllerから得て渡す)
{
...
}
みたいな形になるのではないでしょうか.
↑は動作要求を引数で受けてますが,提示されているコードの形に合わせれば
コード:
void CPlayer::Proc()
{
//あたかじめ指定されているControllerから,動作要求情報を取得する
const unsigned int ActionRequestFlags = m_pAttachedController->GetRequestFlags(); //うまい名前が思いつかないな…
...
}
みたいな形でしょうか.
つまり,Controllerが教えてくれるのは, 3種の動作要求が今現在なされているかどうか という情報であって,
仮想的なキーの状態を返すものではない,と.
(仮想キー群の押下状態を出力するのは Input の役目)
Re: キー入力クラスの設計
Posted: 2014年11月06日(木) 19:01
by ISLe()
usaoさんは読み違えてませんよ。
コマンド技とかの複合操作になれば違いははっきりします。
前のトピックに出てたダッシュ操作とか。
連続押しの場合、入力モジュールからコントローラーモジュールに対しては、歩くボタンが連続で押されたという通知です。
コントローラーモジュールからアプリケーションに対しては、『歩け』『走れ』という命令(イベント)が発せられるわけです。
『走れ』はアプリケーションに対して必要なイベントであって、イベントコードは必要ですが、ボタンの仮想コードはなくても良いのです。
もちろん、『走るボタン』というものを入力モジュール側とのやり取り用に用意しても構いませんが。
オプションでダッシュの連続押し受付時間を変更できるとしたら、プレイヤーモジュールでボタンの状態を判定するとリプレイ時に設定が異なるとダッシュしなかったり暴発したりすることになります。
だとしたら、オプションの内容もリプレイに記録するのか、ということになります。
イベントを記録すれば、余計なことを考えなくて済みます。
コントローラーモジュールまでで解決するので、プレイヤーモジュールは、そもそもコマンド技や複合操作が存在するということすら知らずに済みます。
知らずに済むというのはコードが汚染されないということです。
ユーザーの操作が正しく反映されるかどうか、という部分と、画面のキャラクターが正しく振る舞うかどうか、という部分を分離独立してコーディング・デバッグできるわけです。
Re: キー入力クラスの設計
Posted: 2014年11月07日(金) 10:27
by usao
Controllerって,こんな感じで操作対象毎に複数あるってことですよね?
コード:
●項目選択時用Controller(例えばタイトル画面とかで使う)
●キャラクタ操作用Controller
├ ユーザ入力で操作用Controller
├ AI的なController
└ リプレイ用Controller
●なんかまた別の…
オフトピック
とにかく何かがあって,そいつに「何をさせたいのか,どう動かしたいのか」を要求してくるもの という感じか.
それを表すのに "Controller" という単語は(ゲームパッド的なもの(Input)をどうしても思い浮かべてしまう的な意味で)いまいちなのかも…?
なんだろう,「動作要求者」「意思決定器」「命令側」とでもいうか… ??
Re: キー入力クラスの設計
Posted: 2014年11月07日(金) 18:16
by ISLe()
コントローラーはMVCアーキテクチャのCのことです。
プレイヤーコントローラー以外に、メニューコントローラーなど場面(ビュー)ごとに対応するコントローラーは必要になります。
複数切り替えるだけでなく、コントローラーの共通インターフェースで数珠つなぎにして拡張することもできます。
これらの説明は既出です。
設計が主旨であるはずのトピックで、要件定義すらされないのが問題だと思うのです。
それを回答者がやってしまったら丸投げ、というか設計を軽く見ることを助長すると思うのです。
GUIプログラミングを知っていたら『コントローラー』という単語だけで全部理解できるかとも思うのですが
デザインパターン然り実際のところ単語を知っているだけでは何も通じないのかもしれません。
Re: キー入力クラスの設計
Posted: 2014年11月27日(木) 21:00
by Rittai_3D
返信ありがとうございます。返信が遅れてしまって申し訳ありません。
設計が主旨であるはずのトピックで、要件定義すらされないのが問題だと思うのです。
それを回答者がやってしまったら丸投げ、というか設計を軽く見ることを助長すると思うのです。
要件定義は、たとえば
コード:
・キーボードとゲームパッドの入力は統合する
のようなものでしょうか。今まで特に考えずにやってきたのでこの程度でよいのか、根本的に違うのか、全く分かりません。
それをすると、コントローラーが入力の実装に依存するので、コントローラーと入力の分離という大前提が崩れます。
既に説明したことですが、将来的に、現実に接続される装置と複数プレイヤーへの対応などのマネージメントが必要になります。
それはコントローラーの役割ではないので、外部からコントローラーモジュールと入力モジュールのアタッチやデタッチができるように考慮しておくべきです。
きちんと分離できていれば新たに考慮することはないとも言えます。
「外部からコントローラーモジュールと入力モジュールのアタッチやデタッチができるように考慮しておくべきです。」
の部分ですが、コントローラークラスでインスタンス化しないとなると、どこで入力装置からの入力を取得すれば良いのでしょうか?
メインでインスタンス化して、引数に渡す、といった感じでしょうか?
コード:
// 「こんな感じ?」というイメージ
int main( void )
{
// 入力クラスのインスタンスをメインで
auto input = new CInput;
// コントローラークラスに渡す
auto controller = new CController( input );
return 0;
}
Re: キー入力クラスの設計
Posted: 2014年11月28日(金) 18:41
by ISLe()
「キーボードとゲームパッドの入力は統合する」というのは、それをするために必要な事柄を表していません。
「キーボードとゲームパッドの入力は統合したい」というところが本当だと思いますがそれは『要求』です。
要件定義とは、要求に合わせて行うものです。
要件定義を行うためには、要求をまとめなければいけません。
要求をまとめることを、企画と呼ぶ場合もあります。
要求があいまいということは、何がやりたいのか分かっていない、ということです。
要件定義は、要求を実現するために必要そうなことを洗い出してまとめることです。
実装に違い部分もありますが、矛盾がないかどうかといったところを論理的に検討する作業です。
設計についての書籍やネットの記事、補助ツールなどたくさんあります。
調べてみてはいかがでしょうか。
3D_3D さんが書きました:「外部からコントローラーモジュールと入力モジュールのアタッチやデタッチができるように考慮しておくべきです。」
の部分ですが、コントローラークラスでインスタンス化しないとなると、どこで入力装置からの入力を取得すれば良いのでしょうか?
メインでインスタンス化して、引数に渡す、といった感じでしょうか?
これまで何回も書いてますが、コントローラークラスのコントローラーというのは入力装置のことではありません。
現実の周辺機器は環境依存のものなので、それを管理するモジュールは当然分離されるべきです。
コントローラーは常に抽象化された対象を扱います。
例えば、新たにゲームパッドが接続されたことを検出するのは、いままで話題になっていない第三のモジュールです。
その装置に対応するインプットモジュールを具体化し、コントローラーモジュールにアタッチします。
コントローラーモジュールは、新たにアタッチされたインプットモジュールを一定のルールでオブジェクトの操作に割り当てます。
どのプレイヤーの操作に割り当てるか指定してアタッチする形態もあるでしょう。それは設計次第です。
これまで話題にならなかったのは必要なかったからですね。
コントローラーの中で具体化するのは適当でないということだけは書きましたけど。
必要ないといっても、やらなくて良いこと、あるいはやってはいけないことをやってしまうのは後に面倒を残すだけです。
予測で手を付けるな、というYAGNIの原則というのがありますが、備えておかなくても良いとは言ってないと思います。
Re: キー入力クラスの設計
Posted: 2014年12月02日(火) 22:46
by Rittai_3D
返信ありがとうございます。
要求があいまいということは、何がやりたいのか分かっていない、ということです。
もともと、小さいサンプルで書いていたものなので、やりたいことがあやふやというのもあり、手間取りました。
考えたのですが、
・プレイヤーはキーコンフィグを気にしない -> 直接のキーコードは自分で定義した仮想キーコードに変換する。
・リプレイ、キー入力の入力は区別しない -> キー入力なら上と同様、リプレイなら保存されているキーコードを渡す
・キーボードとゲームパッドの入力は統合する -> 複数の入力装置は、ひとつの入力装置からの入力とみなす。
となりました。もうすこし考えてみます。
また、入力装置からの入力を変換したステータスはどのように渡せばよいのでしょうか。
インターフェイスを介して取得するのでしょうか。
Re: キー入力クラスの設計
Posted: 2014年12月02日(火) 23:49
by ISLe()
3D_3D さんが書きました:また、入力装置からの入力を変換したステータスはどのように渡せばよいのでしょうか。
インターフェイスを介して取得するのでしょうか。
入力装置(インプット)側とのやり取り、
アプリケーション側とのやり取り、
コントローラーモジュールはそのどちらも抽象化します。
わたしが以前Javaで書いたサンプルコードは、インプットとコントローラーの境界を示すものであって、それらの形態を決定付けるものではありません。
より良い設計というのなら、要求に対して具体的な提案とともにインプットやコントローラーの仕様を練る必要があります。
コントローラーもインプットも、それ自体がインターフェースとなります。
リプレイのトピックで書いたように、派生させて組み合わせて使うことを考慮してます。
クラスにする、という時点でそれは備えとしてあるべきですし、そして、実際にそれをするのは必要になったときです。
Re: キー入力クラスの設計
Posted: 2014年12月02日(火) 23:51
by ISLe()
ISLe() さんが書きました:入力装置(インプット)側とのやり取り、
アプリケーション側とのやり取り、
コントローラーモジュールはそのどちらも抽象化します。
そして、その2つは区別されなければいけません。
Re: キー入力クラスの設計
Posted: 2014年12月05日(金) 21:08
by Rittai_3D
なんとなくですがわかってきた気がします。
前回のリプレイのトピックからずいぶんと長くなってしまいました。
このトピックがなければ仕様策定や設計の大切さや大変さを分からなかったと思います。
個人的な理由もあり、解決とさせていただきます。回答してくださった皆様、本当にありがとうございました。