Oyun döngülerinde oluşturma hakkında bilgi edinin

Oyun döngüsünü uygulamanın çok popüler bir yolu şu şekildedir:

while (playing) {
    advance state by one frame
    render the new frame
    sleep until it’s time to do the next frame
}

Bununla ilgili birkaç sorun var. En temeli, şu fikrin geçerlidir: bir "çerçeve"nin ne olduğunu düşünülebilir. Farklı ekranlar farklı zamanlarda yenilenir oranıdır ve bu oran zaman içinde değişiklik gösterebilir. Kareler gösterebileceği için arada sırada bir tane bırakmanız gerekir. Oluşturduğunuz SurfaceFlinger bunları çok yavaş yavaş yaptığında, önceki kareyi alır ve yeniden gösterir. Bu durumların her ikisi de hatalara neden olabilir.

Yapmanız gereken şey ekranın kare hızını eşleştirmek ve oyun durumunu ilerletmek önceki kareden bu yana ne kadar süre geçtiğine göre de belirlenir. Birkaç tane şu yolları izleyebilirsiniz:

  • Android Frame Pacing kitaplığını kullan (önerilir)
  • BufferQueue'yu doldurmak ve "değiştirme arabelleklerini" kullanmak arka basınç
  • Koreograf Kullanma (API 16+)

Android Çerçeve İlerleme Hızı kitaplığı

Daha fazla bilgi için Uygun kare hızına ulaşma bölümüne bakın kullanmaya devam edebilirsiniz.

Sıra doldurma

Bunu uygulamak çok kolay: tamponları mümkün olduğunca hızlı bir şekilde değiştirin. İlk zamanlarda hiçbir sürümün yüklü olmadığının tespit edilmesine yarar. Bu da, SurfaceView#lockCanvas(), 100 ms. boyunca uyumanı sağlar. Şimdi hızı BufferQueue ile belirlenir ve BufferQueue en kısa sürede SurfaceFlinger bunu yapabilir.

Bu yaklaşımın bir örneğini Android Breakout'ta görebilirsiniz. Google GLSurfaceView, uygulamanın öğesini çağıran bir döngüde çalışan GLSurfaceView onDrawFrame() geri çağırması yapar ve ardından arabelleği değiştirir. BufferQueue doluysa eglSwapBuffers() çağrısı, bir arabellek kullanılabilir hale gelene kadar bekler. Arabellekler, SurfaceFlinger bunları kullanıma sunduğunda kullanılabilir hale gelir ve yeni bir tane ediniyoruz. Bu işlem VSYNC'de gerçekleştiğinden, çizim döngünüz zamanlama, yenileme hızıyla eşleşecektir. Çoğunlukla.

Bu yaklaşımla ilgili bazı sorunlar vardır. İlk olarak, uygulama ve farklı sürelerde tamamlanacak SurfaceFlinger etkinliği ve CPU süresi için mücadele edip etmediğine bağlı olarak benzerlikler bulunur. Oyun durumunuz arasında geçiş yaparsanız animasyonunuz tutarlı bir hızda güncellenmez. Zaman 60 fps'de çalışıyorken zaman içinde ortalaması alınacak tutarsızlıklarla, fark etmeyeceksiniz.

İkinci olarak, ilk birkaç tampon değişimi, çünkü BufferQueue henüz dolmadı. Kareler arasında hesaplanan süre, sıfıra yakın olması gerekir, bu nedenle oyun hiçbir şeyin olmadığı birkaç kare oluşturur. Her yenilemede ekranı güncelleyen Breakout gibi bir oyunda sıra oyun ilk başlatıldığında (veya devam ettirilmediğinde) her zaman dolu olduğundan fark edebilirsiniz. Animasyonu ara sıra duraklatan ve daha sonra hızlı modda garip aksaklıklarla karşılaşabilirsiniz.

Koreograf

Koreograf, bir sonraki VSYNC'de etkinleşecek bir geri çağırma ayarlamanızı sağlar. İlgili içeriği oluşturmak için kullanılan gerçek VSYNC zamanı bağımsız değişken olarak aktarılır. Böylece uygulamanız uyanmasa bile anında, ne zaman yenilendiğini ve ekranın ne zaman yenilendiğini dönemi başladı. Geçerli saat yerine bu değeri kullanırsanız, durumu güncelleme mantığınız için tutarlı bir zaman kaynağı kullanın.

Ne yazık ki her VSYNC'den sonra geri arama almanız geri aramanızın zamanında gerçekleştirileceğini veya hızlı bir şekilde işlem yapabilecektir. Uygulamanızın şunları algılaması gerekir: ve kareleri manuel olarak atladığı durumlar olabilir.

"Record GL uygulaması" bunun bir örneğidir. Bazı cihazlarda cihazlarda (ör. Nexus 4 ve Nexus 5) bir cihaz kullanıyorsanız etkinlik, şu durumlarda kareleri atlamaya başlar: oturup izliyorsunuz. GL oluşturma işlemi sıradan değildir ancak zaman zaman Görünüm yeniden çiziliyor ve ölçüm/düzen geçişi çok uzun sürebilir. cihaz düşük güç moduna geçmiştir. (Sstrace'e göre, Android 4.4'te saatler yavaşladığında 6 ms yerine 28 ms. sürer. Sürüklerseniz etkinlikle etkileşimde bulunduğunuzu düşündüğünde, Böylece saatiniz yüksek hızda kalır ve hiçbir zaman kare atlamazsınız.)

Basit çözüm, mevcut karelerde bir kareyi devreye sokmaktı. zamanı, VSYNC zamanından sonra N milisaniyeden fazladır. İdeal olarak N değeri daha önce gözlemlenen VSYNC aralıklarına göre belirlenir. Örneğin, yenileme süresi 16,7 ms (60 fps) ise bu süreden daha uzun süre çalıştırıyorsanız bir kare atlayabilirsiniz 15 ms'den fazla geçilemez.

"Record GL uygulamasını" izlerseniz tıkladığınızda, çerçevenin boş bir sayacının artar, hatta kareler düştüğünde kenarlıkta kırmızı renkte yanıp söner. Aksi hâlde gözleriniz çok iyi, ancak animasyonda takılma yaşamazsınız. 60 fps'de, uygulama herhangi bir kullanıcının haberi olmadan ara sıra kareyi animasyon sabit bir hızda ilerlemeye devam eder. Tekliften ne kadar yararlanabileceğiniz ne çizdiğinize, onun özelliklerine bağlı olarak, ne kadar iyi performans gösterdiğinin bir göstergesidir.

İleti dizisi yönetimi

Genel olarak, bir SurfaceView, GLSurfaceView veya TextureView oluşturma işlemini özel bir iş parçacığında yapmak istiyorsunuz. Hiçbir zaman yapma "ağır yük" Belli bir süre içinde tamamlanacak olan Kullanıcı arayüzü iş parçacığı. Bunun yerine, oyun için iki ileti dizisi oluşturun: bir iş parçacığı oluşturun. Oyununuzun performansını iyileştirme konusuna bakın konulu videomuzu izleyin.

Breakout ve "Record GL uygulaması" özel oluşturucu ileti dizileri kullanır ve ayrıca animasyon durumunu güncelleyebilirsiniz. Bu makul bir yaklaşımdır; oyun durumu hızlıca güncellenebilir.

Diğer oyunlarda oyun mantığı ile oluşturma tamamen ayrı tutulur. Bir her 100 ms'de bir blok taşımaktan başka bir şey yapmayan basit bir oyun olsaydı bunu biraz önce yapan özel ileti dizisi:

run() {
    Thread.sleep(100);
    synchronized (mLock) {
        moveBlock();
    }
}

(Kaymayı önlemek için uyku süresini sabit bir saatten çıkarabilirsiniz: uyku() tamamen tutarlı değildir vemoveBlock() işlevi sıfır olmayan bir miktarda ama ana fikrini anladınız.)

Çizim kodu uyandığında, yalnızca kilidi tutar, mevcut konumu alır kilidini açar ve çekiliş yapar. Kesirli sayılar yerine kareler arası delta sürelerine göre hareket halindeyken, hareket eden ve başka bir ileti dizisi, nerede olurlarsa olsunlar onları çeken anlamına gelir.

Karmaşıklık düzeyi ne olursa olsun bir sahne için yaklaşan etkinliklerin listesini oluşturabilirsin uyanma vaktine ve bir sonraki etkinliğin tarihine kadar uykuya göre sıralanmıştır, ancak fikir edinmiş oldunuz.