Oyun motorlarında zor kodlamadan nasıl kaçınılır


22

Benim sorum kodlayıcı bir soru değil; genel olarak tüm oyun motoru tasarımı için geçerlidir.

Sabit kodlamayı nasıl önlersiniz?

Bu soru göründüğünden çok daha derin. Söyleyin, işlem için gerekli dosyaları yükleyen bir oyun çalıştırmak istiyorsanız load specificfile.wad, motorun kodunda olduğu gibi bir şey söylemekten nasıl kaçınırsınız ? Ayrıca, dosya yüklendiğinde, nasıl söylemekten kaçınırsınız load aspecificmap in specificfile.wad?

Bu soru hemen hemen tüm motor tasarımları için geçerlidir ve motorun mümkün olduğunca az kodlanması gerekir. Bunu başarmanın en iyi yolu nedir?

Yanıtlar:


42

Veri odaklı kodlama

Bahsettiğiniz her şey, verilerde belirtilebilecek bir şeydir. Neden yükleniyor aspecificmap? Oyun konfigürasyonu, bir oyuncu yeni bir oyuna başladığında ilk seviye olduğunu ya da oyuncunun yeni kaydettiği dosyadaki mevcut kaydetme noktasının adı olduğu için böyle olduğunu söylüyor.

Nasıl buluyorsunuz aspecificmap? Çünkü harita kimliklerini ve diskteki kaynaklarını listeleyen bir veri dosyasında.

Yalnızca kodlamayı önlemek için yasal olarak zor olan veya imkansız olan özellikle küçük bir "çekirdek" kaynak kümesi gerekir. Bir miktar çalışmayla, bu, benzeri veya benzeri tek bir sabit kodlanmış varsayılan varlık adıyla sınırlandırılabilir main.wad. Bu dosya potansiyel olarak çalışma zamanında, yani bir komut satırı argümanına, aka geçilerek değiştirilebilir game.exe -wad mymain.wad.

Veriye dayalı kod yazmak, birkaç başka ilkeye dayanır. Örneğin, bir sistem veya modüllerin belirli bir kaynak istediğinden ve bu bağımlılıkları tersine çevirmekten kaçınabilir. Başka bir deyişle, başlangıç ​​kodunda DebugDraweryükleme yapma debug.font; bunun yerine, DebugDrawerbaşlatma kodunda bir kaynak tanıtıcısı bulundurun. Bu tanıtıcı ana oyun yapılandırma dosyasından yüklenebilir.

Kod tabanımızdan somut bir örnek olarak, kaynak veritabanından yüklenen bir "global data" nesnesine sahibiz (bu, varsayılan olarak ./resourcesklasördür ancak bir komut satırı argümanı ile aşırı yüklenebilir). Bu küresel verinin kaynak veritabanı kimliği, kod tabanında yalnızca gerekli kodlanmış kaynak adıdır (bazen programcılar tembelleşse de başkaları da vardır, ancak sonunda bunları düzeltir / kaldırırız). Bu küresel veri nesnesi, tek amacı yapılandırma verileri sağlamak olan bileşenlerle doludur. Bileşenlerden biri, diğer bazı yapılandırma öğeleri arasında tüm ana UI kaynaklarına (yazı tipleri, Flash dosyaları, simgeler, yerelleştirme verileri vb.) Kaynak tanıtıcılarını içeren UI Global Veri bileşenidir. Bir UI geliştiricisi ana UI varlığını yeniden adlandırmaya karar /ui/mainmenu.swfverdiğinde/ui/lobby.swfsadece bu küresel veri referansını günceller; hiçbir motor kodunun değişmesi gerekmez.

Bu küresel verileri her şey için kullanıyoruz. Tüm oynanabilir karakterler, tüm seviyeler, kullanıcı arayüzü, ses, temel varlıklar, ağ yapılandırması, her şey. (peki, her şey değil , ama diğer şeyler düzeltilmesi gereken hatalardır.)

Bu yaklaşımın başka birçok avantajı vardır. Birincisi, tüm süreç için kaynak paketleme ve donatma yapar. Motordaki sabit kodlama yolları, aynı yolların hangi komut dosyalarında veya araçlarda oyun varlıklarını paketlediğinin kodlanmasının zorunlu olması gerektiği ve bu yolların senkronizasyondan çıkabileceği anlamına gelir. Bunun yerine tek bir temel varlık ve referans zincirlerine dayanarak, tek bir komutla bir varlık paketi oluşturabilir bundle.exe -root config.data -out main.wadve istediğimiz tüm varlıkları içereceğini biliyoruz. Ayrıca, paketleyici yalnızca kaynak referanslarını takip edeceğinden, yalnızca ihtiyaç duyduğumuz varlıkları içereceğini ve kaçınılmaz olarak bir projenin ömrü boyunca biriktirilen tüm geri bırakılan tüyleri atlayacağını biliyoruz (artı otomatik olarak bunun listesini oluşturabiliriz. budama için kabartmak).

Her şeyin zor bir köşesi senaryosunda. Motoru veri güdümlü yapmak kavramsal olarak kolaydır, ancak komut dosyalarının veri olarak kabul edildiği ve dolayısıyla sadece kaynak yollarını ayrım gözetmeden kullanmalarına "izin verildiği" pek çok proje (AAA'ya hobi) gördüm. Yapma bunu. Eğer bir Lua dosyası bir kaynağa ihtiyaç duyuyorsa ve sadece buna benzer bir işlevi çağırıyorsa textures.lua("/path/to/texture.png"), varlık boru hattı, betiğin /path/to/texture.pngdoğru çalışması gerektiğini ve bu dokunun kullanılmamış ve gereksiz olduğunu düşünebilir. Betikler diğer kodlar gibi ele alınmalıdır: kaynaklar veya tablolar da dahil olmak üzere ihtiyaç duydukları veriler motor ve kaynak boru hattının bağımlılıkları denetleyebileceği bir yapılandırma girişinde belirtilmelidir. "Script kodla foo.lua" diyen veriler bunun yerine "demeli"foo.luave bu parametrelere "parametrelerin gerekli kaynakları içerdiği yerlerde verin. Eğer bir komut dosyası, örneğin rasgele bir düşman ortaya çıkarsa, muhtemel düşmanların listesini bu yapılandırma dosyasındaki komut dosyasına aktarabilirsiniz. Motor daha sonra düşmanları seviye ile önceden yükleyebilir ( mümkün yumurtlar tam listesi bilir) ve kaynak boru hattı onlar kesin konfigürasyon verileri tarafından başvurulan konum beri) (oyun ile tüm düşmanları paketlemekte bilir. komut yol adlarının dizeleri oluşturursa ve sadece çağırır beri loadsonra ne işlevi motor veya kaynak boru hattının, betiğin hangi varlıkları yüklemeye çalışacağını bilmenin herhangi bir yolu yoktur.


İyi cevap, çok pratik ve aynı zamanda insanların bunu uygularken yaptıkları tuzakları ve hataları açıklıyor! 1
whn

+1. Modding özelliğini etkinleştirmek istiyorsanız yapılandırma verilerini içeren kaynaklara işaret etme modelini takip etmenin de çok yararlı olduğunu ekleyeceğiz. Kendi verilerinizi oluşturmak yerine orijinal veri dosyalarını değiştirmenizi gerektiren oyunları değiştirmek çok daha zor ve daha tehlikelidir. Tanımlanmış bir öncelik sırasına sahip birden fazla dosyayı işaret edebiliyorsanız daha da iyi.
Jeutnarg

12

Genel fonksiyonlarda kodlamadan kaçındığınız gibi.

Parametreleri iletiyorsunuz ve bilgilerinizi yapılandırma dosyalarında tutuyorsunuz.

Bu durumda, yazılım mühendisliği ile motor yazmak ve sınıf yazmak arasında kesinlikle bir fark yoktur.

MgrAssets
public:
  errorCode loadAssetFromDisk( filePath )
  errorCode getMap( mapName, map& )

private:
  maps[name, map]

Daha sonra, müşteri kodunuz , varlıklar dosyalarının nerede olduğunu ve hangi haritayı içerdiğini söyleyen bilgileri içeren bir "ana" konfigürasyon dosyasını okur ( bu , kodlanmış veya komut satırı argümanı olarak yazılmıştır).

Oradan, her şey "ana" yapılandırma dosyası tarafından tahrik edilir.


1
Evet, bu artı özel mantık getirmek için bir çeşit mekanizma. Motorun temel özelliklerini kullanıcı tanımlı işlevlerle genişletmek için C #, python vs. gibi bir dil
ekleyerek olabilirsiniz

3

Diğer cevapları seviyorum, bu yüzden biraz aykırı olacağım. ;)

Verilerinizle ilgili bilgileri motorunuza kodlamaktan kaçınamazsınız. Bilgi nereden gelirse, motor onu aramayı bilmelidir. Ancak, gerçek bilginin kendisini motorunuza kodlamaktan kaçınabilirsiniz.

"Saf" veri güdümlü bir yaklaşım, çalıştırılabilir dosyayı ilk konfigürasyonu yüklemek için gerekli olan komut satırı parametreleri ile başlatmanızı sağlar, ancak motorun bu bilgiyi nasıl yorumlayacağını bilmek için kodlanması gerekir. Yapılandırma dosyaları, JSON ise Örneğin, motor aramaya bilmek zorunda kalacak sert koduna sen mesela aramaya değişkenleri ihtiyaç "intro_movies"ve "level_list"vb.

Bununla birlikte, "iyi yapılandırılmış" bir motor, yapılandırma verilerini ve referans verilerini değiştirerek birçok farklı oyun için çalışabilir.

Bu yüzden mantra, zor kodlamadan kaçınmak için çok fazla değildir, çünkü mümkün olan en az çabayla değişiklik yapabilmenizi sağlamaktır.

Veri dosyaları yaklaşımına (gönülden desteklediğim) karşı çıkmak için, verileri motorunuza derlemeniz uygun olabilir. Bunu yapmanın "maliyeti" düşükse, o zaman gerçek bir zararı yoktur; üzerinde çalışan tek kişi sizseniz, daha sonraki bir tarihte dosya işçiliğini erteleyebilir ve mutlaka kendinizi mahvetmeyebilirsiniz. İlk birkaç oyun projemde, oyunun içine kodlanmış büyük veri tabloları vardı, örneğin bir silah listesi ve çeşitli veriler:

struct Weapon
{
    enum IconID icon;
    enum ModelID model;
    int damage;
    int rateOfFire;
    // etc...
};

const struct Weapon g_weapons[] =
{
    { ICON_PISTOL, MODEL_PISTOL, 5, 6 },
    { ICON_RIFLE, MODEL_RIFLE, 10, 20 },
    // etc...
};

Böylece bu verileri referans alması kolay bir yere koyarsınız ve gerektiği şekilde düzenlemek kolaydır. İdeal olan, bu tür şeyleri bir tür yapılandırma dosyasına koymak olacaktır, ancak daha sonra ayrıştırma ve çeviri yapmanız ve tüm caz işlerinin yanı sıra, yapılar arası referansları kullanmak, gerçekten istemediğiniz ilave bir acı haline gelebilir. uğraşmak.


Jackson'ı ayrıştırmak çok zor değil. Dahil olan tek "maliyet" öğrenmedir. (Özellikle, uygun modül veya kitaplığı kullanmayı öğrenme. Go, örneğin iyi bir json desteğine sahiptir.)
Wildcard

Çok zor değil, ancak öğrenmenin ötesinde yapmayı gerektirir. Örn: JSON’un teknik olarak nasıl ayrıştırılacağını biliyorum, diğer birçok dosya formatı için ayrıştırıcı yazdım, ancak üçüncü taraf bir çözüm bulup kurmalı (ve bağımlılıkları çözmeliyim ve nasıl kurmalı) ya da kendiminkini kullanmalıyım. Yapmamaktan daha fazla zaman alıyor.
çizgi-tom-bang,

4
Her şey yapmamaktan daha fazla zaman alır. Ancak ihtiyacınız olan araçlar zaten yazılmıştır. Tıpkıbir oyun yazmak için bir derleyici tasarlamak veya makine kodu ile uğraşmakzorunda olmadığınız gibi, ancakbirlikte çalıştığınız platform için bir dil öğrenmek zorundasınız. Yani, bir json ayrıştırıcı kullanmayı da öğrenin.
Wildcard

Argümanının ne olduğundan emin değilim. Bu cevapta YAGNI'yi savunuyorum; zamanını size yardım etmeyecek bir şey yaparak harcamak / harcamak zorunda kalmazsanız, o zaman yapmayın. Bunun için zaman harcamak istiyorsanız o zaman harika. Belki daha sonra zaman harcamanız gerekebilir, belki de yapmazsınız, ama bunu baştan yapmak sizi sadece oyunu yapma görevinden uzaklaştırır. Oyun gelişimi önemsizdir; Bir oyun yapmak için giden her görev basittir. Sadece çoğu oyunun milyonlarca basit görevi var ve sorumlu bir geliştirici bu hedefe en hızlı şekilde ulaşanları seçiyor.
dash-tom-bang

2
Aslında, cevabınızı yükselttim; böyle bir gerçek tartışma yok . Sadece JSON'un ayrıştırmanın zor olmadığını not etmek istedim . Tekrar okuduğumda sanırım en çok pasajı yanıtlıyordum "ama sonra ayrıştırma ve çeviri yapmanız ve tüm bu cazı yapmanız gerekir." Ancak kişisel proje oyunları ve bunun gibi YAGNI için aynı fikirdeyim. :)
Wildcard
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.