スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

【コード】boost::shared_ptr、boost::scoped_ptr

先日我がプロジェクトにようやく boost ライブラリを導入致しました。
色々な機能があり、どんな機能があるのかは全然解りませんが
とりあえずスマートポインタという物を始めて触りました。

使ってみた感想は今のところよく解らないですw
今まで通り扱えるように出来てますので大幅な変更は殆どありませんでした。

そんな訳で少し boost::shared_ptr、boost::scoped_ptr に触れてみましたので
コード付きで色々載せてみます。


使う前にプロジェクトのプロパティより、
「構成プロパティ」→ 「C/C++」→ 「全般」にある
「追加のインクルード ディレクトリ」に boost_lib へのファイルパスを通してあります。


boost::shared_ptr、boost::scoped_ptr を扱うためにはそれぞれ
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>


をインクルードします。


内部で参照カウンタと呼ばれるものが存在しているそうで、
この参照カウンタが 0 になると自動的に確保した領域を解放してくれるようです。

参照カウンタが 1 以上だとどこかで参照されていると言う事になるので
解放は起こらないという仕組みだそうです。

例えば、色々なクラス内部でポインタを保持したい場合、
スマートポインタを使わない場合は削除するタイミングを常に意識しなければいけません。
勝手に delete なんかしてしまうと即座にアクセスバイオレーションになります。

これを使うと、そう言った煩わしさがなくなるとのことで早速導入しました。
正直個人のプロジェクトですので気を付けていればどうにでもなるんですけどね…


shared と scoped と二つのクラスが用意されてますが、
どう使い分ければいいのでしょうか…。
私自身あんまし把握出来ておりません。


とりあえず、他のオブジェクトからも参照する場合は shared、
他のオブジェクトから参照されない(存在するのは一つの)場合は scoped を使うようにしてます。
(scoped の方がより限定的に扱うスマートポインタと言う認識ですかね。)


実際使う時はこんな感じで記述します。
boost::shared_ptr<型> spShared;
boost::scoped_ptr<型> spScoped;

boost::shared_ptr<型> spShared(new 型());
boost::scoped_ptr<型> spScoped(new 型());


例えば CBase クラスをスマートポインタにしたい場合は、
boost::shared_ptr<CBase> spShared; // インスタンスの生成はしない
boost::scoped_ptr<CBase> spScoped;

boost::shared_ptr<CBase> spShared(new CBase()); // インスタンスも一緒に生成
boost::scoped_ptr<CBase> spScoped(new CBase());

こうなります。


個人的にちょっとややこしく感じたのがアスタリスクで、ポインタを扱うので
boost::shared_ptr<CBase*> spShared;
boost::scoped_ptr<CBase*> spScoped;

アスタリスクが必要なのではないかと思ったのですが必要ないようです。

メンバメソッドにアクセスしたい場合は、
アロー演算子がオーバーロードされておりますので普段通りのポインタのように扱えます。


int main()
{
boost::shared_ptr<CBase> spShared(new CBase());
boost::shared_ptr<CBase> spScoped(new CBase());

spShared->Uhehe();
spScoped->Uhehe("うへへ");

return 0;
}

クラス内でスマートポインタを使う場合は、
コンストラクタの初期化子リストを使用します。

// ベースクラス
class CBase
{
private:
boost::shared_ptr<CBase> spShared;
boost::shared_ptr<CBase> spScoped;

public:
CBase():spShared(new CBase()), spScoped(){ }
virtual ~CBase(){ }
};

shared の方はインスタンスを生成したバージョンです。
scoped の方は生成しないバージョンです。

今保持しているポインタを破棄して新しいポインタを保持したい場合は reset() メソッドを使うようです。
spShared.reset(new CDerived); // CBase へのポインタを破棄して CDerived へのポインタを保持する
spScoped.reset(new CDerived);


shared(scoped)のメンバですので「. 演算子」でアクセスします。
「-> 演算子」を使うと保持しているクラスのメンバメソッドになってしまいます。



先程も言ってたアスタリスクを付けてないのに
ポインタとして扱えているのか?と言う所に疑問が残り多態性についてもテストしました。
結果はばっちりOKでした。


#include <iostream>
#include <boost/shared_ptr.hpp>

// ベースクラス
class CBase
{
public:
CBase()
{
std::cout<< "CBase::Constructor\n";
}

virtual ~CBase()
{
std::cout<< "CBase::Destructor\n";
}

virtual void Print()
{
std::cout<< "CBase::Print\n";
}
};

// 派生クラス
class CDerived : public CBase
{
public:
CDerived()
{
std::cout<< "CDerived::Constructor\n";
}

~CDerived()
{
std::cout<< "CDerived::Destructor\n";
}

virtual void Print()override
{
std::cout<< "CDerived::Print\n";
}
};


int main()
{
boost::shared_ptr<CBase> spShared(new CDerived());
spShared->Print();
return 0;
}



実行結果はこうなりました。
CBase::Constructor
CDerived::Constructor
CDerived::Print
CDerived::Destructor
CBase::Destructor
続行するには何かキーを押してください . . .


派生クラスの Print() メソッドが呼ばれているのでOKですね。
delete をしなくても勝手に解放されるのデストラクタもしっかり呼ばれてます。



最後に、コンテナ(std::list や std::vector)にもスマートポインタが使用出来るので
その方法を記述して記事を終わりたいと思います。

int main()
{

std::list< boost::shared_ptr<CBase> > List;
List.push_back( boost::shared_ptr<CBase>(new CDerived()) );

std::list< boost::shared_ptr<CBase> >::iterator wIt = List.begin();

(*wIt)->Print();

std::cout<< "\nList.erase()\n";
wIt = List.erase(wIt);

std::cout<< "\nProgram ended\n";

return 0;
}


実行結果はこうなります。

CBase::Constructor
CDerived::Constructor
CDerived::Print

List.erase()
CDerived::Destructor
CBase::Destructor

Program ended
続行するには何かキーを押してください . . .


std::list にスマートポインタを使う例です。
今回無駄にイテレータも作成し、そこから Print() メソッドを呼んでおります。
erase() メソッドが呼ばれるとデストラクタが走っております。


一応テストはここで終了です。
色々足りない所も有りますが、今のところ扱うだけならこれで十分かなと思いました。
(必要ならまた調べればいいですし…)

まだまだ boost の初歩の初歩くらいですが楽しいなと思いましたw
こんなに簡単に使えるんならもっと早く使っとけばよかったorz

【コード】整数型の絶対値を求める

整数型に対応している絶対値を求める関数・・・
<cstdlib> をインクルードすると使える std::abs(std::labs)。
int 版と、long 版(std::labs)があります。


浮動小数点型に対応している絶対値を求める関数・・・
<cmath> をインクルードすると使える std::abs(std::fabs)。
float 版、double 版、long double 版があります。


これを使えば絶対値を求めれるのでわざわざ記事を作るほどでもないんですが、
せっかくなので整数型の絶対値を求める関数を作成しておこうということで記事を書きました。

//*******************************************
// 絶対値を取得する
// 第一引数 : 取得したい絶対値
// 戻り値 : 絶対値
//*******************************************
template<typename T>T MyAbs(T Val)
{
return (Val^(Val>>31)) - (Val>>31);
}

//*******************************************
// 絶対値を取得する
// 第一引数 : 値1
// 第二引数 : 値2
// 戻り値 : 値1と値2の絶対値
//*******************************************
template<typename T>T MyAbs(T Val1, T Val2)
{
T wTemp = Val1 - Val2;
return (wTemp^(wTemp>>31)) - (wTemp>>31);
}

わざわざ template を使う必要は有りませんが、なんとなく使ってみました。
記事にしといてなんですが、これはあまり良くないかもしれません。
素直に int 型、long 型等で分けた方がいい気がします。


使い方は MyAbs(30, 50) のように引数を二つ取る形のもの。
もう一つは MyAbs(10-80) のように計算式を渡すものの二つを用意しました。
上は20、下は70という具合に、どちらも戻り値に絶対値が返って来るようになってます。

あくまで整数型での絶対値ですので浮動小数点型の絶対値は求まりません。
MyAbs(5.2-3.1) とかやろうとしてもエラーが出ます。



2の補数表現を使った方法なのでもしかすると対応していない処理系があるかもしれませんが
少なくとも Visual C++ 2008 Express Edition ではそのような事はありませんので採用しました。
ビット演算を使って求める分、早いのかな~なんて思いますがあんま気にしてません。


当たり判定表示

自機に当たり判定の画像を付けたので画像としてアップしました。
薄くて見難いですが、白い丸の内側に青い丸が表示されててそこが当たり判定になります。
もっと見易く濃い色を付ける予定なので今はこんなもんです。

低速移動中に表示させる方がいいのか、常時表示させとく方がいいのか悩ましい所です。

今は低速移動中のみ表示させてるのですが、一瞬で消えたり表示されたりするので
す~っと浮かび上がるような処理に変える必要があるかもしれません。
いきなり出てくるとやっぱ違和感がありますのでまたこの辺は後で対応したいなと思います。



新アニメが徐々に放送されるようになり、楽しみで仕方ありません。
今の所、カンピオーネ!、貧乏神が!、TARI TARI は見ました。
アルカナファミリアも見たいので今度時間あったら見ようと思います。
楽しみであります。

とりあえず、クロノストーンは超展開超次元過ぎて時空の話とかは雰囲気で楽しんでるくらいになりました。
信長とマキシミックスとかどういうことよwww

ベータが可愛い。
化身出す時の声の変わり具合がたまらん・・・
アームドは何回見ても聖闘士星矢だ・・・。

だが、楽しみだ。
無理だろうけど初代のキャラと今のキャラ達とでサッカーの試合とかやって欲しい。

そんな感じでアニメも堪能しつつ日々過ごしてます。
既に予兆始まってるけど・・・暫く更新止るなこれは・・・。

【コード】template を用いた遷移ベースクラス簡易版

クラス内で遷移をするために関数ポインタを用いた記事を以前紹介した事がありまして
http://dvdm.blog134.fc2.com/blog-entry-24.html

これを基本的な機能をベースクラスにまとめて継承すれば
サブクラスで実装したメソッドをセッターを通して関数へのアドレスをセットしてやれば
使えるのではないかと言う所で改変しました。


template<typename T> class CSceneBase
{
protected:
bool m_isEnd; // 終了フラグ

void (T::*m_pExeScene)(); // 関数ポインタ
void SetScene(void (T::*pExeScene)()); // 実行関数のセット

virtual void Init()=0; // 初期化処理
virtual void Exit()=0; // 終了処理
virtual void Update()=0; // 更新

public:
CSceneBase();
virtual ~CSceneBase();

bool GetisEnd()const; // シーンの終了フラグを取得する
};


とりあえずこんな感じのクラスを作成しました。
m_isEnd は、そのシーンが終了しているか実行中かを表すフラグです。
TRUE の時は実行中、FALSE の時は終了と言う事にしてあります。

メインはこの二行です。
void (T::*m_pExeScene)(); // 関数ポインタ
void SetScene(void (T::*pExeScene)()); // 実行関数のセット

どのクラスの関数ポインタなのかを示すだけなのでそれほど難しいものではないと思います。

例えば継承の際、

class CTitle : public CSceneBase<CTitle>
{
    - 実装 -
}

こんな風に書けば CTitle::*pExeScene になりますし、
COption と言うのを作れば COpiton::*pExeScene になりますので
シーンはこれを継承していけばいいだけです。

protected なメンバに純粋仮想関数を仕込んでありますので
Init, Exit, Update は各サブクラスで実装する事になります。
この辺は好みに合わせればいいのではないかと思います。

責任を持ってサブクラスの Update メソッドをコールすれば
メインはスッキリ書けるかなと思います。
Update メソッドはこんな感じでしょうか。



//*******************************************
// 更新(サブクラスは責任を持ってこのメソッドをコースする)
// 引数 : なし
// 戻り値 : なし
//*******************************************
template<typename T> void CSceneBase<T>::Update()
{
(this->*m_pExeScene)();
}



関数アドレスをセットする SetScene メソッドの中身はこんな感じです。


//*******************************************
// 実行関数のセット
// 第一引数 : 実行する関数のアドレス
// 戻り値 : なし
//*******************************************
template<typename T> void CSceneBase<T>::SetScene(void (T::*pExeScene)())
{
m_pExeScene = pExeScene;
}


引数の書き方が変わったくらいで前の記事と特に変わってませんね・・・。

GeitisEnd メソッドも一応載せておきましょうか・・・。
といっても、フラグをリターンしてるだけで特別何をしている訳でもないんですけどね・・・。


//*******************************************
// シーンの終了フラグを取得する
// 引数 : なし
// 戻り値 : TRUE:終了 FALSE:実行中
//*******************************************
template<typename T> bool CSceneBase<T>::GetisEnd()const
{
return m_isEnd;
}



実行するメソッドのアドレスをセットするにはこんな風に記述します。

//*******************************************
// タイトルの初期化
//*******************************************
void CTitle::Init()
{
this->SetScene(&CTitle::MainTitle);
}


これで CTitle クラスのメンバメソッドである MainTitle メソッドが実行時に呼ばれます。
まぁ関数ポインタが protected ですのでセッターを用意せず
直接代入するのも有りでしょうけど、個人的にはメソッド派なので用意してます。

勿論外部に公開する槓すではありませんので
何の関係もないクラスからセッターを通してどうこうすることは出来ません。

例えばにシーンマネージャクラスに管理して貰い、
必要に応じてマネージャクラスが指示を出したりするのがいいんじゃないかなって個人的に思います。


今回はこんなけです。
今は当たり判定を実装していきたいなと思ってましてそちらの準備をしてます。
当たり判定用の画像も用意しないといけないのでグラフィックどうしようか悩んでます;


ここ最近帰ってきたらすぐ寝てしまうようになりましてやりたい気持ちに体が付いてきません・・・
歳・・・なのかな・・・・・・・・・・・・。
一応年齢的にはアラサーの範囲にギリギリ入ったくらいなのでまだ若い気持ちでいたいんですけどね。
いかんせん精神的におじさんです('A`)

そんな訳でなんとか生きてます。

【コード】Window から FullScreen(デバイスロスト)に対応

ここ三日ほどデバイスロストについて物凄く悩んでおりました。
出来た!と思ってたらその矢先にバグは見つかるし
そのバグを直そうとすると今度は予期せぬエラーで落ちたりしてました。
トライアンドエラーを繰り返し、今回追加した機能が二点。

・フルスクリーン時等に割り込みが発生した場合のデバイスロストへの対応
・ウインドウモードからフルスクリーンへの切り替え時に発生するデバイスロストへの対応

この二点になります。

デバイスロストとはその名の通り「デバイスが消失する状態」を意味しており、
消失するとデバイスの状態を復帰させる、ないしアプリケーションを終了させる等の選択をしなければいけません。


【フルスクリーン時等に割り込みが発生した場合のデバイスロストへの対応】
フルスクリーン時はそのアプリケーションが画面を占有している状態にあるため
割り込みが発生するとロスト状態に陥ります。

割り込みとは例えば、フルスクリーン時に Alt+Tab キーを押してデスクトップ画面に移行したり
更新プログラムが起動しました…等、裏で何かしらのアクションがあった時に発生するものです。
PCのゲームなので常に起こりうる可能性がありますので対応しなければいけません。

if(m_pDev->Present(NULL, NULL, NULL, NULL) == D3DERR_DEVICELOST)
{
DeviceLost();
}


m_pDev は LPDIRECT3DDEVICE9(IDirect3DDevice9*)型のデバイスオブジェクトです。
デバイスロストが発生すると IDirect3DDevice9::Present の戻り値に
D3DERR_DEVICELOST が返ってきますので
この戻り値の場合はロスト対策をしなければいけません。


bool DeviceLost()
{
// デバイスの現在の状態を取得
HRESULT whr = m_pDev->TestCooperativeLevel();

// デバイスの状態は正常である
if(whr == D3D_OK)
{
// デバイスロストが起こったからこの関数が呼ばれているので
// 正直この戻り値のチェックはいらないと思います。
// 成功すると D3D_OK が返るので書いておきました。
return TRUE;
}

// D3DERR_DEVICENOTRESET の場合は描画デバイスを復帰(リセット)できる状態
if(whr != D3DERR_DEVICENOTRESET)
{
return FALSE; // D3DERR_DEVICELOST の状態
}

ResetDevice(); // デバイスのリセット

return TRUE; // デバイスロストの対応が終わりました!
}


デバイスロストが発生しましたのでデバイスを復旧させるのですが
復旧できるかどうかを調べる必要があり、そのメソッドに
IDirect3DDevice9::TestCooperativeLevel を使います。

このメソッドは失敗すると次のエラーを返します。
D3DERR_DEVICELOST 又は D3DERR_DEVICENOTRESET

D3DERR_DEVICELOST の場合はデバイスがロストしていてリセットが出来ない状態(後述)
D3DERR_DEVICENOTRESET の場合はデバイスはロストしているがリセット可能な状態(後述)。

IDirect3DDevice9::TestCooperativeLevel を定期的に(ゲームループ等で)呼び出し
D3DERR_DEVICENOTRESET が返ってくる(復旧可能になる)まで待たないといけません。

その場で待機させるだけだと効果はありませんので
間違っても Sleep() なんかを使ってその場で待機させてはいけません。
要するにロスト中もメインループはそのまま動かし続けないといけないという事です。
(Windows のメッセージポンプを行う)



void ResetDevice()
{
// リソースの解放
// ここで特定のリソースを解放しなければいけません!
// それについては後述


// デバイスのリセット(解放しなければいけないリソースを解放しなければ成功しない)
// 戻り値が D3D_OK 以外の場合、復元出来ないのでアプリケーションを終了する
if(m_pDev->Reset(&m_D3Dpp) != D3D_OK)
{
PostQuitMessage(0);
return;
}

// リソースの復元
// 解放したリソースを再作成する必要があります!!


}

D3DERR_DEVICENOTRESET が返ってくるとデバイスをリセットできる状態になりますが、
その前に注意しなければいけない所が水色文字の部分です。
一部リソースを解放しなければいけません。
MSDN には以下のような記述があります。

IDirect3DDevice9::Reset を呼び出すと、すべてのテクスチャ メモリ サーフェイスが失われて、
管理下のテクスチャがビデオ メモリからフラッシュされ、すべてのステート情報がクリアされる。
デバイスに対して IDirect3DDevice9::Reset メソッドを呼び出す前に、
アプリケーションでは明示的なレンダリング ターゲット、深度ステンシル サーフェイス、
追加スワップ チェーン、ステート ブロック、およびデバイスに関連付けられている
D3DPOOL_DEFAULT リソースを解放する必要がある。


これらのリソースを Reset の前に解放しなくてはいけません。

・IDirect3DDevice9::CreateRenderTarget メソッドで作成したもの
・IDirect3DDevice9::CreateDepthStencilSurface メソッドで作成したもの
・IDirect3DDevice9::CreateAdditionalSwapChain メソッドで作成したもの
・テクスチャ等を D3DPOOL_DEFAULT で作成している場合これも解放
 (D3DPOOL_MANAGED や D3DPOOL_SYSTEMMEM は解放の必要はありません)
・D3DXCreateFontIndirect で作成したフォントオブジェクト等々

この解放が躓いた部分でした。
D3DXCreateFontIndirect で作成したフォントオブジェクトも解放して
作り直さないといけないようで、見落としているだけだと思いたいのですが
MSDN にはそのような記述を見つける事が出来ませんでした。


デバイスのリセットには IDirect3DDevice9::Reset を使います。

第一引数に &m_D3Dpp とい引数がありますが、
これはプレゼンテーションパラメータ構造体(D3DPRESENT_PARAMETERS)のアドレスです。
この構造体はデバイスを作成する時にも使っているはずですので割愛させて頂きますが、
その時の情報を保持しておかなくてはいけないのでクラス内のメンバ変数等で保持してあげて下さい。
そうするとその情報でデバイスがリセットされ復帰が完了します。

その後は先程解放したリソースを作成し直します。
こういう時のためにリソースを初期化するためのメソッドを用意しておくといいと思います。

気をつけなければいけないのが、解放して再作成するのでポインタが保持するアドレスが
違う場所を指していると言う部分です。
面倒な場合はテクスチャを D3DPOOL_MANAGED で作成してしまうのも手だと思います。
(解放しなくていいので考えなくて良い)

ここまでくればデバイスロストから復帰できる状態になります。



【ウインドウモード ⇔ フルスクリーンへの切り替え】
これもデバイスロストが発生するので処理は同じです。
違う部分はウインドウのサイズを変更する点と、フラグを切り替えると言う事ですが
そこまで難しいものではありませんのでソースを載せときます。

bool CDevice::ChangeDispMode()
{
// ディスプレイモードの切り替え
// m_D3Dpp.Windoed が TRUE ならウィンドウモード
// FALSE ならフルスクリーンになります。
(m_D3Dpp.Windowed == DISPMODE_FULLSCREEN) ? m_D3Dpp = m_D3DppFull : m_D3Dpp = m_D3DppWnd;

ResetDevice(); // デバイスのリセット

// フルスクリーン時のウインドウサイズ
if(m_D3Dpp.Windowed == DISPMODE_FULLSCREEN)
{
SetWindowLong(hWnd, GWL_STYLE, WS_POPUP);
SetWindowPos( hWnd, HWND_TOPMOST, // 最前面に表示
0,
0,
フルスクリーン時の幅,
フルスクリーン時の高さ,
SWP_SHOWWINDOW);

ShowCursor(FALSE); // マウスカーソルを表示しない
}
// ウインドウモード時のウインドウサイズ
else
{
// ここでは RECT 型への参照を返すメソッドから
// ウインドウのサイズと位置を取得してます
const RECT &wRect = pCWindow->GetWindowRect();

//ウィンドウモードへ変更する。
SetWindowLong(hWnd, GWL_STYLE, ウインドウ作成時のウインドウスタイル(WS_POPUPとか));

//ウィンドウモードへ変更したとき、ウィンドウの位置を変更する。
SetWindowPos( hWnd,
HWND_NOTOPMOST, // 最前面の表示を解除
wRect.left,
wRect.top,
wRect.right-wRect.left,
wRect.bottom-wRect.top,
SWP_SHOWWINDOW);
ShowCursor(TRUE); // マウスカーソルを表示する
}

return TRUE;
}


切り替える時にこのような記述をしてます。
(m_D3Dpp.Windowed == DISPMODE_FULLSCREEN) ? m_D3Dpp = m_D3DppFull : m_D3Dpp = m_D3DppWnd;

m_D3Dpp は今のパラメータが入っており、
m_D3DppWnd, m_D3DppFull に関してはデバイスの初期化の時点で
ウインドウモードの情報とフルスクリーンの情報が入っております。

ウインドウを切り替える時にウインドウの情報を m_D3Dpp に代入し、
それを Reset(&m_D3Dpp) で渡してあげる形です。

D3DPRESENT_PARAMETERS::Windowed のフラグを切り替えるだけで
Window ⇔ FullScreen の対応ができるので非常に便利であります。
ここまでくれば後はウインドウの情報を元にサイズの変更をかけるだけです。


長くなりましたがここ数日にかけて悩んでいた部分が解決できて嬉しいです。
気になるのが他の PC でどの様な挙動になるのか全くわからない点です。
これも後々テストしないといけませんね・・・。

【コード】[追記]n-way弾の実装

[追記は一番下]

偶数パターンの n-way 弾
偶数n-way

奇数パターンの n-way 弾
奇数n-way

n-way 弾の実装が完了しました。
弾を生成するコードも前に比べてスッキリしたように思います。
もっとスッキリ書けるのではないかと思い、
ソース付きで n-way 弾の生成アルゴリズムを探してたのですが
概念的なお話が多かったように思います。

n-way 弾のポイントは、n-way 弾の中心角度、弾と弾の間隔、
そして偶数パターンか、奇数パターンかだと思います。
0度を右方向、90度を下方向、180度を左方向として説明します。

まず、n-way 弾の中心角度(BaseAngle)を決めます。
固定でもいいですし、自機と敵の角度を取得しても構いません。
ここでは説明のために180度をベースに、弾の間隔が30度の5way弾を撃つ事にしましょう。

ベースが180度で、30度の間隔、5方向に発射と言う事は
弾の角度はそれぞれ[ 240度, 210度, 180度(ベース), 150度, 120度 ] と言う事になります。

単純に、生成する時に一番端の角度(240度)が解れば
それに沿って30度ずらしながら生成するだけで5way弾が発射できます。


// 一番端の角度を計算
float 一番端の角度 = BaseAngle + WayNum/2*ChangeAngle;

この式を展開すると「 180度 + 5way/2 * 30度 」と言う事になります。
計算すると 180 + 2 * 30 = 240 となり端の角度が求まります。
(5way/2 が2.5にならないのは整数の計算だからです)

で、この240度から変化分を引いていって WayNum 分ループさせていけば弾の生成は完了です。

for(int i=0; i<WayNum; ++i, 一番端の角度 -= ChangeAngle)
  NWayCreate(一番端の角度を渡して生成);

これで毎ループ、一番端の角度-=ChangeAngle が実行され
[ 240度, 210度, 180度, 150度, 120度 ] の角度が得られて生成出来ます。


では、偶数パターンについて上の式を適用してみます。
同じく180度をベースに、弾の間隔が30度の6way弾を撃つ事にしましょう。

ベースが180度で、30度の間隔、6方向・・・
奇数パターンの場合は真ん中に発射する弾があったので変化角度分ずらすのは簡単でした。
偶数はベースの角度に配置されません。
なぜならベースを境に左右できっちり別れるからです。

と言う事は弾の角度はそれぞれ
[ 255度, 225度, 195度, 170度, 140度, 110度 ]と言う事になります。
ベースを通らない弾の生成が必要な訳です。

丁度中心の角度計算が出来ればさほど難しいものではありませんが、
奇数よりちょっとややこしいです。

// 一番端の角度を計算
float 一番端の角度 = BaseAngle + WayNum/2*ChangeAngle - ChangeAngle/2: ;

偶数パターンの計算式から、ChangeAngle/2 を引いている所がポイントです。
ベースである180度のお隣を見て貰えれば
ベースの次の角度が「195度」ですので、30度の変化ではなく15度のため
ChangeAngle/2 引かないといけません。

この式を展開すると「 180度 + 6way/2*30度 - 30度/2 」と言う事になります。
計算すると 180 + 3*30 - 15 = 255 となり、これで偶数パターンの端の角度が求まります。

8way でベースが同じく180度、変化角度が20度の場合はこうなります。
「 180度 + 8way/2*20度 - 20度/2 」
計算すると、180 + 80 - 10 = 250 となり、やはり端の角度が求まります。


生成する関数は、端の角度から変化分引いていくループなので、
生成する関数は偶数パターンと同じです。
変わるのは、求める端の角度だけです。

float 一番端の角度 = (WayNum%2) ? BaseAngle + WayNum/2*ChangeAngle : BaseAngle + WayNum/2*ChangeAngle - ChangeAngle/2;

for(int i=0; i<WayNum; ++i, wEndAngle-=ChangeAngle)
{
  NWayCreate(一番端の角度を渡して生成);
}


一番端の角度 = (WayNum%2) とすることで、条件分岐し、偶数・奇数パターンでの端の角度が代入されます。


と言う訳で今回は n-way 弾の実装でしたがこんな感じで実装出来ますよと言う一例でした。
ではでは、外は雨でしょうもないですが今回はこんな感じで!


[--- 追記 ---]
なんと・・・今回も神の啓示がありました。
Justy さん曰く「N-Wayは空間を N-1に分割するということ」だそうです。

例えば添付画像の上が6way、下が5wayになってますが、
「空間を N-1」と言うのは、この弾と弾の間の空間の数の事を指しています。

6wayだと弾と弾の間の空間は5つ。
5wayだと4つ、2wayだと1つあると言う事になります。

n-way 弾は、端の角度さえ解ってしまえば変化角度分ずらしながら Way数分ループすると言うのは
上で書いたとおりですが、端の計算方法が変わります。

上の記事だと、

float 一番端の角度 = (WayNum%2) ? BaseAngle + WayNum/2*ChangeAngle : BaseAngle + WayNum/2*ChangeAngle - ChangeAngle/2;

偶数パターンの場合と、奇数パターンの場合で条件分岐されてました。

Justy さんの計算方法は分岐を使うことなく端の角度を計算する方法です。

BaseAngle + (WayNum - 1)/2.f*ChangeAngle

空間を N-1 に分割すると言う発想があるからこそできる計算法です!
と言う訳で、条件分岐がなくなったため長々と書いた記事がこれ一発で終了になります。

float 一番端の角度 = BaseAngle + (WayNum - 1)/2.f*ChangeAngle;

上の例通り、180度ベースの5wayで30度ずつ変化の場合。
180度 + (5way - 1)/2.0f*30度
180 + 2.0*30 = 240度。

端の角度が出てきてますね!

もういっちょ、180度ベースの8wayで20度ずつ変化の場合。
180度 + (8way - 1)/2.0f*20度
180 + 3.5*20 = 250度。

やはり端の角度が計算されてます!
計算式一つで端の角度が出るなんて思わなかったので非常に勉強になりました!

改めて、Justy さんにお礼申し上げます!!
ほんと、いつもありがとうございますw
≪PREVNEXT≫
検索フォーム
プロフィール

DVDM

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

 
Pixiv バナー


ブロとも申請フォーム
最新記事
カテゴリ
最新コメント
最新トラックバック
RSSリンクの表示
リンク
ブロとも一覧
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。