180 likes | 254 Views
プロジェクト演習 Ⅳ ・ Ⅵ インタラクティブゲーム制作. 第 10 回 オブジェクト指向設計論. 今日の内容. どうオブジェクト指向で作るのか クラスの設計理論 包含か、継承か new の使いどころはどこなのか. なぜ オブジェクト指向で作るのか. これはもう皆さん理解しているはず ゲーム内のオブジェクトを、それを構成する変数と関数をセットにして表現するため 変数 1 つで 複雑なものが表現でき、変数を量産すれば同じ種類のものを量産できる ある物事についての処理が隔離できるため、あちこちに処理が分散しなくてすむ ではこれをどう設計すればいいのか?.
E N D
プロジェクト演習Ⅳ・Ⅵインタラクティブゲーム制作プロジェクト演習Ⅳ・Ⅵインタラクティブゲーム制作 第10回 オブジェクト指向設計論
今日の内容 • どうオブジェクト指向で作るのか • クラスの設計理論 • 包含か、継承か • newの使いどころはどこなのか
なぜオブジェクト指向で作るのか • これはもう皆さん理解しているはず • ゲーム内のオブジェクトを、それを構成する変数と関数をセットにして表現するため • 変数1つで複雑なものが表現でき、変数を量産すれば同じ種類のものを量産できる • ある物事についての処理が隔離できるため、あちこちに処理が分散しなくてすむ • ではこれをどう設計すればいいのか?
皆さんが作る多くのクラスは「参照オブジェクト」です皆さんが作る多くのクラスは「参照オブジェクト」です • 「C++クラス設計のノート」を参照 • http://www.ogis-ri.co.jp/otc/hiroba/technical/CppDesignNote/ • シーン、キャラクター、エフェクトなど大抵のゲーム内に登場する物は全て該当します • 参照オブジェクトを効果的に運用するために、値オブジェクトを設計することもあります • fk_Textureに対するfk_TexCoordなど
包含関係(Have-a関係) • 難しい表現ですが、ぶっちゃければ「メンバ変数にする」ことです • アプリケーションはWindowを持つ • アプリケーションはシーンを持つ • シーンはマップを持つ • マップは地形データを配列で持つ • シーンはキャラを持つ • キャラはモデルを持つ • キャラはミサイルを持つ
メンバ関数にした場合の問題点 • 自分を持っているクラスのメンバを利用できない • シーンはキャラを制御できるが、キャラからはシーンを参照できない • 解決策:メンバにthisポインタを渡す • 関数レベルでのバインドをおすすめする • ガチガチに結びつける手段もあるが、必要最低限にとどめた方が良い
関数レベルのバインド // 飛び道具を表すクラス class Missile { void funcHoge(HogeChara *); }; // キャラを表すクラス class HogeChara { Missile arrow; void update(void); }; void Missile::funcHoge (HogeChara *argOwner) { argOwner->…; } void HogeChara::update(void) { arrow.funcHoge(this); }
ガチガチバインド // 飛び道具を表すクラス class Missile { HogeChara *ownerChara; Missile(HogeChara *); void funcHoge(void); }; // キャラを表すクラス class HogeChara { Missile *arrow; HogeChara(void); }; Missile::Missile (HogeChara *argOwner) { ownerChara = argOwner; } void Missile::funcHoge(void) { ownerChara->…; } HogeChara::HogeChara(void) { arrow= new Missile(this); }
ガチガチバインドの問題点 • あまりクラス分けした意味がなくなる • 特にfriend宣言付けると無法地帯になるので節度を持った参照を心がけること • オブジェクトの寿命がずれると悲惨 • 別の場所でdeleteされたポインタを持ち続けるケースが発生しやすくなる • メンバに引数を渡すのがちょっと面倒 • ポインタにしてnew/deleteする • コンストラクタの初期化リストを使えばポインタ化しないで済むが、正直気持ち悪い
継承関係(Has-a関係) • 「(子クラス)は(親クラス)の1種である」と言える場合のみに使うこと! • TestAppはfkut_AppBaseの1種である • タイトル画面はシーンの1種である • ゲームのメイン画面はシーンの1種である • 剣士はジョブの1種である • 魔法使いはジョブの1種である • カプセルは当たり判定の1種である • OBBは当たり判定の1種である
継承の狙い~何がうれしいのか? • OOPを理解する上での最後の障壁 • 内容が高度なせいもある、要因の1つに「メリットが多すぎる」ことがある • ざっくりと2つに絞って話をする • 差分プログラミング • ポリモフィズム
差分プログラミング • 既にあるクラスへの付け足しや書き換え • fk_Modelを継承して描画処理を変更するなど • 共通の処理や構造をまとめておき、状況に合わせて必要なものを付け足す • fkut_AppBaseを継承してプログラムを作成
ポリモフィズム(多態性) • 共通点のあるクラスをまとめられる • まとめた上で、共通の型で管理できる • 「キャラクター」「シーン」と言った大まかな体系を表すクラスを継承 • 具体的な子クラスを作成して実際にはそちらを利用するが、プログラム上では「キャラクター」クラスの変数、「シーン」クラスの変数として扱う • Parent *obj1 = new ChildA(); • Parent *obj2 = new ChildB();
継承を使わなかったらどうなるか? • 手間はかかるが何とかなる • 差分プログラミングの代わりに • 毎回全部のメンバを書いたクラスをコピーして書き換えて使う • ポリモフィズムの代わりに • 想定される全ての機能、データを盛り込んだクラスを作り、オブジェクトごとにモードを切り替えて使う
継承を使うのは必要に迫られてからでいい • 無理して使うと設計がぐちゃぐちゃになって破綻する • が、使えると色々スッキリするのは事実 • 同じコードをコピペしないで済む • それぞれ別々の変数を用意しなくて済む • スマートになりますよね • 条件分岐がオブジェクト生成時だけで済む • これが一番でかい
new/deleteが必要な場所 • 引数を取るメンバオブジェクトを作る時 • オブジェクトが作られるタイミング以外で新たなオブジェクトを生成したい場合 • スコープの枠を超えた寿命を持つオブジェクトを作りたい場合 • スタックのサイズを超える配列を作りたい場合 • 可変長オブジェクト配列を作りたい場合
解体責任をはっきりさせること • コンストラクタでnewしたらデストラクタで始末する • ポインタ変数はNULLで初期化しておく • newするときはNULLチェックして、多重newを防ぐ • deleteするときはポインタにNULLを代入 • 解体場所がずれる設計をする場合は、必ずその場所をコメントに残す
デザインパターンとの付き合い方 • 実は今日話した内容のいくつかは、デザインパターンのいくつかに当てはまります • 実践的な設計の経験が無い状態で眺めてみても、あまり意味がありません • あれはプログラミング中級者以上の「あるある集」だと思いましょう • ある程度分かるようになったら中級者の証 • その勢いで他のパターンにも手を出して試してみるのが吉