今、仕事も無いのにいきなりスプライト管理ルーチン作ってる。 さらに、作っても使う予定が無い(汗)。どうするんだ、オレ。
STG、RPG、ACT汎用にしようと思ってるんですが、なかなか難しい・・・ 汎用性を高めると、基底クラスではほとんどやることがなくなるか、 考えつくかぎりの事をやろうとしないといけなくなってしまう。 特に、オブジェクト自身になんでもさせようとすると、 オブジェクトの外の世界を知らないといけなくなる。 たとえばオブジェクト同士が衝突してるかを判定するは 一般的に下のようになるだろう。
class CSpriteBase
{
public:
POINT pos;
RECT rect;
メンバ色々・・・
};
class CSpriteControl
: vector<CSpriteBase*>
{
void CheckCollision()
{
for( int i = 0 ; i < size() ; i++ )
{
at(i)->pos.x ・・・あたり判定を行う
}
};
}
「オブジェクトクラスは単にデータ保持のために存在する」というルールだ。 こうやってスプライト間に起きることを、全て保持しているクラスに 任せていくとある点で破綻する。 CSpriteBaseから派生したクラスのメンバにアクセスできない(笑) 仮想メンバ関数を使えばいいと思うかもしれないが、 汎用性を維持するために基底クラスでは設定できないはずだ。
(2001/02/18加筆) CSpriteBaseは、私がスプライト管理クラスのためにデザインするものだ。 将来あなたがCSpriteBaseに何を要求するかは解るはずもない。
で、会社で暇してる 知り合い をチャットで掴まえて、勝手にディスカッションした。 その時に思いついたのがこれ。
class CSpriteBase
{
public:
CSpriteControl * m_control; // ここがミソ
}
class CSpriteControl
: vector<CSpriteBase*> // この中に自分(CSpriteControl)へのポインタが含まれる
{
public:
AddObject(CSpriteBase * pObj )
{
pObj->m_control = this;
push_back(pObj); // 面倒なのでpush_back(汗)
};
}
CSpriteObjectBaseは、スプライトの集合を保持したい時に使う基底クラスだ。 CSpriteObjectControlは、スプライトの集合を保持・管理するクラスで、 STLのvectorから派生することで保持している。
(2001/02/18加筆) こうするとvectorのメンバが全て公開されるのでプロトタイプの作成には便利。
ただ、問題なのはスプライト自身がvectorのどこに格納されているか 解らないことだ。n番目の要素が自分自信かは m_control->at(n) == this で判別がつくが、全ての要素をなめない限り知る手段がない。 vectorに登録時に何番目に登録されたかを教えてやるのも手かもしれない。 しかし、それだとコントローラ側で swap(at(n),at(m))などとした時矛盾が生じる。
汎用性を考えた時、スプライト管理クラスでやることが、まだ明確じゃない。 なので、今後の課題ということで。(続く)
(2001/02/18加筆) スプライトにCSpriteControlのポインタが必要なのか、と質問があったので補足を。
CSpriteControlのすべきことはスプライトをポインタで保持すること、 スプライトの実行関数(下のサンプルではRun関数)を呼び出すこと、 スプライトのポインタを追加・削除すること、 などである。 一見するとCSpriteControlがスプライトを保持しているかと錯覚するが、それは間違いである。 なぜならCSpriteControlは新しくスプライトを作成(new)したりしないからだ。 スプライトを作成するのは、CSpriteControlを呼び出すあなたのクラスや、 あなたの作った何かのスプライトだったりが行うことを前提としている。 つまりCSpriteControlのメモリの枠の中にスプライトが入っているのではなく、 CSpriteControlの外側に、スプライトが存在している様子を想像して欲しい。 CSpriteControlにCSpriteBaseのポインタがあり、これでスプライトを知ることができる。 では、何かのスプライトが自分以外のスプライトの座標を知りたいときどうするのか? 方法はいくつかあるが、CSpriteControlがそうだったようにスプライトもCSpriteControlの ポインタでCSpriteControlを知っていたほうが手っ取り早い。 そういうわけでスプライトの中でCSpriteControlがあった訳だ。
速攻続いてます(汗) スプライトがコントローラ経由で知る方法は制限付ですが解決しました。
一つめは前回いったように、thisと比較する方法。 もう一つは、スプライト管理クラスが今呼び出しているのがどのスプライトか を保持する方法だ。
class CSpriteControl
: vector<CSpriteBase*>
{
public:
int lookup;
void Run(void)
{
for( int i = 0 ; i < size() ; i++ )
{
lookup = i;
at(i)->Run();
}
}
};
まず最初に、スプライト管理クラスの外から、CSpriteControl::Run()が呼び出される。 この関数はvectorの全てのスプライトのCSpriteObjectBase::Run()を実行する。 あるスプライトの中で自分がどこなのか知りたい時はm_control->lookupを見ればいい。
ただし制限がある。 CSpriteBase内で他のスプライトのメンバ関数を呼び出す時、 たとえばm_control->at(n)->DoEvent()、この関数はlookupを参照してはいけない。 したがってスプライトのメンバ関数が、スプライト管理クラスかスプライトどちらから 呼び出されるのか区別しないといけないだろう。
そもそも何故スプライト内で、自分を含んだvectorが知りたいのか? それは、スプライトの自己削除を行いたいからだ。((2001/02/18加筆)それ以外にも理由はある) 通常、newされたオブジェクトはポインタで管理されることになる。 オブジェクトが内部で自己削除(delete this)を行った時、ポインタは不正になる。 これを回避するにはスプライトを指すポインタへのポインタをスプライトに 教えてやればいいかもしれない。しかしそれだとスプライトを指すポインタが 移動した時、スプライトにポインタが移動したことを通知しなければならない。 さらに言うならスプライトを指すポインタは一つと限らないので、 双方で何番目のポインタか管理しなければならない。(あとで作ってみよう)
結論としてCSpriteBaseは、すでに自分へのポインタの一部の要素を持っている。 m_controlがそれだ。問題は、自分へのポインタがどこに格納されているか、 知るすべがスマートでは無かった点だ。しかし、それもlookupを使えば解決する。 ただし、この場合はlookupはCSpriteBase::Run()関数を 実行する時しか有効ではない。 あるスプライトのRun()関数内から、他のオブジェクトのRun()関数を 呼び出した時の動作は保証できない。 しかし、自己削除という点はクリアした。 しかしながら、それを得るために多くの制約がつきまとうが。
void CSprite::Run()
{
・・・・色々な処理・・・
// 自分を削除
m_control->at(m_control->lookup) = NULL;
delete this;
}
更新しない日々が続く・・・ 何もやってなかったわけではなくて、ここの原稿やらスプライト管理クラスを 作っていたのだが、原稿は紛失するは、スプライト管理クラスの作成は 行き詰まるはで大変だった。以前はスプライトから他のスプライトを 参照するとき、ポインタで処理するようにしていたのだが、 これをハンドルで処理するように書き直していたらもうわけがわからなくなった。 コンパイルできない環境でやっていたのと、ソースの管理が甘かった(恥ずかしい)。 しかし得たものも多い。スプライト管理クラスのデザイン上で重要なことが いくつか判明したからだ。
ひとつはCSpriteBaseの実装はこの時点では できないということ。管理クラスから見たスプライトはCSpriteBaseという 名前で、管理クラスから呼び出す関数がある、ということしかはっきりしない。 しかし、スプライトから見た他のスプライトは様様な関数を持っていることだろう。
もうひとつはスプライトをグループに分け、2次元管理したほうがいいだろう、ということ。 シューティングであれば、自機グループ、自弾グループ、敵グループ、敵弾グループ等に分ける。 そうすれば、自機と自弾の間に衝突判定がおきたりすることを簡単に防げる。
そのほか色々。
なんにしても、もうちょっとデザインを考えないとなぁ、と思ったり。