6

私は単純なゲームに取り組んでいます。これは私の最初のゲーム プロジェクトです。

私が見つけたほとんどのサンプルには、すべてのゲーム ロジックも作成されているレンダー ループがあり、私はこれが好きではありません。X=0 のボールと X=10 の壁があり、遅いマシンでは、最初のループで X=7 にボールを配置し、2 番目のループで X=14 にボールを配置するとします。ゲームがクラッシュするだけです!

この「レンダー ループ」はゲームを作成する正しい方法ですか? すべてのフレームでこのようなことをチェックするコードを書く必要がありますか? 例、新しいフレーム X=14、最後のフレームは X=7 なので、X=7 から X=14 までの何かがあるかどうかを確認する必要があります??

ゲーム ロジック用に別のスレッドを作成し、レンダリング ループで、現在のゲーム ロジックの「スナップショットを取得」して表示する必要があると考えていました。

経験豊富なゲーム開発者の皆さん、これをどのように回避していますか?

ありがとう!

4

7 に答える 7

5

別の回答が述べたように、あなたが見ている問題は「トンネリング」と呼ばれています。それは「紙を通る弾丸」の問題です。弾丸は速く動いており、紙は薄いです。衝突が起こったことをどうやって知ることができますか?

世界の境界が単純であれば、それは簡単です。たとえば、テトリスでは、ブロックは側面に当たるまで左右にしか移動できず、一番下の座標が「地面」に当たるかどうかを簡単にテストできます。一度に 1 つの軸を実行できるため、これらのテストは簡単です。側面に対する衝突は、ピットの底に対する衝突とは異なる意味を持ちます。長方形の部屋がある場合、移動オブジェクトの座標をクランプして部屋の外に移動した場合は、移動オブジェクトを「停止」します。つまり、部屋の幅が -3 から +3 で、オブジェクトの X が 5 の場合、それを 3 に変更するだけで完了です。

より複雑な世界を扱いたい場合は、少しトリッキーになります。「スイープ」ジオメトリの衝突について読みたいと思うでしょう。基本的に、円がある場合は、代わりにカプセルを使用して衝突テストを行う必要があります。カプセルは、円を始点から終点まで「スイープ」することによって作成される形状です。両端に半円がある長方形のようになります。数学は驚くほど単純ですが (IMHO)、正しく理解し、何が起こっているのかを真に理解するのは難しい場合があります。それは価値があります!

編集:スレッドの問題について-物事を複雑にする必要はありません。糸は1本でいいです。更新フレームをスキップすることも厄介になる可能性があり、実際に「未来」を把握し、その時点までのすべての興味深い値を補間する必要があるため、かなり高度です。レンダリング ループはプロセスの一部に過ぎないため、私自身はこれを「レンダリング」ループとは呼びません。

def GameLoop():
   while True:
      ReadInputs()
      FigureOutWhatStuffDoes()
      DrawItAll()

編集 2:これは興味深い議論のようです: http://www.gamedev.net/community/forums/topic.asp?topic_id=482397

于 2010-05-06T22:32:56.660 に答える
3

このために別のスレッドを作成すると、対処したくない複雑な問題が多数発生します。1スレッド1ループで扱いやすいです。

基本的にやりたいことは、ロジックとレンダリングの両方を行うループを持つことですが、必ずしもすべての反復で行う必要はありません。次の疑似コードを参照してください。

while(true) {
   oldTime = currentTime;
   currentTime = systemTime();
   timeStep = currentTime - oldTime;

   // Only do logic x times / second
   if( currentTime > lastLogicTime + logicRefreshTime ){
      doGameLogic( currentTime - lastLogicTime );
      lastLogicTime = currentTime;
   }

   // Extrapolate all movements using timeStep
   renderGraphics( timeStep );

   wait( screenRefreshTime );
}

void doGameLogic( timeStep ) {
   // Update all objects
   for each( gameObject obj )
     obj.move( timeStep );
}

すべての固体可動オブジェクトは、SolidObject クラスを継承します。そのメソッドを呼び出すSolidObject.move(timeStep)と、オブジェクトが指定された範囲内で移動できる距離が確認されますtimeStep。このポイントの前に壁がある場合、オブジェクトは停止し、跳ね返り、方向を変えたり、死ぬなど、好きなことをする必要があります。


編集:

2 つのオブジェクトが移動する場合、衝突するかどうか、および衝突する場所を確認したい場合があります。多くのゲームではこれがうまくできませんが、次のようにします。

最初に、移動するすべてのオブジェクトについて、oldTimeと の間の移動線を計算します。currentTime次に、線を比較して、2 つの線が交差しているかどうかを確認します。オブジェクトのサイズを考慮する必要があることに注意してください。交点は、オブジェクトが衝突する場所です。この方法を使用すると、移動オブジェクトの衝突を正確に検出できます。

于 2010-05-06T22:18:48.410 に答える
3

update-threaddrawing-threadを別々にすることは可能ですが、簡単ではありません! 通常、同じ変数へのマルチスレッド アクセスを防ぐために多くのミューテックスチェックを行う必要があるため、これは実際には実行可能ではありません (さらに、半分更新された状態で処理したくない場合もあります)。正しく実装するには、最後のレンダリング状態の何らかの形式のスナップショットが必要です。関連する困難を気にしない場合は、ここにある優れた実装があります。

http://blog.slapware.eu/game-engine/programming/multithreaded-renderloop-part1/

http://blog.slapware.eu/game-engine/programming/multithreaded-renderloop-part2/

否定論者に落胆させないでください。それは可能であり、実行可能で効率的です。唯一の欠点は、実装が非常に難しいため、おそらく時間の価値がないことです (非常に CPU を多用するゲームを使用している場合を除きます)。

于 2012-10-11T03:04:26.213 に答える
2

スレッド化しないでください。解決するよりも多くの問題を引き起こすことになります。物事をスレッド化して、ロジックの更新とレンダリングを分離することはできますが、正しく行うのは難しく、ゲーム ループの大部分は本質的にシングルスレッドです。

代わりに、デルタ タイムを使用してゲーム ループを進め、ロジックの更新がフレームをむさぼり食うマシンの能力に大きく依存しないようにします。

簡単に言えば、デルタを使用して物事をスケーリングする場合、フレームを通過するのにかかる時間に関係なく、ボールが部屋の一方の側から別の側に移動するのに同じ時間がかかります。 PCと遅いもの。

たとえば、ボールが 1 秒間に 10 単位移動し、最後の更新から 0.1 秒が経過したと判断できる場合 (高性能タイマーまたは利用可能なものを使用)、単純に動きを 0.1 単位でスケーリングすると、ボールは 1 単位移動します。 .

例えば

private const float BallSpeedInMetresPerSecond = 10;

public void Update(float deltaTimeInSeconds)
{
    float adjustedSpeed = deltaTimeInSeconds * BallSpeedInMetresPerSecond;
    // set ball's speed / move it etc. using adjusted speed
}

これで問題が完全に解決するわけではありません (何かが非常に高速な場合でも、壁にはまってしまいます!) が、より複雑な問題に直面するまで、物事を予測可能で一貫性のある状態に保つためのシンプルで効果的な方法です。

それが機能するようになり、さらに複雑な問題を解決したい場合は、dash-tom-bang が言ったように、スイープ衝突検出を調べてください。

于 2010-05-06T22:45:27.847 に答える
1

ゲーム ロジック用に別のスレッドを作成し、レンダリング ループで、現在のゲーム ロジックの「スナップショットを取得」して表示する必要があると考えていました。

ゲームの状態の巨大な塊のスナップショットを簡単、安全、迅速に取得する方法はありません。これはおそらく次善の策です。しかし、とにかく問題を解決するわけではないので、少なくともこの目的のためには、これを行うことはありません.

X=0 のボールと X=10 の壁があり、遅いマシンでは、最初のループで X=7 にボールを配置し、2 番目のループで X=14 にボールを配置するとします。ゲームがクラッシュするだけです!

使用するすべてのコンピューターが常に X=1、X=2、X=3... X=10 をチェックするのに十分な速度であることを保証できない限り、この 2 つをスレッド化しても問題は解決しません。この保証はできません。できたとしても、位置に整数を使用することはほとんどありません。X=0.0000001、X=0.0000002、X=0.0000003 ... X=0.9999999、X=10.00000 を繰り返し確認できますか? いいえ。

経験豊富なゲーム開発者の皆さん、これをどのように回避していますか?

通常、まだ 1 つのループがあります。入力、更新、レンダリング、繰り返し。あなたが言及した衝突の問題は、オブジェクトが通過する領域を計算する衝突検出方法を使用して解決されます。X=[0 ~ 17] の解決。非常に遅いマシンでは X=[0-50] であり、高速なマシンでは X=[0-5] の後に X=[5-10] が続く場合がありますが、それぞれが期待どおりに機能します。

于 2010-05-07T11:21:38.977 に答える
0

ロジックの更新が通常安価で、レンダリングが時々高価な場合、最も簡単な方法は、1 秒あたり N 個のロジックの更新を行うことです。N=60 が一般的ですが、ゲームが適切に機能する最小の値を選択するか、値を選択して、そのレートで機能するまでゲームを微調整するか、または (より可能性が高い) 2 つの組み合わせを選択する必要があります。

実行時に、実際に経過した時間を追跡し、(実行された更新に関して) 論理的に経過した時間を追跡し、1.0/N 秒を超える不一致がある場合 (レンダリングに時間がかかりすぎるため) 追加の更新を実行します。追いつくために。これは、任意の期間に相当する更新を一度に実行しようとするよりも優れています。予測可能性が高いからです。(読者が同意しない場合は、難しい方法でこれを見つけてください。)

このシステムの欠点は、レンダリングに特に時間がかかり、そのためにロジックがあまりにも多くの更新を実行する必要がある場合、2 つの同期が少しずれてロジックが追いつかないことです。固定されたシステムをターゲットにしている場合、これは多くのことをしようとしていることを示しているだけであり、何とかして減らす必要があるか、または (このような状況がまれである可能性が高い場合) アイデア全体を破棄して実行する必要があります。 1:1 のレンダリング:更新。Windows PC のような変数をターゲットにしている場合は、キャッチアップ ロジックの更新の数を固定するだけで済み、これにより事態が元に戻ることを期待できます。

(ロジックがより高価な場合、このアプローチは適切ではありません。ただし、これが問題になるゲームに取り組んだことはありません。)

于 2010-05-07T01:10:12.453 に答える
0

ゲーム デザインと AI の経験が限られているため、論理ループと表示ループ (XNA のセットアップと同様) があると言えます。論理ループ (XNA の Update メソッド) は基本的に位置の更新とそれ以外を処理しますが、表示ループ (XNA の Draw メソッド) はすべてを画面に描画します。衝突検出に関しては、私は個人的にそれをあなたのボールにローカライズします. 動いたら、衝突を探して適切に反応させます。

スレッド化は別のトピックですが、私の意見では、更新と描画を分離しないでください。1 回の更新で 2 回のドローが発生する可能性がある、またはその逆の可能性があるというのは、私には本質的に間違っているように思えます。何も更新されていないのになぜ描画するのか... または、ユーザーに何が起こっているのかを示す前に何度も更新するのか。

あくまでも私の意見です。

于 2010-05-06T21:19:55.213 に答える