[C++]タイルマップのクラス設計を見てほしいです

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

[C++]タイルマップのクラス設計を見てほしいです

#1

投稿記事 by moba » 8年前

お世話になります。VC++(2010Express)/DxLibでゲームを作りたい初心者です。
マップチップを配置するタイプのマップ、タイルマップを表示できるようにしました。
そのクラス設計? を見ていただきたいです。このような質問の仕方をしてもいいでしょうか。

シングルトンを使うメリットが分からなかったため、呼び出しやすい名前空間を使ってみました。
今のところ問題は出ていませんが、このような書き方をした例を見たことがないので、不安になっています。

ソースについて、自分で気付いたこととしては、

//とりあえず読み飛ばして大丈夫
・名前空間のMapDataという名前がややこしい。(データだけではなくて、機能も入っている)
・MapData::MapLayer この名前が不適切。(layerという名前のレイヤ構造体と、関数を持つ)
・MapData::MapLayer::layer[0] と書くのが面倒くさい
・MapData::EventDataに、キャラ画像の描画用の補正座標が入っているのは不適切?

他に質問させていただきたいこととしては、

・namespaceとsingletonはどちらを使うべきだと考えられるでしょうか
・このような設計? について学べる本で、お勧めはありますか

以下ソースです。よかったら「こう組んだ方が良い」などの指摘などをお願いしたいです。

//map.h
► スポイラーを表示
//map.cpp 一応こっちも入れました
► スポイラーを表示

アバター
usao
記事: 1887
登録日時: 11年前

Re: [C++]タイルマップのクラス設計を見てほしいです

#2

投稿記事 by usao » 8年前

>・namespaceとsingletonはどちらを使うべきだと考えられるでしょうか

namespace関係ありますか?

コード:

//[A.h]
int GetValue();
void SetValue( int val );

//[A.cpp]
static int TheValue = 0;
int GetValue(){ return TheValue; }
void SetValue( int val ){ TheValue = val; }
という形のコードを大量に書いているに過ぎないですよね.

名前がややこしいだとか不適切かいうのはご自身で好きな名前にすればいいのでは.
「どんな名前がいいでしょう? 思いつきません」 とかそういう話?

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#3

投稿記事 by moba » 8年前

> namespace関係ありますか?

オブジェクト指向の考え方で組みたいため、namespaceを使いました。
初めはMapDataをクラスにしていたのですが、namespaceの形に書きなおしました。
namespaceを使うことで、「MapDataというオブジェクト」としてまとまるのではないかと考えました。

> 名前がややこしいだとか不適切かいうのはご自身で好きな名前にすればいいのでは.
> 「どんな名前がいいでしょう? 思いつきません」 とかそういう話?

質問したかったのは名前よりも構造でした。ただ、今日まで適切な名前を思いついていないので、一応問題点として挙げました。

オブジェクト指向の考え方を分かっておらず、見る目もないため、組んだものを一度点検していただくことを通じて、考え方を身につけたいと思っています。
もし何か失礼があれば、すみません、気付いていないので教えてください。そっちも見る目がありません…

アバター
usao
記事: 1887
登録日時: 11年前

Re: [C++]タイルマップのクラス設計を見てほしいです

#4

投稿記事 by usao » 8年前

>初めはMapDataをクラスにしていたのですが、namespaceの形に書きなおしました。


今の状態は,暗黙的に(?)単一の"オブジェクト"が存在しているという感じなのかと思います.
私は専門家ではないのでその書き方が良いのか悪いのかわかりませんが,
これでは 単一のオブジェクトしか存在できない という不自由さがわざわざ存在しているように個人的に感じてしまいます.

MapDataがクラスであったとして「1個だけあればいいのであれば,インスタンスを1個だけ作ればいい」んじゃないかなぁ,と.
(2個目が必要になったら作れる)


なんというか
このプログラムの実行中に長期的に保持されるデータというのは単一です というのと シングルトンとかいう書き方をすることとは
全く別の概念なのではないかと思います.
(前者は作るものの「内容」の話だけれども,後者はプログラムの「書き方」に対する制限,というか)

アバター
usao
記事: 1887
登録日時: 11年前

Re: [C++]タイルマップのクラス設計を見てほしいです

#5

投稿記事 by usao » 8年前

それはそれとして,

extern MapBase *layer[3];

というのをヘッダに書いてますが,
これは class で言うところのpublicなメンバ変数的状態になっている気がします.

外部からこのポインタにダイレクトにアクセスしないのであれば
class MapBase という型自体,外部に公開しなくても良いのかも.

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#6

投稿記事 by ISLe » 8年前

namespaceは名前の衝突を避けるためのものなので、オブジェクト指向とは直接の関係はありません。
アクセス制御がなければカプセル化もないですからね。

ちなみに名前の衝突がない場所であればusingを使って記述を省略できます。

まとまりがなくなってきたからとりあえずnamespaceで囲ってみた、というふうにしか見えないです。
オブジェクト指向を目指すなら、インスタンスベースで考えましょう。

レイヤー数が3で固定になっているのは、必要に応じて書き換えて使おうということでしょうか。
そういう考え方をしているとクラスの設計はできないですよ。
クラス(に限らずライブラリ)はコードには二度と手を加えず応用できないとメリットがないですからね。

サンプルコードをコピペする感覚で使えると楽だと考えるのでしょうが、
サンプルコードというのは特定の状況向けに特化しているものなので、
それを繰り返すと応用の効かないコードが大量に残ることになるのですよ。


例えば、

矩形イメージを描画するための、Imageクラスを用意します。
Tilemapクラスは、Imageクラスを継承し、事前に設定した矩形の大きさでタイルマップを描画します。
Tilemapクラスは、インデックスに対応したタイルの描画を、Imageクラスのリストを使って行います。
タイルマップのタイルがさらにタイルマップになっているという万華鏡みたいなこともできます。

Tilemapクラスは、タイルを選択するためのインデックスをTileIndexerクラスを使って取得します。
タイルマップのデータには、描画用のタイル番号だけでなく、さまざまな情報が含まれる場合があり、
また、要素毎のデータサイズもまちまちである場合があるためです。

タイルマップの重ね合わせを事前にライブラリ化しておく必要は極めて低いでしょう。
TilemapクラスかImageクラスのリストに透過のオンオフ機能があれば便利かもしれないくらいで
常に透過ありで特に問題ありませんね。

さらに、
Imageクラスに委譲する形で、オフスクリーン描画させたあと、拡縮回転他各種加工を加えて描画するクラスを後付けすることもでき、
その場合、Imageクラスを継承したTilemapもとうぜんそのまま使えるわけです。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#7

投稿記事 by ISLe » 8年前

PCの恵まれた環境でしかプログラミングしたことのないひとのトンデモ発言は珍しくないことですが
シングルトンの解釈も同じところからきている気がします。

(特に組み込み系に多いですが)
ハードウェア的な外部リソースにアクセスするのに、副作用がある場合も少なくないです。
そんなとき、ハードウェアのウォームアップを待ってインスタンスを一回だけ生成するわけです。
そもそもシングルトンは中の人が選択するものであって、
利用する人が同じインスタンスが返ってくることを期待するものではないと思うんですよね。

クラスの設計がシングルトンかどうかで左右されることは一切あり得ないはずなんですけどね。

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#8

投稿記事 by moba » 8年前

返信が遅れてすみません。
お二人とも、質問文やコードを読んで、助言もくださってありがとうございます。

> usaoさん

> このプログラムの実行中に長期的に保持されるデータというのは単一です というのと シングルトンとかいう書き方をすることとは
> 全く別の概念なのではないかと思います.
> (前者は作るものの「内容」の話だけれども,後者はプログラムの「書き方」に対する制限,というか)ます

僕は自分のゲーム専用にMapDataを組んでいましたが、もっと抽象的に書くべきみたいですね。MapDataクラスを利用する側と、MapDataというクラスの定義を混同していました。
書き方の制限としてのシングルトン、いつか例を見てみたいです。僕がシングルトンについて全く分かっていないことが分かりました。

> extern MapBase *layer[3]; について

これは、今はマップの自動生成クラスから書きこむのに使っています。いい構造なのかは分かりませんが…
例:MapBase::Layer[0]->SetCell_chipID(x,y,chipID);//dungeon.cpp

ISLeさんへの返信は分けます

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#9

投稿記事 by moba » 8年前

>ISLeさん

> サンプルコードをコピペする感覚で使えると楽だと考えるのでしょうが、
> サンプルコードというのは特定の状況向けに特化しているものなので、
> それを繰り返すと応用の効かないコードが大量に残ることになるのですよ。

クラスの定義と、それを実体化して利用する側のコードを混同してMapDataを書いていました。
クラス自体は、抽象的に書いて、汎用的に利用できるようにするべきでした。

定義を書いたヘッダファイルと、クラスを実体化する部分を分けてみます。

○追加質問(トピックを分けるべき場面だったならすみません)
C++ではグローバル変数を使うべきではないと何度か耳にしました。このような形でグローバル変数を使うのも、C++ではNGなのでしょうか。他の場面でグローバル変数を使う例は思いつかないのですが。

コード:

//map.h
//マップレイヤ、タイルデータを持つクラス
class MapData;

//global.h
#include "map.h"
//このゲームでは同時に1つだけマップの情報を保持する
MapData currentMap;//グローバル変数 

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#10

投稿記事 by moba » 8年前

>ISLeさん

> 例えば~以下

クラスの設計の1つの例を、例えば~以下で示してくださったのでしょうか。
僕の実力では全体的に分からなかったので、もう少し詳しく教えていただけませんか。

> Tilemapクラスは、インデックスに対応したタイルの描画を、Imageクラスのリストを使って行います。
> タイルマップのタイルがさらにタイルマップになっているという万華鏡みたいなこともできます。

機能としては、例えばツクールのオートタイルを実現できるということでしょうか。
(※オートタイル:隣接したマスを見て、表示が変わるタイル。例えば、
11
11  というマップが
┏┓
┗┛ として表示される)
TileMapクラスはImageクラスを継承したのに、Imageクラスのリストを持っているというのが分かりませんでした。
また、Imageがどのように書かれているか想像できませんでした。

Image、TileIndexer、TileMapの構造を丸ごと示して欲しいということになります。
充分示したので自分で考えなさい、ということでしたらすみません。せめて、なんとかオブジェクト指向を学ぶ手段はないでしょうか。

> さらに、
> Imageクラスに委譲する形で、オフスクリーン描画させたあと、拡縮回転他各種加工を加えて描画するクラスを後付けすることもでき、
> その場合、Imageクラスを継承したTilemapもとうぜんそのまま使えるわけです。

例えば、正方形の画像をひし形に変形して描画することを考えます。この場合、

・Imageクラスをひし形に表示するクラスに差し替えて、class QTileMapを作る
・利用する側は、Imageのポインタを持って、多態性を使う
 のではなく、
・TileMapを利用する側がひし形に変形すればよい

ということでしょうか。
それとも、TileMapをさらに継承するクラスを作る、ということでしょうか。
どちらでも良い?

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#11

投稿記事 by ISLe » 8年前

moba さんが書きました:○追加質問(トピックを分けるべき場面だったならすみません)
C++ではグローバル変数を使うべきではないと何度か耳にしました。このような形でグローバル変数を使うのも、C++ではNGなのでしょうか。他の場面でグローバル変数を使う例は思いつかないのですが。
グローバル変数の危険性は知っていますが、
C++ではグローバル変数を使うべきではない」
というのをわたしは知りません。

グローバル変数は、それが必要な場面では使って良い・使うべきでしょう。
ただし、そういう場面は極めて限られます。
mobaさんがこのような形とおっしゃっている例は、使うべき場面ではありません。
ここはスコープを翻訳単位に制限すべきです。

No.1のmap.cppで宣言されている外部変数は、すべてグローバル変数になっています。
あとからいくらでも無制限に読み書きできるようにできてしまいます。

namespaceが、
同じnamespaceからしかアクセスできないような仕組みを持つ(Javaのpackageのような)ものだと思っているのでしたら
それは間違っているので、
ちゃんと基礎を押さえてください。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#12

投稿記事 by ISLe » 8年前

moba さんが書きました:機能としては、例えばツクールのオートタイルを実現できるということでしょうか。
いいえ、違います。
フラクタル図形で、一部を抜き出すとそこにまた同じ図形があるように、
タイルマップのタイルに、また別のタイルマップがある状態が作れる
ということです。

オブジェクト指向でもっとも重要なのは、インスタンス、です。
クラス定義を見ているだけでは、再帰性のある構造の理解は難しいでしょう。
moba さんが書きました:TileMapクラスはImageクラスを継承したのに、Imageクラスのリストを持っているというのが分かりませんでした。
また、Imageがどのように書かれているか想像できませんでした。
TilemapクラスがImageクラスを継承していることと、Imageクラスのリストを持つことはまったく別のものです。
因果関係がありませんから、そもそも分かる必要がありません。

継承元のクラス(へのポインタ)にアップキャストできることは、C++の文法の基礎範囲です。

TilemapクラスがImageクラスを継承していれば、TilemapクラスをImageクラスとして(Imageクラスの範囲で)扱えます。
なので、TilemapクラスがImageクラスを継承していれば、TilemapクラスとImageクラスは混ぜて使うことができます。

TilemapクラスとImageクラスは混ぜて使うことができる、というとき、Imageクラスの中身を考える必要はありません。
アップキャストに必要なのはポインタだけですから。
Imageクラスは画像の描画に関して基礎となるクラスなのだな、くらいに意識しておけば良いだけです。
必要な機能はあとからじっくり考えればよいのです。
moba さんが書きました:Image、TileIndexer、TileMapの構造を丸ごと示して欲しいということになります。
充分示したので自分で考えなさい、ということでしたらすみません。せめて、なんとかオブジェクト指向を学ぶ手段はないでしょうか。
各クラスの関係性こそがオブジェクト指向そのものだと思います。
定義ばかりに集中しているといつまでたってもオブジェクト指向は理解できないと思いますよ。
moba さんが書きました:例えば、正方形の画像をひし形に変形して描画することを考えます。この場合、

・Imageクラスをひし形に表示するクラスに差し替えて、class QTileMapを作る
・利用する側は、Imageのポインタを持って、多態性を使う
 のではなく、
・TileMapを利用する側がひし形に変形すればよい

ということでしょうか。
それとも、TileMapをさらに継承するクラスを作る、ということでしょうか。
どちらでも良い?
『委譲』という単語に気付きませんか?

TilemapクラスがImageクラスの派生であるように、Imageクラスには複数の派生先が存在すると考えられます。
というか考えます。
一直線に継承するだけだったら継承の意味がないですからね。

これもTilemapクラスの最初の説明と同じですよ。
ひし形に変形して描画するクラスは、Imageクラスへのポインタをメンバに持つ、Imageクラスを継承したクラスです。


サンプルコードについてはまとまった時間が必要なのでしばらくお待ちください。
似たようなものなら(しかもJavaですが)わたしのブログで公開してます。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#13

投稿記事 by ISLe » 8年前

ちなみにオートタイルの実装は、
TileIndexerクラスを派生したクラス次第で

タイル番号を要求されたときにリアルタイムにタイル番号を差し替えるとか

Tilemapのスクロールと連動して(連動の仕方は別件)
新たに画面に見えるようになった範囲のタイルを処理してバッファに蓄えておいて使うとか

いろいろ考えられます。
どれを選ぶにしても、既存には影響を与えずあとから好きに実装できます。

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#14

投稿記事 by moba » 8年前

【グローバル変数について】
グローバル変数は、それが必要な場面では使って良い・使うべきでしょう。
ただし、そういう場面は極めて限られます。
mobaさんがこのような形とおっしゃっている例は、使うべき場面ではありません。
ここはスコープを翻訳単位に制限すべきです。
クラスの定義を渡すという、
我ながら意味の分からない例を上げてしまいました…

共有するインスンタンスを渡す例を上げたかったです。
例えば、Dungeon.cppでは、インスタンスlayer[0]->SetChipID(x,y)や関数MapData::IsInsideMap(x,y)を使いたいです。
これらの宣言をexternで渡すのはどうでしょうか。
No.1のmap.cppで宣言されている外部変数は、すべてグローバル変数になっています。
あとからいくらでも無制限に読み書きできるようにできてしまいます。

namespaceが、
同じnamespaceからしかアクセスできないような仕組みを持つ(Javaのpackageのような)ものだと思っているのでしたら
それは間違っているので、
ちゃんと基礎を押さえてください。
extern namespace MapData;
void TEST(){MapData::mapWidth = 0;}
が動くのを確認しました。
インクルードした範囲しか見えないためカプセル化できている、と思っていました。
おかげでグローバル変数と無名名前空間内の変数の区別がつくようになりました。

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#15

投稿記事 by moba » 8年前

【ISLeさんの示した下さったTilemapについて】(No.6の例えば以下について)

■僕がどう勘違いしているか(流してもらって大丈夫です)
TilemapクラスがImageクラスを継承していることと、Imageクラスのリストを持つことはまったく別のものです。
因果関係がありませんから、そもそも分かる必要がありません。
> 矩形イメージを描画するための、Imageクラス
ということでしたので、
Imageクラスを画像表示機能だと思っています。

コード:

//こんな感じかと。
class Image
{
    //矩形の描画機能
    DrawRect(int x, int y, int handle)
    {
        //結局DxLibを使う
        DrawGraph(x,y,handle,TRUE);
    }
};
//描画機能を継承したクラス
class TileMap : public Image;
TileMap is aの関係にある画像表示機能Imageの
リストを持つとはどういうことだろうと考えていました。
オフトピック
今見ると、TileMap is a Image which consists of images という形なのかと思いました
『委譲』という単語に気付きませんか?
「Tilemapをそのまま使える」 = 「多態性を使わずTilemapを使う」だと思ってしまいました。
勘違いしてしまってすみません。

描画権利を? Imageに委譲して、Tilemapの亜種(Image QTilemap)が後から変形する感じですね。
オフトピック
僕の場合は、No.1で描画機能そのものを差し替えて、自由変形を使っていました。
class MapBase;//レイヤ機能の抽象クラス
class TileMap : public MapBase;
class QTileMap : public Mapbase;
■オブジェクト指向について
各クラスの関係性こそがオブジェクト指向そのものだと思います。
定義ばかりに集中しているといつまでたってもオブジェクト指向は理解できないと思いますよ。
また僕は誤解している気がしますが、
関係性の前に、どのようにクラスを分けるかが(僕の中で)問題になっています。

ISLeさんの例について。ImageやTilemapは何か、タイル情報は何が持っているかなど、
オブジェクト同士のやり取りの前に、そのオブジェクトが何かを分かっていません。
サンプルコードについてはまとまった時間が必要なのでしばらくお待ちください。
似たようなものなら(しかもJavaですが)わたしのブログで公開してます。
とんでもなく厚かましいお願いを、聴き入れてくださるってありがとうございます。
ブログも拝見させていただきます。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#16

投稿記事 by ISLe » 8年前

どのくらい砕いたら良いか悩みましたがとりあえずのサンプルコードです。

注目すべき点はコメントで書いてあります。
細かい不備はありますが、大きなくくりで見てください。

image.hpp
► スポイラーを表示
image_impl.cpp
► スポイラーを表示
tilemap.hpp
► スポイラーを表示
main.cpp
► スポイラーを表示
sample.png
実行結果
sample.png (7.66 KiB) 閲覧数: 17136 回
添付ファイル
source.zip
ソース一式
(2.78 KiB) ダウンロード数: 170 回

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#17

投稿記事 by ISLe » 8年前

moba さんが書きました:共有するインスンタンスを渡す例を上げたかったです。
例えば、Dungeon.cppでは、インスタンスlayer[0]->SetChipID(x,y)や関数MapData::IsInsideMap(x,y)を使いたいです。
これらの宣言をexternで渡すのはどうでしょうか。
わたしのサンプルコードでいうと、そのあたりの処理は、TileIndexerがアクセスする大元のマップデータの範疇です。
なので、タイルマップとは無関係なのです。
無関係なので、【TileIndexerというインターフェースをタイルマップに渡す以外には】接点を持ちません。

やはり、そもそもいらないよね、というお返事になってしまいます。

マップとのあたり判定とかマップの変化を伴うイベントとかも独立しているというわけです。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#18

投稿記事 by ISLe » 8年前

moba さんが書きました:また僕は誤解している気がしますが、
関係性の前に、どのようにクラスを分けるかが(僕の中で)問題になっています。

ISLeさんの例について。ImageやTilemapは何か、タイル情報は何が持っているかなど、
オブジェクト同士のやり取りの前に、そのオブジェクトが何かを分かっていません。
わたしが、どんなものか、を考えるときは既存のライブラリを参考にします。
引数とか、まあ、いろいろ、ですね。
で、そのあたりはプラットフォームの都合で決まることがほとんどで、
自分の考えを押し通せるところではないので、
ぼんやりとしか考えません。
経験や知識が活きるところではあるかもしれませんが。

No.12で、Imageは~だなくらいに意識しておけばよいと書きましたが
実際にImageは抽象クラスだったりします。

No.12を書いた時点では、
ノートにペンで四角を書いたような極めてシンプルなイメージが頭の中にあって、
次にその四角を頭の中でいろいろと動かしてみて、
そこから情報としては何が変化するのかを(これもシンプルを意識して)抜き出します。
で、その抜き出した情報っていうのはそのまま(後付けの)パラメータなんですね。
わたしの場合はそんなふうに頭の中で処理してコードに落とすみたいな感じです。

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#19

投稿記事 by moba » 8年前

本当にありがとうございます。
スマートポインタを知らないため、読むのに時間がかかっています。コードについての返信は、もうしばらく後にさせてください。

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#20

投稿記事 by moba » 8年前

コードを読みました。
僕は、レイヤをどう実装するか、から考え始めましたが、ISLeさんはスタートの視点が違うのですね。
Imageから始まるのがびっくりです。


■他ファイルで共有したいインスタンス
dungeon.cppでは、[現在のマップに相当するTileIndexer]の機能を使いたいとします。
ゲーム内では同時に1つのマップしか開かないのだから、
共有したいインスタンスのポインタをグローバル変数かシングルトンから得る、というのはどうでしょうか。

■入れ子について
例えば、複数のレイヤの実装に使うのは、使い方としてはどうですか。

イメージリスト:(タイルマップ0, タイルマップ1, タイルマップ2)
マップデータ:(イメージ0, イメージ1, イメージ2)
 →実行すると ( タイルマップ0->Draw, タイルマップ1->Draw, タイルマップ2->Draw )

他に使い方はあるでしょうか。

■複数のレイヤを実装する

「他ファイルで共有したいインスタンス」の具体例の1つになるのかと思います。

レイヤの数をnとします。
配列 Tilemap tilemap[n] と、 TileIndexer tileIndexer[n]があるとします。
任意ののレイヤ番号をkとして、
Tilemap tilemap[k] が TileIndexer tileIndexer[k] の参照ポインタ?(&) を持つのがいいかと考えました。

レイヤを実装すると、複数のレイヤを見て返す関数もほしくなります。
 IsPassable(x,y)//全レイヤの通行設定を見て、このマスが通行可能か返す

この関数、どこに置くのが適切でしょうか。

今でも僕は、全レイヤを見る関数と、レイヤに相当する情報を
クラスでくくる形しか思いつきません(ほぼNo.1のクラス版)。
今の僕が素直に考えると、下記をシングルトンにして使いたいです。

コード:

//開かれている1つのマップの情報
class GameMap
{
    TileIndexerA *tileIndexer;
    Image **tileMap;//多態性を使う場合はダブルポインタ?
    int IsInsideMap(int x,int y);//全レイヤを見て返す関数
    int scrollX,scrollY;//この情報も扱う、の意味
    FOV fov;
    //その他、マップの情報毎に決まる些細な情報
    int charaDrawShiftX,charaDrawShiftY;
};

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#21

投稿記事 by ISLe » 8年前

視点が違うというのはあるかもしれませんが、前にも書いた通り、決められるところから決めているだけです。

No.6で、オブジェクト指向はインスタンスベースで、と書きましたが、それはどういうことかというと
使うひとが使いたいときに実体化する、ということです。

アクセスする手段は使うひとがその都度決める
アクセスして欲しくないところはあらかじめアクセスできないようにしておく
それがクラスです。

mobaさんはずっと、どうやってアクセスするか、ということを第一に考えていらっしゃるようです。
オブジェクトの中身を決めかねていることで反対から攻めようという気持ちが働いているのでしょうか。
そこを最初に決めてしまうというのは制限でしかないのです。

わたしはImageを描画する方法だけを最初に決めました。
同じようにタイルマップをレイヤー状にしたり、それにアクセスする方法をクラスに込めることができます。
そのように考えることがオブジェクト指向に近づくことだと思います。

moba さんが書きました:■他ファイルで共有したいインスタンス
dungeon.cppでは、[現在のマップに相当するTileIndexer]の機能を使いたいとします。
ゲーム内では同時に1つのマップしか開かないのだから、
共有したいインスタンスのポインタをグローバル変数かシングルトンから得る、というのはどうでしょうか。
わたしならdungeon.cpp内だけでマップデータ(用の領域)を管理して、外からはアクセスできないようにします。

dungeon.cppの中で作成したTileIndexer(の派生)を、
より上位に位置するであろう、ステージ管理オブジェクトに渡す、という形をとります。

ステージ管理オブジェクトへのアクセスは、それへの参照を返す関数を公開状態にして、
オブジェクトに対しても、アクセスに必要な最低限のインターフェースだけ公開するよう、
関数が参照を返す際に、必要であれば加工します。

そうやって実装に依存しないという点で固めていきます。
ソースコードを変更したときの影響範囲を絞れ、コンパイル時間の短縮にもなります。
開発効率の上昇に大きく貢献します。

moba さんが書きました:■入れ子について
例えば、複数のレイヤの実装に使うのは、使い方としてはどうですか。

イメージリスト:(タイルマップ0, タイルマップ1, タイルマップ2)
マップデータ:(イメージ0, イメージ1, イメージ2)
 →実行すると ( タイルマップ0->Draw, タイルマップ1->Draw, タイルマップ2->Draw )

他に使い方はあるでしょうか。

■複数のレイヤを実装する

「他ファイルで共有したいインスタンス」の具体例の1つになるのかと思います。

レイヤの数をnとします。
配列 Tilemap tilemap[n] と、 TileIndexer tileIndexer[n]があるとします。
任意ののレイヤ番号をkとして、
Tilemap tilemap[k] が TileIndexer tileIndexer[k] の参照ポインタ?(&) を持つのがいいかと考えました。
わたしならタイルマップを重ねて描画するためのクラス、
TilemapLayerManagerとかLayeredTilemapとかいう感じのもの、
を作って使います。

イメージリストを用意する側も、マップデータを用意する側も、描画周り側も
みんな自分でインターフェースとなるオブジェクトを作成して、誰かに渡すという形です。
誰かに渡す、の誰かを公開された手段で取得するというわけで、
そこはバラバラに受け渡しするのではなく関連するものを(クラス等に)まとめて扱います。

moba さんが書きました:レイヤを実装すると、複数のレイヤを見て返す関数もほしくなります。
 IsPassable(x,y)//全レイヤの通行設定を見て、このマスが通行可能か返す

この関数、どこに置くのが適切でしょうか。

今でも僕は、全レイヤを見る関数と、レイヤに相当する情報を
クラスでくくる形しか思いつきません(ほぼNo.1のクラス版)。
今の僕が素直に考えると、下記をシングルトンにして使いたいです。
マップデータで進行の可不可を判定するのは、描画とは関係ないので切り離すべきです。
階層構造のマップで、描画では上下の順が重要だとしても、
マップデータとしては、
特定のマスで別のマップに飛ぶというイベントで処理できるので1階の上に2階がある必要はないのです。
マップデータは今いる場所を中心に処理すれば良く、階層という概念を持ち込むのはやはり制限になります。

ここまで「タイルマップ」という言葉があいまいに使われてました。すみません。
マップデータも、タイルで表現するの前提ならタイルマップですよね。
サンプルプログラムのTilemapは描画のみです。
マップデータは自由に実装できるので、それ単体で設計すればよいです。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#22

投稿記事 by ISLe » 8年前

視点が違うという点では、
そもそも、想定しているマップデータの仕様が違うと思いました。

mobaさんは、1マス分のマップデータ=地形イメージの番号、という考え方なのでしょう。
わたしは、1マス分のデータにいろいろな情報を詰め込むことを考えてました。

例えば、1マス分のデータが16ビットだとすると、
10ビットで1024種類もの地形を表現できます。
すると残り6ビットも自由に使えます。
そのうちの1ビットを通行できるかできないかのフラグにします。

地形とフラグを分けるメリットは…

素通りできる壁とできない壁でも、同一の地形とすることができます(そうしない自由もある)。
地形として別物にすると、画像を重複して持つことになるなど、より大きなコストの無駄が生じます。

目の前の壁を壊して進むようなシーンで、壁が壊れるアニメーションがある場合
地形を頼りにするとアニメーションが処理に深く食い込んでしまいます。
アニメーションは独立して行い、適当なタイミングでフラグを操作するという方法だと、
通れるようになるタイミングは、
壁を壊したときでも、ある程度壊れたときでも、崩れた壁が画面から消えたときでも、
選択が容易です。
あとから選択を変えることも容易です。
演出の自由度が高くなります。

その地形を通れるか通れないかは、その理由に関係なく、オンかオフかだけなのでプログラムも簡単になります。

扉一枚のために全体マップもう一枚分のメモリを確保する必要ありません。
(マップイベントとして)扉の開閉で、扉の位置のフラグを操作する。
フラグを操作する部分は地形に依存せず、処理も汎用化できます。

サンプルプログラムではTileIndexerを使うとマップデータの実装が自由になりました。

そこに何があるのかと何が起きるのかは分離して考えます。
オブジェクト指向をうまく扱うためにもそういう考え方が重要だと思います。

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#23

投稿記事 by moba » 8年前

何度か返信しようとしたのですが、まとまらなくて時間がかかってしまいました。
[2:59に修正]
mobaさんはずっと、どうやってアクセスするか、ということを第一に考えていらっしゃるようです。
オブジェクトの中身を決めかねていることで反対から攻めようという気持ちが働いているのでしょうか。
そこを最初に決めてしまうというのは制限でしかないのです。
dungeon.hppでインターフェイス用のクラスを用意して、それをアップキャストしたクラスのインスタンスの参照を渡せばいいのですね。
dungeon.cppでは、マップの情報を書きかえるのに、どうしても[現在のマップのTileIndexer]が必要だと思っていました。
でもdungeon.cpp側から直接書きかえる必要はなく、dungeon.cppで作ったTileIndexerを、常にステージ管理オブジェクトへの返し値で渡してしまえば良い。
ということであっていますか。

コード:

//ステージ管理オブジェクト
void StageManage::MakeDungeon(){
    //ダンジョンのインスタンスを作る書いては省略
    tileIndexer.mapData = dungeon.DungeonGeneration();
}
どうしても"あのインスタンス"にアクセスする必要がある、という状況を考えたつもりだったのですが
その必要は無かった!
わたしならdungeon.cpp内だけでマップデータ(用の領域)を管理して、外からはアクセスできないようにします。
dungeon.cppの中で作成したTileIndexer(の派生)を、
より上位に位置するであろう、ステージ管理オブジェクトに渡す、という形をとります。
dungeon.cppはダンジョンのランダム生成をします。説明不足ですみませんでした。
ステージはマップと思ってもいいですか。ステージ管理オブジェクトが、現在のマップ情報としてTileIndexerの派生クラスやスクロール情報を持っている。他のマップを読み込んだりする機能も持っている?

重複しますが
外部からダンジョンを作らせようとしたら、StageManage Aがあって、A.dungeonMake(); → 関数内で、Aがdungeon.cppによる生成結果を受け取る、という流れで合っていますか。常にdungeon.cppは返し値でダンジョンの結果を渡すと。
dungeon.cppがマップデータの使い手のつもりでしたが、ステージ管理オブジェクトがdungeon.cppを使うと都合が良い?


それともこういうことかなと思ったのは、

コード:

//これをdungeon.cppから呼び出して利用する
TileIndexer& GetCurrentMapData(){
    static TileIndexer tileIndexer;
    return &tileIndexer;
}
//ただし、これだとdungeon.cppに返し方を直接記述しなくてはならない
すみません、どう意図されたのか教えてください。
ステージ管理オブジェクトへのアクセスは、それへの参照を返す関数を公開状態にして、
オブジェクトに対しても、アクセスに必要な最低限のインターフェースだけ公開するよう、
関数が参照を返す際に、必要であれば加工します。
これはステージ管理オブジェクトに関連した一般的な話ですよね。dungeon.cppからのアクセスではなくて。

最後の行の意味が分かりません。インターフェースを制限して返すようなことはできるのでしょうか。

ステージ管理オブジェクトへの参照を変えす関数について。
何か分かっていません。これが必要な場面は、例えば何かがダンジョン生成をさせたい時?
この関数は、"ある特定のインスタンス"の参照を返す関数なのでしょうか。(「今使っているマップ」とは別のものを作って、その参照を返されても仕方が無いと思うので)
だとすると以下のような関数を考えましたが、あっていますか。あてずっぽうです。
GetInst(){
static ClassA classA;
return(&classA);
}

返信は続きます
最後に編集したユーザー moba on 2016年2月03日(水) 14:59 [ 編集 2 回目 ]

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#24

投稿記事 by moba » 8年前

[3:04修正]
わたしはImageを描画する方法だけを最初に決めました。
同じようにタイルマップをレイヤー状にしたり、それにアクセスする方法をクラスに込めることができます。
そのように考えることがオブジェクト指向に近づくことだと思います。
すみません、もう少し詳しく教えていただけませんか。
Imageの話からは帰納できず、どういう趣旨のお話なのか分かりませんでした。
わたしならタイルマップを重ねて描画するためのクラス、
TilemapLayerManagerとかLayeredTilemapとかいう感じのもの、
を作って使います。

イメージリストを用意する側も、マップデータを用意する側も、描画周り側も
みんな自分でインターフェースとなるオブジェクトを作成して、誰かに渡すという形です。
誰かに渡す、の誰かを公開された手段で取得するというわけで、
そこはバラバラに受け渡しするのではなく関連するものを(クラス等に)まとめて扱います。
2段落目の意味が分かりませでした。使い手がインターフェースクラスの派生クラスを作って、渡して使うアレのことでしょうか。
また、LayerMapの中身も考えられませんでした。No.20のGameMapとは違うのですよね。
当てずっぽう:

コード:

class LayerMap
{
public:
voidDraw()
{
    for(int i; i<allLayerNum; i++){
        //layerDrawers[i]->Draw();
    }
}
private:
    TileMap **layerDrawers;//配列 アップキャスト? 前提
    int allLayerNum;
}
マップデータで進行の可不可を判定するのは、描画とは関係ないので切り離すべきです。
マップデータ側に置きます。
LayerTileIndexerがあったら良さそうですね。
サンプルプログラムのTilemapは描画のみです。
マップデータは自由に実装できるので、それ単体で設計すればよいです。
ではclass GameMap{ }でくくらずに組んでいく方法を考えていきたいです。
確かに、dungeon.cppはマップデータを、control.cppは描画機能だけを使うので、GameMapのくくり方は不適切だと思いました。
階層構造のマップで、描画では上下の順が重要だとしても、
マップデータとしては、
特定のマスで別のマップに飛ぶというイベントで処理できるので1階の上に2階がある必要はないのです。
マップデータは今いる場所を中心に処理すれば良く、階層という概念を持ち込むのはやはり制限になります。
あ、レイヤはダンジョンの1階、2階ではありません。念のため。
床、壁、影、装飾、というように、1つのマップが複数のレイヤで構成されています。

レイヤの概念はどこかで必要だと思うのですが、TileIndexerを配列にするのはまずいですか。
最後に編集したユーザー moba on 2016年2月03日(水) 15:04 [ 編集 3 回目 ]

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#25

投稿記事 by moba » 8年前

> 地形とフラグを分ける

それでは、マップエディタでは、チップ毎の通行設定からマスとしての通行設定を設定するか、マス毎に通行可否を設定するか、マス毎に設定できるようにしようと思います。

また、通行フラグを分けても、例えば壁が壊れたとき、その下にマグマがあって通れないかもしれません。
その時は、チップを見てマスの通行設定を更新しようかと思います。


返信がドロドロになってごめんなさい

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#26

投稿記事 by ISLe » 8年前

ダウンキャストが危険をともなうというのは疑う余地のないことだと思いますが
それと同じように
ステージ全体を管理する、上位に位置するオブジェクトが
マップという、ステージの一部を構成する、下位に位置するオブジェクトの仕様に合わせてはいけないのです。
moba さんが書きました:dungeon.hppでインターフェイス用のクラスを用意して、それをアップキャストしたクラスのインスタンスの参照を渡せばいいのですね。
dungeon.cppでは、マップの情報を書きかえるのに、どうしても[現在のマップのTileIndexer]が必要だと思っていました。
でもdungeon.cpp側から直接書きかえる必要はなく、dungeon.cppで作ったTileIndexerを、常にステージ管理オブジェクトへの返し値で渡してしまえば良い。
ということであっていますか。

コード:

//ステージ管理オブジェクト
void StageManage::MakeDungeon(){
    //ダンジョンのインスタンスを作る書いては省略
    tileIndexer.mapData = dungeon.DungeonGeneration();
}
前半は良いですが、後半で逆になってます。

ステージ管理オブジェクトは、
マップデータにアクセスする方法(TileIndexer)は知っていても、
ダンジョンを生成するモジュールのことは知らない(知っていてはいけない)のです。

でもダンジョンを生成するモジュールはステージ管理オブジェクトのことは知っています。

ダンジョンを生成するモジュールが、TileIndexer(の派生)インスタンスを作成して、
ステージ管理オブジェクトのインターフェースを取得して、
そのインターフェースのメンバ関数を使って、作成したTileIndexer(の派生)インスタンスをセットします。

知っている・知っていない、というのは設計で上位か下位かという立場を表すものです。
現時点で具体的に何かを知っているという意味ではありません。
以前書いたような、ただぼんやりと「知っている」という単語がそこにあるだけの状態。
それが設計時には大切なことだったりします。

moba さんが書きました:dungeon.cppはダンジョンのランダム生成をします。説明不足ですみませんでした。
ステージはマップと思ってもいいですか。ステージ管理オブジェクトが、現在のマップ情報としてTileIndexerの派生クラスやスクロール情報を持っている。他のマップを読み込んだりする機能も持っている?
ステージは、主にゲームの演出面を管理するもので、
マップは、マップそのものですね。

スクロール情報は、マップの一部を表示するのに使われる演出に関わるものです。
これもマップ自体とは関係ないので切り離すべきです。

スクロールは主人公キャラの位置などと関わりがあります。
それらに合わせてマップデータを更新したりすることもあります。
そういった異なるオブジェクトとの情報の橋渡しをするのがステージ管理オブジェクトの役割りです。
だから、ステージ管理オブジェクトは設計として上位に位置します。
下位のオブジェクトは互いのことは知らないままで(独立して)、
ステージ管理オブジェクトに情報を渡し、
ステージ管理オブジェクトから受け取った情報だけで自らの挙動を決めます。

moba さんが書きました:外部からダンジョンを作らせようとしたら、StageManage Aがあって、A.dungeonMake(); → 関数内で、Aがdungeon.cppによる生成結果を受け取る、という流れで合っていますか。常にdungeon.cppは返し値でダンジョンの結果を渡すと。
dungeon.cppがマップデータの使い手のつもりでしたが、ステージ管理オブジェクトがdungeon.cppを使うと都合が良い?
前半は表現が逆なんですよね。
Aが受け取るのではなくて、Aに渡すのです。
だから、AにdungeonMakeという具体的な関数は作りません。
dungeonMake関数は、dungeon.cppの中に独立して存在して、ふつうにメインルーチンからの流れで呼び出される形になるでしょう。


文章から、やはりmobaさんが逆の発想をしているという印象です。
とりあえず、メインルーチンからの流れで
ダンジョンを生成してから、最初に描画するまでの流れを想像してみてください。
呼び出すほうと呼び出されるほうのどちらもそれぞれの立場できちんと考える必要がありますよ。

インターフェース関連については、別枠で。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#27

投稿記事 by ISLe » 8年前

moba さんが書きました:
わたしはImageを描画する方法だけを最初に決めました。
同じようにタイルマップをレイヤー状にしたり、それにアクセスする方法をクラスに込めることができます。
そのように考えることがオブジェクト指向に近づくことだと思います。
すみません、もう少し詳しく教えていただけませんか。
Imageの話からは帰納できず、どういう趣旨のお話なのか分かりませんでした。
TilemapとImageの関係と同じですよ。

複数レイヤーのタイルマップを扱いたいのなら、
Imageから派生してLayeredTilemapを作り、描画に関してImageと同様に扱えるようにする。
複数のタイルマップに対してどういう操作ができるかは、LayeredTilemapの実装次第。

最初に書いたように、インスタンスベース、で考えましょうってことです。

moba さんが書きました:また、LayerMapの中身も考えられませんでした。No.20のGameMapとは違うのですよね。
当てずっぽう:

コード:

class LayerMap
{
public:
voidDraw()
{
    for(int i; i<allLayerNum; i++){
        //layerDrawers[i]->Draw();
    }
}
private:
    TileMap **layerDrawers;//配列 アップキャスト? 前提
    int allLayerNum;
}
TilemapLayerManagerは、mobaさんの書かれたようなImageを継承しないものを考えてました。
その場合、表示上、重ねて描画する機能という点では同じだけれど、
複数のレイヤーをまったく別個に扱う、演出に特化したものを想定しました(だからManager)。

対してLayeredTilemapは名前通りに考えると複数重ねたTilemap。
オブジェクト指向的にLayeredTilemapとTilemapはis aの関係になります。

コード:

class LayeredTilemap : public Image
{
private:
    std::vector<std::shared_ptr<Tilemap>> TilemapList;
public:
    void Draw(int x, int y) {
        for (auto &o : TilemapList) o->Draw(x, y);
    }
    /* Tilemapの登録・削除とかまとめて操作するとかのメンバ関数とか他いろいろ */
};


moba さんが書きました:
マップデータで進行の可不可を判定するのは、描画とは関係ないので切り離すべきです。
マップデータ側に置きます。
LayerTileIndexerがあったら良さそうですね。
サンプルプログラムのTilemapは描画のみです。
マップデータは自由に実装できるので、それ単体で設計すればよいです。
ではclass GameMap{ }でくくらずに組んでいく方法を考えていきたいです。
確かに、dungeon.cppはマップデータを、control.cppは描画機能だけを使うので、GameMapのくくり方は不適切だと思いました。
階層構造のマップで、描画では上下の順が重要だとしても、
マップデータとしては、
特定のマスで別のマップに飛ぶというイベントで処理できるので1階の上に2階がある必要はないのです。
マップデータは今いる場所を中心に処理すれば良く、階層という概念を持ち込むのはやはり制限になります。
あ、レイヤはダンジョンの1階、2階ではありません。念のため。
床、壁、影、装飾、というように、1つのマップが複数のレイヤで構成されています。

レイヤの概念はどこかで必要だと思うのですが、TileIndexerを配列にするのはまずいですか。
ごちゃごちゃ長く書いてしまいましたが、この辺に関してわたしの投稿の趣旨は
マップデータはマップデータで自由に実装できるのが良い
というのが言いたかったことで、mobaさんの実装方法に対して間違っていると言っているわけではないです。

強いて言えば、空白だらけのマップデータでメモリを消費するイメージが浮かんで
極めて無駄が多いのではないだろうか、という感想を持ちました。

サンプルプログラムで、イメージリストはとりあえず、でした。
ここもクラスで実装して、
タイル番号に修飾情報も付けてやり取りできるようなふうにしたらよくないですか。

新しいイメージリストは、
地形番号順のイメージリストを持ち地形番号でイメージを描画
さらに修飾情報で修飾イメージを重ねて描画
というふうに。
Imageの派生で、地形タイルを修飾して描画するクラスを作って使う感じです。

合わせてTileIndexerも変更しなければいけませんが、配列にする意味はないと思います。
配列というのは実装の都合で、そういうのはできるだけ見えないようにしたほうが良いと思いますし。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#28

投稿記事 by ISLe » 8年前

ゲームプログラムは、一般的なシステム開発とくらべるとオブジェクト同士の関係が複雑です。
場面ごとに異なるオブジェクト同士と連携する必要があります。

その関係性を管理オブジェクトへのインターフェースという形でカテゴリ分けすると開発効率が良くなります。

いちばんてっぺんにすべてのカテゴリのルーツとなるオブジェクトを設けます。
仮にGameContextと名付けます。
ここから各カテゴリへアクセスするためのインターフェースを取得するので
GameContextにはそのためのメンバ関数を定義します。
カテゴリとはキャラクター管理だったりステージ管理だったりイベント管理だったりです。

コード:

class CategoryAContext;
class CategoryBContext;
class CategoryCContext;
class StageContext;
class GameContext {
    CategoryAContext *getCategoryAContext();
    CategoryBContext *getCategoryBContext();
    CategoryCContext *getCategoryCContext();
    StageContext *getStageContext();
};
ここで注目するところは、インターフェースオブジェクトは不完全定義であるという点です。
GameContextは大元なのでプログラム全体で使います。
でも、StageContextの中身は、ステージ関連のモジュールだけが使えればよいので
ステージ関連のモジュールだけが、StageContextの中身の定義をインクルードします。

そのすると、StageContextの中身が変更されたとき再コンパイルが必要なのはステージ関連のモジュールだけになります。

インターフェースは入れ子にできるので、
CategoryAContextからCategoryASubAContextを取得するという構造にもできます。

そうやって影響範囲を絞りつつプログラム全体の構造を決めていきます。


もちろん最初から完璧な設計ができるわけではありません。
あとからカテゴリ分けがマズかったという状況になることはしばしばあります。

新しいカテゴリ用のインターフェースを作ったりすることになるわけですが
それをそのままGameContextの定義に突っ込むと全体に影響が及んでしまいます。

そこでインターフェースオブジェクトを加工して返すという手段を使います。

コード:

/*
    カテゴリAとカテゴリBにまたがった新カテゴリ
*/
class NewCategory {
/* 省略 */
};
std::shared_ptr<NewCategoryContext> GetNewCategoryContext(GameContext *context)
{
    CategoryAContext *a = context->getCategoryAContext();
    CategoryBContext *b = context->getCategoryBContext();
    return std::make_shared<NewCategoryContext>(a, b);
}
加工は、ビルドスケジュールに合わせた一時的な対応であったり、
余分な機能にアクセスできないようにラップを掛けたりといった使い方が主になります。

あらかじめきちんと定義しておくカテゴリと動的に作れるカテゴリがあって、そこは適材適所ですね。

どんなレベルでもGameContextから直接取得する形にして
インターフェース関連だけをモジュールに閉じ込めることができるので
#GameContextすら不完全定義にしてしまえる
カテゴリ分けはそれほど神経質にならなくても大丈夫だったりします。

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#29

投稿記事 by moba » 8年前

■レイヤー
僕ではISLeさんの意図が分かりませんでした。
すみません、レイヤーの話だけ、もう少し説明していただけませんか。
1から10まで全て教えてもらうのは気が引けますが。厚顔で行きます…お願いします。
ごちゃごちゃ長く書いてしまいましたが、この辺に関してわたしの投稿の趣旨は
マップデータはマップデータで自由に実装できるのが良い
というのが言いたかったことで、mobaさんの実装方法に対して間違っていると言っているわけではないです。

強いて言えば、空白だらけのマップデータでメモリを消費するイメージが浮かんで
極めて無駄が多いのではないだろうか、という感想を持ちました。
階層の概念を持ち込まずに、複数レイヤ分のマップデータを作れるでしょうか?
自由なマップデータの実装とはどんな感じですか。<vector>でしょうか。
サンプルプログラムで、イメージリストはとりあえず、でした。
ここもクラスで実装して、
タイル番号に修飾情報も付けてやり取りできるようなふうにしたらよくないですか。

新しいイメージリストは、
地形番号順のイメージリストを持ち地形番号でイメージを描画
さらに修飾情報で修飾イメージを重ねて描画
というふうに。
Imageの派生で、地形タイルを修飾して描画するクラスを作って使う感じです。

合わせてTileIndexerも変更しなければいけませんが、配列にする意味はないと思います。
配列というのは実装の都合で、そういうのはできるだけ見えないようにしたほうが良いと思いますし。
地形情報、修飾情報というのは、たぶんそれぞれ1つのレイヤのことですよね。
1つの要素が複数レイヤ分の描画機能を持つイメージリスト、つまりリスト1つでマップ全てを描画できるイメージリスト
ということであっていますか。

また、TIleIndexerを変更するというのは、複数のレイヤに対応させるということですよね。
mapdataを[1マス情報分の<vector>]の<vector>でタイルマップデータにするのかなと思いました。

------------
ステージ管理オブジェクトについて
「あのインスタンス」に情報を渡すには、ステージ管理オブジェクトを介せば良い、ということですか。
その話ならば、以下については、セガの社員さんが出している本で出てきたことがあったと思うので、そちらを読んでから出直してきます。
また質問する時があれば、別トピックで立てさせていただきます。

■dungeon.cpp
ダンジョンを生成するモジュールが、TileIndexer(の派生)インスタンスを作成して、
ステージ管理オブジェクトのインターフェースを取得して、
そのインターフェースのメンバ関数を使って、作成したTileIndexer(の派生)インスタンスをセットします。
ダンジョン側で作ったTileIndexerを、
ダンジョン → ステージ管理 → マップ の順で渡すということですか。
だとすれば、ステージ管理が、マップにTileIndexerを渡す具体的な関数を持たねばならないと思うのですが、
これは上位のオブジェクト(ステージ管理)が下位のオブジェクトに絡み過ぎていませんか。

ステージ管理からマップオブジェクトへの参照を渡してもらえば、
ダンジョン側から「マップセット」のような関数を読んで、マップデータを更新できるかと思いました。
こういう意味であっていますか。
それか、ダンジョンのインターフェースをアップキャストする時に、直接ダンジョン側からマップデータを変更するようにするか。
文章から、やはりmobaさんが逆の発想をしているという印象です。
とりあえず、メインルーチンからの流れで
ダンジョンを生成してから、最初に描画するまでの流れを想像してみてください。
呼び出すほうと呼び出されるほうのどちらもそれぞれの立場できちんと考える必要がありますよ。
誰がダンジョン生成の関数を呼び出すか考えたのですが、
マップの初期化だったり、罠だったりします。罠の場合もマップの初期化に飛ぶと考えられます。
マップ側からステージ管理オブジェクトから参照を受け取ってダンジョン生成を呼んで、返し値でTileIndexerを受け取ってもいいかと思ってしまいました。

■状態遷移?
ステージ管理オブジェクトは、
マップデータにアクセスする方法(TileIndexer)は知っていても、
ダンジョンを生成するモジュールのことは知らない(知っていてはいけない)のです。
ゲームプログラムは、一般的なシステム開発とくらべるとオブジェクト同士の関係が複雑です。
場面ごとに異なるオブジェクト同士と連携する必要があります。

その関係性を管理オブジェクトへのインターフェースという形でカテゴリ分けすると開発効率が良くなります。
依存関係逆転の原則(DIP) というのがヒットしました。
http://d.hatena.ne.jp/asakichy/20090128/1233144989
長くなりそうなので今は手を出しません

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#30

投稿記事 by ISLe » 8年前

moba さんが書きました:階層の概念を持ち込まずに、複数レイヤ分のマップデータを作れるでしょうか?
自由なマップデータの実装とはどんな感じですか。<vector>でしょうか。
階層の概念を持ち込まないのではなくて、インスタンスベースで考えましょうっていうことなんですけどね。
考えましょうってところは、構造化プログラミング的な抽象化だと思ってもらえれば。

自由ってのは、そのまんまの意味で自由ってことですよ。

マップデータは配列でもいいのですよ。自由ですから。

タイルマップが、マップデータが配列であることに依存する
のは良くないという話です。

moba さんが書きました:依存関係逆転の原則(DIP) というのがヒットしました。
http://d.hatena.ne.jp/asakichy/20090128/1233144989
長くなりそうなので今は手を出しません
Tilemapの実装が上位で、TileIndexerとイメージリストの実装は下位。
TileIndexerを派生して使うのが抽象化。
TileIndexer(抽象)を通して描画すべき情報をやり取りする。
Tilemap(詳細)はマップデータの実装(詳細)は知らない。
マップデータの実装側(詳細)も、TileIndexer(抽象)を派生して渡すがTilemap(詳細)の中身は知らない。

最初からそういう話をしているんですけどね。

管理オブジェクトに関しても、タイルマップ関連の関係性を理解すれば
その関係図は、以前書いたように、フラクタル図形のような相似形なので。



前後しますが、以下気になったところ。
moba さんが書きました:地形情報、修飾情報というのは、たぶんそれぞれ1つのレイヤのことですよね。
1つの要素が複数レイヤ分の描画機能を持つイメージリスト、つまりリスト1つでマップ全てを描画できるイメージリスト
ということであっていますか。

また、TIleIndexerを変更するというのは、複数のレイヤに対応させるということですよね。
mapdataを[1マス情報分の<vector>]の<vector>でタイルマップデータにするのかなと思いました。
サンプルプログラムでイメージリストを配列(vectorですが)で実装したのでそれに引っ張られてるんですかね。
イメージリストをクラスで実装したなら、別に複数のイメージで構成されてなくてもよいのです。
「マップ全てを描画できる」というのは意図は測りかねますがある意味あってますかね。
あくまでイメージリストの役割りは、1マス(ずつ)描画するというものですけど。

moba さんが書きました:ダンジョン側で作ったTileIndexerを、
ダンジョン → ステージ管理 → マップ の順で渡すということですか。
だとすれば、ステージ管理が、マップにTileIndexerを渡す具体的な関数を持たねばならないと思うのですが、
これは上位のオブジェクト(ステージ管理)が下位のオブジェクトに絡み過ぎていませんか。

ステージ管理からマップオブジェクトへの参照を渡してもらえば、
ダンジョン側から「マップセット」のような関数を読んで、マップデータを更新できるかと思いました。
こういう意味であっていますか。
それか、ダンジョンのインターフェースをアップキャストする時に、直接ダンジョン側からマップデータを変更するようにするか。
管理オブジェクトに渡すの意味は後者の意味であってますね。
マップオブジェクトがどれだけ抽象化されているかが重要ですけど。


レイヤーというか地形修飾に関しては、またサンプルプログラムを用意したいと思います。
しばらくお待ちください。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#31

投稿記事 by ISLe » 8年前

ISLe さんが書きました:
moba さんが書きました:階層の概念を持ち込まずに、複数レイヤ分のマップデータを作れるでしょうか?
自由なマップデータの実装とはどんな感じですか。<vector>でしょうか。
階層の概念を持ち込まないのではなくて、インスタンスベースで考えましょうっていうことなんですけどね。
考えましょうってところは、構造化プログラミング的な抽象化だと思ってもらえれば。

自由ってのは、そのまんまの意味で自由ってことですよ。

マップデータは配列でもいいのですよ。自由ですから。

タイルマップが、マップデータが配列であることに依存する
のは良くないという話です。
mobaさんは、
タイルマップをレイヤーにしたい
レイヤーを配列で実装しよう
レイヤーをタイルマップと結び付けよう→【どうやったら?】
という考え方ですよね。

わたしは、
タイルマップをレイヤーにしたい
レイヤーをタイルマップと結び付けよう→【どうやったら?】
レイヤーを配列で実装しよう
という考え方です。

【どうやったら?】と実装の位置関係が違いますよね。

mobaさんは、具体(詳細)的に、どうやったら?と考え
わたしは、抽象的に、どうやったら?と考えている。

わたしの【どうやったら?】は実装よりも前にあります。
だから、
「自由なマップデータの実装とはどんな感じですか」
と問われても
「何でもいいですよ」
としか答えることができません。
そもそも想定してないのだから。

ではどうしてそこで【どうやったら?】を求めることができるのか。
その方法は、
レイヤーにしたいのなら
レイヤーではない場合と突き合わせる
というものです。
その差は、レイヤーとしての条件や特徴、
残りが共通事項、
です。
この残りの部分が重要なんですよ。
ここを実装するのがコードの抽象化そのものなのだから。

たいていのひとが、足したものを大事にしたいと思うみたいですが
抽象化ってのは、それをいったん捨てることなんですよね。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#32

投稿記事 by ISLe » 8年前

レイヤー状のマップのサンプルプログラムを作ってみました。

タイルマップの描画周りは、マップデータの形式は何でもかまわない、
ということを示すために、あえてテンプレートを使ってみました。

きわめて簡易な必要最小限のものですがステージ管理オブジェクトも入れてみました。
キャラを実装しててもしてなくてもこんなふうにイベントのテストができると
どこからでもコードを書き始められるし、デバッグにも便利です。

理解したいなら、実際に動かして、コードの流れを追ってみてください。
実装重視の考え方をしていると、あっちこっちに飛ぶので、わけが分からないかもしれません。
でも、設計図だときれいに上から下に三角形を描く形になります。

dungeon.cpp
► スポイラーを表示
main.cpp
► スポイラーを表示
tilemap.hpp
► スポイラーを表示
stagecontext.hpp
► スポイラーを表示
stagecontext.cpp
► スポイラーを表示
image.hppとimage.cppは前回と同じなので省略
sample.png
起動時の画面
sample.png (10.03 KiB) 閲覧数: 14129 回
添付ファイル
src.zip
ソース一式
(4.45 KiB) ダウンロード数: 140 回

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#33

投稿記事 by moba » 8年前

頭の回転が悪くて、抽象的な説明を理解するのに時間がかかりました。
階層の概念を持ち込まないのではなくて、インスタンスベースで考えましょうっていうことなんですけどね。

タイルマップが、マップデータが配列であることに依存する
のは良くないという話です。
たぶん分かりました。
具体的には、インターフェースをアップキャストして渡して使う、あのパターンで行こうということですね。
イメージリストをクラスで実装したなら、別に複数のイメージで構成されてなくてもよいのです。
イメージリストをImageクラスのリストにしたのは便宜的なもので、
全く別のクラスが描画を担ってもいいのでしょうか。
なんとなく、フラクタル図形? 風に書かなければならないのかと思っていました。

僕が素直に書くと

コード:

class TileImageList
{
	TileIndexer &idxr;
	char *fileName;
	int handle;
	int *graphDiv;
public:
	void Draw(int x, int y, int index){
		DrawGraph(x, y, graphDiv[idxr.GetTileIndex(x, y)], TRUE);
	}
};
>レイヤー(地形情報?)
サンプルプログラム、マジですかっ ありがとうございます
最後に編集したユーザー moba on 2016年2月09日(火) 15:21 [ 編集 2 回目 ]

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#34

投稿記事 by moba » 8年前

まだ表示が更新されていなくて、2ページ目に気付いておらず、
ISLeさんの追加の返信に気付いていませんでした。
読むのにまたしばらくかかります。サンプルをありがとうございます!!

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#35

投稿記事 by ISLe » 8年前

書き忘れてましたが、
No.32のレイヤーマップのサンプルプログラムのイメージリストを使う場面で、
今回は、
イメージリストに描画メソッドを用意するのではなく、
イメージリストからイメージを取得して、そのイメージで描画するという方法を使いました。

ここもあえてこんな形をとってみました。

このプログラムでは、イメージリストに登録したイメージ配列(vector)を参照していますが
ここでもやりたいように何でもできるよ、というメッセージを込めました。

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

Re: [C++]タイルマップのクラス設計を見てほしいです

#36

投稿記事 by moba » 8年前

回答までに一週間ほども空いてしまってごめんなさい。
白状しますと、頭が回らなくて理解できず、何度か後回しにしてしまいました。
早めにもうちょっと頑張るべきでした。

先入観があったり、ISLeさんの言葉を勘違いした部分が多かったのですが、
期間を置くと意味が分かってきて、コードが読めました。
質問しようと思った部分のコードも、いったり来たりしてよく読むと分かりました。

そうしてみると、何が分からなかったのか分からなってきました。
今は分からないことが何か分かっていないのだと思います。
でも、見よう見まねで取り組めるようになったと思います。

僕はまだ抽象的な考えを持てていないと思いますが、
これから取り組んでいく中で、少しずつ自分の中に取り込んでいきたいです。

ツールを使っていたときは、分割コンパイル等もなく、
グローバルな構造体と関数(にそれぞれ相当するもの)の集まりでやりくりしていました。
そういう頭(主観)を持っていた僕には、ISLeさんの「実装」などの言葉(概念)がヒントになっています。

パターンも僕の中に溜りました。
今回書いた下さったコードでは、ファイルを超えてインスタンスを共有する方法の例、
メンバ変数と引数が同名の場合の書き分けのルールなどです。

ご丁寧に本当にありがとうございました。頑張ります


追記:しばしばこのトピックを読み返させていただきます。

ISLe
記事: 2650
登録日時: 13年前
連絡を取る:

Re: [C++]タイルマップのクラス設計を見てほしいです

#37

投稿記事 by ISLe » 8年前

自分の中での変化(ブレイクスルー)に気付くことそのものが滅多にない貴重な経験だと思います。
それをお手伝いできたことをとても嬉しく思います。

考え方の道筋を示すためにサンプルプログラムを書きましたが、
実装に関しては正解というものはなく、
わたし自身も日々勉強です。
回答することは日頃の経験や考えを改めてまとめる良い機会になりました。
お礼申し上げます。

閉鎖

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