ゲームはどのようにしてすべてのキャラクターを一度に処理できますか?


31

この質問は、ゲームが一度に非常に多くのキャラクターを処理する方法についての知識を得るためのものです。私はゲームに慣れていないので、事前にご容赦ください。

タワーが構築され、各タワーが一定の速度で発射物を放出する15のタワースロットがあるタワーディフェンスゲームを作成しています。言うことができます毎秒は、2回の発射は、塔のそれぞれによって作成され、そこにある戦場に行進敵は、70を言うことができます、彼らが動き回るように変化れる、などのHPなどの属性の10種類、マナ、各(戦場)。

概要

タワーカウント = 1
秒間に各タワーで作成される 15の発射物 = 2
1秒間に作成される発射物の総数 = 30
戦場でのユニット数 = 70

さて、ゲームはこれらの30個の発射体と70個のユニットを100個の異なるスレッド(PCにとっては大きすぎる)またはそれらすべてを移動させ、値を減らすなどの1つのスレッドで処理することで処理しますか(少し遅くなります) 、 おもう)?

私はこれについて手がかりを持っていないので、誰がこれがどのように機能するかについて私をガイドできますか?


コメントは詳細なディスカッション用ではありません。この会話はチャットに移動さました
マイケルハウス

他の答えに加えて...いくつかの大規模なゲームの例。Skyrimでは、ほとんどのゲームロジックが単一のスレッドで更新されました。これをうまく管理する方法は、遠くのNPC(数マイル離れたNPC)がスケジュールに従って概算されることです。ほとんどのMMOは単一のスレッドでゲームロジックを更新しますが、マップの各部分は異なるスレッドまたはサーバーラックに存在します。
moonshineTheleocat

回答:


77

さて、ゲームはこれらの30個の発射物と70個のユニットを、100個の異なるスレッドで処理することにより、どのように処理するのか

いいえ、絶対にしないでください。リソースごとに新しいスレッドを作成しないでください。これは、ネットワーキングでは拡張されず、エンティティの更新でも拡張されません。(Javaのソケットごとに読み取り用のスレッドが1つあったときを覚えていますか?)

それらのすべてを動かす1つのスレッドは、その価値などを減らしますか?

はい、手始めに、これが道です。「大きなエンジン」はいくつかの作業をスレッド間で分割しますが、これはタワーディフェンスゲームのような単純なゲームを開始するために必要ではありません。この1つのスレッドで行うすべてのティックを行うには、おそらくさらに多くの作業があります。そうそう、もちろんレンダリング。

(これはちょっと遅いと思う)

まあ...あなたの遅いの定義は何ですか?100エンティティの場合、コード品質と使用している言語に応じて、0.5ミリ秒を超えることはなく、おそらくさらに短くなります。また、2ミリ秒かかったとしても、60 tps(この場合はフレームについて話すのではなく、1秒あたりのティック数)に達するのに十分です。


34
変なことをしていない限り、0.5マイクロ秒に近い。しかし、最も重要なことは、作業を複数のスレッドに分割すると、すべてが悪化することであり、改善されることはありません。マルチスレッドが非常に難しいことは言うまでもありません。
ルアーン

13
+1最新のゲームエンジンのほとんどは、数千または数万のポリゴンをリアルタイムでレンダリングしています。これは、メモリ内の100個のオブジェクトの動きを追跡するよりもはるかに強力です。
phyrfox

1
「タワーディフェンスゲームのようなシンプルなゲーム。」うーん...あなたはDefense Grid:The Awakening、またはその続編をプレイしたことがありますか?
メイソンウィーラー

4
「リソースごとに新しいスレッドを作成することはありません、これはネットワーキングではスケールしない...」エヘンいくつかの非常にスケーラブルなアーキテクチャは、正確にこれを行います!
NPSF3000

2
@BarafuAlbino:それは奇妙なことです。利用可能なコアよりも多くのスレッドを作成する正当な理由はたくさんあります。他の設計決定と同様に、複雑さ/パフォーマンス/などのトレードオフです。
ディートリッヒEpp

39

マルチスレッドのルール番号1は、パフォーマンスまたは応答性のために複数のCPUコアで並列化する必要がない限り、使用しないでください。「ユーザーの観点からxとyが同時に発生する」という要件は、マルチスレッドを使用する十分な理由ではありません。

どうして?

マルチスレッドは難しいです。各スレッドが実行されるタイミングを制御することはできません。これにより、あらゆる種類の問題(「競合状態」)を再現できなくなります。これを回避する方法(同期ロック、クリティカルセクション)がありますが、これらには独自の問題(「デッドロック」)があります。

通常、わずか数百という非常に少数のオブジェクトを扱うゲーム(はい、これはゲーム開発ではそれほど多くありません)は通常、共通のforループを使用して各ロジックティックをシリアルに処理します。

比較的弱いスマートフォンのCPUでさえ、毎秒何十億もの命令を実行できます。つまり、オブジェクトの更新ロジックが複雑で、オブジェクトおよびティックごとに約1000の命令が必要であり、1秒あたり100ティックの寛大さを目指している場合でも、何万ものオブジェクトに十分なCPU容量があることを意味します。はい、これは非常に単純化されたエンベロープの裏側の計算ですが、アイデアを与えてくれます。

また、ゲーム開発における一般的な知恵は、ゲームロジックがゲームのボトルネックになることはほとんどないということです。パフォーマンスが重要な部分は、ほとんどの場合グラフィックスです。はい、2Dゲームでも。


1
「マルチスレッドのルール1は、パフォーマンスまたは応答性のために複数のCPUコアで並列化する必要がない限り、使用しないでください。」ゲーム開発に当てはまるかもしれません(しかし、私はそれを疑っています)。リアルタイムシステムを使用する場合、スレッドを追加する主な理由は、期限を守ることと、論理的に単純にするためです。
サム

6
リアルタイムシステムの@Sam Meeting期限は、応答性のためにマルチスレッドが必要な場合です。ただし、スレッド化によって一見論理的にシンプルになったとしても、デッドロック、競合状態、リソース不足などの隠れた複雑さが生じるため、多くの場合危険です。
フィリップ

残念ながら、パスファインディングの問題がある場合、ゲームロジックがゲーム全体を行き詰まらせるのを何度も見ました。
ローレンペクテル16

1
@LorenPechtel私もこれを見ました。しかし、通常は、不要なパス計算を実行せず(すべてのティックですべてのパスを再計算するなど)、頻繁に要求されるパスをキャッシュし、多層パス検索を使用し、より適切なパス検索アルゴリズムを使用することで解決できました。これは、熟練したプログラマーが多くの最適化の可能性を通常見つけることができるものです。
フィリップ

1
@LorenPechtelたとえば、タワーディフェンスゲームでは、通常、少数の目的地しか存在しないという事実を利用できます。したがって、各目的地に対してダイクストラのアルゴリズムを実行して、すべてのユニットをガイドする方向マップを計算できます。これらのマップをフレームごとに再計算する必要がある動的な環境でも、これは手頃な価格であるはずです。
-CodesInChaos

26

他の答えは、現代のコンピューターのスレッディングとパワーを処理しました。しかし、より大きな質問に対処するために、ここでやろうとしていることは、「n乗」の状況を避けることです。

たとえば、1000個の発射物と1000個の敵がある場合、単純な解決策は、それらをすべて互いに確認することです。

つまり、最終的にp * e = 1,000 * 1,000 = 1,000,000の異なるチェックになります!これはO(n ^ 2)です。

一方、データをより適切に整理すれば、その多くを回避できます。

たとえば、グリッドの各正方形に敵がその正方形に含まれているものをリストした場合、1000個の発射物をループして、グリッド上の正方形をチェックするだけです。今、あなたは正方形に対して各発射物をチェックする必要があります、これはO(n)です。各フレームを100万回チェックする代わりに、必要なのは1000だけです。

その組織のためにデータを整理し、効率的に処理することを考えることは、これまでになし得る最大の単一の最適化です。


1
いくつかの要素を追跡するためだけにグリッド全体をメモリに保存する代わりに、各軸に1つずつbツリーを使用して、衝突などの可能性のある候補をすばやく検索することもできます。 "; ヒット領域を指定し、衝突のリストを要求すると、ライブラリがそれを提供します。これは、開発者がゼロから書くのではなくエンジンを使用する必要がある理由の1つです(もちろん、可能な場合)。
phyrfox

@phyrfox確かに、それを行うにはさまざまな方法があります-ユースケースに応じて、どちらが良いかは大幅に異なります。
ティムB

17

リソース/オブジェクトごとではなく、プログラムロジックのセクションごとにスレッドを作成してください。例えば:

  1. ユニットと発射物を更新するスレッド- 論理スレッド
  2. 画面をレンダリングするためのスレッド-GUIスレッド
  3. ネットワーク用スレッド(マルチプレイヤーなど)-IOスレッド

これの利点は、ロジックが遅い場合、GUI(ボタンなど)が必ずしもスタックしないことです。ユーザーはゲームを一時停止して保存できます。また、グラフィックをロジックから分離したので、マルチプレイヤー用にゲームを準備するのにも適しています。


1
初心者の場合、必要なデータをコピーしない限り、ゲーム状態をレンダリングするにはゲーム状態への読み取りアクセスが必要なので、描画中にゲーム状態を変更できないため、グラフィックスレッドとロジックスレッドを別々に使用することはお勧めしません。
CodesInChaos

1
あまり頻繁に描画しない(たとえば、1秒間に50回以上)ことは非常に重要であり、この質問はパフォーマンスに関するものでした。分割プログラムは、実際のパフォーマンスを向上させるために行う最も簡単なことです。これにはスレッドに関する知識が必要ですが、その知識を取得する価値があります。
トマーシュザト-復帰モニカ

プログラムを複数のスレッドに分割することは、プログラマーにとって最も難しいことです。ほとんどの本当に迷惑なバグはマルチスレッドに由来し、それだけで手間の巨大な量であり、ほとんどの時間それだけの価値はありません -最初のルール:チェックあなたはパフォーマンス上の問題を抱えている場合は、THEN最適化。そして、ボトルネックのある場所を最適化します。特定の複雑なアルゴリズムのための単一の外部スレッドかもしれません。しかし、それでも、このアルゴリズムが完了するまでに3秒かかると、ゲームロジックがどのように進むかを考える必要があります
Falco

@Falcoプロジェクトとプログラマーエクスペリエンスの両方で、このモデルの長期的な利点を監督しています。最も難しいと思うあなたの主張は、実際に対処することはできません。それは単なる意見です。私にとって、GUIのデザインはもっと恐ろしいものです。進化したすべての言語(C ++、Java)には、かなり明確なマルチスレッドモデルがあります。そして、本当にわからない場合は、初心者のマルチスレッドのバグに悩まされないアクターモデルを使用できます。ほとんどのアプリケーションが私が提案したように設計されているのには理由があることは知っていますが、それについてさらに議論することは自由です。
トマーシュザト-復帰モニカ

4

Space Invadersでさえ、相互作用する多数のオブジェクトを管理しました。一方、HD H264ビデオの1フレームのデコードには数億の算術演算が必要です。あなたは持っている多くの利用可能な処理能力のを。

とは言っても、無駄にしたとしても、それを遅くすることができます。問題は、実行される衝突テストの数ほどオブジェクトの数ではありません。各オブジェクトを他のオブジェクトと照合する単純なアプローチにより、必要な計算の数が2倍になります。この方法で1001個のオブジェクトの衝突をテストするには、100万回の比較が必要になります。多くの場合、これは、たとえば、発射体の衝突をチェックしないことによって対処されます。


2
Space Invadersが最良の比較であるかどうかはわかりません。ゆっくりと開始して敵を倒すとスピードが上がるのは、そのように設計されたためではなく、ハードウェアが一度に多くの敵のレンダリングを処理できなかったためです。en.wikipedia.org/wiki/Space_Invaders#Hardware
マイクケロッグ

各オブジェクトは、次の1秒間に衝突したり、1秒間に1回更新されるか、方向が変わるたびに更新される可能性がある、すべてのオブジェクトのリストを保持していますか?
Random832

モデリングしているものに依存します。空間分割ソリューションは、別の一般的なアプローチです。世界をリージョンに分割します(たとえば、レンダリングのためにとにかく行う必要のあるBSP、またはクアッドツリー)。同じリージョン内のオブジェクトとしか衝突できません。
pjc50

3

ここで他の回答のいくつかに反対するつもりです。ロジックスレッドを分離するのは良い考えであるだけでなく、処理速度にとって非常に有益です(ロジックが簡単に分離できる場合)

あなたの質問は、その上に追加のロジックを追加できる場合、おそらく分離可能なロジックの良い例です。たとえば、スレッドをスペースの特定の領域にロックするか、関連するオブジェクトをミューテックスすることにより、いくつかのヒット検出スレッドを実行できます。

たぶんスケジューラーを行き詰まらせる可能性が高いという理由だけで、可能性のあるすべての衝突に対して1つのスレッドは必要ありません。スレッドの作成と破棄に関連するコストもあります。システムのコアの周りにいくつかのスレッドを作成する(または古いのようなメトリックを利用する#cores * 2 + 4)方が良いでしょう。そしてそれらのプロセスが終了したらそれらを再利用します。

ただし、すべてのロジックが簡単に分離できるわけではありません。操作がすべてのゲームデータに一度に到達する場合があり、スレッド化が役に立たなくなります(実際、スレッド化の問題を回避するためにチェックを追加する必要があるため、有害です)。さらに、ロジックの複数のステージが特定の順序で発生する相互に大きく依存している場合、順序依存の結果が得られないようにスレッドの実行を制御する必要があります。ただし、スレッドを使用しないことでその問題が解消されるわけではなく、スレッドはそれを悪化させるだけです。

たいていのゲームは、平均的なゲーム開発者が通常最初にボトルネックになっていないものを処理する意思がある/できるよりも複雑であるという理由だけで、これを行いません。ゲームの大部分はGPUに制限されており、CPUに制限されていません。CPU速度を改善することは全体的には役立ちますが、通常は焦点ではありません。

とはいえ、物理エンジンは複数のスレッドを使用することが多く、複数のロジックスレッド(たとえば、HOI3などのParadox RTSゲーム)の恩恵を受けると思うゲームをいくつか挙げることができます。

この例でスレッドを使用する必要はないとしても、それが有益である可能性があるとしても、他の投稿には同意します。スレッド化は、他の方法では最適化できない過度のCPU負荷がある場合に予約する必要があります。これは大きな取り組みであり、エンジンの基本構造に影響を与えます。それは事実の後に取り組むことができるものではありません。


2

他の答えは、質問のスレッド部分に焦点を合わせすぎて、質問の重要な部分を見逃していると思います。

コンピューターは、ゲーム内のすべてのオブジェクトを一度に処理するわけではありません。それらを順番に処理します。

コンピューターゲームは、離散時間ステップで進行します。ゲームとPCの速度に応じて、これらのステップは通常、1秒あたり30または60ステップ、またはPCが計算できる限り多く/少ないステップです。

そのようなステップの1つで、コンピューターは各ゲームオブジェクトがそのステップ中に何をするかを計算し、それに応じてそれらを順次更新します。スレッドを使用して高速化することもできますが、すぐにわかるように、速度はまったく問題になりません。

平均CPUは2 GHz以上である必要があります。つまり、1秒あたり10 9クロックサイクルです。私たちは、毎秒60回のタイムステップを計算すると、葉10その9 /60クロック・サイクル時間ステップあたり= 16666666のクロック・サイクル。70ユニットの場合、残りのユニットあたり約2,400,000クロックサイクルが残っています。最適化が必要な場合、ゲームロジックの複雑さによっては、240サイクルで各ユニットを更新できる場合があります。ご覧のとおり、コンピューターはこのタスクに必要な速度よりも約10,000倍高速です。


0

免責事項:私の一番好きなタイプのゲームはテキストベースであり、私はこれを古いMUDの長年のプログラマーとして書いています。

自問する必要がある重要な質問はこれだと思います:スレッドさえ必要ですか?グラフィカルゲームはMTをより多く使用する可能性があることを理解していますが、ゲームの仕組みにも依存すると思います。(GPU、CPU、および現在ある他のすべてのリソースを使用すると、はるかに強力であるため、リソースに対する懸念があなたに思われるほど問題になることも考慮する価値があるかもしれません。実際、100個のオブジェクトは事実上ゼロです)。また、「すべての文字を一度に」定義する方法にも依存します。まったく同じ意味ですか?ピーターが正当に指摘しているように、文字通りの意味では一度にすべては無関係です。このようにしか表示されません。

スレッドを使用する場合:100スレッドを考慮する必要はありません(CPUに多すぎるかどうかについては説明しません。複雑さと実用性のみを参照します)。

しかし、これを覚えておいてください:マルチスレッドは簡単ではなく(Philippが指摘しているように)、多くの問題があります。他の人は私がMTよりもはるかに多くの経験を持っていますが、彼らも同じことを提案すると言います(たとえ彼らが私よりも能力があるとしても-特に私の練習なしで)。

スレッドが有益ではないと意見する人もいれば、各オブジェクトにスレッドが必要だと主張する人もいます。しかし、(これもすべてテキストですが、複数のスレッドを考慮する場合でも、オブジェクトごとに考慮する必要はありませんし、そうすべきではありません)Philippがゲームはリストを反復する傾向があると指摘しているためです。しかし、それは、ごくわずかなオブジェクトのためだけではありません(彼が示唆しているように、彼はごく少数のオブジェクトのパラメーターに応答しているだけです)。MUDでは、私はプログラマーです。これは、リアルタイムで発生するすべてのアクティビティではないため、これも念頭に置いてください。

(インスタンスの数はもちろん異なります-高いものと低いもの)

モバイル(NPC、つまりプレイヤー以外のキャラクター):2614; プロトタイプ:1360オブジェクト:4457; プロトタイプ:2281部屋:7983; プロトタイプ:7983。通常、各部屋には独自のインスタンスがありますが、動的な部屋、つまり部屋内の部屋もあります。またはモバイル内の部屋、たとえばドラゴンの胃。またはオブジェクト内の部屋。たとえば、魔法のオブジェクトを入力します。これらの動的な部屋は、実際に定義されているオブジェクト/部屋/モバイルごとに存在することに注意してください。はい、これはWorld of Warcraftに非常によく似ています(私はそれをプレイしませんが、しばらくの間Windowsマシンを持っていたときに友人にプレイしてもらいました)。

スクリプト:868(現在)(奇妙なことに、統計コマンドではプロトタイプの数が表示されないため、追加します)。これらはすべてエリア/ゾーンで開催されており、そのうち103個があります。また、異なる時間に処理する特別な手順もあります。他にもイベントがあります。次に、ソケットも接続しました。モバイルは動き回ったり、戦闘以外のさまざまな活動をしたり、プレイヤーとやり取りしたりします。(他の種類のエンティティも同様です)。

遅滞なくこれらすべてをどのように処理しますか?

  • ソケット:select()、キュー(入力、出力、イベント、その他)、バッファー(入力、出力、その他)など。これらは1秒間に10回ポーリングされます。

  • キャラクター、オブジェクト、部屋、戦闘、すべて:すべてが異なるパルスの中央ループにあります。

また、(創業者/他のプログラマと自分との議論に基づいた実装)には、広範なリンクリストの追跡とポインタの有効性のテストがあり、実際に必要な場合は十分な空きリソースがあります。このすべて(私たちが世界を拡大したことを除く)は、RAM、CPUパワー、ハードディスクスペースなどが少なかった数年前に存在しました。実際、それでも問題はありませんでした。説明されているループ(スクリプトは、他のことと同様にエリアのリセット/再配置を行う)で、モンスター、オブジェクト(アイテム)、およびその他のものが作成されたり、解放されたりします。接続も受け入れられ、ポーリングされ、その他すべての接続が期待されます。

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