何百もの「ゲーム内」キャラクターをどのように構成/管理するか


10

UnityでCrusader Kings 2のような何百ものキャラクターを含むシンプルなRTSゲームを作成してきました。それらを格納するための最も簡単なオプションはスクリプト可能なオブジェクトを使用することですが、実行時に新しいオブジェクトを作成できないため、これは良い解決策ではありません。

そこで、すべてのデータを含む「Character」というC#クラスを作成しました。すべてが正常に動作していますが、ゲームをシミュレートすると、常に新しいキャラクターが作成され、一部のキャラクターが殺されます(ゲーム内イベントが発生するため)。ゲームが継続的にシミュレートすると、数千のキャラクターが作成されます。機能の処理中にキャラクターが「生きている」ことを確認する簡単なチェックを追加しました。したがって、パフォーマンスは向上しますが、家系図を作成するときに彼/彼女の情報が必要になるため、死んでいる「キャラクター」を削除することはできません。

ゲームのデータを保存するには、リストが最善の方法ですか?または、10000のキャラクターが作成されると問題が発生しますか?考えられる解決策の1つは、リストが特定の量に達したら別のリストを作成し、その中のすべての無効な文字を移動することです。


9
廃止後にキャラクターのデータのサブセットが必要になったときに私が過去に行ったことの1つは、そのキャラクターの「墓石」オブジェクトを作成することです。トゥームストーンは、後で調べる必要がある情報を運ぶことができますが、生きているキャラクターのような一定のシミュレーションを必要としないため、より小さく、頻繁に繰り返すことはできません。
DMGregory


2
ゲームはCK2のようなものですか、それとも多くのキャラクターを持つことの一部にすぎませんか?ゲーム全体がCK2のようなものだと理解しました。その場合、ここでの回答の多くは不正確ではなく、優れたノウハウを含んでいますが、質問の要点を逃しています。それが実際にグランドストラテジーゲームであるのに、CK2をリアルタイムストラテジーゲームと呼んでも役に立たない。それは厄介に思えるかもしれませんが、あなたが直面している問題に非常に関連しています。
Raphael Schmitz

1
たとえば、「数千のキャラクター」と言うと、人々は画面上で同時に数千の3Dモデルまたはスプライト考えています。つまり、Unityでは1000のGameObjectsです。CK2では、同時に見た最大のキャラクター数は、コートを見て、そこに10〜15人の人がいたときでした(私はそれほど遠くはプレーしませんでした)。同様に、3000人の兵士を持つ軍はたった1人GameObjectで、数字「3000」を表示します。
Raphael Schmitz、2018年

1
@ R.Schmitzはい、すべてのキャラクターにゲームオブジェクトがアタッチされていないことを明確にすべきでした。必要に応じて、あるポイントから別のポイントにキャラクターを移動します。Aiロジックを持つそのキャラクターのすべての情報を含む別のエンティティが作成されます。
ポールp

回答:


24

考慮すべき3つの点があります。

  1. それはない、実際にパフォーマンス上の問題を引き起こしますか?実際、1000はそれほど多くありません。最近のコンピューターは非常に高速で、多くのものを処理できます。キャラクターの処理にかかる時間を監視し、心配する前に実際に問題が発生するかどうかを確認します。

  2. 現在最小限アクティブなキャラクターの忠実度。初心者のゲームプログラマのよくある間違いは、画面上の文字と同じ方法で画面外の文字を正確に更新することに取り付かれることです。これは間違いです。誰も気にかけません。代わりに、画面外のキャラクターがまだ演技しているような印象与えるように努める必要があります。画面外の受信である更新文字の量を減らすことで、処理時間を大幅に削減できます。

  3. データ指向設計を検討してください。1000文字のオブジェクトを用意し、それぞれに同じ関数を呼び出す代わりに、1000文字のデータの配列を用意し、1000文字を1つの関数でループさせ、それぞれを順番に更新します。この種の最適化により、パフォーマンスが劇的に向上します。


3
エンティティ/コンポーネント/システムはこれに適しています。必要なものごとに各「システム」を構築し、数千または数万の文字(それらのコンポーネント)を保持させ、「文字ID」をシステムに提供します。これにより、さまざまなデータモデルを分離してより小さく保つことができ、不要なシステムから不要な文字を削除することもできます。(現在使用していない場合は、システムを完全にアンロードすることもできます。)
Der Kommissar

1
画面外の受信である更新文字の量を減らすことで、処理時間を大幅に増やすことができます。減るという意味ですか?
Tejas Kale

1
@TejasKale:はい、修正されました。
Jack Aidley、2018年

2
1000文字はそれほど多くありませんが、それぞれが常に他の文字を去勢できるかどうかを確認しているときに、全体的なパフォーマンスに大きな影響を与え始めます...
curiousdannii

1
常に実際に確認するのが最善ですが、ローマ人がお互いを去勢したいというのは一般的に安全な作業の前提です;)
curiousdannii

11

この状況では、Compositionを使用することをお勧めします。

クラスがポリモーフィックな動作を実現し、その構成によってコードの再利用を実現するという原則(必要な機能を実装する他のクラスのインスタンスを含めることにより)


この場合、Characterクラスは神のようになり、ライフサイクルのすべての段階でキャラクターがどのように動作するかについての詳細がすべて含まれているようです。

たとえば、死んだキャラクターは家系図で使用されているため、依然として必要であることに注意します。ただし、生きているキャラクターのすべての情報と機能が家系図に表示するためだけに必要なことはほとんどありません。たとえば、名前、生年月日、ポートレートアイコンが必要なだけかもしれません。


解決策は、の個別の部分を、がインスタンスを所有するCharacterサブクラスに分割することCharacterです。例えば:

  • CharacterInfo 名前、生年月日、死者の日付、および派閥を含む単純なデータ構造である場合があります。

  • Equipmentあなたのキャラクターが持っているすべてのアイテム、またはそれらの現在の資産を持っているかもしれません。また、これらを関数で管理するロジックを備えている場合もあります。

  • CharacterAIまたはCharacterController、キャラクターの現在の目標、ユーティリティ関数などについて必要なすべての情報が含まれている場合があります。また、個々のパーツ間の意思決定/相互作用を調整する実際の更新ロジックも含まれている場合があります。

キャラクターを分割すると、更新ループでAlive / Deadフラグを確認する必要がなくなります。

代わりに、あなたは単に作ると思いますAliveCharacterObject持っていることCharacterControllerCharacterEquipmentおよびCharacterInfo付属のスクリプトを。キャラクターを「殺す」には、関係のなくなった部分(などCharacterController)を削除するだけです。これにより、メモリや処理時間を無駄にすることがなくなります。

CharacterInfo家系図に実際に必要な唯一のデータであることに注意してください。クラスをより小さな機能に分解することで、AI駆動のキャラクター全体を保持する必要なしに、死後のデータの小さなオブジェクトをより簡単に保持できます。


このパラダイムがUnityが使用するために構築されたものであることは言及する価値があります-そしてそれが多くの別々のスクリプトで物事を処理する理由です。Unityでデータを処理するために、大規模な神オブジェクトを構築することはほとんどありません。


8

処理するデータが大量にあり、すべてのデータポイントが実際のゲームオブジェクトで表されているわけではない場合、通常、Unity固有のクラスを放棄して、単純な古いC#オブジェクトを使用することは悪い考えではありません。これにより、オーバーヘッドを最小限に抑えることができます。だからあなたはここで正しい軌道に乗っているようです。

1つのリスト(または配列)に生きているか死んでいるかを問わずすべての文字を格納すると、そのリストのインデックスが正規の文字IDとして機能するため、便利です。インデックスによるリストの位置へのアクセスは非常に高速な操作です。ただし、死んだ文字よりもはるかに頻繁にIDを繰り返す必要があるため、生きているすべての文字のIDのリストを個別に保持しておくと便利です。

ゲームメカニクスの実装が進むにつれて、他にどのような種類の検索を最もよく実行するかを確認することもできます。「特定の場所にいるすべての生きているキャラクター」または「特定のキャラクターのすべての生きているか死んだ祖先」のように。これらの種類のクエリ用に最適化されたいくつかの2次データ構造を作成すると有益な場合があります。それらのそれぞれを最新に保つ必要があることを覚えておいてください。これには追加のプログラミングが必要であり、追加のバグの原因となります。ですから、顕著なパフォーマンスの向上が見込まれる場合にのみ実行してください。

CKII「プルーン」そのデータベースから文字リソースを節約するために重要ではないとして、それはそれらをと考えます。死んでいるキャラクターの山が長期実行ゲームで多くのリソースを消費する場合は、同様のことを実行することをお勧めします(これを「ガベージコレクション」と呼びたくありません。「リスペクトインクリメンター」とは思わないでしょうか?)。

ゲーム内のすべてのキャラクターに実際にゲームオブジェクトがある場合、新しいUnity ECSおよびジョブシステムが役立つことがあります。非常に類似した多数のゲームオブジェクトをパフォーマンスの高い方法で処理するために最適化されています。しかし、それはあなたのソフトウェアアーキテクチャをいくつかの非常に厳密なパターンに強制します。

ちなみに、私はCKIIと、何千ものユニークなAI制御のキャラクターがいる世界をシミュレートする方法が本当に好きなので、このジャンルであなたのテイクをプレイできることを楽しみにしています。


こんにちは、返信ありがとうございます。すべての計算は1つの単一のゲームオブジェクトマネージャーによって行われます。必要な場合にのみ、ゲームオブジェクトを個別のアクターに割り当てます(ある位置から別の位置にキャラクターの軍隊が移動するように表示する場合など)。
ポールp

1
Like "all living characters in a specific location" or "all living or dead ancestors of a specific character". It might be beneficial to create some more secondary data-structures optimized for these kinds of queries.CK2の改造に関する私の経験から、これはCK2がデータを処理する方法に近いものです。CK2は、基本的に基本的なデータベースインデックスであるインデックスを使用しているようです。これにより、特定の状況で文字をすばやく検索できます。文字のリストではなく、文字の内部データベースがあり、それに伴うすべての欠点と利点があります。
Morfildur 2018年

1

プレーヤーの近くに数人しかいない場合は、数千のキャラクターをシミュレート/更新する必要はありません。現時点でプレーヤーが実際に見ることができるものを更新するだけでよいので、プレーヤーから離れているキャラクターは、プレーヤーが彼らに近づくまで一時停止する必要があります。

ゲームメカニクスが時間の経過を示すために離れたキャラクターを必要とするためにこれが機能しない場合、プレーヤーが近づいたときに1つの「大きな」更新でそれらを更新できます。ゲームメカニクスで、各キャラクターがプレイヤーやイベントに関係なく、実際に発生したときにゲーム内イベントに実際に応答する必要がある場合は、プレイヤーから離れたキャラクターがいる頻度を減らすことができます。更新されます(つまり、ゲームの他の部分と同期して更新されますが、それほど頻繁ではありません。そのため、遠くのキャラクターがイベントに応答するまでに少し遅れがありますが、これは問題を引き起こしたり、プレーヤーによって気づかれたりすることはほとんどありません)。または、ハイブリッドアプローチを使用することもできます。


これはRTSです。常に、かなりの数のユニットが実際に画面に表示されていると想定する必要があります。
トム・

RTSでは、プレーヤーが見ていなくても、世界は継続する必要があります。大きな更新は同じくらい時間がかかりますが、カメラを動かすと大きなバーストになります。
PStag 2018年

1

質問の明確化

UnityのCrusader Kings 2のような何百ものキャラクターを含むシンプルなRTSゲームを作成してきました。

この回答では、ゲーム全体が多数のキャラクターを持つという部分だけではなく、CK2のように想定されていると想定しています。CK2の画面に表示されるものはすべて簡単に実行でき、パフォーマンスを危険にさらしたり、Unityでの実装が複雑になったりすることはありません。その背後にあるデータは、ここで複雑になります。

機能しないCharacterクラス

そこで、すべてのデータを含む「Character」というC#クラスを作成しました。

ゲームのキャラクター単なるデータなので、良いです。画面に表示されるのは、そのデータの単なる表現です。これらのCharacterクラスはゲームの核心であり、「神のオブジェクト」になる危険性があります。だから私はそれに対する極端な対策をアドバイスします:それらのクラスからすべての機能を削除します。GetFullName()姓と名を組み合わせる方法ですが、実際には「何かを行う」コードはありません。置くことつのアクションを行う専用のクラスにコードを。たとえば、BirtherメソッドCharacter CreateCharacter(Character father, Character mother)を持つCharacterクラスは、クラスにその機能を持たせるよりもはるかにクリーンになります。

コードにデータを保存しない

それらを格納するための最も簡単なオプションは、スクリプト可能なオブジェクトを使用することです

いいえ。UnityのJsonUtilityを使用して、JSON形式で保存します。これらの機能を持たないCharacterクラスを使用すると、簡単に行うことができます。これは、ゲームの初期設定とsavegamesへの保存で機能します。しかし、それはまだ退屈なことなので、私はあなたの状況で最も簡単なオプションを提供しました。テキストファイルに保存されているときに人間が読み取ることができる限り、XMLやYAML、またはその他の形式を使用することもできます。CK2も同じことを行いますが、実際にはほとんどのゲームで同じです。それはまた、人々があなたのゲームを変更できるようにするための優れた設定ですが、それはずっと後で考えられます。

抽象的に考える

処理中にキャラクターが「生きている」ことを確認する簡単なチェックを追加しました[...]が、家系図の作成中に彼の情報が必要なため、死んでいる場合は「キャラクター」を削除できません。

これは、自然な考え方と衝突することが多いため、言うのは簡単です。あなたは「自然な」方法で「キャラクター」を考えています。ただし、ゲームの観点から見ると、「キャラクターである」データには少なくとも2種類あるようです。これをActingCharacterと呼びますFamilyTreeEntry。死んだキャラクターFamilyTreeEntryは更新する必要はなく、おそらくアクティブなものよりもはるかに少ないデータが必要ActingCharacterです。


0

少しの経験から、厳密なOO設計からエンティティコンポーネントシステム(ECS)設計に移ります。

、私はあなたのようでしたが、似たようなプロパティを持つさまざまな種類のものがたくさんあり、さまざまなオブジェクトを構築して、それを解決するために継承を使用しようとしました。非常に賢い人は、それをしないで、代わりにEntity-Component-Systemを使用すると私に言った。

現在、ECSは大きな概念であり、正しく理解するの困難です。エンティティ、コンポーネント、システムを適切に構築するために、多くの作業が行われます。ただし、その前に、用語を定義する必要があります。

  1. エンティティ:これはある、プレーヤー、動物、NPC、何でも。それはそれに接続されたコンポーネントを必要とするものです。
  2. コンポーネント:これは、「名前」、「年齢」、「親」などの属性またはプロパティです。
  3. システム:これは、コンポーネントの背後にあるロジック、または動作です。通常、コンポーネントごとに1つのシステムを構築しますが、それが常に可能であるとは限りません。さらに、システムは他のシステムに影響を与える必要がある場合があります。

だから、これは私がこれで行くところです:

何よりもまず、IDキャラクターのを作成します。アンintGuidあなたが好き。これが「エンティティ」です。

次に、あなたが行っているさまざまな行動について考え始めます。「家系図」のようなもの-それはふるまいです。それをエンティティの属性としてモデル化する代わりに、そのすべての情報を保持するシステムを構築します。その後、システムはそれをどう処理するかを決定できます。

同様に、「キャラクターは生きているのか死んでいるのか」というシステムを構築したいと考えています。これは、他のすべてのシステムに影響を与えるため、設計で最も重要なシステムの1つです。一部のシステムは「死んだ」文字(「スプライト」システムなど)を削除できますが、他のシステムは新しいステータスをより適切にサポートするために内部で再配置できます。

たとえば、「スプライト」、「描画」、または「レンダリング」システムを構築します。このシステムは、キャラクターをどのスプライトとともに表示する必要があるか、およびどのように表示するかを決定する責任があります。次に、キャラクターが死亡したら、それらを削除します。

さらに、キャラクターに何をすべきか、どこに行くべきかなどを伝えることができる「AI」システム。これは他の多くのシステムと相互作用し、それらに基づいて決定を行う必要があります。繰り返しますが、死んだ文字は実際には何もしていないため、おそらくこのシステムから削除できます。

「名前」システムと「ファミリーツリー」システムは、キャラクター(生きているか死んでいるか)をメモリに保持しているはずです。このシステムは、キャラクターの状態に関係なく、その情報を呼び出す必要があります。(私たちが彼を葬った後でも、ジムはまだジムです。)

これには、システムがより効率的に反応するとき変更するという利点もあります。システムには独自のタイマーがあります。一部のシステムは迅速に起動する必要がありますが、そうでないシステムもあります。ここから、ゲームを効率的に実行する方法について説明します。ミリ秒ごとに天気を再計算する必要はありません。おそらく5か月ごとに行うことができます。

また、よりクリエイティブなレバレッジを提供します。AからBへのパスの計算を処理できる「パスファインダー」システムを構築でき、必要に応じて更新できるため、ムーブメントシステムが「どこに必要なのか」と言うことができます。次に行きますか?」これらの懸念を完全に分離し、より効果的に推論することができます。ムーブメントはパスを見つける必要はありません。そこにたどり着くだけです。

システムの一部を外部に公開する必要があります。あなたのPathfinderシステムではおそらくが必要でしょうVector2 NextPosition(int entity)。このようにして、これらの要素を厳密に制御された配列またはリストに保持できます。より小さなstruct型を使用できます。これにより、コンポーネントをより小さな連続したメモリブロックに保持できるため、システムの更新を大幅に高速化できます。(特に、システムへの外部の影響が最小限の場合は、のような内部状態のみを考慮する必要がありますName。)

しかし、これを強調することはできません。タイル、オブジェクトなどを含むEntityは単なるIDです。エンティティがシステムに属していない場合、システムはそれを追跡しません。これは、「ツリー」オブジェクトを作成し、それらをSpriteMovementシステムに格納し(ツリーは移動しないが、「位置」コンポーネントがある)、それらを他のシステムから遠ざけることを意味します。ペーパードリングを除いて、ツリーのレンダリングはキャラクターと同じなので、ツリーの特別なリストは必要ありません。(Spriteシステムが制御できるのか、システムが制御できるのかPaperdoll。)これで、NextPosition少し書き直すことができます:Vector2? NextPosition(int entity)、そして、null気にしないエンティティの位置を返すことができます。これを私たちのにも適用します。木や岩にNameSystem.GetName(int entity)戻りますnull


私は近くにこれを描きますが、ここでの考え方はあなたにECSにいくつかの背景を与えることです、そしてあなたができるか、本当にそれはあなたのゲームのよりよい設計を与えるために活用しています。パフォーマンスを向上させ、無関係な要素を分離し、より整理された方法で物事を維持できます。(これは、F#やLINQなどの関数型言語/セットアップともペアリングします。まだ行っていない場合はF#をチェックすることを強くお勧めします。C#と組み合わせて使用​​すると、非常にうまくペアリングできます。)


こんにちは、そのような詳細な応答をありがとう。他のすべてのゲーム内キャラクターへの参照を含む1つのゲームオブジェクトマネージャーのみを使用しています。
ポールp

Unityでの開発は、GameObjectあまり機能しないがComponent実際の作業を行うクラスのリストを持っていると呼ばれるエンティティを中心に展開します。10年前ECSに関するパラダイムシフトがありまし。動作中のコードを別のシステムクラスに配置する方がクリーンなためです。Unityも最近そのようなシステムを実装しましたが、彼らのGameObjectシステムはECSであり、常にそうでした。OP はすでに ECSを使用しています。
Raphael Schmitz、2018年

-1

Unityでこれを行う場合、最も簡単なアプローチは次のとおりです。

  • キャラクターまたはユニットタイプごとに1つのゲームオブジェクトを作成する
  • これらをプレハブとして保存します
  • 必要なときにいつでもプレハブをインスタンス化できます
  • キャラクターが殺されたら、ゲームオブジェクトを破壊すると、CPUやメモリを消費しなくなります

コードでは、オブジェクトへの参照をリストのようなものに保持して、Find()とそのバリエーションを常に使用しないようにすることができます。CPUサイクルをメモリと交換していますが、ポインタのリストはかなり小さいので、数千のオブジェクトが含まれていても、それほど問題にはなりません。

ゲームを進めていくと、個々のゲームオブジェクトを使用すると、ナビゲーションやAIなど、多くの利点が得られることがわかります。

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