スライド 1

プロジェクト演習Ⅳ
インタラクティブゲーム制作
プログラミング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を代入
• 解体場所がずれる設計をする場合は、
必ずその場所をコメントに残す
デザインパターンとの
付き合い方
• 実は今日話した内容のいくつかは、デザイン
パターンのいくつかに当てはまります
– 実践的な設計の経験が無い状態で眺めてみても、
あまり意味がありません
• あれはプログラミング中級者以上の「あるあ
る集」だと思いましょう
– ある程度分かるようになったら中級者の証
– その勢いで他のパターンにも手を出して試してみ
るのが吉