AssetManager nasıl tasarlanır?


26

Bir oyunun grafikleri, sesleri, vb. Referanslarını alacak bir AssestManager tasarlamaya en iyi yaklaşım nedir?

Bu varlıklar bir anahtar / değer Harita çiftinde saklanmalı mı? Yani "arkaplan" varlığını istiyorum ve Harita ilgili bitmap'i döndürüyor? Daha iyi bir yolu var mı?

Özellikle bir Android / Java oyunu yazıyorum, ancak cevaplar genel olabilir.

Yanıtlar:


16

Bu, oyununuzun kapsamına bağlıdır. Bir varlık yöneticisi, daha büyük oyunlar için kesinlikle daha önemlidir, daha küçük oyunlar için ise çok daha az.

Daha büyük başlıklar için aşağıdakiler gibi sorunları yönetmeniz gerekir:

  • Paylaşılan varlıklar - tuğla dokusu birden fazla model tarafından kullanılıyor mu?
  • Öğe ömrü - 15 dakika önce yüklediğiniz öğeye artık gerek yok mu? Varlıkların bir şeyle ne zaman biteceğini bildiğinizden emin olmak için sayma
  • DirectX 9'da, belirli varlık türleri yüklenirse ve grafik cihazınız kaybolursa (bu, diğer şeylerin yanı sıra Ctrl + Alt + Del tuşlarına basarsanız olur) - oyununuzun bunları yeniden oluşturmanız gerekir
  • İhtiyaç duymadan önceden varlıkları yükleme - bu olmadan büyük açık dünya oyunları kuramazsınız
  • Toplu yükleme varlıkları - Yükleme sürelerini iyileştirmek için genellikle çok sayıda öğeyi tek bir dosyaya paketleriz - diskin çevresinde arama yapmak çok zaman alır

Daha küçük başlıklar için, bunlar daha az sorun yaratır, XNA gibi çerçeveler içinde varlık yöneticilerine sahiptir - yeniden icat etmenin çok az noktası vardır.

Kendinizi bir varlık yöneticisine ihtiyaç duyduğunuzu görürseniz, gerçekten tek bir çözüme uygun tek bir çözüm yoktur, ancak anahtarın dosya adının bir hash * * anahtarını içeren bir karma haritası olduğunu gördüm (azaltılmış ve tüm ayırıcılar 'sabit') Çalıştığım projeler için iyi çalışıyor.

Genellikle uygulamanızdaki dosya adlarını kodlamanız önerilmez, başka bir veri formatının (xml gibi) dosya adlarını 'ID'lere göstermesi genellikle daha iyidir.

  • Eğlenceli bir not olarak, normalde proje başına bir karma çarpışma yaşarsınız.

Varlıkları yönetmeniz gerektiğinden, büyük olasılıkla çok fazla yöntemi, düşük performansı ve çamurlu bellek semantiği olan büyük harfli bir isim olan AssetManagers'ı gerektirmez. Karşılaştırma için, çok sayıda proje yönetiminiz varsa (genellikle iyi) ve daha sonra çok sayıda proje yöneticiniz olduğunda (genellikle kötü) ne olacağını düşünün .

2
@Joe Wreschnig - icStatic'in bir varlık yöneticisi kullanmadan belirttiği beş şartı nasıl ele alacaksınız?
antinome

8

(“Varlık yöneticisini kullanma” ndan uzak durmaya çalışıyorum -bu konuda tartışmasız, çünkü offtopic olduğunu düşünüyorum.)

Bir anahtar / değer haritası çok kullanışlı bir yaklaşımdır.

Farklı Kaynak türleri için Fabrikaların kayıt olabileceği bir ResourceManager uygulamamız var.

"GetResource" yöntemi, istenen kaynak için doğru Fabrikayı bulmak için şablonlar kullanır ve belirli bir ResourceHandle döndürür (yine bir SpecificResourceHandle döndürmek için şablonu kullanarak).

Kaynaklar ResourceManager tarafından (ResourceHandle içinde) belirtilir ve artık gerekmediğinde serbest bırakılır.

Yazdığımız ilk eklenti, herhangi bir kodu değiştirmeden veya oyunu yeniden yüklemeden kaynakları çalışan motorun dışından değiştirmemizi sağlayan "yeniden yükle (XYZ)" yöntemiydi. (Bu, sanatçılar konsollarda çalışırken önemlidir;)

Çoğu zaman yalnızca ResourceManager örneğine sahibiz, ancak bazen sadece bir seviye veya harita için yeni bir örnek yaratıyoruz. Bu şekilde levelResourceManager'da "kapatma" diyebilir ve hiçbir şeyin sızdırmadığından emin olabiliriz.

(kısa) örnek

// very abbreviated!
// this code would never survive our coding guidelines ;)

ResourceManager* pRm = new ResourceManager;
pRm->initialize( );
pRm->registerFactory( new TextureFactory );
// [...]
TextureHandle tex = pRm->getResource<Texture>( "test.otx" ); // in real code we use some macro magic here to use CRCs for filenames
tex->storeToHardware( 0 ); // channel 0

pRm->releaseResource( pRm );

// [...]
pRm->shutdown(); // will log any leaked resource

6

Özel Yönetici sınıfları neredeyse hiçbir zaman doğru mühendislik aracı değildir. Varlığa yalnızca bir kez ihtiyacınız varsa (arka plan veya harita gibi), yalnızca bir kez talep etmeli ve bununla işiniz bittiğinde normal şekilde ölmesine izin vermelisiniz. Belirli bir nesneyi önbelleğe almanız gerekiyorsa, önce önbelleği kontrol eden ve başka bir şey yükleyen, önbelleğe yerleştiren ve sonra döndüren bir fabrika kullanmalısınız; bu fabrika yalnızca statik bir değişkene erişen statik bir işlev olabilir , bir tür değil.

Steve Yegge (birçoğunun, birçoğunun yanı sıra), singleton paterni sayesinde, işe yaramaz yönetici sınıflarının nasıl bittiği hakkında iyi bir hikaye yazdı. http://sites.google.com/site/steveyegge2/singleton-considered-stupid


2
Tamam, elbette. Ancak Android (veya diğer oyunlar) gibi durumlarda oyuna başlamadan önce oyuna başlamadan önce belleğe çok fazla grafik / ses yüklemelisiniz. Bir yükleme ekranında bunu yapmak için ne söylediğinizi (fabrikalar) nasıl kullanabilirim? Sadece fabrikadaki her nesneyi yükleme ekranında vurup onları önbelleğe alsın.
Bryan Denny,

Android detaylarını bilmiyorum ama "oyuna başlamadan önce" ile ne kastettiğinizi bilmiyorum. Program başlatmak için değil, ihtiyaç duyduğunuzda (veya 'yakında' gerektiğinde) ihtiyacınız olduğunda kaynak yüklemek gerçekten imkansız mıdır? Bunu son derece düşük buluyorum, aksi halde, örneğin Android'in yetersiz RAM'inden daha fazla dokuya asla sahip olamayacaksınız.

@Joe, "yükleme ekranları" hakkındaki diğer soruma bir göz atın: gamedev.stackexchange.com/questions/1171/… Boş bir önbellek vurmak, diske gitmenin uzun sürmesi anlamına gelir ve bu ilk aramalarda bazı FPS performans sonuçlarına neden olabilir . Önceden neye varacağınızı zaten biliyorsanız, önceden önbelleğe almak için yükleme sırasında da vurabilir mi?
Bryan Denny,

Yine Android ile konuşamıyorum, ancak genellikle diske gitmek, FPS isabetlerini almadan tam olarak yapabileceğiniz şeydir, çünkü diske giden iş parçacığı herhangi bir CPU kullanmaz. Sadece, pop-in alamamanız için yeterince önceden bütçelemeniz gerekir. Neye ihtiyacınız olduğunu önceden bildiğiniz için her şeyi önbelleğe alacaksanız, gerçekten bir AssetManager'a ihtiyacınız yoktur, çünkü varlıkları hiçbir şekilde yönetmeniz gerekmez - hepsi zaten el altındadır.

1
@Joe, bir fabrika da bir "Özel Yönetici" değil mi?
MSN,

2

Her zaman iyi bir varlık yöneticisinin birkaç çalışma moduna sahip olması gerektiğini düşündüm. Bu modlar büyük olasılıkla ortak bir arayüze yapışan ayrı kaynak modülleri olabilir. İki temel çalışma modu şöyle olacaktır:

  • Üretim Modu - tüm varlıklar yereldir ve tüm meta verilerden çıkarılır
  • Geliştirme Modu - değerler ek meta verilerle bir veritabanında (örn. MySQL, vb.) Saklanır. Veritabanı, paylaşılan bir veritabanını önbelleğe alan yerel bir veritabanına sahip iki katmanlı bir sistem olacaktır. İçerik oluşturucular, paylaşılan veritabanını düzenleyebilir ve güncelleyebilir ve geliştirici / KG sistemlerine otomatik olarak iletilen güncellemeleri yapabilir. Yer tutucu içeriği oluşturmak da mümkün olmalıdır. Her şey bir veritabanında bulunduğundan, üretim durumunu analiz etmek için oluşturulan raporlar ve veritabanında sorgular yapılabilir.

Tüm varlıkları paylaşılan veritabanından alıp üretim veri setini oluşturabilecek bir araca ihtiyacınız olacak.

Bir geliştirici olarak yıllarımda, böyle bir şeyi hiç görmedim, ancak bir avuç şirket için çalıştım, bu yüzden görüşüm gerçekten temsili değil.

Güncelleştirme

Tamam, bazı olumsuz oylar. Bu tasarım üzerinde genişleyeceğim.

Öncelikle, fabrika sınıflarına gerçekten ihtiyacınız yok çünkü:

TextureHandle tex = pRm->getResource<Texture>( "test.otx" );

türünü biliyorsunuz, o yüzden sadece yapın:

TextureHandle tex = new TextureHandle ("test.otx");

ama sonra, yukarıda söylemeye çalıştığım şey, zaten açık dosya isimleri kullanmayacağınız, yüklenecek dokunun, dokunun kullanıldığı model tarafından belirtileceği, aslında okunabilir bir isme ihtiyaç duymadığınız, CPU'nun kullanımı çok daha kolay olan bir 32 bit tam sayı olabilir. Yani, TextureHandle'ın yapıcısında:

if (texture already loaded)
  update texture reference count
else
  asset_stream = new AssetStream (resource_id)
  asset_stream->ReadBytes
  create texture
  set texture ref count to 1

AssetStream, verilerin konumunu bulmak için resource_id parametresini kullanır. Bunu yapma şekli, içinde bulunduğunuz ortama bağlı olacaktır:

Geliştirme aşamasında: akış, bir dosya adı almak için bir veritabanındaki kimliği arar (örneğin SQL kullanarak) ve ardından dosyayı açar, yerel olarak mevcut değilse veya dosyadan çıkarsa, dosya yerel olarak önbelleğe alınabilir veya bir sunucudan çıkarılabilir. tarihi geçmiş.

Yayınlanma Sürümü: akış, büyük / paketlenmiş bir dosyaya (Doom'un WAD dosyası gibi) bir ofset / boyut almak için bir anahtar / değer tablosundaki kimliği arar.


Size oy verdim, çünkü gerçek bir VCS kullanmak yerine her şeyi birincil anahtarlarla bir SQL tablosuna atmayı önerdiniz. Ayrıca, dize adları için erken optimizasyon yerine opak ID'ler kullanmayı düşünüyorum. Yüzlerce, çok uzun dize anahtarımız olan çeviri anahtarları dışındaki tüm varlıklar için iki büyük projede karakter dizileri kullandım (ve ardından sadece konsollara yerleştirmek için). Genellikle normalize edildi, bu yüzden string karşılaştırmasından ziyade pointer karşılaştırmasını kullanabildik, ama string karşılaştırması genellikle yine de gerçek karşılaştırmaya değil, bellek alımının maliyetine hükmediyor.

@Joe: Ben sadece bir örnek olarak SQL verdim ve daha sonra sadece bir geliştirme ortamında, eşit olarak bir VCS kullanabilirsiniz. SQL veritabanını önerdim çünkü saklanan nesnelere fazladan bilgi ekleyebilir ve veritabanındaki bilgileri sorgulamak için SQL işlevlerini kullanabilirsiniz (her şeyden daha fazla yönetim kazancı). Erken optimizasyon olarak opak ID'ler söz konusuysa - bazıları tahmin ettiğim gibi görebilir, ancak daha sonraki bir aşamada göstermeye çalışmak yerine, bununla başlamanın daha kolay olacağını düşünüyorum. Kimlik veya dizeler kullandıysanız gelişimi çok fazla etkileyeceğini sanmıyorum.
Skizz

2

Varlıklar için yapmayı sevdiğim şey, götürü bir yönetici ayarlamak . Doom motorundan ilham alan topaklar, bir topak dosyada saklanan varlıkları içeren veri parçalarıdır. adlarını, uzunluklarını, türünü (bitmap, ses, gölgelendirici, vb.) Ve içerik türünü (dosya, başka bir topaklanma, içinde) bildiren götürü dosyasının kendisi). Başlangıçta, bu topaklar ikili bir ağaca girilir, ancak henüz yüklenmez. Her harita (ayrıca bir topaktır), haritanın çalışması gereken topakların adları olan bir bağımlılık listesine sahiptir. Bu topaklar, daha önce yüklenmediyse, harita yüklendiğinde yüklenir. Ek olarak, haritanın bitişik haritalarının topakları aynı anda değil, aynı zamanda motor bir nedenden dolayı rölantide iken yüklenir. Bu, haritaları kesintisiz hale getirebilir ve yükleme ekranı yoktur.

Benim yöntemim açık dünya haritaları için mükemmel, ancak seviye tabanlı bir oyun bu yöntemin size sunduğu kusursuzluktan fayda görmeyecek. Bu yardımcı olur umarım!

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.