ゲームのオブジェクトの設計について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
ランダマイズ
記事: 9
登録日時: 9年前
住所: 東京

ゲームのオブジェクトの設計について

#1

投稿記事 by ランダマイズ » 9年前

C++とDxライブラリを使用して簡単なゲームエンジンのようなものを作りたいと考えたのですが、ゲームオブジェクトの設計でモヤモヤしているので質問させてください。

現在、ゲームのオブジェクトの基本的なイベントや処理をまとめ"Node"クラスを作っておき、それを継承して"Player"クラスや"Enemy"クラスなどを作成していこうかと考えているのですが、
"Node"クラスにはないパラメーターを継承先で作ってしまった場合どのように他のNodeを継承したクラス(EnemyクラスやPlayerクラスなど)からそれらをコントロールすればよいのでしょうか?
あるいは、全く違った考え方を検討するべきなのでしょうか。

OSはwin8.1、開発環境はVisualC++2010Express、C++歴は一年ほどです。

コード:


#define GET_LENGTH(array) //配列の要素数をかぞえる処理

/*
  他のゲームでも使いまわしたい
  なるべく隠蔽したい
*/
class Node{
   /*
   座標などの基本的なパラメーター
   (略)
   */

   /*
   毎ループ時の処理やクリックされたときの処理
   */
public : virtual void loop( Node *nodes[] );

   /*
   あたり判定などの基本的な処理
   */
public : virtual bool checkHit( Node *node );
};


/*
  Nodeクラスを継承してゲームに必要なオブジェクトを作っていく
*/
class Enemy : public Node{
   private: int hp; //新しく追加したパラメーター
   public : virtual void loop(Node *nodes[]){
      //移動したりする
   };

};

class Shot : public Node{
   public : virtual void loop(Node *nodes[]){
      for( int i=0; i<GET_LENGTH(nodes); i++){
	     if( nodes[i]->checkHit( this ) ){
		    /*
			  HPを減らす処理ができない!!
			*/
		 }
	  }
   };

};

よろしくお願いします。

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前
住所: 北海道札幌市
連絡を取る:

Re: ゲームのオブジェクトの設計について

#2

投稿記事 by Dixq (管理人) » 9年前

ゲームプログラムの設計については
http://dixq.net/g/
の「ゲームプログラミング設計」を参考ください。

私は全てのクラスの継承元を以下のように定義しています。

コード:

#pragma once
class Task
{
public:
	Task() {}
	virtual ~Task() {}
	virtual bool initialize() { return true; }
	virtual void finalize() {}
	virtual bool update() { return true; }
	virtual void draw() {}
};
全てのクラスはこれを継承する形で実装します。
例えばEnemyもShotもこのTaskを継承するわけですが、全てTaskを継承しているので、
マネージャークラスはTaskポインタのリストを持ってすべてに

コード:

for(auto p : mList){
    p->update();
}
するだけで更新処理ができます。
また描画処理も

コード:

for(auto p : mList){
    p->draw();
}
だけで実現できます。
重なりの順番を指定したければ要素に「int mZ;」を持たせてZ軸ソートするようにすればOKです。

*node[]の引数は何に使うのか分かりませんが、全てのクラスのポインタだとすると、
全てのモジュールにすべてのモジュールを操作できるようなインスタンスの渡し方は危険なのでやめた方がいいと思います。
後、配列は可変でなく使いにくいのでstd::vectorかstd::listを使った方が良いでしょう。

アバター
Ketty
記事: 103
登録日時: 11年前

Re: ゲームのオブジェクトの設計について

#3

投稿記事 by Ketty » 9年前

こんにちは(^v^)

記載されているコードから推測すると、
どうやらShotが、当たり判定をして、HPを減らす機能を持っているようにうかがえます。

私なら、こういう考え方をして設計します。

・ショットがHPを減らすのではなく、
・ショットとキャラクターの、当たり判定係りがHPを減らす。
・もしくは、当たり判定係りをさらに管理するクラスがHPを減らす。

例)
shotクラス・・・座標を持つ
キャラクタークラス・・・座標を持つ
当たり判定クラス・・・shotとキャラクターの座標から当たり・はずれをチェックする
さらに管理するクラス・・・当たり判定係りクラスが「当たり」と言ったらキャラクターのHPを減らす

ランダマイズ
記事: 9
登録日時: 9年前
住所: 東京

Re: ゲームのオブジェクトの設計について

#4

投稿記事 by ランダマイズ » 9年前

Dixq(管理人)様
返信ありがとうございます!
Dixq (管理人) さんが書きました:]後、配列は可変でなく使いにくいのでstd::vectorかstd::listを使った方が良いでしょう。
std::vectorかstd::listを使うと配列の長さを気にしなくても済むのですね。
Dixq (管理人) さんが書きました:全てのモジュールにすべてのモジュールを操作できるようなインスタンスの渡し方は危険なのでやめた方がいいと思います。
考えてみれば敵も味方も弾もなんでも参照できてしまうのはまずいですよねぇ...。


ところで、たとえばTaskクラスに座標情報を持たせたい場合は

コード:

#pragma once
class Task
{
public:
    Task() {}
    virtual ~Task() {}
    virtual bool initialize() { return true; }
    virtual void finalize() {}
    virtual bool update() { return true; }
    virtual void draw() {}

private:
    int x,
        y,
        width,
        height;

};
のように随時必要な情報を追加していくということでしょうか。


Ketty様
こんにちは!
返信ありがとうございます!

おっしゃる通り、弾がモノに危害を加えるという考え方で実装しようと考えていたのでこのようなコードになっています。
Ketty さんが書きました: ・ショットがHPを減らすのではなく、
・ショットとキャラクターの、当たり判定係りがHPを減らす。
・もしくは、当たり判定係りをさらに管理するクラスがHPを減らす。
つまり、ガチガチにモノとしての振る舞いをクラスにするのではなく決まり切った処理はクラスを分けてしまう
ということでよいでしょうか。

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前
住所: 北海道札幌市
連絡を取る:

Re: ゲームのオブジェクトの設計について

#5

投稿記事 by Dixq (管理人) » 9年前

いえ、Taskはこのまま変更しません。
必要に応じて継承元の変数を全て追加していくような設計をすると大規模な設計をする時に
継承元の変数やメソッドはものすごい膨大な数になってしまいます。
クラス図を描くとこんなイメージです。
class.png
class.png (28.85 KiB) 閲覧数: 5624 回
PlayerやEnemyはCharactorから継承してCharactorに座標情報を持たせればよいです。

で、当たり判定の計算方法ですが、例えばPlayerもEnemyも弾リストを持っているはずです。
お互いがお互いの弾リスト情報を取得できるように
BulletsGettableインターフェイスクラスを作り、それをそれぞれに実装させます。
そしてお互いの弾情報を取得できるように実装し、
それぞれにGettableインターフェイスポインタを持ち、
参照できるようにすると良いかと思います。

ランダマイズ
記事: 9
登録日時: 9年前
住所: 東京

Re: ゲームのオブジェクトの設計について

#6

投稿記事 by ランダマイズ » 9年前

そういった継承の使い方もあるんですね!
http://dixq.net/g/の"メニュー画面の作り方(C++編)"を読んで理解できました。
ありがとうございます。
Dixq (管理人) さんが書きました:それぞれにGettableインターフェイスポインタを持ち、
参照できるようにすると良いかと思います。
この場合敵が複数体存在する場合はPlayerクラスは

コード:

- EnemyBullets : std::list< std::shared_ptr<BulletsGettable> >
といったかたちでインターフェイスのポインタを持つことになるのでしょうか。

何度も質問してすいません...。

ランダマイズ
記事: 9
登録日時: 9年前
住所: 東京

Re: ゲームのオブジェクトの設計について

#7

投稿記事 by ランダマイズ » 9年前

訂正
std::list< std::shared_ptr<BulletsGettable> >のBulletsGettableはBulletsGetTableです。

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前
住所: 北海道札幌市
連絡を取る:

Re: ゲームのオブジェクトの設計について

#8

投稿記事 by Dixq (管理人) » 9年前

館で紹介しているような考え方を「ポリモーフィズム」と呼びます。
C++はこれを使ってこそ意味がある言語だと思うのでお勧めです。
なお、
loop();
という関数を各モジュールに持たせるのは良くないと思います。
一部分でループするのも良くないですし、ループしないんならループという名前は変です。
館でも紹介しているように、全ての描画する要素は
update();

draw();
に分類します。
update()処理で、描画する座標計算や当たり判定処理等を全て行います。
draw()の中では計算された座標を描画するだけです。
何故こんな設計をするかと言うと、
処理落ちした時に描画を間引くとき、
update()だけ呼んでやり、draw()を呼ばないという方法が取れるからです。
ゲーム処理にかかる時間のほとんどはdrawです。
よって処理落ちした時等はdrawを間引いてやることで端末依存しない動作が実現できます。
また、drawの中にrand関数を入れるのもご法度です。
何故かと言うと、draw関数を間引いて呼ばなかったとき、次にupdate内でコールしたrandの値が変わってしまうからです。
リプレイデータ等はキーの入力状態のみを保存し、
乱数の出方などをプレイした時と同じようにシミュレートして再現する方法が一般的ですが、
rand関数の呼び方が変わると値が変わるのでリプレイデータが再現できなくなってしまいます。
なので、おぜん立ては全てupdateでしてやり、drawはupdateで用意された座標にただ描画してやるだけ、という設計にします。

> この場合敵が複数体存在する場合はPlayerクラスは
> EnemyBullets : std::list< std::shared_ptr<BulletsGettable> >
> といったかたちでインターフェイスのポインタを持つことになるのでしょうか。

んーと、それをすると大変なので、プレイヤーが多くの敵の弾情報を取得するんじゃなくて
敵がプレイヤーの座標情報を取得するようにしたらどうですか?
そうすればGettableインターフェイスは一つで済みます。
座標情報を取得するインターフェイスクラス「PositionGettable 」を定義し
敵クラスであるEnemyクラスののメンバ変数に

private:
PositionGettable mPlayerPositonGettable;

のように定義して、コンストラクタ等で

Enemy::Enemy(PositionGettable* gettable){
  mPlayerPositionGettable = gettable;
}

こんな風に格納してあげればいいと思います。

コード:

class Pos{
public:
    float x,y;
};
とでもしておき、

コード:

void Enemy::updateHit(){
    Pos pos = mPlayerPositionGettable->get();
    //pos.xとpos.yで当たり判定計算
}
こんな感じでどうですか?
実際には当たり判定の計算には自機の当たり判定範囲やらなんやら他にも情報いると思いますが適宜追加してください。

ランダマイズ
記事: 9
登録日時: 9年前
住所: 東京

Re: ゲームのオブジェクトの設計について

#9

投稿記事 by ランダマイズ » 9年前

Dixq (管理人) さんが書きました: 一部分でループするのも良くないですし、ループしないんならループという名前は変です。
館でも紹介しているように、全ての描画する要素は
update();

draw();
に分類します。
loopに関してはネーミングセンスがとことん悪かったです。
更新処理と描画処理は分けてあります。描画処理は決まりきったことしかしない予定だったので書いていませんでした、すいません...。
一応 Draw() の代わりに paint() が実装されています。
Dixq (管理人) さんが書きました: んーと、それをすると大変なので、プレイヤーが多くの敵の弾情報を取得するんじゃなくて
敵がプレイヤーの座標情報を取得するようにしたらどうですか?
確かにプレイヤーは一人ですもんね。こっちのほうが楽に決まってます。
なんで気づかなかったんだorz

結局のところ、TaskクラスをもとにしてEnemyクラスやPlayerクラスなどを作っていき
あたり判定などのクラス間での情報交換が必要な時はインターフェイスを使って行こうと思います。
と、思ったら日記にそのままのクラス構造図を書いていただいて本当にありがとうございます!
話題がドンピシャで1秒くらい思考停止してしまいました。

ふんわりとした質問に付き合っていただきありがとうございました!
とりあえず疑問点はキーワード『インターフェース』で解決しました。
もっと勉強して良いコードを書けるように頑張りたいと思います。

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前
住所: 北海道札幌市
連絡を取る:

Re: ゲームのオブジェクトの設計について

#10

投稿記事 by Dixq (管理人) » 9年前

解決されたようで何よりです。
また、日記の方新しく更新して、当たり判定を当たった後、
Playerクラスにあたったことを通知する方法について書きました。
http://dixq.net/forum/blog.php?u=53&b=5960

四聖龍神録2公式Twitterアカウントで四聖龍神録2の設計方法についてツイートしています。
https://twitter.com/ryujinroku_2

我流ですが、よければ参考にどうぞ。


ランダマイズさんはC++で実装できる力があるのに、それを活かすノウハウをまだご存じないとお見受けします。
ここでお勧めなのが「EffectiveC++」という本です。

ある程度知識がある人がもう一段解上にステップアップするための中級者用の本ですが
これを読むだけで一気にステップアップできます。
細かい部分はにごして覚えていた部分もこれを見ればすっきりしますし、
C++の使い方がいっきにステップアップすることでしょう。
この本は昔からC++erのバイブル的なポジションにあり、その時代ごとにリメイクされてきましたが、
それでも一番新しい第三版は2006年著です。
言語は日々進化しているので、お勧めしている機能が古かったりしますが、
そこはEffectiveC++を一通り見た後で「C++11」等で検索してC++の新しい機能を学習してください。
(例えば本書ではBoostをお勧めしていますが、C++11にはかなりその機能がC++標準で入っているので近年その存在を潜めています)

ランダマイズ
記事: 9
登録日時: 9年前
住所: 東京

Re: ゲームのオブジェクトの設計について

#11

投稿記事 by ランダマイズ » 9年前

Dixq (管理人) さんが書きました: また、日記の方新しく更新して、当たり判定を当たった後、
Playerクラスにあたったことを通知する方法について書きました。
おお、やっぱりインターフェイスを使うことになるんですね。
わかりやすかったです。

EffectiveC++ですか、いい栄養になりそうです。
いい値段するようですが何とか手に入れて読みたいと思います。

四聖龍神録2楽しみにしています!

いろいろとありがとうございました。

閉鎖

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