std::list と boost::shared_ptr を使った弾生成、弾管理クラス

Twitter でぶつぶつ独り言のようにつぶやいてた部分について一旦まとまったので記事にしてみました。
なんてことない記事なのですが、いつものメモ代わりもこめて。

今回はシューティングを作成していることもあって、弾の生成、弾の管理についてまとめていきます。

- 弾の管理は std::list を使ってリストで管理
- 弾は動的に生成するため、スマートポインタの boost::shared_ptr を使ってみました
(C++11 の std::shared_ptr も代用はきくと思いますが、詳しくしは知りません)

弾のベースクラスを作成しよう
何はともあれ弾のメインとなる情報を持ったベースクラスを作成しました。


// 弾ベースクラス
class CBulletBase
{
public:
virtual void update() { std::cout << "CBulletBase::update()\n"; }

CBulletBase() { };
virtual ~CBulletBase() {};
};

ベースクラスから派生しよう
このクラスを派生し、自機狙いや円形弾など固有の動きをするクラスを作成してみました。



// 自機狙い弾クラス
class CBulletAiming : public CBulletBase
{
public:
virtual void update() override { std::cout << "CBulletAiming::update()\n\n"; }
CBulletAiming() { std::cout << "Create CBulletAiming\n"; };
virtual ~CBulletAiming() { };
};

// 円形弾
class CBulletCircle : public CBulletBase
{
public:
virtual void update() override { std::cout << "CBulletCircle::update()\n\n"; }
CBulletCircle() { std::cout << "Create CBulletCircle\n"; };
virtual ~CBulletCircle() { };
};


弾生成の骨組みを作成しよう
この辺をどうしようかなと思ったのですが、とりあえずファクトリクラスなるものを作成し、
弾の ID を引数として受け取り、その ID を元に、自機狙い弾、円形弾などを生成します。
簡単ではありますが、実装はこんな感じになりました。


// 弾生成
class CBulletFactory
{
public:
boost::shared_ptr<CBulletBase> create(int bulletID)
{
switch (bulletID)
{
case 0: return boost::shared_ptr<CBulletBase>(new CBulletAiming());
case 1: return boost::shared_ptr<CBulletBase>(new CBulletCircle());
}
return NULL;
}
};


case 0、case 1 とかマジックナンバーになってますが、
enum で列挙しておくと使いやすそうですね。(今回はマジックナンバーにしました)

また、スマートポインタを使わず管理してもよさそうですが、
せっかく便利な機能があるので使ってみようかなということでこうしてます。
これで、弾の生成や弾の基本情報を持ったクラスの作成が完了しました。


弾管理クラスを作成しよう
これらのクラスをどう扱うか考えたのですが、
弾の管理クラスを作成し、その中でリスト管理してみる方向にしました。
弾管理クラスはこんな実装になりました。


// 弾管理マネージャ
class CBulletMng
{
private:
CBulletFactory m_Factory; // 弾生成クラス
std::list< boost::shared_ptr<CBulletBase> > m_List; // リスト

public:
void create(int bulletID)
{
m_List.push_back( m_Factory.create(bulletID) );
}

void update()
{
std::list< boost::shared_ptr<CBulletBase> >::iterator wIt;

// リスト内を全て更新
for (wIt = m_List.begin(); wIt != m_List.end(); ++wIt)
(*wIt)->update();
}
};


std::list で弾をリストで保持するメンバ変数を用意し、
create メソッドに引数である bulletID(この例では、0か1)を渡すと、対象の弾が生成され
このリストの中に push_back されます。

update メソッドではリスト内にある弾クラスの update メソッドを呼び出しているので
自機狙い、円形弾などのバリエーションごとに更新を呼び出してます。

特に必要ないとおもいますが、メインはこんな感じで生成と更新をするようになりました。



int main()
{
CBulletMng wMng;

// ID を1→ 0→ 1→ 0 と変化させて交互に生成
for (int i = 1; i < 6; ++i)
wMng.create(i%2);

wMng.update();

return 0;
}


ID さへ渡せば中で弾の生成やリストでの管理を行ってくれます。
今回はマジックナンバーなのでベタに for文で適当に交互に ID を渡すようにしました。

この部分は一例として

wMng.create(TYPE_AIMING);
wMng.create(TYPE_NWAY);

のようにしていれば、自機狙い弾、nway弾の生成してそうだな
ってのが一目で見て分かるかな…

スマートポインタになっているので、明示的な delete をする必要も(多分)なさそうですし、
より安全に動的作成してもよさそうかなと思いました。

m_List.clear(); と呼ぶとリスト内を全て消すことも出来ますし
リスト管理は色々便利ですね。


一応出力結果は下記になります。


Create CBulletCircle
Create CBulletAiming
Create CBulletCircle
Create CBulletAiming
Create CBulletCircle

CBulletCircle::update()
CBulletAiming::update()
CBulletCircle::update()
CBulletAiming::update()
CBulletCircle::update()


それぞれの弾クラスの update メソッドが呼ばれておりますので多態勢も大丈夫そうです。


これだけではないのですが、大まかにこんな流れの骨組みをここ1-2日で作成しておりました。
現在はボタンを押すと弾が発射され、移動するというところまでの実装は出来ましたので
まずは気になっていた大きな部分を一つクリアしたかなという印象です。

プログラムやっぱり楽しいですが、知らん間に時間が経って睡眠時間が削られてたりすることが多いです。
しばらく離れていたのでなんとか進めたいです・・・

std:min、std::max 使用時に出る C2589 '(': スコープ解決演算子 (::) の右側にあるトークンは使えません

無事長崎から帰ってきました。
すごくいい所でした。
ハウステンボス楽しみにしてたので凄く満足です!
美味しいものいっぱいありますね・・・!


帰宅後はまたボチボチとプログラムをやってます。
今日出たエラーについて記事にしてみました。

#include <algorithm>
を定義し、std::min、std::max を使用した時下記エラーが出ました。

サンプルコード:
abc = std::max(abc - 1, 0);

C2589 '(': スコープ解決演算子 (::) の右側にあるトークンは使えません

何でだろうと思い調べてみるとこんな記事が。

https://support.microsoft.com/ja-jp/kb/143208 より抜粋。

問題は、min と max の競合する定義で発生します。
Min および max」は次のように Windef.h でマクロとして定義されます。


何気なしに使ってた <windows.h> との競合が原因だったようです。
解決方法については


NOMINMAX のプリプロセッサ シンボルを定義します。
これは、Developer Studio プロジェクトをビルド、設定、C または C++] タブの [プリプロセッサ] カテゴリで、上で実行できます。これは、最小、Windef.h で最大の定義に表示されません。


日本語については置いておくとして、

#define NOMINMAX
#include <windows.h>

windows.h を include する前にこんな感じで防ぐことが出来るみたいです。
サンプルプログラムでよく使ってたのですが何気に Windows アプリケーション開発の中では初だったんだなと思いました。

ふ~んって感じの記事でしたがとりあえず書いてみました。

マクロ展開しないとかほかの方法で回避も出来ると思いますが、せっかく公式に書いてたので
この方法を使ったら普通に使えるようになりました。

数値を文字列に変換する( boost::lexical_cast )

数値を文字列に変換して、文字列Aと文字列Bの連結をしたいなと思ってたのですが、
どんな関数を使ったらいいのか知らずに検索してたら、boost ライブラリにかなり良さそうなものがあったので記録。

boost::lexical_cast<変換する型名>(変換対象)

使い方は結構シンプルでした。

int Val = boost::lexical_cast<int>("150");
これで、"150" を数値の150に変換してくれたり、

std::string Str = boost::lexical_cast<std::string>(200);
これで数値の200を "200" に変換してくれたりするので非常に便利です。

たとえばループで Sample1~Sample10 みたいな連番を作成するのも簡単にできそうです。

for(int i=1; i<=10; ++i)
{
Str = "Sample" + boost::lexical_cast<std::string>(i);
std::cout<< Str<< std::endl;
}


こんな感じでしょうか。
便利だな~。
これからは是非使わせてもらうことにしましょう('w`)

Direct X の IDirect3DDevice9::SetViewport メソッドで指定範囲の描画をする

今更感丸出しな上、久々にプログラムを触ったなぁ~。
今回はタイトルにあるように、指定範囲の描画メソッドを作りましょうということで作成。

まずは MSDN ライブラリより、IDirect3DDevice9::SetViewport メソッドに関してのリンク。
http://msdn.microsoft.com/ja-jp/library/cc324255.aspx

関数の説明:デバイスにビューポート パラメータを設定する。

このなんとも言い難い相変わらずの説明具合・・・。
なんのこっちゃわからん!

要するにこのメソッド使えば、指定範囲の描画ができますよということです。
とりあえずざくっとソース載っけときます。


//*******************************************
// ビューポートの設定(指定範囲を描画する用)
// 第一引数 : 指定描画位置X
// 第二引数 : 指定描画位置Y
// 第三引数 : 描画幅
// 第四引数 : 描画高さ
// 戻り値 : D3D_OK:成功 E_FAIL:失敗
//*******************************************

HRESULT SetViewport(DWORD Xpos, DWORD Ypos, DWORD Width, DWORD Height)
{

m_Viewport.X = Xpos;
m_Viewport.Y = Ypos;
m_Viewport.Width = Width;
m_ViewPort.Height = Height;
m_Viewport.MinZ = 0.0f;
m_Viewport.MaxZ = 1.0f;

// ビューポートの設定に失敗したら終了
if( m_pDev->SetViewport(&m_Viewport) == D3DERR_INVALIDCALL)
return E_FAIL;

return D3D_OK;
}


ビューポートを指定するために、Direct X で用意してくれてる D3DVIEWPORT9 構造体 を使用します。
m_ViewPort は、D3DVIEWPORT9 型となってます。
また、m_pDev はお馴染みの LPDIRECT3DDEVICE9 型のデバイスオブジェクトとなってます。
作成に関しては割愛しております。

引数でもらってきた値をセットしているだけなので特に何もないんですが、一応念のため。

http://msdn.microsoft.com/ja-jp/library/cc323943.aspx

SetViewport メソッドのリンクにもありますが、構造体の中身はわかりやすいです。


D3DVIEWPORT9::X
  描画の開始位置X座標。
  この X 地点から描画される。

D3DVIEWPORT9::Y
  描画の開始位置Y座標。
  この Y 地点から描画される。

D3DVIEWPORT9::Width
  この幅分描画される。

D3DVIEWPORT9::Height
  この高さ分描画される。

D3DVIEWPORT9::Min
  ほとんどのアプリケーションでは、この値を 0.0 に設定する。
  と書いてるくらいなので、0.0f を指定してます。

D3DVIEWPORT9::MaxZ
  ほとんどのアプリケーションでは、この値を 1.0 に設定する。
  と書いてるくらいなので、1.0f を指定してます。


このメソッドは、成功した場合は、D3D_OK を返す。
失敗した場合は、D3DERR_INVALIDCALL を返す。

とあるので、戻り値のチェックも入れてます。


使い方は、SetViewport(0, 0, 640, 480);
こんな風にすれば、一般のウインドウサイズのアプリケーション全体を描画することができます。
このサイズからはみ出た分は描画されないので、何かの演出に使ってみたりするのいいかもしれませんね~。

ちゃんとブログ更新することができたけど・・・
一応環境が移って、少しずつ元に戻ってきてます。

何とか、遊べる形に・・・と欲張ると、また更新がとまりそうな気配がするので
地道にできることをやって生きたいと思います。

【コード】[追記]std::map と C2061: 構文エラー : 識別子 '_Wherenode'

[追記は一番下]

つい先日 std::map を使ってみようと思い、
早速プロジェクトに導入しようとしました。

サンプルプログラムでキーと要素の登録は出来ましたので
どういう場面で使うのか…
そこが問題ではありましたが、丁度うちのゲームでそろそろ敵を出したいな~
と思ってたのでそこに使ってみようと思いました。

Excel で表を作成し、敵の出現情報を管理させるのが簡単そうだったので
そこで敵の ID を一緒に埋め込んでファイル読み込み時に ID と情報を関連付けさせる計画を立てました。


#include <map>
#include <string>

class CEnemyData;

class CSample
{
private:
std::map<std::string, CEnemyData*> m_EnemyData;
public:
CSample();
~CSample();
};


単なるサンプルですが、ヘッダにこんな感じのクラスを作成し
ソース側で m_EnemyData を使ってみると…

error C2061: 構文エラー : 識別子 '_Wherenode'

こんなエラーが…
エラーを F1 のヘルプで検索しても全然意味不明。
サンプルではこんなん出なかったのに何なんだろうか…。


#ifdef _DEBUG
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif


原因は先日導入したメモリリークの場所を特定するこのマクロのようです。
このマクロより後に #include <map> をすると駄目なようです。
なので、このマクロより前に include してしまうか、
削除することで解決できるようです。

この記述はデバッグ版でビルドしている時に起こるエラーで、
リリース版でのビルドではエラーは出ないんですが、
なんか腑に落ちない解決の仕方でした。

とりあえず直ったので、
ファイルから敵情報を読み込むためのパーサーでも作成していきたいと思います。



[--- 追記 ---]
map や vector 等のコンテナは内部で operator new が定義されており、
このマクロの new と内部の new とが噛み合ってなかったためにこのエラーが出てきました。

本来 C/C++ の予約語等の名前と同じマクロを書いてしまっている…
それが間違いだった事に気付けませんでした。


#ifdef _DEBUG
#define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif


例えば解決策として名前を変更したり、
どうしても new がいいんだ! と言うのであれば上記の解決策のように
ヘッダをマクロよりも手前で include してしまうのがいいでしょうけど、
やっぱりやめといた方がいいと思います。

予約語とバッティングしたエラーが出たのは今まで始めてだったので
凄くいい勉強になりました。

この追記記事が書けるようになったのと、解決に導いて下さった Justy さん。
本当にありがとうございます!!
NEXT≫
検索フォーム
プロフィール

DVDM

Author:DVDM
自作ゲームの開発過程ブログ。
赤髪愛なら誰にも負けない。

 
Pixiv バナー


ブロとも申請フォーム
最新記事
カテゴリ
最新コメント
最新トラックバック
RSSリンクの表示
リンク
ブロとも一覧