私は、ビデオゲーム、特に巨大な3Dプロジェクトでオブジェクトの向きがどの程度使用されているのか、常に疑問に思っていました。両方ともその属性troll
をwerewolf
継承しenemy
、それらの一部を上書きしたのはクールだと思います。しかし、クラスは重すぎて実用的ではないかもしれません。
私は、ビデオゲーム、特に巨大な3Dプロジェクトでオブジェクトの向きがどの程度使用されているのか、常に疑問に思っていました。両方ともその属性troll
をwerewolf
継承しenemy
、それらの一部を上書きしたのはクールだと思います。しかし、クラスは重すぎて実用的ではないかもしれません。
回答:
あなたの例に来るために、ビデオゲームのエンティテについての私の考えを説明します。オブジェクトとクラスの一般的な長所と短所については説明しません。ビデオゲームにおけるそれらの役割全体についても説明しません。
小さなビデオゲームのエンティティは、主に別々のクラスで定義されます。大きなゲームでは、多くの場合ファイルで定義されます。次の2つの一般的なアプローチがあります。
拡張:あなたの例Troll
でWerewolf
は拡張しEnemy
、拡張しEntity
ます。Entity
一方で、などの運動、健康などの基本的な事柄についての世話をするEnemy
(Minecraftのは、このアプローチを以下の)敵としてサブクラスを述べると、他のことをするだろう。
コンポーネントベース:2番目のアプローチ(および私のお気に入り)はEntity
、コンポーネントを持つ1つのクラスです。コンポーネントはMoveable
またはEnemy
です。これらのコンポーネントはの世話を1運動や物理学のようなもの。この方法により、長い拡張チェーンが破損し、競合が発生するリスクが最小限に抑えられます。例:移動可能なキャラクターを継承する場合、移動できない敵を作成するにはどうすればよいですか?。
World of WarcraftやStarcraft 2のような大きなビデオゲームでは、エンティティはクラスではなく、エンティティ全体を指定するデータのセットです。エンティティがクラスではなくスクリプトで定義するので、スクリプトも重要です(一意の場合は、移動などではありません)。
私は現在RTSをプログラミングしており、ゲーム内で動的なEntites、Skill、Items、およびその他すべての記述子とスクリプトファイルを使用して、コンポーネントベースのアプローチを採用しています。
component
このコンテキストで何がわからない。リンクをいただければ幸いです。
トロルと狼男の両方が敵からその属性を継承し、それらのいくつかを上書きしたことはクールだと思います。
残念ながら、90年代に人気の多型へのこのアプローチは、実際には悪い考えであることが示されています。敵リストに「オオカミ」を追加することを想像してください-それは、いくつかの属性を狼と共有しているので、共有された基本クラスにそれらを統合したいと思います。「ウルフライク」。ここで、「Human」を敵リストに追加しますが、狼男は時々人間であるため、2本の足で歩く、話すことができるなどの属性も共有します。人間と狼男の共通の「Humanoid」ベースを作成しますか、そしてウルフライクに由来する狼男を停止する必要がありますか?または、両方から継承しますか?その場合、ヒューマノイドの何かがWolfLikeの何かと衝突するときにどの属性が優先されますか?
問題は、実世界をクラスのツリーとしてモデル化しようとしても効果がないことです。3つのことを考えてみると、それらの動作を共有と非共有に分ける4つの異なる方法を見つけることができます。オブジェクトが全体を構成するいくつかの小さなオブジェクトで構成され、必要に応じて小さなオブジェクトを交換したり変更したりできるように、多くの人が代わりにモジュール式またはコンポーネントベースのモデルに変わりました。ここには、1つの敵クラスのみがあり、その歩行方法(2足歩行と4足歩行など)、攻撃方法(咬傷、爪、武器、拳など)、皮膚(緑色)のサブオブジェクトまたはコンポーネントが含まれます。トロール、狼男と狼の毛皮など)
これはまだ完全にオブジェクト指向ですが、有用なオブジェクトの概念は、教科書で一般的に教えられているものとは異なります。さまざまな抽象クラスの大規模な継承ツリーと、ツリーの先端にいくつかの具体的なクラスを持たせるのではなく、通常、抽象概念(たとえば「敵」)を表す1つの具体クラスだけが、より抽象的な概念を表すより具体的なクラスを含む(例、攻撃、肌のタイプ)。これらの2番目のクラスは、1レベルの継承(たとえば、基本攻撃クラスと特定の攻撃の複数の派生クラス)を介して最適に実装できる場合もありますが、深い継承ツリーはなくなります。
しかし、クラスは重すぎて実用的ではないかもしれません。
現代のほとんどの言語では、クラスは「重く」ありません。これらはコードを記述するための別の方法に過ぎず、通常は、構文が簡単な場合を除き、とにかく記述した手続き型アプローチをラップするだけです。しかし、必ず、関数が実行するクラスを作成しないでください。クラスは本質的に優れているのではなく、コードを管理または理解しやすくする場合のみです。
「重すぎる」とはどういう意味かわかりませんが、C ++ / OOPは他の場所で使用されているのと同じ理由でゲーム開発の共通語です。
キャッシュを吹き飛ばすという話は設計上の問題であり、言語機能ではありません。C ++は過去15〜20年間ゲームで使用されてきたため、それほど悪くはなりません。Javaは、スマートフォンの非常に厳しい実行環境で使用されるため、それほど悪くはなりません。
さて、本当の問題に移ります。長年、クラス階層は深くなり、それに関連する問題に悩まされ始めました。最近では、クラス階層がフラット化されており、論理駆動型の継承ではなく、構成やその他のデータ駆動型の設計が行われています。
それはすべてOOPであり、新しいソフトウェアを設計するときのベースラインとして使用されますが、クラスが具体化するものに焦点を当てているだけです。
クラスには、実行時のオーバーヘッドは含まれません。実行時ポリモーフィズムは、関数ポインタを介して呼び出しているだけです。これらのオーバーヘッドは、Draw呼び出し、同時実行同期、ディスクI / O、カーネルモードスイッチ、メモリのローカリティの低下、動的メモリ割り当ての過剰使用、アルゴリズムの選択の不備、スクリプト言語の使用、リストなどの他のコストと比較して非常に小さいです。に行く。オブジェクト指向がそれ以前に来ていたものに本質的に取って代わる理由があります。
留意すべきことの1つは、オブジェクト指向コードの概念は、多くの場合、言語での実装よりも価値があることです。特に、メインループでエンティティに対して1000回の仮想更新呼び出しを行うと、360やPS3などのプラットフォームのC ++でキャッシュが混乱する可能性があります。スピードの。
多くの場合、継承とカプセル化のオブジェクト指向の概念に従います-言語自体とは異なる方法で実装するだけです(たとえば、奇妙な再帰テンプレート、継承の代わりに構成(Marcoによって記述されたコンポーネントベースの方法))。コンパイル時に継承を常に解決できる場合、パフォーマンスの問題はなくなりますが、テンプレート、カスタムRTTI実装(ここでも組み込みのものは通常非実用的です)、またはマクロなど
iOS / Mac用のObjective-Cには、メッセージングスキームに同様の、しかしより悪い問題があります(派手なキャッシングの場合でも)...
使用しようとしているメソッドは、クラスの継承とコンポーネントを組み合わせています。(まず、私はEnemyをクラスにしません。それをコンポーネントにします。)すべてに1つの汎用Entityクラスを使用し、必要なコンポーネントを追加してエンティティを肉付けするという考えは好きではありません。代わりに、コンストラクターでコンポーネント(およびデフォルト値)を自動的に追加するクラスを作成することを好みます。次に、サブクラスはコンストラクターに追加のコンポーネントを追加するため、サブクラスにはコンポーネントと親クラスコンポーネントが含まれます。たとえば、武器クラスはすべての武器に共通する基本的なコンポーネントを追加し、次に剣サブクラスは剣に固有の追加コンポーネントを追加します。私はまだtext / xmlアセットを使用してエンティティ(または特定の剣など)の値を定義するかどうかについて議論しています。またはそれをすべてコードで実行するか、適切な組み合わせを選択します。これにより、ゲームはコード言語の型情報とポリモーフィズムを引き続き使用でき、ObjectFactoriesがより簡単になります。