ページ 11

ゲームパッドのクラス設計について

Posted: 2010年6月26日(土) 11:33
by わふー
こんにちは。
お世話になります。

私はゲームを作っています。
そのゲームではパッド(コントローラ)を使いますので、パッドの入力を検知したいです。
そのためにパッド用クラスCPadを作ったのですが、このクラスをゲーム本体クラスに対してどう使っていったらいいか悩んでます。

ゲーム本体は、CGameクラスを作り、WinMain関数中に
CGame game = new CGame();
game->run();
と記述するだけで動くようにしています。
肝心のゲーム内容は、CRPGだったりCShootingだとかのクラスをCGameクラス内で持ち、同様に
void CGame::Run(){
rpg->run();
}
というようにしています。(実際にはCGame::Runはもっと複雑ですが、色々と省略しています。)

このような構造をしている場合、CPadをCGameに持たせるべきか、グローバルにするべきか、迷っています。
CGameクラスに持たせた方が意味的には合ってる(パッドなんてゲーム以外使わない……)気がします。ですが、そうしますと、CRPGには(作るRPGではパッドを使うので)CGameクラスへの参照が必要になり、更にCRPGの中に色々なクラスを作ると、それらのクラスの中でパッドを使う操作をするクラスは全てCGameへの(直接または間接的な)参照を持っていなければならなくなってしまいます。


そこで、皆様の場合、CPadクラスはどういう扱いにされますか?
よろしくお願いいたします。

Re:無題

Posted: 2010年6月26日(土) 11:34
by わふー
またタイトル忘れ;
何度もすいません。。。

Re:無題

Posted: 2010年6月26日(土) 11:55
by わふー
ってこれ、CPadクラスの内容がまったくないですね。
申し訳ありません。


内容としては、
class CPad
{
public:
        // 各ボタンの状態(ボタンを押し続けている時間)保持用変数
        // 名前が思いっきりスーファミコントローラなのは仕様です
    int Up;
    int Down;
    int Left;
    int Right;

    int A;
    int B;
    int X;
    int Y;
    int L;
    int R;
    int Start;

        void Run(); // 入力をチェックし、各ボタンの値を更新
};
という感じです。
Run()をメインループ内の先頭で行い、毎フレーム各ボタンの値を更新します。

他のクラスなどから入力された値を参照する場合は、例えば、
if ( pad.A ) jump(); // Aを押したらジャンプする
といったような使い方をします。

Re:無題

Posted: 2010年6月26日(土) 12:32
by MNS
私だったら、グローバル変数ないしはシングルトンクラスにするでしょうね。
いずれにせよ、CPadのメンバが全てpublicであることは見直すべきです。

Re:無題

Posted: 2010年6月26日(土) 12:39
by Poco
質問が2点あります。
・CGameクラスの役割は何でしょうか?CRPGクラスの役割は何でしょうか?
 #パッと見ると、CGameクラスは何もしていない様に見えます。

・「それらのクラスの中でパッドを使う操作をするクラスは全てCGameへの(直接または間接的な)参照を持っていなければならなくなってしまいます。」とありますが、問題点は何でしょうか?

Re:無題

Posted: 2010年6月26日(土) 12:43
by Justy
>CPadをCGameに持たせるべきか、グローバルにするべきか
 パッドそのものはゲームとは何ら関係ないただのデバイスですので 
グローバル的な扱い(シングルトンとか)でいいと思います。

 CGameがどういうものなのか正確に把握できていませんが、仮に何かのクラスに持たせるとしたら
「右移動キーが押された」「ジャンプキーが押された」「決定キーが押された」という情報を扱うクラスでしょう。

 つまりプログラム的には「Aボタンが押されたからジャンプする」じゃなくて、
「ジャンプボタンが押されたからジャンプする」ようにするということです。

 こういうクラスならゲーム全体(?)だと思われる CGameや、或いはもっと下のプレイヤークラスとか
タイトルメニューのクラスが持っていてもおかしくないと思います。


 ところで、

>CPadクラスの内容
 この設計だと外部から無制限に各ボタンの状態を外部の都合で変更できるようですが、問題ないですか?
 又これだと1つしかパッドの情報が参照できないのですけど、複数パッドがあった場合とか
どうするのでしょうか?

Re:無題

Posted: 2010年6月26日(土) 13:52
by わふー
ありがとうございます。

とりあえず色々な箇所でご指摘の多いCGameクラスの説明をさせていただきたいと思います。
これはウィンドウのサイズを記述したり、ウィンドウを生成したり、ゲームのタイトルを記述(ウィンドウの上の方に出る文字列ですね)したり、使うゲーム用ライブラリの初期化……といったことをします。
どんなジャンルのゲームでも共通の処理はあると思いますので、それをまとめております。
ですが、ここでCGameの名前を出すのは無意味かもしれませんね。
省略すれば良かったと反省しております。申し訳ありません。
ですので、話の簡略のため、CRPGやCShootingといったクラスが根っこの部分になると考えていただければ問題ないと思います……たぶん。
(でも、CGameクラスもまとめてシングルトンにしてしまえばいいのかな……とか思ったり思ったり)


>いずれにせよ、CPadのメンバが全てpublicであることは見直すべきです。
>この設計だと外部から無制限に各ボタンの状態を外部の都合で変更できるようですが、問題ないですか?
各ボタンの値を自クラス以外から変更することは、まずないと思います。
ですので、まさか余所で値を変更することなんてないだろうと踏んでしまい、そこまで考えておりませんでした。
全ての値をprivateにして、
int getState[ボタン名]()
とか
int getState(int button)
とかにすればいいですかね……?


>・CGameクラスの役割は何でしょうか?CRPGクラスの役割は何でしょうか?
> #パッと見ると、CGameクラスは何もしていない様に見えます。
上記の通りです。
申し訳ありません。


>・「それらのクラスの中でパッドを使う操作をするクラスは全てCGameへの(直接または間接的な)参照を持っていなければならなくなってしまいます。」とありますが、問題点は何でしょうか?
まず、CGameをCRPGに置き換えさせて下さい。(CRPGがCPadを持っていることになります。)
んで。
例えば、キャラがジャンプするためのクラスCCharaJumpを作るとしましょう。
このクラスからしたら、パッドの入力は検知したいが、RPGに関する他の部分の情報は必要ありません。
ただ「ボタンが押されたらjump()を実行する」ためのクラスですので、キャラのHPは要りません。
ですので、隠蔽が上手くできてないのではないかと感じてしまうのです。


> パッドそのものはゲームとは何ら関係ないただのデバイスですので 
まったくその通りですね。




> CGameがどういうものなのか正確に把握できていませんが、仮に何かのクラスに持たせるとしたら
>「右移動キーが押された」「ジャンプキーが押された」「決定キーが押された」という情報を扱うクラスでしょう。
> つまりプログラム的には「Aボタンが押されたからジャンプする」じゃなくて、
>「ジャンプボタンが押されたからジャンプする」ようにするということです。
すいません、私にとっては難しく、意味があまり分かりません。
これは、Padをグローバル的な扱いにした上で、例えば、
if ( pad.A ) jump(); // A を押したらジャンプする
ではなく、
if ( jumpButton() ) jump();
と言う風にするということでしょうか。
いずれにしても、
int jumpButton(){
return pad.A;
}
というのが必要になり、「pad.A」の「pad」の部分はどうなるのか、という話になってしまいますが……。
なんだか私がすごい勘違いしてそうですね。すいません。


>これだと1つしかパッドの情報が参照できないのですけど、複数パッドがあった場合とかどうするのでしょうか?
論点ではないので、省略させていただきました。(実際には、このクラスのコンストラクタにどのパッドを検知対象にするのかという情報を得て対応しております。)
ですが、こういうとこもご指摘を下さる風潮がココにあるのでしたら、あまり省略しない方が良いのかもしれませんね。





ご返信ありがとうございます。

Re:無題

Posted: 2010年6月26日(土) 14:22
by Poco
> > CGameがどういうものなのか正確に把握できていませんが、仮に何かのクラスに持たせるとしたら
> >「右移動キーが押された」「ジャンプキーが押された」「決定キーが押された」という情報を扱うクラスでしょう。
> > つまりプログラム的には「Aボタンが押されたからジャンプする」じゃなくて、
> >「ジャンプボタンが押されたからジャンプする」ようにするということです。
> すいません、私にとっては難しく、意味があまり分かりません。
> これは、Padをグローバル的な扱いにした上で、例えば、
> if ( pad.A ) jump(); // A を押したらジャンプする
> ではなく、
> if ( jumpButton() ) jump();
> と言う風にするということでしょうか。
> いずれにしても、
> int jumpButton(){
> return pad.A;
> }
> というのが必要になり、「pad.A」の「pad」の部分はどうなるのか、という話になってしまいますが……。
> なんだか私がすごい勘違いしてそうですね。すいません。
>

大体あっていると思います。各クラスの役割は何なのってハナシです。
何通りかのパターンが考えられます。
・パターン(A)
人間→(Aを押す)→CPad(Aが押されたよ)→CGame(Aが押されたよ)→CRPG(決定だってさ)→CRPGSub(決定が押された時の処理をする)
                                           →CAction(ジャンプだってさ)→CActionSub(ジャンプ時の処理をする)

・パターン(B)
人間→(Aを押す)→CPad(Aが押されたよ)→CGame(決定だってさ)→CRPG(決定だってさ)→CRPGSub(決定が押された時の処理をする)
                                           →CAction(決定だってさ)→CActionSub(決定時の処理をする)

・パターン(C)
人間→(Aを押す)→CPad(Aが押されたよ)→CGame(Aが押されたよ)→CRPG(Aが押されたよ)→CRPGSub(Aが押された時の処理をする)
                                           →CAction(Aが押されたよ)→CActionSub(Aが押された時の処理をする)

各パターンの違いは、ただの入力データ(上記例では「A」)を、ゲーム上で意味がある行動に変換するクラスはどこか?です。
(A)ではCRPGやCACtionがそれを担い、(B)ではCGameがそれを担い、(C)ではCRPGやCActionの子クラスがそれを担っています。どれを選択するかによって、ゲームのつくり、ゲームエンジンの作りがまるで変わってきます。

どれを選択するのが適切か?
これが一番重要な点だと思います。

キレイな設計を行うためには、適切なレイヤーに分割する必要があります。

適切なレイヤーとは何か?というのは、各自で違うと思いますが、
私はクラス名、関数名、変数名が同じ抽象レベルの言葉で表現されているかで
レイヤーの分け方が適切かどうか判断しています。
各レイヤーには役割があり、より上位のレイヤーのための抽象概念を用意する必要があるからです。
#用意できないのであれば、それはレイヤーになりえません。

上記パターン(C)にて、ゲームの内容部分を実装するクラス(CRPGSub等)にキーボードの「A」という概念は
似つかわしくなく、私ならこのパターンは採用しません。

残ったパターン(A)と(B)ですが私としては、どっちでも良いです(最終的には折衷案になると思いますが)。
理由は、ゲームエンジンとしてそれを使う側から見た場合、関係がないからです。
他人(もしくは未来のわふーさん)がこれを再利用しようとした場合、CRPGSub2を作成するだけなので、
CPadもCGameも見えません。
画像

Re:無題

Posted: 2010年6月26日(土) 16:12
by Justy
>全ての値をprivateにして
>とかにすればいいですかね……?
 はい、そんな感じで。
 強いて言うならメソッドに constをつけるくらいですね。



 さて、本題とは離れてしまったこちらの件ですが。
 良い感じにぽこさんが補足して下さってますね。ありがとうございます。

>と言う風にするということでしょうか
 そんな感じです。
 
 要するに末端のクラス……プレイヤーやメニューなどのクラスはパッドの情報を直接参照しないで、
パッドの情報を解釈し役割に応じた適切な名前をつけたものから参照するようにする、ということです。

 メリットとしては、中間を挟むことで入力イベントを抽象化できます。
 これによって、例えば RPGの調べるコマンドをAからBに変えたいときや、AとXを調べるに割り当てたいとき
後からキーボードやタッチパネルの入力に対応したい、といった要望があったときも
基本的にこのクラスの修正だけで済み、末端のプレイヤーなどのクラスでは
「調べるトリガーが発生したから調べるの処理を行った」という形になり、その調べるトリガーが
どういうものだったのかを気にしなくていい、ということになります。

 わふーさんが先に書かれたジャンプのコードを例にしますと、
[color=#d0d0ff" face="monospace]
if((Aボタンがジャンプの時 &&Aが押された) || (Bボタンがジャンプの時 && Bが押された)
|| 画面の特定のところがクリックされた|| 画面の特定のところがタッチされた || キーボードスペースキーが押された)
「ジャンプ」の処理;
[/color]

こう書くよりかは
[color=#d0d0ff" face="monospace]
if ( ジャンプトリガーが発生した ) 「ジャンプ」の処理;
[/color]
とした方がコードが意味もわかりやすいですし、すっきりとしますよね。

 勿論実際のところ、それをインスタンスとしてどこかのクラスに持たせるか、
これもまたグローバル的なクラス・関数などにするかは検討しなければならないところではありますが。


>「pad」の部分はどうなるのか
 グローバル的な扱いにした上で、ということなので、そのままでいいと思いますけど。


>このクラスのコンストラクタにどのパッドを検知対象にするのかという情報を得て対応しております
 なるほど。
 だとすると、このゲームではパッドを1つしか使用しないのかもしれませんが、
ひょっとすると複数のパッドを束ねて管理できるクラスを作って、そちらをシングルトンなどの
グローバル的な扱いにした方がいいのかもしれませんね。


>あまり省略しない方が良いのかもしれませんね
 多すぎるのも問題ですが、情報が不足しすぎても適切な回答できなくなるので、
さじ加減が難しいところではありますがある程度提示してもらった方が回答側としては助かります。

Re:無題

Posted: 2010年6月27日(日) 04:36
by わふー
こんばんは。
出かけてました。

大体把握しました。
ありがとうございます。
ちょっと思うところだけツッコミます~。


>適切なレイヤーとは何か?というのは、各自で違うと思いますが、
>私はクラス名、関数名、変数名が同じ抽象レベルの言葉で表現されているかで
>レイヤーの分け方が適切かどうか判断しています。
>各レイヤーには役割があり、より上位のレイヤーのための抽象概念を用意する必要があるからです。

レイヤー分けの判断ってのは、すごい難しそうですね。
多人数でやるなら、この辺はすり合わせをしないといけないのかな……。

また、シングルトンでいけば、
・パターン
人間→(Aを押す)→CPad(Aが押されたよ)→CRPG(決定だってさ)→以下略
となるのですかね。
CGameは飛ばしちゃう感じで。


> 要するに末端のクラス…… プレイヤーやメニューなどのクラスはパッドの情報を直接参照しないで、
>パッドの情報を解釈し役割に応じた適切な名前をつけたものから参照するようにする、ということです。

とのことですが、

> if ( ジャンプトリガーが発生した ) 「ジャンプ」の処理;

とある中の「bool [ジャンプトリガーが発生した]()関数」の中ではパッドの情報が必要になるのでは……。
CRPGがCPadを参照していればCRPGから取って来れますが、CRPGの中がトリガー関数で溢れることになりそうですね……。

トリガーまとめクラスとか別に作るのかな? うーん?

というか、そのためにこのクラスはCRPGへの参照を必要としてしまうのですね。
CPadのためだけに完全に独立したクラスが作れないというのは、それはそれで問題になる気もするんですが、そんなもんではないんでしょうかね。



> だとすると、このゲームではパッドを1つしか使用しないのかもしれませんが、
>ひょっとすると複数のパッドを束ねて管理できるクラスを作って、そちらをシングルトンなどのグローバル的な扱いにした方がいいのかもしれませんね。

現状では、パッドの数だけクラスを作ることができたので、問題になりませんでした。
シングルトンにするならば、そういう対応にしなければなりませんね。





ご返信ありがとうございます。

Re:無題

Posted: 2010年6月27日(日) 15:09
by Justy
>とある中の「bool [ジャンプトリガーが発生した]()関数」の中ではパッドの情報が必要になるのでは
 なりますね。


>CRPGの中がトリガー関数で溢れることになりそうですね……。
>トリガーまとめクラスとか別に作るのかな?
 (クラスになるかどうかは別として)既存のものとは別にまとめることを想定しています。
 なので、CRPGの中がそれで溢れることはありません。


>CPadのためだけに完全に独立したクラスが作れないというのは、それはそれで問題になる気もするんですが
 CPadのインスタンスがどこにあったとしても、CPadの中で CGameや CRPGを参照する必要はないので、
CPadは独立していますよ?
 独立できない可能性があるのは CPadを参照してジャンプだとか決定の入力イベントが発生したことを
検出するクラス・モジュールの方じゃないでしょうか。
 こちらの方は中身にも依りますが CGameとか CRPGなどを参照する必要が出てくるかもしれません。


>パッドの数だけクラスを作ることができたので、問題になりませんでした
 なるほど。

Re:無題

Posted: 2010年6月27日(日) 20:29
by わふー
>>CRPGの中がトリガー関数で溢れることになりそうですね……。
>>トリガーまとめクラスとか別に作るのかな?
> (クラスになるかどうかは別として)既存のものとは別にまとめることを想定しています。
> なので、CRPGの中がそれで溢れることはありません。

「既存のものとは別にまとめる」が分かりません……。
クラスにすることも、この「まとめる」の1手法という認識でいいのですかね……?
そして、まだ他に色々と手法があると。


> CPadのインスタンスがどこにあったとしても、CPadの中で CGameや CRPGを参照する必要はないので、CPadは独立していますよ?

独立して欲しいのはCPadではなく、CRPGSubやCActionといったクラスですねー。
確かにCPadは簡単に独立してくれそうですしね。

Re:無題

Posted: 2010年6月27日(日) 21:42
by dic
CRPG が CPad を持っている CRPG has CPad の関係か
CRPG と CPad 各自独立しているか という関係かでしょうかね

Re:無題

Posted: 2010年6月27日(日) 23:34
by Poco
> 「既存のものとは別にまとめる」が分かりません……。
> クラスにすることも、この「まとめる」の1手法という認識でいいのですかね……?

そういう認識でいいと思いますよ。

> そして、まだ他に色々と手法があると。

関数+変数にしてもいいと思いますよ。

> 独立して欲しいのはCPadではなく、CRPGSubやCActionといったクラスですねー。
> 確かにCPadは簡単に独立してくれそうですしね。

正直、独立という概念が理解できません。
あるレイヤーが直下のレイヤーのクラスorインタフェースを利用するのは当然ですよね?
これが下のさらに下のインタフェースを利用するとおかしい気がします。

デバイス層にあるCPadが提供するpublicなメンバ関数はゲームエンジン層にあるCRPG(CGame)が使用します。
ゲームエンジン層のCRPGが提供するpublicかprotectedなメンバ関数は個別ゲームの層にあるCRPGSubが
使用/オーバライドします。
ここで重要なのは、CRPGSubがデバイス層のクラスやインタフェースを使わないことです。

Re:無題

Posted: 2010年6月28日(月) 00:39
by わふー
確かに、独立という言葉は不適切かもしれませんね。
私の認識としては、例えばMathクラスのようなものです。
「Math.abs(-5)」と言う風なやつですね。

このMathクラスを友人に作ってもらうという場面を考えます。
友人に知らせなくてはならない情報は、単に「Math.abs([int])という形で使えるような、引数はint1つでその絶対値を返すようなクラス作って」というような言い方で済むはずです。
それをこちらがどう使うか、どういうものを作っているか、は知らせなくても済むはずです。
こういうようなものが理想です。


----------

と、ここまで書いたところでやっと気づいたのですが、CRPGSubとはCRPGを継承したクラスだったのですね……。
CRPGが(class CRPG{ CRPGSub sub; };のように)持つクラスなのかと勘違いしておりました。
RPGでしたら、CCharaですとか、CEnemyとかですかね。

確かにぽこさんの仰る実装方法ならば、私もそのやり方にも納得がいくかもしれません。
(まだその考え方が浸透しておりませんので、改めて疑問が出てしまうかもしれませんが……。)

Re:無題

Posted: 2010年6月28日(月) 01:03
by Poco
> 確かに、独立という言葉は不適切かもしれませんね。
(省略)
> こういうようなものが理想です。

凝集(cohesion)性を指しているんですかね。

> と、ここまで書いたところでやっと気づいたのですが、CRPGSubとはCRPGを継承したクラスだったのですね……。
> CRPGが(class CRPG{ CRPGSub sub; };のように)持つクラスなのかと勘違いしておりました。
> RPGでしたら、CCharaですとか、CEnemyとかですかね。

私の中ではCRPGと同格(同レイヤー)であるCActionが、CRPGSubと同格で扱われていたので、
なんとなく認識にズレがあるなとは思っていました。
まあ、言いたいことは伝わったみたいなので結果オーライってことで。


> 確かにぽこさんの仰る実装方法ならば、私もそのやり方にも納得がいくかもしれません。
> (まだその考え方が浸透しておりませんので、改めて疑問が出てしまうかもしれませんが……。)

私はゲーム屋さんではないので、このやり方がゲームにフィットするかどうかは分かりません。
#んが多分、間違っていないと思いますよ、多分。

Re:無題

Posted: 2010年6月28日(月) 22:56
by わふー
>凝集(cohesion)性を指しているんですかね。

今ぐぐって少し調べた程度の知識ですが、恐らくそういうノリだと思います。



色々とありがとうございました。
解決踏みますね。