C言語+DXライブラリでシューティングゲームを作っているのですが、敵の種類ごとに処理を分けるにはどうしたらよいでしょうか。敵の情報についてはそれぞれ違う構造体を使おうと考えています。
具体的な方法としては、種類ごとに別々の関数をつくり、オーバーロードすることくらいしか思いつかないのですがこの方法でよいのでしょうか。ほかにも何かあれば教えていただけると有難いです。
STGで敵の種類ごとに処理を分ける方法について
Re: STGで敵の種類ごとに処理を分ける方法について
>敵の情報についてはそれぞれ違う構造体を使おうと考えています。
これは,敵の種類が3種類あったら,構造体も3種類作るということですか?
(だとしたら,種類ごとに全く保持すべきデータの種類や数が異なるということ?)
そこまで全く別々のデータ形式なのだとしたら,
種類ごとに分けたい処理 を個別の関数にするくらいしかないと思います.
その場合でも,関数の型をそろえておくと,関数ポインタを使うとかできると思います.
どうにかして関数を分けないでやりたいとかいう場合だと… うーん,例えば
みたいな感じで済むようであれば,
EnemyType値で分岐するとか,
ParameterOfAlgorithm値を何らかの計算に用いることで結果として異なる動作結果になるようにする,とか?
これは,敵の種類が3種類あったら,構造体も3種類作るということですか?
(だとしたら,種類ごとに全く保持すべきデータの種類や数が異なるということ?)
そこまで全く別々のデータ形式なのだとしたら,
種類ごとに分けたい処理 を個別の関数にするくらいしかないと思います.
その場合でも,関数の型をそろえておくと,関数ポインタを使うとかできると思います.
どうにかして関数を分けないでやりたいとかいう場合だと… うーん,例えば
struct EnemyInfo
{
//共通の項目
int HP;
....
//この値で,敵の種類を表す
int EnemyType;
//共通の処理関数内で動きを異ならせるための何かしらのパラメタ
int ParameterOfAlgorithm;
};
EnemyType値で分岐するとか,
ParameterOfAlgorithm値を何らかの計算に用いることで結果として異なる動作結果になるようにする,とか?
Re: STGで敵の種類ごとに処理を分ける方法について
回答ありがとうございます。
>種類ごとに分けたい処理 を個別の関数にするくらいしかないと思います.
やはり別々の構造体を作るとなるとこの方法が良いようですね。
しかし、この方法はあまり一般的ではないのでしょうか?
もし、全ての敵をひとつの構造体にまとめるとなると、敵の種類によって必要となるパラメータも異なるはずなので構造体自体がかなり複雑になってしまう気がします。
その辺はある程度目をつぶるということですか?
>種類ごとに分けたい処理 を個別の関数にするくらいしかないと思います.
やはり別々の構造体を作るとなるとこの方法が良いようですね。
しかし、この方法はあまり一般的ではないのでしょうか?
もし、全ての敵をひとつの構造体にまとめるとなると、敵の種類によって必要となるパラメータも異なるはずなので構造体自体がかなり複雑になってしまう気がします。
その辺はある程度目をつぶるということですか?
Re: STGで敵の種類ごとに処理を分ける方法について
私はSTGを本格的には作ったことないですけど,一般的?にはどうなんでしょう.
構造体の型自体を変えてしまうと,「複数の敵のインスタンス」を管理する方法がひどく面倒になりそうですが,どうなんでしょう.
敵の種類っていうのがどれだけのデータ多様性を生むのかわかりませんが
それほどに異なるデータ形式になるものなのでしょうか?
おそらく
{すべての敵で共通するデータ + 種類ごとの特殊なデータ} みたくなる気がします.
のようにして,実際の処理関数の中では
pSpecialDataを実際の型にキャストして使うとかの方が,いくらかマシに思います.
for( すべての敵 ){ 処理 }
みたいなことが書けるので.
構造体の型自体を変えてしまうと,「複数の敵のインスタンス」を管理する方法がひどく面倒になりそうですが,どうなんでしょう.
敵の種類っていうのがどれだけのデータ多様性を生むのかわかりませんが
それほどに異なるデータ形式になるものなのでしょうか?
おそらく
{すべての敵で共通するデータ + 種類ごとの特殊なデータ} みたくなる気がします.
struct SpecialDataForEnemyA{ ... };
struct SpecialDataForEnemyB{ ... }:
struct EnemyData
{
//共通データ
int HP;
...
//敵毎に異なるデータへのポインタ
void *pSpecialData; //このインスタンスの敵種類用のデータ.
//種類ごとに分けたい処理
void (*ProcFunc)( EnemyData * ); //このインスタンスの敵種類用の処理関数へのポインタ
};
pSpecialDataを実際の型にキャストして使うとかの方が,いくらかマシに思います.
for( すべての敵 ){ 処理 }
みたいなことが書けるので.
Re: STGで敵の種類ごとに処理を分ける方法について
うっかり送信したのでつづき.
こんな感じ.
こんな感じ.
//----------
//種類Aの敵の作成
//(1)メモリ確保
EnemyData *pNewEnemy = (EnemyData*)malloc( sizeof(EnemyData) ); //※多分動的にmallocとかで用意する.
SpecialDataForEnemyA *pNewSPD = (SpacialDataForEnemyA)malloc( sizeof(SpecialDataForEnemyA) );
pNewEnemy->pSpecialData = pNewSPD;
//(2)pNewSPDの内容をちゃんと準備する.
pNewSPD->XXX = ...;
//(3)Aに合わせた処理関数を設定しておく
pNewEnemy->ProcFunc = MoveFuncOfEnemyA;
//(4)例えば「今いる敵リスト」みたいなのにpNewEnemyを追加する.
AddToEnemyList( pNewEnemy );
//----------
//敵の移動処理をやるところ
for( すべての敵 pEnemyについて ) //例えば「今いる敵リスト」内のすべての敵について
{
pEnemy->ProcFunc( pEnemy ); //インスタンスごとの処理関数を呼ぶ
}
//----------
//種類A用の移動処理関数
void MoveFuncOfEnemyA( EnemyData *pEnemy )
{
//void*を適切な型にキャスト
SpecialDataForEnemyA *pDataForA = ( SpecialDataForEnemyA* )( pEnemy->pSpecialData );
//処理…
}
Re: STGで敵の種類ごとに処理を分ける方法について
クラスの機能を使うとまとめられるかなと思います。
具体的には敵の基底クラスを作ってあとは派生先で個別の実装処理を書いていく、という方法です。
を継承先で実装する感じです。
どのタイミングで呼ぶべきかは考えてみましょう。
この辺の実装方法はアクションゲームなんかでも共通で、たとえばステージに配置されるギミックみたいなものも
このような方法で実装できます。
ほとんどデータみたいなものなのでスクリプトにしてあげることも可能らしいですが、
私は仕事でもプライベートでもスクリプトを使って実装したことがないのでその実装方法に関しては書けません。
この辺のロジックに対する考え方は「タスクシステム」で検索してみたり、
「弾幕 最強のシューティングゲームを作る(だったかな?)」
という技術書を買ってみると参考になるかもしれません。
P.S
日本語でクラス名とか関数名を書いたほうがわかりやすいかなと思ったのですが、
かえってわけわかんなくなった気がしております(;´・ω・)
具体的には敵の基底クラスを作ってあとは派生先で個別の実装処理を書いていく、という方法です。
class 敵基底 {
public :
virtual void 生成時処理(){}
virtual void あたり判定成立イベント(){}
virtual void 死亡イベント(){}
virtual void 更新処理実装(){}
void 更新処理基底(){
生成時処理();
あたり判定成立イベント();
死亡イベント();
更新処理実装();
}
void 描画処理基底(){}
};
class 敵A : public 敵基底{
敵A専用の処理があれば各関数に記述
};
class 敵B : public 敵基底{
敵B専用の処理があれば各関数に記述
};
class 敵C : public 敵基底{
敵C専用の処理があれば各関数に記述
};
class 敵D : public 敵基底{
敵D専用の処理があれば各関数に記述
};
std::list<敵基底*> 敵生きてるリスト
敵生きてるリスト.push_back( new 敵A );
敵生きてるリスト.push_back( new 敵B );
敵生きてるリスト.push_back( new 敵C );
敵生きてるリスト.push_back( new 敵D );
for( std::list<敵基底*>::iterator 敵個別 = 敵生きてるリスト.begin() ; 敵個別 != 敵生きてるリスト.end() ; 敵個別++ ){
(*敵個別)->更新処理()
(*敵個別)->描画処理()
}
virtual void 生成時処理(){}
virtual void あたり判定成立イベント(){}
virtual void 死亡イベント(){}
virtual void 更新処理実装(){}
どのタイミングで呼ぶべきかは考えてみましょう。
この辺の実装方法はアクションゲームなんかでも共通で、たとえばステージに配置されるギミックみたいなものも
このような方法で実装できます。
ほとんどデータみたいなものなのでスクリプトにしてあげることも可能らしいですが、
私は仕事でもプライベートでもスクリプトを使って実装したことがないのでその実装方法に関しては書けません。
この辺のロジックに対する考え方は「タスクシステム」で検索してみたり、
「弾幕 最強のシューティングゲームを作る(だったかな?)」
という技術書を買ってみると参考になるかもしれません。
P.S
日本語でクラス名とか関数名を書いたほうがわかりやすいかなと思ったのですが、
かえってわけわかんなくなった気がしております(;´・ω・)
ヽ(*゚д゚)ノ カイバー
Re: STGで敵の種類ごとに処理を分ける方法について
>usaoさん
なるほど・・・。
{すべての敵で共通するデータ + 種類ごとの特殊なデータ} をこんな方法で実装できるんですね。私は関数へのポインタは使ったことが無いのでこんなスマートな方法があるとは思いませんでした。
C言語でもこんな処理ができるとはちょっと驚きです。これからしっかり関数へのポインタを勉強しようと思います。わかりやすい例をありがとうございました。
>せんちゃさん
回答ありがとうございます。
オブジェクト指向はまだ勉強中なので残念ながら今回はクラスを使う予定はありません。
ですがオブジェクト指向のトレーニングでそのうちまたシューティングを作ろうと思っているのでそこで参考にさせていただきます。やはりSTLも活用するべきですね。
なるほど・・・。
{すべての敵で共通するデータ + 種類ごとの特殊なデータ} をこんな方法で実装できるんですね。私は関数へのポインタは使ったことが無いのでこんなスマートな方法があるとは思いませんでした。
C言語でもこんな処理ができるとはちょっと驚きです。これからしっかり関数へのポインタを勉強しようと思います。わかりやすい例をありがとうございました。
>せんちゃさん
回答ありがとうございます。
オブジェクト指向はまだ勉強中なので残念ながら今回はクラスを使う予定はありません。
ですがオブジェクト指向のトレーニングでそのうちまたシューティングを作ろうと思っているのでそこで参考にさせていただきます。やはりSTLも活用するべきですね。
Re: STGで敵の種類ごとに処理を分ける方法について
私が示した疑似コードは
>基底クラスを作ってあとは派生先で個別の実装処理を書いていく
…的な仕組みをCで自前で書いてみた感じのものです(C言語 とのことだったので).
敵の情報をまとめるstruct → クラス
敵の種類を問わない共通のデータ → 基底クラスのメンバ変数
敵の種類ごとにことなるデータ(void *) → 派生クラスのメンバ変数
敵の種類ごとに処理を分岐するための関数ポインタ → 仮想関数
みたいな感じで,C++ではこれを言語の機能でやってくれるわけですから,
「全てCで書く」とかいう縛りが特段ないのであれば
{クラス→継承→ポリフォリズム}あたりまで少し情報収集してみると今後の制作においてかなり楽ができると思います.
>基底クラスを作ってあとは派生先で個別の実装処理を書いていく
…的な仕組みをCで自前で書いてみた感じのものです(C言語 とのことだったので).
敵の情報をまとめるstruct → クラス
敵の種類を問わない共通のデータ → 基底クラスのメンバ変数
敵の種類ごとにことなるデータ(void *) → 派生クラスのメンバ変数
敵の種類ごとに処理を分岐するための関数ポインタ → 仮想関数
みたいな感じで,C++ではこれを言語の機能でやってくれるわけですから,
「全てCで書く」とかいう縛りが特段ないのであれば
{クラス→継承→ポリフォリズム}あたりまで少し情報収集してみると今後の制作においてかなり楽ができると思います.