プロジェクト演習Ⅳ インタラクティブゲーム制作 プログラミング4 2011/11/22 オブジェクト指向設計論 &みんなで設計に突っ込みあおう 今日の内容 • どうオブジェクト指向で作るのか – クラスの設計理論 • 包含か、継承か – newの使いどころはどこなのか なぜ オブジェクト指向で作るのか • これはもう皆さん理解しているはず – ゲーム内のオブジェクトを、それを構成する 変数と関数をセットにして表現するため – 変数1つで複雑なものが表現でき、変数を量 産すれば同じ種類のものを量産できる – ある物事についての処理が隔離できるため、 あちこちに処理が分散しなくてすむ • ではこれをどう設計すればいいのか? 皆さんが作る多くのクラスは 「参照オブジェクト」です • 「C++クラス設計のノート」を参照 – http://www.ogis-ri.co.jp/otc/hiroba/technical/CppDesignNote/ • シーン、キャラクター、エフェクトなど 大抵のゲーム内に登場する物は全て該当 します • 参照オブジェクトを効果的に運用するた めに、値オブジェクトを設計することも あります – fk_Textureに対するfk_TexCoordなど 包含関係(Has-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(this) { } ガチガチバインドの問題点 • あまりクラス分けした意味がなくなる – 特にfriend宣言付けると無法地帯になるので節度 を持った参照を心がけること • オブジェクトの寿命がずれると悲惨 – 別の場所でdeleteされたポインタを持ち続けるケ ースが発生しやすくなる • メンバに引数を渡すのがちょっと面倒 – ポインタにしてnew/deleteするか、 – コンストラクタの初期化子リストを使う • 私も最近まで使うの嫌でしたが、使うようになった 継承関係(Is-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(); 継承を使わなかったら どうなるか? • 手間はかかるが何とかなる – JavaとかC#だとそうは行かないが… • 差分プログラミングの代わりに – 毎回全部のメンバを書いたクラスをコピーし て書き換えて使う • ポリモフィズムの代わりに – 想定される全ての機能、データを盛り込んだ クラスを作り、オブジェクトごとにモードを 切り替えて使う 継承を使うのは 必要に迫られてからでいい • 無理して使うと設計がぐちゃぐちゃにな って破綻する • が、使えると色々スッキリするのは事実 – 同じコードをコピペしないで済む – それぞれ別々の変数を用意しなくて済む • スマートになりますよね – 条件分岐がオブジェクト生成時だけで済む • これが一番でかい new/deleteが必要な場所 • オブジェクトが作られるタイミング以外 で新たなオブジェクトを生成したい場合 – 「クラスのインスタンスが生成された時」と 「スコープに入った時」以外 • スコープの枠を超えた寿命を持つオブジ ェクトを作りたい場合 • スタックのサイズを超える配列を作りた い場合 • 可変長オブジェクト配列を作りたい場合 解体責任をはっきりさせること • コンストラクタでnewしたらデストラク タで始末する • ポインタ変数はNULLで初期化しておく – newするときはNULLチェックして、 多重new(メモリリーク一直線)を防ぐ – deleteするときはポインタにNULLを代入 • 解体場所がずれる設計をする場合は、 必ずその場所をコメントに残す デザインパターンとの 付き合い方 • 実は今日話した内容のいくつかは、デザイン パターンのいくつかに当てはまります – 実践的な設計の経験が無い状態で眺めてみても、 あまり意味がありません • あれはプログラミング中級者以上の「あるあ る集」だと思いましょう – ある程度分かるようになったら中級者の証 – その勢いで他のパターンにも手を出して試してみ るのが吉
© Copyright 2025 ExpyDoc