26-07-2021, 15:28
Crusader Kings III geliştirici günlüklerinde bu hafta oyunun yüklenmesine dair bilgiler veriliyor.
https://forum.paradoxplaza.com/forum/thr...g.1482960/ :Herkese merhabalar. Ben Matthew, Crusader Kings III'te programcı olarak çalışıyorum. Bu yazı Oyunun Anatomisi adını verdiğim, teknik odaklı geliştirici günlüklerinin ilki olacak. Bu seride oyun geliştirilişine dair sizlere bilgiler verecek, oyunların aslında "nasıl çalıştığını" anlatacağız.
Oyunu başlattığınızda tam olarak ne oluyor? Oyun DLCleri ve modları nasıl yüklüyor? Oyunda 1 gün ilerlediğinizde neler hesaplanıyor? Yapay zeka size karşı bir istila başlatmaya nasıl karar veriyor? Bu seride bu soruları yanıtlamaya çalışacağım.
Bu yazılar geleneksel geliştirici günlüklerimizin yerine yazılan yazılar değiller, eğer gelecekte sizleri bekleyen içeriklere dair bilgi peşindeyseniz, şu an yanlış yerdesiniz. Eğer oyunun teknik anlamda nasıl işlediğini, nasıl çalıştığını merak ediyorsanız, belki bir gün oyun geliştiricisi olmayı umuyorsanız, işte o zaman doğru yerdesiniz.
Oyunun Yüklenmesi
Bu yazıda, her şeyin başından bahsedeceğiz. Oyunu açtınız ve oynamak üzere bir karakter seçeceksiniz. Oyun bu esnada neler yapıyor?
Basit bir soru gibi görünebilir, karakter seçebilmeniz için her şeyin yüklenmesi gerek değil mi? Peki bunu olabildiğince esnek ve hızlı nasıl yapabiliriz? Belki de bazı şeyleri daha sonra yükleyebiliriz? Modlar ve DLC'ler gibi mevcut içeriğin üstüne yazılan unsurlar nasıl işliyor?
Oyunu başlattığınızda kabaca şu adımlar gerçekleşiyor:
Sıralamaya baktığınızda kendi içerisinde bir mantığı olduğunu görürsünüz, dosya sistemi olmadan dosya açamayız, kayıt sistemi olmadan kayıt alamayız. Bu sebeple ilk raddede arkaplanda çalışan unsurlar harekete geçiyor. Bu raddede elbette çok sayıda ayrıntı denebilecek unsuru es geçiyoruz. En önemli unsurlar 3. ila 5. adımlar arasında gerçekleşiyor, oyunun gerçek anlamda yüklendiği kısım orası ve optimizasyon anlamında çalışmaların büyük bir kısmı da orası için yapılıyor.
- Oyun motoru, belli bir sırayla dosyaları çalıştırıyor: dosya sistemi, kayıt sistemi, threading sistemi.
- SDL adı verilen bir kütüphaneyi çalıştırıyor, bu oyunların donanıma erişmesini sağlıyor.
- Oyun motorunun görevini gerçekleştirebilmesi için çok sayıda dosya çalıştırılıyor.
- Oyun için bir pencere oluşturuluyor.
- Oyun için ekstradan dosyalar çalıştırılıyor.
- Oyun artık yüklendi ve oynanmaya hazır.
Oyunun yüklenmesine yönelik optimizasyonlar neden önemli? Her ne kadar yükleme ekranlarımız ve oyun içi müziklerimiz harika olsa da, bir ekranda beklemektense oyunu oynamayı tercih edersiniz. Biz ise gün içerisinde sayısız kez oyunu aç kapa yapıyoruz, dolayısıyla oyun ne kadar hızlı yüklenirse o kadar zamandan kar etmiş oluyoruz. Beklediğimiz her fazladan saniye vakit ve nakit kaybı anlamına geliyor. Üstelik biz oyunu çalıştırdığımızda compiler olmadan yapıyoruz, dolayısıyla oyun bizde, sizde olduğundan daha yavaş açılıyor.
Elbette optimizasyon öncelikli olarak oyunun yüklenmesine odaklanan bir olay değil. Günlük ilerleme hızı ve framerate'e de oldukça ciddi zaman ayırıyoruz. Ancak gündelik olarak yaptığımız çalışmalarda aklımızda bulundurduğumuz alanlardan birisi diyebilirim.
Peki bu kadar farklı unsuru nasıl optimize edebilirsiniz? İlk önceliğimiz işlemci zamanının daha etkili bir şekilde kullanılmasını sağlamak ve beklenen süreyi azaltmak. İşlemci zamanı birkaç farklı kola ayrılıyor: daha fazla multithreading yaparak işlemciyi daha etkili bir şekilde kullanmak, düşük kademe algoritmaları optimize etmek, donanımı işlemci branchıyla uyumlu bir şekilde kullanıp, bellek sorunlarını ortadan kaldırmak.
Bekleme hususunda ise hedefimiz bunun daha nadir yaşanması. Beklemekten kastımız işlemcinin bir kaynağa erişmek için beklemek zorunda kaldığı anlar, örneğin threaded bir işin bitmesini beklemesi, diskteki bir veriyi almak için beklemesi vs.
Bu konuda daha ayrıntılı bilgiler vereceğim kısım yüksek oranda multithreading ile alakalı olacak. Bu yılın başında oyun kısmında veri yüklemeye dair yaptığımız ilerlemeler büyük oranda multithread geliştirmeleri ile alakalıydı.
Yükleme Ekranı
Oyun yüklenirken en önemli şeylerden birisi yükleme ekranının olabildiğince erken bir şekilde belirmesi. Böylelikle oyuncular oyunun çökmediğini ve açılmak üzere olduğunu anlayabilir.
Yukarıda gördüğünüz logo arkaplanda yaşanan yüklemeler için bir vitrin görevi görüyor. Yükleme ekranını size sunduktan sonra, mouse textureları ve metnin yükleme ekranı üzerinde nasıl gözükeceğini belirleyen azami arayüz dosyalarını yüklüyoruz.
Hızlı bir şekilde yüklenmesinin önem sarfettiği bir diğer husus ise müzik veritabanı. Böylelikle yükleme ekranına sessiz bir şekilde bakmaktansa, bestecimizin etkileyici parçalarını dinleyebiliyorsunuz. Her ne kadar geliştirici olarak nihai amacımız oyuncunun ana müziği olabildiğince az duymasını sağlamak olsa da, kusura bakma Andreas...
Diğer oyunlarda geçilemeyen cutscenelerle karşılaşabilirsiniz, örneğin birisi kapıyı açar ve karakteriniz içeri girer. Bunlardan bazıları bir süre sonra geçilebilir hale gelebilir. İşin aslında bunlar arkaplanda oyunun yaptığı yüklemeleri gizleyen yükleme ekranından ibaret.
Neyse ki günümüzde donanımlar her geçen gün gelişiyor ve çok daha hızlı bir şekilde bu kısımları geçebiliyorsunuz, ancak eski oyunları oynadığınızda çok sayıda geçilemeyen cutscene ile karşılaşırsınız.
Modern bir örnek verelim, Mass Effect 1'deki asansör sahneleri aslında birer yükleme ekranı ve oyunu en güncel donanımla dahi oynasanız bu sahneleri geçemiyorsunuz. Yeni çıkan Mass Effect Legendary Edition'da artık donanımlar çok daha güçlü olduğu için asansör sahnelerinde bir geçme butonu bulunuyor. Elbette hala bu sahneleri geçmeyip izleyebiliyorsunuz.
Bu raddeye kadar multithreaded çok sayıda unsur yükledik ancak bu noktadan itibaren oyun tarafında threaded unsurlar yükleyeceğiz. Arayüzün geri kalanı, 3 boyutlu görseller için veriler, harita verileri, oyun mantık dosyaları, frontend diğer unsurlar. Tüm bu yüklemeler tamamlanırken ana thread "yükleme ekranını" gösterdiğimize emin olmakla ve işletim sisteminin arkaplanda yapılan çalışmaları görmezden gelmesini sağlamakla mükellef.
Bunun sebebi, Windows'un karşınıza çıkan bir pencerenin input eventinde yanıt alamaması durumunda, uygulamanın çöktüğünü zannetmesi. Oyun yüklenirken herhangi bir anlamlı input sağlayamayacağımız için, onları alıp görmezden gelmesini sağlıyoruz, böylelikle Windows uygulamanın çöktüğünü zannetmiyor.
Önemli bir diğer unsur, aslında birçok şeyin siz ana menüye ulaştığınızda dahi yüklenmemiş olması. Ortalama bir kullanıcı hangi oyun dosyasından devam edeceğini düşünür, genellikle bir süre ana menüde bekler, bu sebeple her şeyi o noktada yüklememiz şart değil. Bu sebeple oyun ana menüye geçtiğinde de arkaplanda oyunla alakalı bazı şeyler yüklenmeye devam ediyor.
Bunun bir örneği tarih veritabanı. Bu veritabanı oldukça büyük ve ana menüde ihtiyaç duyulmayan bir unsur, sadece yeni bir oyun başlatırken ihtiyaç duyuyoruz. Kayıt dosyaları oynanış süresi içerisinde kendi tarih veritabanlarına sahipler. Bu sebeple tarih veritabanı siz oyunun ana menüsüne geldiğinizde arka planda yüklenmeye devam eden şeylerden birisi ve genellikle yeni bir oyun başlatmak istediğinizde çoktan yüklenmiş oluyor.
Veritabanı Yüklenmesi
Bu zamana kadar yüklenen şeylerden ve bunların önceliklerinden bahsettik. Şimdi biraz daha spesifik bir kısımdan bahsedeceğim, oyunun ve mod dosyalarının veritabanıyla olan ilişkisi.
DLC'lerin ve modların işleyişi oldukç.a basit. Dosya sisteminde PhysicsFS, veya daha kısa olarak PhysFS'in bütünleştirilmiş ve düzenlenmiş bir versiyonunu kullanıyoruz. Bu program tek bir hiyerarşi altında, birden fazla konumdan dosya oluşturabilmemizi mümkün kılıyor. Başlangıç esnasında ilk olarak dosya sistemini çalıştırıp oyunun dosyalarını yüklüyoruz, ardından oyuncunun aktifleştirdiği DLCleri ve modları yüklüyoruz. Örneğin "common/landed_titles" dosyası yüklenirken, ilk olarak oyundaki versiyonu, ardından yüklenmiş ve aktifleştirilmiş modlardaki versiyonu yükleniyor ve kod açısından eşdeğer görülüyor.
Peki bu veritabanlarını nasıl yüklüyoruz? CK3'ün şu an erişebildiğiniz versiyonunda onları kademeli bir biçimde arkaplan işlemi olarak yüklüyoruz. Bu durum uzun bir süre boyunca "yeterliydi" ancak daha fazla sistem oluşturdukça bu yaklaşım sıkıntılar yaratmaya başladı. Tüm threaded yüklemeler arasında en büyük pay veritabanlardaydı ve optimizasyon açısından sıkıntı teşkil ediyordu.
Veritabanlarının paralel bir şekilde yüklenebilmesi, HoI4'ün kod şefi MatRopert'in Stellaris'te yaptığı çalışmalar sayesinde mümkün olabildi. Stellaris'te çalışırken PhysFS dosya sisteminin threaded bir şekilde işlemesini sağlamıştı. Kendisinin yazdığı şu blog yazısını okumanızı öneririm, ancak bu yazıdan daha fazla kodculara hitap eden bir yazı olduğunu belirtmeliyim.
Peki biz ne yaptık? İlk olarak veritabanı dependency'lerini kodda üzerinde çalışılabilecek graph objectler haline getirdik. Dependency graph'ı oluşturmak oldukça basitti, her veritabanı kodu analiz edilerek strict dependency gerekip gerekmediği görülebiliyor. Bu durum kabaca mevzubahis dosyanın, başka bir veritabanını refere edip etmediğini inceliyor.
Bu grafik performans kazanımları edinebilmemizi sağlıyor zira hangi veritabanının hangisine muhtaç olduğunu görebiliyor, hangi veritabanının paralel bir şekilde yüklenmeye ihtiyaç duymadığını görebiliyoruz. Ardından bir veritabanı yüklendiğinde ona muhtaç olan veritabanlarına "yüklenebilirsin" izni veriyoruz ve onlar da yükleniyor.
Threadingle alakalı işlerde genellikle anlatmak yapmaktan daha kolaydır. Yaptığınız işlemlerin birbiriyle çakışmadığından emin olmanız, hızlandırma adına yaptığınız işlerin senkronizasyon esnasında ortadan kaybolmadığından emin olmanız gerekir. Tüm bu çalışmaların ardından bazı gizli dependencyleri sisteme katmam, daire şeklinde dependencyler oluşturmam, çeşitli threading problemlerini çözmem, bazı ekstra sistemleri optimize etmem gerekti.
Meneth ile birlikte yaptığımız çalışmalar sonrasında:Elbette bir anda yüklenip oyunun ana müziğini duymanıza engel olmayacak, ancak zamandan kar etmemizi ve Twitter'da esprili görseller incelemek yerine oyun üzerine daha fazla çalışabilmemizi sağlayacak.
- Debug buildlerinin yüklenmesi 20 saniyeden fazla sürüyordu, artık 4 saniyenin altında.
- Çıkış buildlerinin yüklenmesi kabaca 7 saniye sürüyordu, artık 1 saniyenin altında.
Tarihten Gerçeğe
Ana menüye geldikten sonra, sıra oyuna girmeye geliyor. Bu raddede dilerseniz bir kayıt dosyasını yükleyebilir, dilerseniz yeni bir oyun başlatabilirsiniz. Bu iki işlem de birbirine oldukça benzer, o sebeple ikisinden bir arada bahsedeceğim.
Kayıt dosyası yüklemek çok daha basit, ana gamestate'ini oluşturuyor, üstüne kayıt dosyasını yüklüyoruz. Kayıt dosyalarımız ve oyunlarımızdaki diğer tüm dosyalar okunması kolay bir syntaxla çalışıyor.
Simülasyonlarımız çok sayıda değer ve veri oluşturabildiği için kayıt dosyaları büyüyebiliyor, bu sebeple çoğu şeyi eğer varsayılan değerse kayıt dosyasında belirtmiyoruz veya oyun içerisinde cache olarak saklıyoruz. Bunun bir örneği karakterlere uygulanan modifierlar, işin özünde bu modifierlar kayıt dosyalarında yer almıyor. Onları sadece senkronizasyon hatalarını incelerken kayda alıyoruz ve cacheler arasında bir farklılık olup olmadığını inceliyoruz.
Yeni oyun başlatmak aslında başlangıç tarihindeki bir kayıt dosyasını yüklemek gibi. Eğer harita üzerinde size sunulan karakterlerden birini seçerseniz, o karakterin seçili olduğu kayıt dosyasıyla başlıyorsunuz. Eğer tüm harita üzerinden bir karakter seçmek isterseniz bu sefer kayıt dosyasının lobisine geçmiş oluyorsunuz.
Kayıt dosyası oluşturmak tarih dosyalarını okuyup varsayılan değerleri değiştirmek şeklinde. Başlangıçta yarı-rastgele içerikler de oluşturuyoruz, böylelikle her oyununuz birbirinin aynısı olmuyor. Elbette Stellaris gibi tüm oyun değişmiyor, tarihsel arkaplanı olabildiğince tutarlı bir şekilde korumaya çalışıyoruz.
Eğer modlama işleriyle ilgileniyorsanız, history dosyalarının çok sayıda küçük dosyalar yerine, az sayıda büyük dosyalara evrimleştiğini fark etmiş olabilirsiniz. Bunun sebebi Windows'un bir dosyayı açmasının Linux veya OSX kadar hızlı olmaması, bu sebeple olabildiğince az sayıda dosya açmak istiyoruz. Oyunun yüklenmesinin daha paralel ve daha hızlı bir şekilde olmasını hedeflediğimiz için bu tür bir yaklaşım benimsedik.
Oyundayız!
Ve artık oyundayız! Şimdi oyunu oynama vakti. Tabii eğer benim yerimdeyseniz, optimizasyonla uğraşma vakti. Oyunu kapatıp, bazı kodları değiştirip, tekrar oyunu açıp çeşitli ölçümler yapmak şeklinde...
Umuyorum oyun geliştirilişine dair kaleme alınmış bu metinden keyif almışsınızdır. Olabildiğince yüzeysel anlatmaya gayret gösterdim zira okuyanların bir çoğunun yazılım mühendisi diploması olmayacağının farkındayım. Oyun geliştirilişinin oyuncular tarafından, kitaplara, filmlere veya müziklere nazaran daha kapalı bir kutu olduğunu düşündüğüm için bu tür bir yazı dizisi başlatmak istedim. Herkes kitap yazmanın nasıl bir süreç olduğu, bir enstrümanın kabaca nasıl çalındığı hakkında fikre sahip fakat oyun geliştirmek öyle değil. Dünyadaki en büyük eğlence sektöründe müşteriler satın aldıkları öğenin nasıl ortaya çıktığı konusunda yanılgılara sahip. Zaman içerisinde bu yanılgıları ortadan kaldırıp, sis perdesini aralayamayı umuyorum.
Bu haftalık bizden bu kadar.