“Sabit Oyun Hızı Maksimum FPS” Oyun Döngüsü Üzerinden ÇOK Karışık


12

Son zamanlarda Game Loops ile ilgili bu makaleyi okudum: http://www.koonsolo.com/news/dewitters-gameloop/

Önerilen son uygulama beni derinden karıştırıyor. Nasıl çalıştığını anlamıyorum ve tam bir karmaşaya benziyor.

İlkeyi anlıyorum: Oyunu sabit bir hızda güncelleyin, kalan ne olursa olsun, oyunu mümkün olduğunca çok kez işleyin.

Ben alayım ki kullanamazsınız:

  • 25 kenede girdi alın
  • 975 keneler için oyunu render

Yaklaşımın ikincisinin ilk kısmı için girdi alacağınızdan ve bu garip hissedeceğinizden mi? Yoksa makalede neler oluyor?


esasen:

while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)

Bu nasıl geçerli?

Değerlerini varsayalım.

MAX_FRAMESKIP = 5

Ana oyun döngüsü söylenmeden önce başlatıldıktan sonra anlar atanan next_game_tick'i varsayalım ... 500.

Son olarak, oyunum için SDL ve OpenGL kullandığım için, OpenGL yalnızca oluşturma için kullanıldığından, GetTickCount()SDL_Init çağrıldığından bu yana geçen süreyi döndürdüğünü varsayalım .

SDL_GetTicks -- Get the number of milliseconds since the SDL library initialization.

Kaynak: http://www.libsdl.org/docs/html/sdlgetticks.html

Yazar ayrıca şunları varsayar:

DWORD next_game_tick = GetTickCount();
// GetTickCount() returns the current number of milliseconds
// that have elapsed since the system was started

İfadeyi genişletirsek while:

while( ( 750 > 500 ) && ( 0 < 5 ) )

750 çünkü zaman next_game_tickatandı. loopsmakalede gördüğünüz gibi sıfırdır.

Bu yüzden while döngüsüne girdik, biraz mantık yapalım ve bazı girişleri kabul edelim.

Yadayadayada.

Size ana oyun döngümüzün içinde olduğunu hatırlattığım while döngüsünün sonunda:

next_game_tick += SKIP_TICKS;
loops++;

While kodunun bir sonraki yinelemesinin nasıl göründüğünü güncelleyelim

while( ( 1000 > 540 ) && ( 1 < 5 ) )

1000 çünkü GetTickCount () öğesinin geri çağrıldığı döngünün bir sonraki başlangıcına ulaşmadan önce girdi alma ve bir şeyler yapma zamanı geçti.

540 çünkü 1000/25 = 40 kodunda, 500 + 40 = 540

1 çünkü döngümüz bir kez yinelendi

5 , neden biliyorsun.


Yani, bu While döngüsü MAX_FRAMESKIPamaçlanan değil TEMİZLE bağımlı olduğundanTICKS_PER_SECOND = 25; çünkü oyun nasıl düzgün çalışması gerekiyordu?

Bunu koduma uyguladığımda, kullanıcı girişini işlemek ve oyunu makalenin yazarının örnek kodunda ne olduğuna göre yeniden çizmek için fonksiyonları yeniden adlandırdığımda doğru bir şekilde ekleyebileceğim sürpriz değildi .

Yerleştirdim fprintf( stderr, "Test\n" );Oyun döngüsü bitene kadar basılmayan bir while döngüsü içerisine .

Bu oyun döngüsü saniyede 25 kez çalışıyor, garanti ediliyor ve olabildiğince hızlı oluşturuluyor?

Bana göre, BÜYÜK bir şey eksik sürece, hiçbir şey ... gibi görünüyor.

Ve bu while döngüsü, bu yapı saniyede 25 kez çalışıyor ve daha sonra makalenin başında daha önce bahsettiğim oyunu tam olarak güncellemiyor mu?

Eğer durum buysa, aşağıdaki gibi basit bir şey yapamadık:

while( loops < 25 )
{
    getInput();
    performLogic();

    loops++;
}

drawGame();

Ve başka bir şekilde enterpolasyon için sayın.

Son derece uzun sorumu affedin, ama bu makale bana faydadan çok zarar verdi. Şimdi ciddi bir şekilde kafam karıştı - ve tüm bu sorulardan dolayı uygun bir oyun döngüsünün nasıl uygulanacağı hakkında hiçbir fikrim yok.


1
Sıralamalar makalenin yazarına daha iyi yönlendirilir. Nesnel sorunuzun hangi kısmı ?
Anko

3
Bu oyun döngüsü bile geçerli, birisi açıklar. Testlerimden saniyede 25 kez çalışacak doğru yapıya sahip değil. Neden yaptığımı açıkla. Ayrıca bu bir rant değil, bu bir dizi soru. İfadeler kullanmalı mıyım, kızgın görünüyor muyum?
tsujp

2
Sorunuz "Bu oyun döngüsü hakkında ne anlamıyorum" diye sorduğundan ve çok sayıda kalın yazı tipi kelimeniz olduğundan, en azından bıkkın olarak ortaya çıkıyor.
Kirbinator

@Kirbinator Bunu takdir edebilirim, ancak bu makalede olağandışı bulduğum her şeyi topraklamaya çalışıyordum, bu yüzden sığ ve boş bir soru değil. Her neyse, bazı geçerli noktalara sahip olduğumu düşünmek istiyorum - sonuçta bu öğretmeye çalışan bir makale, ama böyle iyi bir iş yapmıyorum.
tsujp

7
Beni yanlış anlamayın, bu iyi bir soru, sadece% 80 daha kısa olabilir.
Kirbinator

Yanıtlar:


8

Bence yazar küçük bir hata yaptı:

while( GetTickCount() > next_game_tick && loops < MAX_FRAMESKIP)

olmalı

while( GetTickCount() < next_game_tick && loops < MAX_FRAMESKIP)

Yani: henüz bir sonraki çerçevemizi çizmenin zamanı gelmediği sürece ve MAX_FRAMESKIP kadar çerçeveyi atlamasak da beklemeliyiz.

Ayrıca neden next_game_tickdöngüde güncelleme anlamıyorum ve bunun başka bir hata olduğunu varsayalım. Bir karenin başlangıcında, bir sonraki karenin ne zaman olması gerektiğini belirleyebilirsiniz (sabit bir kare hızı kullanırken). next game tickBiz güncellenmesi ve render sonrası arta kalan ne kadar zaman bağlı değildir.

Yazar ayrıca başka bir yaygın hata yapar

kalan ne olursa olsun oyunu mümkün olduğunca çok kez render.

Bu, aynı kareyi birden çok kez oluşturmak anlamına gelir. Yazar bunun farkında bile:

Oyun saniyede 50 kez düzenli olarak güncellenecek ve render işlemi mümkün olduğunca hızlı yapılacaktır. Oluşturma işlemi saniyede 50 kereden fazla yapıldığında, sonraki bazı karelerin aynı olacağını unutmayın, bu nedenle gerçek görsel kareler saniyede maksimum 50 karede görüntülenir.

Bu yalnızca GPU'nun gereksiz iş yapmasına neden olur ve oluşturma işlemi beklenenden daha uzun sürerse, bir sonraki karenizde amaçlanandan daha sonra çalışmaya başlamanıza neden olabilir, böylece işletim sistemine vermeniz ve beklemeniz daha iyidir.


+ Oy. Hımmm. Bu gerçekten beni o zaman ne yapacağım konusunda şaşkın bırakır. Anlayışınız için teşekkür ederim. Muhtemelen bir oyun oynayacağım, sorun FPS'yi sınırlama veya FPS'yi dinamik olarak ayarlama ve bunu nasıl yapacağım hakkında bilgi. Zihnimde sabit girdi kullanımı gerekiyor, böylece oyun herkes için aynı hızda çalışıyor. Bu sadece basit bir 2D Platformer
MMO'dur

Döngü doğru gibi görünse de, next_game_tick artırımı bunun için var. Simülasyonun daha yavaş ve daha hızlı donanımda sabit hızda çalışmasını sağlamak için var. Oluşturma kodunu "olabildiğince hızlı" (veya yine de fizikten daha hızlı) çalıştırmak, yalnızca hızlı nesneler vb. (Makalenin sonu) için daha pürüzsüz hale getirmek için oluşturma kodunda enterpolasyon olduğunda mantıklıdır, ancak herhangi bir çıkış cihazının (ekranın) gösterebileceğinden daha fazlasını oluşturuyor.
Dotti

Yani kodu geçerli bir uygulama, şimdi. Ve bu atıkla yaşamak zorunda mıyız? Yoksa bunun için arayabileceğim yöntemler var mı?
tsujp

İşletim sistemine teslim olmak ve beklemek, işletim sisteminin size ne zaman kontrolü geri vereceğine dair iyi bir fikriniz varsa iyi bir fikirdir - ki bu istediğiniz zaman olmayabilir.
Kylotan

Bu cevaba oy veremem. İnterpolasyon şeylerini tamamen kaçırıyor, bu da cevabın ikinci yarısının tamamını geçersiz kılıyor.
AlbeyAmakiir

4

Belki biraz basitleştirmek en iyisi:

while( game_is_running ) {

    current = GetTickCount();
    while(current > next_game_tick) {
        update_game();

        next_game_tick += SKIP_TICKS;
    }
    display_game();
}

whilemainloop'un içindeki döngü, simülasyon adımlarını olduğu yerden, şimdi olması gereken yere çalıştırmak için kullanılır. update_game()işlevi her zaman yalnızcaSKIP_TICKS son çağrıdan bu yana zamanın geçtiğini . Bu, oyun fiziğinin yavaş ve hızlı donanımda sabit hızda çalışmasını sağlayacaktır.

Arttırım next_game_tickmiktarına göre SKIP_TICKSdaha yakın geçerli zamana hamle. Bu, geçerli zamandan daha büyük olduğunda, kırılır ( current > next_game_tick) ve ana döngü geçerli kareyi oluşturmaya devam eder.

Oluşturduktan sonra, sonraki çağrı GetTickCount()yeni geçerli saati döndürür. Bu süre daha yükseksenext_game_tick , simülasyondaki 1-N adımların arkasında olduğumuz anlamına gelir ve simülasyondaki her adımı aynı sabit hızda çalıştırarak yetişmeliyiz. Bu durumda daha düşükse, aynı kareyi yeniden oluşturur (enterpolasyon yoksa).

Orijinal kod, çok uzakta bırakılırsak döngü sayısını kısıtlamıştı ( MAX_FRAMESKIP). Bu sadece bir şey göstermesini sağlar ve örneğin askıya alma veya oyun hata ayıklayıcıda uzun süre duraklatıldığında ( GetTickCount()o süre boyunca durmadığı varsayılarak ) zamana yetişene kadar kilitli gibi görünmüyor.

İçinde enterpolasyon kullanmıyorsanız, aynı kare oluşturma işleminden kurtulmak için, aşağıdaki display_game()gibi bir ifadeyi içeriye sarabilirsiniz:

while (game_is_running) {
    current = GetTickCount();
    if (current > next_game_tick) {
        while(current > next_game_tick) {
            update_game();

            next_game_tick += SKIP_TICKS;
        }
    display_game();
    }
    else {
    // could even sleep here
    }
}

Bu da bununla ilgili iyi bir makale: http://gafferongames.com/game-physics/fix-your-timestep/

Ayrıca, fprintfoyununuz sona erdiğinde çıktınızın neden sadece kızarmaması olabilir.

İngilizcem için üzgünüm.


4

Kodu tamamen geçerli görünüyor.

whileSon setin döngüsünü düşünün :

// JS / pseudocode
var current_time = function () { return Date.now(); }, // in ms
    framerate = 1000/30, // 30fps
    next_frame = current_time(),

    max_updates_per_draw = 5,

    iterations;

while (game_running) {

    iterations = 0;

    while (current_time() > next_frame && iterations < max_updates_per_draw) {
        update_game(); // input, physics, audio, etc

        next_frame += framerate;
        iterations += 1;
    }

    draw();
}

"Oyun çalışırken şu anki saati kontrol et - çalışan kare hızı sayımımızdan daha büyükse ve 5 kareden daha az çizim atladık, sonra çizimi atlayın ve girişi güncelleyin. fizik: başka sahne çizin ve bir sonraki güncelleme yinelemesini başlatın "

Her güncelleme gerçekleştiğinde, "next_frame" süresini ideal kare hızınıza göre artırırsınız. Sonra zamanınızı tekrar kontrol edersiniz. Geçerli saatiniz artık next_frame'in güncellenmesi gerektiğinden daha azsa, güncellemeyi atlayıp elinizde olanı çizersiniz.

Current_time zamanınız daha büyükse (son çizim işleminin gerçekten uzun sürdüğünü hayal edin, çünkü bir yerde bir miktar hıçkırık veya yönetilen bir dilde bir sürü çöp toplama veya C ++ veya herhangi bir şekilde yönetilen bellek uygulaması), atlanır alır ve çizmek next_frameile güncellenir başka birini güncellemeler saati olması gereken yerde yetişmek kadar zaman ekstra çerçeve değerinde, yoksa oyuncu görebilirsiniz yüzden kesinlikle çizmek gEREKİR yeterli çerçeveler çizim atlanır ettik ne yapıyorlar.

Makineniz süper hızlıysa veya oyununuz çok basitse, sık sık current_timedaha az olabilir next_frame, yani bu noktalar sırasında güncelleme yapmıyorsunuz demektir.

İnterpolasyon burada devreye girer. Aynı şekilde, döngülerin dışında ilan edilen ve "kirli" alan ilan etmek için ayrı bir bool olabilir.

Güncelleme döngüsünün içinde, dirty = truegerçekten bir güncelleme gerçekleştirdiğinizi belirterek ayarladınız .

Sonra, sadece aramak yerine şunu draw()söylerdiniz:

if (is_dirty) {
    draw(); 
    is_dirty = false;
}

Ardından, yumuşak hareket için herhangi bir enterpolasyonu kaçırırsınız, ancak yalnızca güncellemeler olduğunda (durumlar arasındaki enterpolasyonlar yerine) güncelleme yaptığınızdan emin olursunuz.

Eğer cesursanız, "Timestep'inizi Düzeltin!" GafferOnGames tarafından.
Soruna biraz farklı yaklaşıyor, ancak bunu çoğunlukla aynı şeyi yapan daha güzel bir çözüm olarak görüyorum (dilinizin özelliklerine ve oyununuzun fizik hesaplamalarına ne kadar önem verdiğinize bağlı olarak).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.