http://dixq.net/forum/viewtopic.php?f=3&t=11734
お久しぶりです。前回「STGクラス設計」というトピックで質問させていただきました、タンタルと申します。
あの後、結局普通にC言語で2Dゲームを作ったり、C#でフォームアプリケーションを作ったりして、ようやくクラスのイメージもつかめてきて、上のトピックで言われたことの意味が分かってきたところです。
ただ、クラスの利用に関してはいいものの、いざ自分で設計となると、どうしようかなとなってしまうところがあるので、そこの質問をさせていただきたいと思います。大まかにゲームの仕様をまとめている段階なので、具体的なコードは出せませんが、よろしくお願いします。
敵クラスの設計についてです。
現在、基底クラスに座標やカウンタ,フラグなどの共通要素を持ったObjectクラスを定義して、それを継承してEnemyクラスを作り、EnemyManageクラスがEnemyクラスのインスタンスをlistなり固定配列なりで管理する、という設計にしようかなと考えています。
ただ、同じEnemyクラスでも、いろんな種類の敵がいるわけで、いろんな画像で、いろんな攻撃をして、いろんな動きをして...一体ごとに異なってくると思います。ここで、その設計方法なのですが
1.Enemyクラスにkind,movePattern,attackPatternのような分岐用の変数を持たせて、毎回switchしたり関数ポインタでコールバック関数を作って動かす。
2.Enemyクラスをさらに継承して、あらゆる種類の敵クラスを作り、移動や攻撃する関数をオーバーライドする。
とりあえずこの二つがぱっと思い浮かびました。ですが、はたしてどちらがいいのか...おそらくどちらでもよくて、どちらにも問題はあるとは思うのですが、いまいちそれぞれの利点と欠点が分からないため、評価もできない状態です。
皆さんはどうでしょうか。1はこんなところがいい、悪い。2はこんな使い方ができるからから便利、不便。いや、こういう設計の方が絶対にいい!など、皆さんの考えを教えてください。
以下は本編とは関係ないことですので聞き流していただいて構いません。
こういう機能を実装したいって質問は多いですけど、クラス設計についての質問ってあまり見かけないんですよね。みなさんどこで勉強してるんでしょうか...クラス使うならJAVAだから、C++での情報が少ないとかですかね。
異なる種類の敵はクラスを分けるべきか
Re: 異なる種類の敵はクラスを分けるべきか
私ならば、敵の動きに関する処理はLuaやその他のスクリプト言語で記述すると思います。
敵の種類や挙動は大量のパターンが必要ですし、直接記述をすればその分C++のコードは肥大します。
そうなれば調整やデバッグの際のリコンパイルにかかる時間も馬鹿にならなくなる(かもしれない)でしょう。
スクリプトなどの外部ファイルに投げることによって、リコンパイルなしに敵の挙動やゲームの進行を変更することが出来ます。
>以下は本編とは……
以前このフォーラムでみたSTGのクラス設計に関する質問です。
http://dixq.net/forum/viewtopic.php?f=3&t=13591
クラス関連の質問は他にも探せばたくさんあるとは思います。
敵の種類や挙動は大量のパターンが必要ですし、直接記述をすればその分C++のコードは肥大します。
そうなれば調整やデバッグの際のリコンパイルにかかる時間も馬鹿にならなくなる(かもしれない)でしょう。
スクリプトなどの外部ファイルに投げることによって、リコンパイルなしに敵の挙動やゲームの進行を変更することが出来ます。
>以下は本編とは……
以前このフォーラムでみたSTGのクラス設計に関する質問です。
http://dixq.net/forum/viewtopic.php?f=3&t=13591
クラス関連の質問は他にも探せばたくさんあるとは思います。
Re: 異なる種類の敵はクラスを分けるべきか
敵キャラの管理では、継承よりも委譲のほうが適していると思います。
1.です。
外部スクリプトでなくとも翻訳単位で分離できますし、大量に発生する敵キャラのオブジェクトプールを作る場合も共通化が図れてシンプルになると思います。
1.です。
外部スクリプトでなくとも翻訳単位で分離できますし、大量に発生する敵キャラのオブジェクトプールを作る場合も共通化が図れてシンプルになると思います。
Re: 異なる種類の敵はクラスを分けるべきか
涼雅さん、ISLeさん、返信ありがとうございます。
もう少し深くお聞きしてもよろしいでしょうか。
涼雅さんはLuaを使うとおっしゃいましたが、どの移動用関数を使うか(どの関数を呼び出すか)、という判断をどのように行うのでしょうか。
おそらくですが、LuaにはMove_01(),Move_02(), ...といった具合に複数の移動パターンが書かれているのだと思います。ではEnemyはどの関数を使うのか、その判別方法はどのようにしているのでしょうか。
ISLeさんのおっしゃる翻訳単位で分割とは分割コンパイルのことで、移動用の関数のパターンはそれ専用のmovePattern.cppなどにまとめておけばいいということでしょうか。
あと、共通化が図れるとはどういう意味でしょうか。もう少し説明をお願いします。
もう少し深くお聞きしてもよろしいでしょうか。
涼雅さんはLuaを使うとおっしゃいましたが、どの移動用関数を使うか(どの関数を呼び出すか)、という判断をどのように行うのでしょうか。
おそらくですが、LuaにはMove_01(),Move_02(), ...といった具合に複数の移動パターンが書かれているのだと思います。ではEnemyはどの関数を使うのか、その判別方法はどのようにしているのでしょうか。
ISLeさんのおっしゃる翻訳単位で分割とは分割コンパイルのことで、移動用の関数のパターンはそれ専用のmovePattern.cppなどにまとめておけばいいということでしょうか。
あと、共通化が図れるとはどういう意味でしょうか。もう少し説明をお願いします。
Re: 異なる種類の敵はクラスを分けるべきか
翻訳単位での分割はそのとおりです。
Enemyクラスがインターフェースとして働くことになるのでそれに関する部分は自ずと共通化されることになります。
各キャラクタの固有部分が分離されて、オブジェクト別に状況に応じた柔軟な設計が可能になります。
全体的にはシンプルでないかもしれませんが再利用性の高いシンプルな実装の組み合わせで作れるメリットは大きいと思います。
Enemyクラスがインターフェースとして働くことになるのでそれに関する部分は自ずと共通化されることになります。
各キャラクタの固有部分が分離されて、オブジェクト別に状況に応じた柔軟な設計が可能になります。
全体的にはシンプルでないかもしれませんが再利用性の高いシンプルな実装の組み合わせで作れるメリットは大きいと思います。
Re: 異なる種類の敵はクラスを分けるべきか
一例ですが、敵クラスにどのLua関数を呼び出すかのデータを整数値か、あるいは別の形式で持たせ、
そのデータを使ってLua関数にアクセスする、あるいは呼び出すようにします。
そのデータを使ってLua関数にアクセスする、あるいは呼び出すようにします。
Re: 異なる種類の敵はクラスを分けるべきか
返信ありがとうございます。
Enemyクラスにメンバ変数を持たせて、処理を切り替える方法ですね。
涼雅さん、ありがとうございました。ゲーム制作の後半なんかはバランス調整がメインで、移動関数とかこまごまといじって、いちいちコンパイルするのは確かにつらいですから、スクリプトについてはクラス設計の後に考えてみたいと思います。
ISLeさん、一度確認したいのですが、現在私たちは、複数種類の敵を作るときはEnemyクラスを継承せず、メンバ変数に種類判別(パターン)の変数を持たせることで対応する、という前提で話を進めていますよね?
今回の返信を読むと、Enemyクラスを継承して、それぞれ固有の変数を持たせてやり、Move()なりUpdate()なりをオーバーライドすることで、敵全体を同じように扱いながら、個体ごとに別々の動きを可能にする、とおっしゃっているように感じました。
委譲について調べましたが、インターフェース(純粋仮想関数?)クラスを継承するという解釈で間違いないでしょうか。
となると、やはり上記のように、同じMove()でも、Enemyクラスを継承した、Enemy_01,Enemy_02...クラスがMove()をそれぞれオーバーライドする設計になるのでしょうか。
Enemyクラスにメンバ変数を持たせて、処理を切り替える方法ですね。
涼雅さん、ありがとうございました。ゲーム制作の後半なんかはバランス調整がメインで、移動関数とかこまごまといじって、いちいちコンパイルするのは確かにつらいですから、スクリプトについてはクラス設計の後に考えてみたいと思います。
ISLeさん、一度確認したいのですが、現在私たちは、複数種類の敵を作るときはEnemyクラスを継承せず、メンバ変数に種類判別(パターン)の変数を持たせることで対応する、という前提で話を進めていますよね?
今回の返信を読むと、Enemyクラスを継承して、それぞれ固有の変数を持たせてやり、Move()なりUpdate()なりをオーバーライドすることで、敵全体を同じように扱いながら、個体ごとに別々の動きを可能にする、とおっしゃっているように感じました。
委譲について調べましたが、インターフェース(純粋仮想関数?)クラスを継承するという解釈で間違いないでしょうか。
となると、やはり上記のように、同じMove()でも、Enemyクラスを継承した、Enemy_01,Enemy_02...クラスがMove()をそれぞれオーバーライドする設計になるのでしょうか。
Re: 異なる種類の敵はクラスを分けるべきか
委譲というのは簡単に言うと他のオブジェクトに処理を丸投げすることです。
インターフェースというのは言語機能等のことではなく言葉どおりの働きのことです。
インターフェースというのは言語機能等のことではなく言葉どおりの働きのことです。
Re: 異なる種類の敵はクラスを分けるべきか
どっちの方が{良いか,便利か}ではなくて,
メリットデメリットみたいなこと という話に関してですが,
2.の方法のデメリットとして,単純に「コード書くのが面倒そう」とか思いました.
例えば,敵の種類による違いが{移動方法,攻撃方法,鳴き方,ごはんの食べ方}の4種類であって,
それぞれの要素が2パターンずつある場合,最大16通りの敵の種類が作れるけど,
2.の方法を採って,仮に敵の種類を全部クラスで分けるとしたら
16個のクラスを書かないとならない.
移動と攻撃と鳴く方法は一緒だけどご飯の食べ方だけがちょっと違う種類 を用意するために
新しいクラスを作らなければならない というのは面倒そう.
あと,あるタイミングで出現させるべき敵の種類 というのはデータファイルか何かからロードした情報から
決まることになるのだと思うのですが,
「ここでは種類番号105番の種類の敵を出現させろ」とかいうデータを読んだときに,そこから
CEnemy *pNewEnemy = new CEnemy_Type105;
まで行き着く過程
(「105番目の種類」という情報から,CEnemy_Type105 というクラス名を導出するまでの過程)
をどうするのか考えるのも面倒だったりしないかな,とか.
それとは別に,便利さ,という点で考えると
1.の方法だと,振る舞いを動的に変えるようなことも簡単にできますね.
Strategy?
メリットデメリットみたいなこと という話に関してですが,
2.の方法のデメリットとして,単純に「コード書くのが面倒そう」とか思いました.
例えば,敵の種類による違いが{移動方法,攻撃方法,鳴き方,ごはんの食べ方}の4種類であって,
それぞれの要素が2パターンずつある場合,最大16通りの敵の種類が作れるけど,
2.の方法を採って,仮に敵の種類を全部クラスで分けるとしたら
16個のクラスを書かないとならない.
移動と攻撃と鳴く方法は一緒だけどご飯の食べ方だけがちょっと違う種類 を用意するために
新しいクラスを作らなければならない というのは面倒そう.
あと,あるタイミングで出現させるべき敵の種類 というのはデータファイルか何かからロードした情報から
決まることになるのだと思うのですが,
「ここでは種類番号105番の種類の敵を出現させろ」とかいうデータを読んだときに,そこから
CEnemy *pNewEnemy = new CEnemy_Type105;
まで行き着く過程
(「105番目の種類」という情報から,CEnemy_Type105 というクラス名を導出するまでの過程)
をどうするのか考えるのも面倒だったりしないかな,とか.
それとは別に,便利さ,という点で考えると
1.の方法だと,振る舞いを動的に変えるようなことも簡単にできますね.
Strategy?
Re: 異なる種類の敵はクラスを分けるべきか
ISLeさん、usaoさん、ありがとうございます。
実際usaoさんのおっしゃる通りで、いちいちクラスを作って、全部コードを書いてってやると、恐ろしく面倒なだけなんですよね...
とにかく変な形式にこだわらず、出来るところまで1の方法でやってみようと思います。
みなさん本当にありがとうございました。
実際usaoさんのおっしゃる通りで、いちいちクラスを作って、全部コードを書いてってやると、恐ろしく面倒なだけなんですよね...
とにかく変な形式にこだわらず、出来るところまで1の方法でやってみようと思います。
みなさん本当にありがとうございました。