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


9

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

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

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

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

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

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

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

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


2
ゲーム業界の多くは、Entity-Component-Systemアーキテクチャとそのバリエーションに移行しています。これは、従来のOOアプローチとは異なる考え方ですが、うまく機能し、コンセプトが浸透すると意味をなします。Unityはそれを使用します。実際、UnityはEntity-Componentパーツを使用するだけですが、ECSに基づいています。
ダンク

クラスが互いに知らずに相互にコラボレーションできるようにするという問題は、メディエーターの設計パターンによって解決されます。あなたはそれを見ましたか?
Fuhrmanator、2015年

回答:


13

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

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

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


2

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

番号!

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

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

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

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

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

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


1

オブジェクト指向のゲーム開発に精通するために私が作成した非常にシンプルなゲームである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のすべてを検索して、すべてをご覧になることをお勧めします。

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


答えてくれてありがとう(そしてあなたがリンクしたページ;面白そうだ)。私は実際には抽象化と再利用性について知っていますが、私の回答ではそれをうまく説明していなかったと思います。しかし、あなたが提供したサンプルコードから、今私のポイントをよりよく説明することができます!あなたは基本的に敵オブジェクトがすべきではないと言っていますmario.coins = 0;mario.loseCoins();、それは結構で真実です-しかし私の主張は、marioとにかく敵はどうやってオブジェクトにアクセスできるのでしょうか?marioのメンバー変数であることenemyは私には適切ではないようです。
vvye

簡単な答えは、マリオを敵の関数の引数として渡すことです。マリオを引数としてとるmarioNearby()やattackMario()のような関数があるかもしれません。したがって、EnemyとMarioが相互作用するときの背後にあるロジックがトリガーされるたびに、mario.loseCoins();を呼び出すenemy.marioNearby(mario)を呼び出します。後で、マリオにコインを1枚だけ失うか、コインを獲得することになる敵のクラスがあると決めるかもしれません。これで、他のコードに副作用の変更を引き起こさない変更を行う場所が1つあります。
Mark Murfin、2015年

マリオを敵に渡すことによって、あなたはそれらを結合しただけです。マリオと敵は、もう一方が事物であることを知らないはずです。これが、単純なオブジェクトを結合する方法を管理するために高次オブジェクトを作成する理由です。
Tezra

@Tezraしかし、これらの高次オブジェクトは再利用できませんか?これらのオブジェクトは関数として機能しているように感じられ、それらが示す手順としてのみ存在します。
Steve Chamaillard

@SteveChamaillardすべてのプログラムには、他のプログラムでは意味をなさない特定のロジックが少なくとも少しはありますが、このロジックをいくつかの高次クラスに分離しておくことが目的です。マリオ、敵、レベルのクラスがある場合、マリオと敵を他のゲームで再利用できます。敵とマリオを直接結びつけると、一方を必要とするどのゲームよりももう一方も引き寄せる必要があります。
Tezra

0

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.