ゲヌムサむクルたたはElectroCardioGama

ゲヌムルヌプは、すべおのゲヌムのパルスです。 それなしではゲヌムは機胜したせん。 ただし、残念ながらすべおの新しいゲヌム開発者にずっお、このトピックに十分な泚意を払う良い蚘事はネットワヌク䞊にありたせん。 しかし、悲しいこずはありたせん。ゲヌムサむクルの問題に泚意を払うナニヌクな蚘事を読む機䌚が埗られたからです。 勀務䞭、私はしばしば小さなモバむルゲヌムのために倚くのコヌドを扱わなければなりたせん。 そしお、ゲヌムサむクルの実装はいく぀あるのだろうず思うたびに。 あなたも、そのような䞀芋シンプルなものがどのように可胜か疑問に思うかもしれたせん。倚くの実装を思い぀くこずができたす。 しかし、できたす そしお蚘事では、ゲヌムサむクルの最も䞀般的なオプションの長所ず短所に぀いおお話したす。 たた、ゲヌムサむクルを実装するための最善の遞択肢に぀いおも説明したす。

Kao CardosoFélixに感謝したす。この蚘事はブラゞル系ポルトガル語でも利甚できたすロシア語でもおおよそありがずうございたす。



ゲヌムサむクル



各ゲヌムには、ナヌザヌ入力の読み取り、ゲヌムの状態の曎新、AIの凊理、音楜や効果音の再生、グラフィックのレンダリングを行う䞀連の呌び出しが含たれおいたす。 この呌び出しシヌケンスは、ゲヌムルヌプ内で実行されたす。 ぀たり、ティヌザヌで述べたように、ゲヌムサむクルはすべおのゲヌムのパルスです。 蚘事では、䞊蚘のタスクの実装の詳现に぀いおは説明したせんが、ゲヌムサむクルの問題のみに集䞭したす。 同じ理由で、タスクのリストを2぀の機胜状態の曎新ずレンダリングに単玔化したす。 以䞋は、ゲヌムルヌプの最も単玔な実装のサンプルコヌドです。

bool game_is_running = true; while( game_is_running ) { update_game(); display_game(); }
      
      







この実装の問題は、時間を凊理しないこずです。 ゲヌムは実行䞭です。 匱いハヌドりェアでは、ゲヌムの実行速床は遅く、匷いハヌドりェアでは高速です。 むかしむかし、コンピュヌタヌのパフォヌマンスが異なるマシンでほが同じであるこずがわかっおいたずき、そのような実装は問題を匕き起こしたせんでした。 今日、パフォヌマンスが異なるプラットフォヌムが倚数ある堎合、時間凊理が必芁になりたす。 これを行うにはいく぀かの方法がありたす。 それらに぀いおは埌で説明したす。 それたでの間、埌で䜿甚するいく぀かのポむントを明確にしたしょう。



Fps

FPSは「Frames Per Second」1秒あたりの人員、玄Transl。の略です。 䞊蚘のゲヌムルヌプ実装のコンテキストでは、これは1秒間のdisplay_gameの呌び出し回数です。



ゲヌム速床

ゲヌム速床は、1秒間のゲヌム状態の曎新回数です。 ぀たり、1秒あたりのupdate_gameの呌び出し回数。



䞀定のゲヌム速床に応じたFPS



実装


タむミングの問題に察する最も簡単な解決策は、25回/秒の固定呚波数で単玔に呌び出しを行うこずです。 このアプロヌチを実装するコヌドは次のずおりです。

 const int FRAMES_PER_SECOND = 25; const int SKIP_TICKS = 1000 / FRAMES_PER_SECOND; DWORD next_game_tick = GetTickCount(); // GetTickCount() returns the current number of milliseconds // that have elapsed since the system was started int sleep_time = 0; bool game_is_running = true; while( game_is_running ) { update_game(); display_game(); next_game_tick += SKIP_TICKS; sleep_time = next_game_tick - GetTickCount(); if( sleep_time >= 0 ) { Sleep( sleep_time ); } else { // Shit, we are running behind! } }
      
      







これは1぀の倧きなプラスの実装ですシンプル update_gameが1秒間に25回呌び出されるこずがわかるず、残りのコヌドの蚘述は蒞しカブより簡単になりたす。 たずえば、再生機胜の実装は簡単なタスクになりたす。 ゲヌムでランダム倉数が䜿甚されおいない堎合は、ナヌザヌ入力でログむンしお埌でプレむできたす。 テストマシンでは、FRAMES_PER_SECONDの劥協倀を芋぀けるこずができたすが、高速たたは䜎速のハヌドりェアではどうなりたすか 調べおみたしょう。



匱い鉄


アむロンが特定のFPSに耐えるこずができる堎合、問題はありたせん。 マシンがFPSを特定のレベルに維持できない堎合、問題が発生したす。 ゲヌムの実行速床が遅くなりたす。 最悪の堎合、ゲヌムはいく぀かの間隔より遅れたすが、他の堎合は正垞に動䜜したす。 時間はさたざたな速床で流れ、最終的にはゲヌムをプレむできなくなりたす。



生産的な鉄


匷力なハヌドりェアでは問題はありたせんが、コンピュヌタヌはアむドル状態になり、「貎重な」明らかに皮肉ですか-およそTransl。プロセッサヌ時間を浪費したす。 25..30 FPSからゲヌムを開始するこずを恥ずかしく思いたす。 プロセッサを最倧限に䜿甚したずきに衚瀺されるものず比范しお、ゲヌムの魅力が倱われたす。 䞀方、モバむルプラットフォヌムでは、より良い結果が埗られる可胜性がありたす。゚ネルギヌを節玄できたす。



おわりに


FPSを固定ゲヌム速床に結び぀けるこずは、コヌドをシンプルに保぀シンプルな゜リュヌションです。 ただし、問題がありたす。FPSの倀を高く蚭定しすぎるず、匱いハヌドりェアで問題が発生したす。 倀を䜎く蚭定しすぎるず、匷力な鉄が非効率的に䜿甚されたす。



可倉FPSゲヌム速床



実装


この問題の別の解決策は、ゲヌムをできるだけ速く実行し、ゲヌムの速床を珟圚のFPSに䟝存させるこずです。 前のフレヌムの描画に費やした時間を䜿甚しお、ゲヌムが曎新されたす。

 DWORD prev_frame_tick; DWORD curr_frame_tick = GetTickCount(); bool game_is_running = true; while( game_is_running ) { prev_frame_tick = curr_frame_tick; curr_frame_tick = GetTickCount(); update_game( curr_frame_tick - prev_frame_tick ); display_game(); }
      
      





コヌドは耇雑です update_gameで時間差を凊理す​​る必芁がありたす。 しかし、コヌドは少し耇雑になりたした。 私はこのアプロヌチを実装した倚くの賢い開発者を芋たした。 確かに圌らの䞀人は、このようなサむクルを自分で実装する前にこの投皿を読みたいず思うでしょう。 以䞋に、このアプロヌチが匱いハヌドりェアず匷力なハヌドりェアの䞡方で深刻な問題を抱えおいる理由を瀺したすそうです...匷力なハヌドりェアでも。



匱い鉄


ハヌドりェアが匱いず、ゲヌムが「重くなる」堎所で遅延が発生するこずがありたす。 これは、3Dゲヌムで描画されるポリゎンが倚すぎる堎合に必ず圓おはたりたす。 その結果、FPSで障害が発生するず、ナヌザヌの入力凊理が遅くなりたす。 ゲヌムの曎新はFPSの倱敗に察応したす。その結果、ゲヌムの状態は顕著な遅れを䌎っお倉化したす。 その結果、プレむダヌの反応時間はAIず同じように遅くなり、単玔な操䜜でさえ䞍可胜になる可胜性がありたす。 たずえば、通垞のFPSで克服できる障害は、䜎FPSでは克服されたせん。 物理を䜿甚する堎合、匱いハヌドりェアでさらに深刻な問題が発生したす。 物理シミュレヌションは爆発する可胜性がありたす。



匷力な鉄


䞊蚘のゲヌムサむクルの実装が高速ハヌドりェアで正しく動䜜しない可胜性があるこずに驚くかもしれたせん。 残念ながらできたす。 そしお、理由を瀺す前に、コンピュヌタヌでの数孊のいく぀かのポむントを明確にしたしょう。 浮動小数点圢匏の数倀の衚珟の有限ビット深床を考慮しお、䞀郚の倀は衚珟できたせん。 そのため、倀0.1はバむナリ圢匏で衚珟できず、double型の倉数に栌玍されるず䞞められたす。 Pythonコン゜ヌルを䜿甚しおこれを実蚌したす。

>>> 0.1

0.10000000000000001






これ自䜓は倧胆ですが、逐次蚈算では問題が発生したす。 オりムの速床が0.001の車があるず仮定したす自由な翻蚳、玄Transl。。 10秒埌、マシンは10.0オりムの距離を移動したす。 この蚈算をフレヌムに分割するず、FPSをパラメヌタヌずしお次の関数が埗られたす。

>>> def get_distance( fps ):

... skip_ticks = 1000 / fps

... total_ticks = 0

... distance = 0.0

... speed_per_tick = 0.001

... while total_ticks < 10000:

... distance += speed_per_tick * skip_ticks

... total_ticks += skip_ticks

... return distance








そしおヌカ、40 FPSの移動距離を蚈算しおみたしょう。

>>> get_distance( 40 )

10.000000000000075








ちょっず埅っお これは10.0オりムではありたせん どうした すべおがシンプルです... パス蚈算を400フレヌムに分割したため、加算䞭に倧きな゚ラヌが蓄積されたした。 100 FPSで䜕が起こるか想像しおみおください。

>>> get_distance( 100 )

9.9999999999998312








わあ ゚ラヌはさらに倧きくなりたした!!! これは、100 FPSでさらに远加するためです。 その結果、゚ラヌはさらに蓄積されたす。 したがっお、ゲヌムの動䜜は40 FPSず100 FPSで異なりたす。

>>> get_distance( 40 ) - get_distance( 100 )

2.4336088699783431e-13






この違いは取るに足らないものであり、無芖できるず思うかもしれたせん。 ただし、この倀を蚈算に䜿甚するず、問題はより深刻になりたす䟋ずしお、diff。Ur、玄Transl。の統合。 したがっお、゚ラヌは非垞に倧きく蓄積する可胜性があるため、ザカカピット元のトランザクションよりも倚少打ち消されたす倧きいFPS䞊の補品。 これはどれくらいありそうですか おそらく泚目を集めるのに十分です。 このようなゲヌムサむクルの実装を備えたゲヌムを芋るこずができお光栄でした。 そしお実際、倧きなFPSで問題が発生したした。 開発者は、ゲヌムコヌドのコアに「犬が埋もれおいる」こずに気付いた埌、倧量のコヌドをリファクタリングしおバグを修正したした。



おわりに


䞀芋するず、このタむプのゲヌムサむクルは非垞に良いように芋えたすが、最初は初めおです。 匱い鉄ず匷力な鉄の䞡方が問題を匕き起こす可胜性がありたす。 さらに、状態曎新関数の実装は、最初の実装に比べお耇雑になりたした。 だからそれの炉にいるのですか



䞀定のゲヌム速床ず最倧FPS



実装


最初の実装である「䞀定のゲヌム速床に䟝存するFPS」には、匱いハヌドりェアで問題がありたす。 FPSずゲヌムステヌタスの䞡方の曎新の遅れを生成したす。 この問題の可胜な解決策は、固定の頻床で状態を曎新するこずですが、レンダリングの頻床を枛らしたす。 以䞋は、このアプロヌチの実装コヌドです。

 const int TICKS_PER_SECOND = 50; const int SKIP_TICKS = 1000 / TICKS_PER_SECOND; const int MAX_FRAMESKIP = 10; DWORD next_game_tick = GetTickCount(); int loops; bool game_is_running = true; while( game_is_running ) { loops = 0; while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) { update_game(); next_game_tick += SKIP_TICKS; loops++; } display_game(); }
      
      







ゲヌムは1秒あたり50回の固定頻床で曎新され、レンダリングは可胜な限り高い頻床で実行されたす。 状態の曎新よりもレンダリングが頻繁に実行される堎合、隣接するフレヌムの䞀郚が同じになるため、実際にはゲヌムの状態を曎新する頻床によっお最倧FPS倀が制限されるこずに泚意しおください。 匱いハヌドりェアでは、ステヌタス曎新サむクルがMAX_FRAMESKIPに達するたでFPSは枛少したす。 実際には、これは、レンダリングFPSが5= FRAMES_PER_SECOND / MAX_FRAMESKIPを䞋回った堎合にのみ、ゲヌムの速床が䜎䞋し始めるこずを意味したす。



匱い鉄


匱いハヌドりェアでは、FPSはドロップアりトしたすが、ゲヌム自䜓は通垞の速床で高い確率で動䜜したす。 アむロンが最小FPSにも耐えられない堎合、状態の曎新も遅くなり、レンダリングは滑らかなアニメヌションのヒントさえ倱いたす。



匷力な鉄


ゲヌムは問題なく匷力なハヌドりェアで動䜜したすが、最初の実装ず同様に、プロセッサは非効率的に䜿甚されたす。 迅速な曎新ず匱いハヌドりェアで䜜業する胜力ずのバランスを芋぀けるこずが重芁です。



おわりに


固定のゲヌム速床ず可胜な限り最高のFPSを䜿甚するこずは、実装が簡単で、コヌドをシンプルに保぀゜リュヌションです。 しかし、ただいく぀かの問題がありたす状態の曎新頻床を高く蚭定しすぎるず、匱いハヌドりェアで問題が発生したす最初の実装の堎合ほど深刻ではありたせんが。アニメヌションの滑らかさが増したすが、代わりに頻繁なレンダリングに費やされたす。



可倉FPSに䟝存しない䞀定のゲヌム速床



実装


以前の実装を改善しお、匱いハヌドりェアでより速く動䜜し、匷力なハヌドりェアで芖芚的に魅力的にするこずは可胜ですか たあ、私たちにずっお幞いなこずに、はい、それは可胜です ゲヌムの状態を毎秒60回曎新する必芁はありたせん。 ナヌザヌ入力、AI、およびゲヌムの状態の曎新は、1秒間に25回曎新するだけで十分ですこれに同意するわけではありたせんが、垞に玄ではありたせん。 それではupdate_gameを1秒間に25回呌び出しおみたしょう。 ただし、鉄が匕っ匵るのず同じ頻床でレンダリングを実行しおください。 ただし、レンダリングが遅い堎合でも、状態のリフレッシュレヌトには圱響したせん。 これを実珟する方法を次のコヌドに瀺したす。

 const int TICKS_PER_SECOND = 25; const int SKIP_TICKS = 1000 / TICKS_PER_SECOND; const int MAX_FRAMESKIP = 5; DWORD next_game_tick = GetTickCount(); int loops; float interpolation; bool game_is_running = true; while( game_is_running ) { loops = 0; while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP) { update_game(); next_game_tick += SKIP_TICKS; loops++; } interpolation = float( GetTickCount() + SKIP_TICKS - next_game_tick ) / float( SKIP_TICKS ); display_game( interpolation ); }
      
      







その結果、update_gameの実装はシンプルなたたです。 ただし、残念ながら、display_game関数はより耇雑になりたす。 補間ず予枬を実装する必芁がありたす。 しかし、心配しないでください。芋た目ほど難しくありたせん。 埌ほど、補間ず予枬がどのように機胜するかを説明したすが、最初にそれらが必芁な理由を瀺したす。



補間が必芁な理由



ゲヌムの状態は1秒間に25回曎新されたす。 したがっお、補間が䜿甚されない堎合、フレヌムは同じ最倧呚波数で衚瀺されたす。 ここでは、1秒あたり25フレヌムが誰かに芋えるほど遅くないこずに泚意する必芁がありたす。 たずえば、フィルムでは、フレヌムは毎秒24フレヌムの頻床で倉化したす。 したがっお、毎秒25フレヌムで十分ず思われたすが、高速で移動するオブゞェクトには十分ではありたせん。 そのようなオブゞェクトの堎合は、状態のリフレッシュレヌトを䞊げお、よりスムヌズなアニメヌションを取埗する必芁がありたす。 リフレッシュレヌトの増加に代わるものは、正確に補間ず予枬の組み合わせです。

*泚 perev。NeoAxisの物理オブゞェクトの゚ンゞンでは、フラグContinuous Collision Detectionを蚭定できたす。 䞊蚘のゲヌムサむクルの実装ず同様に実行されるのは、単なる凊理であるず思われたす。



補間ず予枬



䞊蚘のように、状態は独立した頻床で曎新されたす。 したがっお、連続する2぀のティックの間で描画が開始される堎合がありたす。 状態を10回曎新したずしたす。 次に、描画が呌び出され、10から11ティックの間に実行されたす。 10.3の離散時間ずしたす。 その結果、「補間」の倀は0.3になりたす。 䟋ずしお、次のように動く車を想像しおください。

position = position + speed;





状態曎新サむクルの10番目のステップで䜍眮が500である堎合、速床は100です。11番目のステップで䜍眮は600になりたす。レンダリング䞭のマシンの䜍眮はどうなりたすか 最埌のステップでポゞションを取るこずができたす。 500.しかし、次のステップで䜍眮を予枬し、時間10.3で補間する方がはるかに優れおいたす。 次の圢匏のコヌドを取埗したす。

view_position = position + (speed * interpolation)





したがっお、マシンは䜍眮530に描画されたす。倉数「補間」には、通垞、珟圚のフレヌムず次のフレヌムの間の時間の盞察䜍眮0〜1の倀が含たれたす理解を深めるためにやり盎したす。 スムヌズなアニメヌションを確保するために、予枬を耇雑にしすぎる必芁はありたせん。 もちろん、衝突怜出の盎前に、あるオブゞェクトが別のオブゞェクトず郚分的に亀差する状況が考えられたす。 しかし、前に芋たように、ゲヌムの状態は1秒あたり25回曎新されるため、レンダリングアヌティファクトは1秒間だけ衚瀺されたすオブゞェクトの密床が高く、衝突が倚い堎合はどうでしょうか-およそTransl。



匱い鉄


ほずんどの堎合、update_gameはdisplay_gameよりもはるかに高速に実行されたす。 実際、匱いハヌドりェアであっおも、update_game関数は1秒間に25回呌び出されるこずは圓然ず考えるこずができたす。 したがっお、1秒あたり15フレヌムの頻床でレンダリングが実行される堎合でも、ゲヌムはナヌザヌ入力を凊理し、問題なく状態を曎新したす。



匷力な鉄


匷力なハヌドりェアでは、ゲヌムは25ティック/秒の固定速床で動䜜したすが、レンダリングは高速になりたす。 補間+予枬は、アニメヌションの魅力を远加したす。 実際、レンダリングはより高いFPSで実行されたす。 矎しさは、このようにあなたがFPSで䞍正であるずいうこずです。 ゲヌムの状態を頻繁に曎新するのではなく、画像のみを曎新したす。 ただし、同時に、ゲヌムのFPSは高くなりたす。



おわりに


曎新ずレンダリングを盞互に分離するこずが最善の解決策のようです。 ただし、display_gameで補間ず予枬を実装する必芁がありたす。 確かに、このタスクはそれほど耇雑ではありたせんプリミティブメカニック、オブゞェクト、およそTranslを䜿甚する堎合のみ。



おわりに



ゲヌムルヌプは、考えられるほど単玔なものではありたせん。 4぀の可胜な実装を怜蚎したした。 そしお、それらのうち、少なくずも1぀状態の曎新がFPSに密接に関連しおいるがあり、絶察に避ける必芁がありたす。 モバむルデバむスでは、䞀定のフレヌムレヌトが蚱容される堎合がありたす。 ただし、ゲヌムを異なるプラットフォヌムに移怍する堎合は、リフレッシュレヌトずレンダリング頻床を分離し、補間ず予枬を実装する必芁がありたす。 予枬ず補間に煩わされたくない堎合、状態を曎新する頻床を高くするこずができたすが、匱くお匷力な鉄の最適倀を芋぀けるのは難しい䜜業です。



<place_your_game_title_here>をコヌディングするために行進したす



関連蚘事翻蚳者から



  1. habrahabr.ru/blogs/silverlight/125037-SLのゲヌムサむクル
  2. habrahabr.ru/blogs/gdev/112444-Unity3Dにはゲヌムルヌプ自䜓はないずいう蚘述がありたす明らかに隠されおいたすか
  3. habrahabr.ru/blogs/gdev/102930-ゲヌム゚ンゞンの䜜成に぀いお
  4. habrahabr.ru/blogs/android_development/136968-ココナッツのゲヌムの䟋
  5. gafferongames.com/game-physics/fix-your-timestep-ゲヌムの離散時間に関する別の蚘事



All Articles