2DRPGのマップ関係のクラス設計を見ていただきたいです

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

2DRPGのマップ関係のクラス設計を見ていただきたいです

#1

投稿記事 by moba » 9年前

お世話になっています。VC++(2010Edition)/DxLibでゲームを作りたい初心者です。
前にこちらで質問させていただき、一度組んだコードを指導していただきました。
今回は、見よう見まねで自分で組んでみました。オブジェクト指向を目指しています。
この設計は良さそうに見えるかという点と、組んでいる間に出てきた疑問点をお伺いしたいです。
一部分だけへの回答でも大丈夫です。よろしくお願いします。

3/21 10:00前:図を書きなおして差し替えました
3/21 21:45前:TileIndexerを忘れていたのでクラス図を差し替えました 何度もごめんなさい

■シーケンス図

本「ゲームプログラマになる前に覚えておきたい技術」で出てきたやり方を真似しました。
今はこの部分をそこまで重要視していませんが、一応貼っておきます。

画像

■クラス図

クラス図だけでオブジェクト指向になっているかどうか分かるものなのでしょうか。
依存関係にある場合は、オブジェクトの生成時に依存先のオブジェクトの参照を渡しています。
ActorManager以下はまだまだ書いている途中です。
ここまででどうでしょうか?! 結構手応えがあるのですが!!

画像

■出てきた疑問

1.列挙体 EPoint をグローバル領域に公開している: int型の引数を指定するのに楽なため
 これは悪手ですか。

コード:

enum EPoint{
	LEFT_UP, // etc.
}
class A{
	void ( int, int, int*, int*, int ); // 5つ目はEPoint.LEFT_UPのようにして指定する
};
2.struct Cellをグローバル領域に公開している

コード:

struct Cell
{
	int layer[3]; // 【警告】マジックナンバ タイル番号を格納
	bool isPassable; // isPassableをタイルからセットする関数が必要
};
また、セル情報を得る側は、セルの参照をもらって、中身を自分で見ています。
これは悪手でしょうか。

コード:

// MapDrawer.cpp
Cell &cell = mMapData.GetCell( xi, yi );
for( int i = 0; i < allLayerNum; i++ ){
		int handle = mTile.GetTileGraph( cell.layer[i] );
		DrawGraph( drawX, drawY, handle, TRUE);
}
3.GameContextかGameParentか
ISLeさんのおっしゃるGameContextがどういうものか僕は分かっていませんが、
離れているオブジェクト同士を結び付ける? ためのものだと思います。
僕は、オブジェクトを持っているGameParentから、直接の参照をもらうようにしています。
例えばPlayクラスでは、

// GameParent *parentを引数でもらってある
e.g. parent->GetMapScaleManager.GetCellSize( 引数 );

これも悪手なのでしょうか。

4.シングルトンはコピペするもの?
僕は、DxLibの館のシングルトンのコードをコピーしては、
コンストラクタや型の名前をシングルトンにするクラスの名前に書き換えています。
例えば継承するだけで使えるような、スマートで速いシングルトンはあるものでしょうか。

5.シングルトンでグローバル変数
は悪手なのでしょうか。
僕がシングルトンにしたのは以下のオブジェクトです。
・Input // キーボード、ゲームパッド、マウス
・GameWindow // ウィンドウサイズ

6.Get系とSet系のインターフェースを分けたくなった
他の場合ではSet系の関数は見えない方がいい、という場面があると思います。
例としては悪いのですが

コード:

// シングルトン
class GameWindow
{
public:
	static GameWindow *Inst(){
		static GameWindow inst;
		return &inst;
	};
	void GetWindowSize( int *width, int *height );
	void SetWindowSize( int width, int height ); // 【警告】これが公開されても良い?
private:
	int mWindowWidth, mWindowHeight;

	// 封印
	GameWindow(){} // default
	GameWindow( const GameWindow& r ){} // copy
	GameWindow& operator=( const GameWindow& r ){} // 代入演算子
};
GameWindow::SetWindowSize( int, int )を使うのは、
今のところゲーム起動時だけです。
他の場合はGet系の関数しか使えない方がいいと思います。
何が良い方法だと思われますか。

シングルトンの例は一度忘れて…
僕が思いついた対策方法は、SetterとGetterでインターフェースを分けて、
実装クラスはSetterとGetterクラスを両方継承するというものです。
Getterのインターフェースのポインタを引数にもらえば、
インターフェースを制限できると思いました。
この方法は悪手でしょうか。

よろしくお願いいたします。

追記:
7.参照ではNULLを返せない?
返すべきものが無かった場合はどうしたらいいのでしょうか。
NULLでも返したいのならば、ポインタを返すべきなのでしょうか。
最後に編集したユーザー moba on 2016年3月21日(月) 20:36 [ 編集 2 回目 ]

とっち
記事: 56
登録日時: 13年前
住所: 岡山

Re: 2DRPGのマップ関係のクラス設計を見ていただきたいです

#2

投稿記事 by とっち » 9年前

私も専門家でないのでいくつかの質問だけお応えしたいと思います。
(後の疑問については他の方お願いします・・・)

では疑問点の1から・・・
[1.列挙体 EPoint をグローバル領域に公開している:]

EPoint次第ですかね
EPointがどこでも使うようなグローバルなものならグローバル領域に公開するべきだと思います。
しかしEPointがclass A内でしか使わないのであれば以下のようなコードはどうでしょうか

コード:

class A{
public:
  enum EPoint{
    ...
  };
  void f();
};
疑問点2
[struct Cellをグローバル領域に公開している]
これもCellがどこで使われるのかによりますねー

コード:

// MapDrawer.cpp
Cell &cell = mMapData.GetCell( xi, yi );
for( int i = 0; i < allLayerNum; i++ ){
  int handle = mTile.GetTileGraph( cell.layer[i] );
  DrawGraph( drawX, drawY, handle, TRUE);
}
このコードですがcellは参照である必要があるのでしょうか?

コード:

Cell cell=mMapData.GetCell(xi,yi);
で十分な気がします。

疑問点6
[Get系とSet系のインターフェースを分けたくなった]
これに関してはmobaさんの方法がいいと私は思います。
私なら以下のように書きますね

コード:

class A{
public:
  getter()=0;
};

class B:public A{
public:
  getter(){}
  setter(){}
};
7.参照ではNULLを返せない?
返せません
NULLを返したければポインタを返すか、あるいは返すべき値がなかった時に返す値を独自に定義するかです。

以上、参考になればと思います。

moba
記事: 82
登録日時: 9年前

Re: 2DRPGのマップ関係のクラス設計を見ていただきたいです

#3

投稿記事 by moba » 9年前

ご回答ありがとうございます。助かります!!

[1. 列挙体 EPoint をグローバル領域に公開している:]

 引数でしか使いません。
 グローバル領域に公開すれば、引数に書くのは" LEFT_UP "で済みます。
 一方、MapScaleManager内にEPointを置くと、
 " MapScaleManager::LEFT_UP" と書かなければなりません。
 ここは妥協せざるを得ないところなのでしょうか。

[2. struct Cellをグローバル領域に公開している]

 セルの情報を得るために、Cellの参照を受け取った側が
 struct Cellの内容に直接アクセスしています。
 ですのでCellは結果的に色々な場所で使われていますが、
 中身を直接見てしまうのは悪手ではないかと不安です。
 隠した方がいいものでしょうか。
 隠そうとしても、インターフェースはCellの中身が変われば変わりそうなので、
 中身を公開することとほぼ変わらないように思えます。

 また、Cell cellと書くのとCell &cellと書くのはどちらが適切でしょうか。
 負荷軽減のためにCellは参照で返していますが、
 受け取る側は実体を受け取るかのようにコードを書けるのですね。
 ただ、Cell& cellと書かなければ、参照で受け取っている(処理負荷の対策をしている)ことを
 忘れてしまうのではないでしょうか。

[6.Get系とSet系のインターフェースを分けたくなった]

 Get系は危険ではないからそのように分けられたのですね。
 悪くないことが分かってよかったです!

[7.参照ではNULLを返せない?]

 ありがとうございます。
 ポインタでNULLを返すか、
 参照で返すようにして、返すべきものが無ければ"即死"させようかと思います。

とっち
記事: 56
登録日時: 13年前
住所: 岡山

Re: 2DRPGのマップ関係のクラス設計を見ていただきたいです

#4

投稿記事 by とっち » 9年前

moba さんが書きました: [1. 列挙体 EPoint をグローバル領域に公開している:]

 引数でしか使いません。
 グローバル領域に公開すれば、引数に書くのは" LEFT_UP "で済みます。
 一方、MapScaleManager内にEPointを置くと、
 " MapScaleManager::LEFT_UP" と書かなければなりません。
 ここは妥協せざるを得ないところなのでしょうか。
LEFT_UPって使うのはMapScaleManagerだけでも普遍的な事実ではありませんか?
例えば、LEFT_UPがMapScaleManagerと"画面クラス"で違うことを意味するならclass内に置き、" MapScaleManager::LEFT_UP" と書くべきでしょう。
しかし、仮にmobaさんのプログラムではMapScaleManagerでしか使わないとしても"左上"という普遍的な事実ならグローバルに公開するべきでしょう。
moba さんが書きました: [2. struct Cellをグローバル領域に公開している]

 セルの情報を得るために、Cellの参照を受け取った側が
 struct Cellの内容に直接アクセスしています。
 ですのでCellは結果的に色々な場所で使われていますが、
 中身を直接見てしまうのは悪手ではないかと不安です。
 隠した方がいいものでしょうか。
 隠そうとしても、インターフェースはCellの中身が変われば変わりそうなので、
 中身を公開することとほぼ変わらないように思えます。

 また、Cell cellと書くのとCell &cellと書くのはどちらが適切でしょうか。
 負荷軽減のためにCellは参照で返していますが、
 受け取る側は実体を受け取るかのようにコードを書けるのですね。
 ただ、Cell& cellと書かなければ、参照で受け取っている(処理負荷の対策をしている)ことを
 忘れてしまうのではないでしょうか。
負荷軽減とありますが、いったいどれくらい軽くなるというのでしょうか?
テストコードを書いてみるとわかりますが、変わりません。
ぶっちゃけこういうことを考えるのは超上級者がめちゃくちゃ特殊な状況において、最終手段として考えるものです。
普通のプログラマは負荷軽減よりも見やすいコードを意識するべきです。

PS.
私も研究として最適化実装をしたことがありますが、数カ月かけて1.3倍程度(数msレベル)しかはやくなりませんでした。
こういう研究領域の実装でしか現代においては考察すべきではありません。


以上、参考になればと思います。

moba
記事: 82
登録日時: 9年前

Re: 2DRPGのマップ関係のクラス設計を見ていただきたいです

#5

投稿記事 by moba » 9年前

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

[1. 列挙体 EPoint をグローバル領域に公開している:]
LEFT_UPって使うのはMapScaleManagerだけでも普遍的な事実ではありませんか?
例えば、LEFT_UPがMapScaleManagerと"画面クラス"で違うことを意味するならclass内に置き、" MapScaleManager::LEFT_UP" と書くべきでしょう。
しかし、仮にmobaさんのプログラムではMapScaleManagerでしか使わないとしても"左上"という普遍的な事実ならグローバルに公開するべきでしょう。
グローバル領域におくとなると、定義は別のファイルに分ける感じですよね。
(二重定義を防ぐため)

EPointと、cppファイルのx, y成分の配列を対応させているので、微妙かとも思いましたが、
EPointをグローバル領域におこうと思います。

[2. struct Cellをグローバル領域に公開している]
負荷軽減とありますが、いったいどれくらい軽くなるというのでしょうか?
テストコードを書いてみるとわかりますが、変わりません。
ぶっちゃけこういうことを考えるのは超上級者がめちゃくちゃ特殊な状況において、最終手段として考えるものです。
普通のプログラマは負荷軽減よりも見やすいコードを意識するべきです。
PS.
私も研究として最適化実装をしたことがありますが、数カ月かけて1.3倍程度(数msレベル)しかはやくなりませんでした。
こういう研究領域の実装でしか現代においては考察すべきではありません。
1)struct Cellをグローバル領域に置くべきかどうか
Cellは、今回のゲーム開発においては「マップのセル情報」としては一般的ですが、
struct Cellの定義をグローバル領域に置くべきでしょうか。
1から10まで聞いてすみません。

2)参照で返すかコピーで返すか
参照にしなければ、マップを描画するためにCellのコピーが毎フレーム350回以上作られます。
解像度を上げればもっと増えます。
これがどの程度の負担なのか僕には分かりませんし、
メモリのキャッシュなど僕の目に入っていないこともあるでしょうから、
実際に処理時間を計測してから考えたいです。

また、Cell &cellと書くのが読みづらいものか僕には分かりませんでした。(繋がりがないことだったのかもしれませんが…)
ただ、参照で返してしまうと、元のデータを書き替えてしまう危険性があると気付きました。

3)Cellの中身に直接アクセスするべきか
Cellを読み書きする側は、現在は

コード:

auto cell = GetCell( index );
int tileID = cell.layer[0]; // layer[]はtileIDの配列
のようにして、Cellの中身に直接アクセスしています。
僕としては書きやすくて万々歳に思えますが、
このやり方はどう評価されますか。

すみません、お願いします_(._.)_

閉鎖

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