web-dev-qa-db-ja.com

階層を強制せずに、オブジェクトを相互に対話および通信させるにはどうすればよいですか?

これらのとりとめのない質問で私の質問が明確になることを願っています。ただし、そうでない場合は完全に理解できます。その場合はその旨をお知らせください。さらに明確にしていきます。

会う BoxPong 、オブジェクト指向のゲーム開発に精通するために私が作った非常にシンプルなゲーム。ボックスをドラッグしてボールを操作し、黄色の物を集めます。
BoxPongを作成することは、とりわけ、基本的な質問を定式化するのに役立ちました:互いに「所属する」必要なしに、互いに相互作用するオブジェクトをどのように作成できますか?言い換えれば、オブジェクトを階層化せずに共存させる方法はありますか? (以下でさらに詳しく説明します。)

オブジェクトの共存の問題はよくある問題だと思うので、解決する確立された方法があるといいのですが。私は四角いホイールを作り直したくないので、私が探している理想的な答えは「ここに、あなたの種類の問題を解決するために一般的に使用される設計パターンがある」と思います。

特にBoxPongのような単純なゲームでは、同じレベルで少数のオブジェクトが共存している、または共存している必要があることは明らかです。箱があり、ボールがあり、収集品があります。私がオブジェクト指向言語で表現できるのは、厳密に言えばHAS-A関係です。これは、メンバー変数を介して行われます。 ballから始めてそれを実行させることはできません。永続的にbelongを別のオブジェクトに渡す必要があります。メインのゲームオブジェクトhasボックス、そしてボックス--hasボール、そしてhasスコアになるように設定しましたカウンター。各オブジェクトには、位置、方向などを計算するupdate()メソッドもあります。ここでも同様の方法で行っています。メインのゲームオブジェクトのupdateメソッドを呼び出し、すべての子のupdateメソッドを呼び出します。次に、すべてのtheir子のupdateメソッドを呼び出します。これがオブジェクト指向のゲームを作るために私が見ることができる唯一の方法ですが、それは理想的な方法ではないと感じています。結局のところ、私はボールをボックスに属していると正確に考えるのではなく、同じレベルにいて、ボールと相互作用していると考えています。これは、すべてのゲームオブジェクトをメインゲームオブジェクトのメンバー変数に変換することで達成できると思いますが、何も解決しないと思います。つまり...明白な混乱をさけて、ボールとボックスがお互いを知る、つまり、相互作用する方法はどのようにありますか?

オブジェクトが相互に情報を渡す必要があるという問題もあります。私はSNESのコードを書くのにかなりの経験があります。ここでは、実質的に常にRAM全体にアクセスできます。たとえば、カスタムの敵を作っているとしますスーパーマリオ世界、そしてあなたはそれをすべてのマリオのコインを削除して、それからゼロを格納して$ 0DBFをアドレス指定するだけで問題ありません。敵がプレイヤーのステータスにアクセスできないという制限はありません。 C++などでは、他のオブジェクト(またはグローバル)から値にアクセスできるようにする方法を考えていることがよくあるからです。
BoxPongの例を使用して、ボールが画面の端から跳ね返るようにしたい場合はどうなりますか? widthheightGameクラスのプロパティであり、これらにアクセスするにはballが必要です。これらの種類の値を(コンストラクターまたは必要なメソッドを介して)渡すことができますが、それは私に悪い習慣を呼び起こします。

私の主な問題は、お互いを知るためにオブジェクトが必要であるということですが、それを行うために私が見ることができる唯一の方法は、厳密で階層的であり、醜く非実用的です。

私はC++の「友達クラス」について聞いたことがありますが、それらがどのように機能するかは知っていますが、それらがすべての解決策である場合、単一のC++全体にfriendキーワードが注がれていないのはなぜですかプロジェクト、そしてなぜすべてのOOP言語に概念が存在しないのですか?(私が最近学んだばかりの関数ポインタについても同じことが言えます。)

あらゆる種類の回答を事前にありがとう—そして、あなたに意味をなさない部分があれば、私に知らせてください。

9
vvye

一般に、同じレベルのオブジェクトがお互いについて知っている場合、それは非常に悪いことが判明します。オブジェクトがお互いについて知ったら、それらは結び付けられるか、またはcoupledがお互いに結び付けられます。これにより、変更、テスト、保守が困難になります。

2つについて知っていて、それらの間の相互作用を設定できる「上」にオブジェクトがある場合、それははるかにうまく機能します。 2つのピアについて知っているオブジェクトは、依存関係の注入、イベント、またはメッセージパッシング(またはその他のデカップリングメカニズム)を介してそれらを結合できます。はい、それは少し人為的な階層につながりますが、物事が単に無意味に相互作用するときに得られるスパゲッティの混乱よりもはるかに優れています。オブジェクトの存続期間を所有するためにも何かが必要なため、C++ではそれがより重要です。

要するに、アドホックアクセスによってオブジェクトをどこにでも並べて配置するだけでそれを実現できますが、それは悪い考えです。階層は順序と明確な所有権を提供します。覚えておくべき主なことは、コード内のオブジェクトは必ずしも実際のオブジェクト(またはゲーム)である必要はないということです。ゲーム内のオブジェクトが適切な階層にならない場合は、別の抽象化が適している場合があります。

13
Telastyn

BoxPongの例を使用して、ボールが画面の端から跳ね返るようにしたい場合はどうなりますか? widthとheightはGameクラスのプロパティであり、それらにアクセスするにはボールが必要です。

番号!

あなたが抱えている主な問題は、あなたが「オブジェクト指向プログラミング」を文字通り少し多すぎると思っていることだと思います。 OOPでは、オブジェクトは「もの」ではなく「アイデア」を表します。つまり、「ボール」、「ゲーム」、「物理」、「数学」、「日付」などを意味します。すべてが有効なオブジェクトです。オブジェクトが何かについて「知っている」必要もありません。たとえば、Date.Now().getTommorrow()は、コンピューターに今日の日を尋ね、難解な日付ルールを適用して明日の日付を計算し、それを呼び出し元に返します。 Dateオブジェクトは他に何も知りません。システムから必要に応じて情報を要求するだけです。また、Math.SquareRoot(number)は、平方根の計算方法のロジック以外に何も知る必要はありません。

だから私が引用したあなたの例では、「ボール」は「ボックス」について何も知らないはずです。ボックスとボールはまったく異なるアイデアであり、互いに話す権利はありません。しかし、物理エンジンは、ボックスとボール(または少なくともThreeDShape)が何であるかを知っており、それらがどこにあるのか、そして何が起きているのかを知っています。したがって、ボールが冷えているためにボールが収縮した場合、物理エンジンはそのボールのインスタンスに、現在ボールが小さいことを通知します。

まるで自動車を作るようなものです。コンピューターチップは車のエンジンについて何も知りませんが、車はコンピューターチップを使用してエンジンを制御できます。小さくて単純なものを一緒に使用して少し大きくて複雑なものを作成するという単純なアイデアは、それ自体が他のより複雑なパーツのコンポーネントとして再利用できます。

そして、あなたのマリオの例では、敵に触れてもマリオスのコインが排出されず、その部屋から彼を追い出すだけのチャレンジルームにいる場合はどうでしょうか。マリオが敵に触れたときにコインを失うのは、マリオまたは敵のアイデア空間の外です(実際、マリオに不死身の星がある場合、代わりに彼は敵を殺します)。したがって、マリオが敵に触れたときに何が起こるかを担当するオブジェクト(ドメイン/アイデア)は、どちらかを知る必要がある唯一のオブジェクトであり、どちらかに対して何でも実行する必要があります(そのオブジェクトが外部からの変更を許可する範囲で) )。

また、子Update()を呼び出すオブジェクトに関するステートメントでは、異なる親からUpdateがフレームごとに複数回呼び出された場合は、バグが発生しやすくなります。 (これを捕まえても、ゲームを遅くする可能性がある無駄なCPU時間です)誰もが必要なときに必要なものだけに触れるべきです。 Update()を使用している場合は、何らかのサブスクリプションパターンを使用して、すべてのUpdateがフレームごとに1回呼び出されるようにする必要があります(これがUnityのように処理されない場合)。

ドメインアイデアを明確で分離された、明確で使いやすいブロックに定義する方法を学ぶことは、OOPをいかにうまく活用できるかについての最大の要素です。

2
Tezra

オブジェクト指向のゲーム開発に精通するために私が作成した非常にシンプルなゲームであるBoxPongをご覧ください。

BoxPongを作成することは、とりわけ、基本的な質問を定式化するのに役立ちました:互いに「属している」必要なしに、互いに相互作用するオブジェクトをどのようにして持つことができますか?

私はSNESのコードを書くのにかなりの経験があります。そこでは、実質的に常にRAM全体にアクセスできます。スーパーマリオワールドのカスタムの敵を作っているとします。マリオのコインをすべて削除し、ゼロを格納して$ 0DBFに対処するだけです。問題ありません。

オブジェクト指向プログラミングの要点が欠けているようです。

オブジェクト指向プログラミングは、アーキテクチャ内の特定の主要な依存関係を選択的に反転させて依存関係を管理することにより、剛性、脆弱性、および非再利用性を防ぐことを目的としています。

依存とは何ですか?依存関係は他のものへの依存です。 $ 0DBFをアドレス指定するためにゼロを格納する場合、そのアドレスはマリオのコインが置かれている場所であり、コインは整数として表されるという事実に依存しています。カスタム敵のコードは、マリオと彼のコインを実装するコードに依存しています。マリオが彼のコインをメモリに保存する場所を変更した場合、メモリの場所を参照するすべてのコードを手動で更新する必要があります。

オブジェクト指向コードとは、コードを詳細ではなく抽象化に依存させることです。だから代わりに

class Mario
{
    public:
        int coins;
}

あなたは書くでしょう

class Mario
{
    public:
        void LoseCoins();

    private:
        int coins;
}

ここで、マリオがコインをintからlongまたはdoubleに格納する方法を変更したり、ネットワークに格納したり、データベースに格納したり、他のいくつかの長いプロセスを開始したりする場合は、1か所で変更を行います。マリオクラス、および他のすべてのコードは変更なしで動作し続けます。

したがって、あなたが尋ねたとき

どのようにして、互いに「所属」する必要なしに、互いに相互作用するオブジェクトを持つことができますか?

あなたは本当に求めています:

どのようにして抽象化なしで互いに直接依存するコードを作成できますか?

これはオブジェクト指向プログラミングではありません。

ここですべてを読むことから始めることをお勧めします: http://objectmentor.com/omSolutions/oops_what.html 次に、YouTubeでRobert Martinのすべてを検索し、すべてを見てください。

私の回答は彼から導き出されたものであり、その一部は彼から直接引用されています。

1
Mark Murfin

メディエーターパターンを適用することで、疎結合を有効にできます。それを実装するには、すべての受信コンポーネントを知っているメディエーターコンポーネントへの参照が必要です。

それは「ゲームマスター、これを起こさせてください」という考え方を実現します。

一般化されたパターンは、パブリッシュ/サブスクライブパターンです。メディエーターに多くのロジックを含めないようにするのが適切です。それ以外の場合は、すべての通話をどこにルーティングするかを知っている手作りのメディエーターを使用し、おそらくそれらを変更します。

通常、イベントバス、メッセージバス、またはメッセージキューと呼ばれる同期および非同期のバリアントがあります。それらを調べて、特定のケースに適しているかどうかを判断します。

0
Hero Wanders