Bagaimana cara mendesain AssetManager?

26

Apa pendekatan terbaik untuk merancang AssestManager yang akan menyimpan referensi grafik, suara, dll. Dari sebuah game?

Haruskah aset ini disimpan dalam pasangan Peta kunci / nilai? Yaitu saya meminta aset "latar belakang" dan Peta mengembalikan bitmap yang terkait? Apakah ada cara yang lebih baik?

Khususnya saya sedang menulis game Android / Java, tetapi jawabannya bisa umum.

Bryan Denny
sumber

Jawaban:

16

Itu tergantung pada ruang lingkup gim Anda. Manajer aset sangat penting untuk judul yang lebih besar, apalagi untuk game yang lebih kecil.

Untuk judul yang lebih besar Anda harus mengelola masalah seperti yang berikut:

  • Aset bersama - apakah tekstur batu bata itu digunakan oleh banyak model?
  • Aset seumur hidup - apakah aset yang Anda muat 15 menit yang lalu tidak lagi diperlukan? Referensi menghitung aset Anda untuk memastikan Anda tahu kapan sesuatu selesai dengan dll
  • Di DirectX 9 jika jenis aset tertentu dimuat dan perangkat grafis Anda 'hilang' (ini terjadi jika Anda menekan Ctrl + Alt + Del di antara hal-hal lain) - gim Anda harus membuatnya ulang
  • Memuat aset sebelum membutuhkannya - Anda tidak bisa membangun game dunia terbuka tanpa ini
  • Aset pemuatan massal - Kami sering mengemas banyak aset ke dalam satu file untuk meningkatkan waktu pemuatan - mencari di sekitar disk sangat memakan waktu

Untuk judul yang lebih kecil hal-hal ini tidak terlalu menjadi masalah, kerangka kerja seperti XNA memiliki manajer aset di dalamnya - ada sedikit gunanya menciptakan kembali itu.

Jika Anda menemukan diri Anda membutuhkan manajer aset, sebenarnya tidak ada solusi satu ukuran untuk semua, tetapi saya telah menemukan bahwa peta hash dengan kunci sebagai hash * dari nama file (diturunkan dan separator semuanya 'diperbaiki') bekerja dengan baik untuk proyek yang saya kerjakan.

Biasanya tidak disarankan untuk nama file hardcode di aplikasi Anda, biasanya lebih baik memiliki format data lain (seperti xml) menggambarkan nama file ke 'ID'.

  • Sebagai catatan samping yang lucu, Anda biasanya mendapatkan satu tabrakan hash per proyek.
icStatic
sumber
Hanya karena Anda perlu mengelola aset tidak mengharuskan AssetManagers, kata benda penting dengan modal besar yang mungkin memiliki terlalu banyak metode, kinerja yang buruk, dan semantik memori yang buruk. Sebagai perbandingan, pikirkan tentang apa yang terjadi jika Anda memiliki banyak manajemen proyek (biasanya baik), dan kemudian ketika Anda memiliki banyak manajer proyek (biasanya buruk).
2
@ Jo Wreschnig - bagaimana Anda mengatasi lima persyaratan yang disebutkan oleh icStatic tanpa menggunakan manajer aset?
Antinome
8

(Mencoba menghindari "jangan gunakan manajer aset" - diskusi di sini, karena saya menganggapnya offtopic.)

Peta kunci / nilai adalah pendekatan yang sangat berguna.

Kami memiliki satu implementasi ResourceManager di mana Pabrik untuk tipe Sumber Daya yang berbeda dapat mendaftar.

Metode "getResource" menggunakan templat untuk menemukan Pabrik yang benar untuk resourcetype yang diinginkan dan mengembalikan ResourceHandle spesifik (sekali lagi menggunakan templat untuk mengembalikan SpecificResourceHandle).

Sumber daya dihitung ulang oleh ResourceManager (di dalam ResourceHandle) dan dirilis ketika mereka tidak diperlukan lagi.

Addon pertama yang kami tulis adalah metode "reload (XYZ)", yang memungkinkan kami untuk mengubah sumber daya dari luar mesin yang sedang berjalan tanpa mengubah kode apa pun atau memuat ulang game. (Ini penting ketika seniman mengerjakan konsol;))

Sebagian besar waktu kita hanya memiliki instance dari ResourceManager, tetapi kadang-kadang kita membuat instance baru hanya untuk level atau peta. Dengan cara ini kita bisa memanggil "shutdown" pada levelResourceManager dan memastikan tidak ada yang bocor.

Contoh (singkat)

// 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
Andreas
sumber
6

Kelas Manajer khusus hampir tidak pernah menjadi alat teknik yang tepat. Jika Anda hanya membutuhkan aset satu kali (seperti latar belakang atau peta), Anda hanya perlu memintanya sekali, dan membiarkannya mati secara normal ketika Anda selesai menggunakannya. Jika Anda perlu men-cache objek tertentu, Anda harus menggunakan pabrik yang pertama-tama memeriksa cache dan sebaliknya memuat sesuatu, memasukkannya ke dalam cache, dan kemudian mengembalikannya - dan pabrik itu dapat berupa fungsi statis mengakses variabel statis , bukan jenisnya sendiri.

Steve Yegge (di antara banyak, banyak lainnya) telah menulis cerita yang bagus tentang bagaimana kelas manajer yang tidak berguna, dengan pola tunggal, pada akhirnya. http://sites.google.com/site/steveyegge2/singleton-considered-stupid


sumber
2
Baik, tentu. Tetapi dalam kasus-kasus seperti Android (atau game lain) Anda harus memuat banyak gambar / suara ke dalam memori sebelum Anda memulai permainan, bukan selama. Bagaimana saya bisa menggunakan apa yang Anda katakan (pabrik) untuk melakukan ini selama layar muat? Tekan saja setiap objek di pabrik pada layar muat sehingga cache mereka?
Bryan Denny
Saya tidak terbiasa dengan detail Android tetapi saya tidak tahu apa yang Anda maksud dengan "sebelum Anda memulai permainan". Apakah benar-benar mustahil untuk memuat sumber daya saat Anda membutuhkannya (atau ketika Anda akan membutuhkannya 'segera') daripada saat Anda memulai program? Saya menemukan itu sangat tidak mungkin, jika tidak misalnya Anda tidak akan pernah bisa memiliki lebih banyak tekstur daripada cocok di RAM Android yang sedikit.
@Ayo lihat pertanyaan saya yang lain tentang "memuat layar": gamedev.stackexchange.com/questions/1171/... Memukul cache kosong berarti waktu yang lama untuk masuk ke disk dan dapat mengakibatkan beberapa hit kinerja FPS pada panggilan pertama itu . Jika Anda sudah tahu apa yang akan Anda buat sebelumnya, mungkin lebih baik tekan saat memuat untuk melakukan pra-cache, kan?
Bryan Denny
Sekali lagi saya tidak dapat berbicara dengan Android, tetapi biasanya pergi ke disk adalah apa yang dapat Anda lakukan tanpa mengambil hit FPS, karena utas ke disk tidak akan menggunakan CPU sama sekali. Anda hanya perlu membuat anggaran untuk melakukannya cukup jauh sebelumnya sehingga Anda tidak mendapatkan pop-in. Jika Anda akan melakukan pra-cache segalanya karena Anda tahu sebelumnya apa yang Anda butuhkan, Anda benar-benar tidak memerlukan AssetManager, karena Anda tidak perlu mengelola aset sama sekali - semuanya sudah ada di tangan.
1
@ Jo, bukankah pabrik juga "Manajer Khusus"?
MSN
2

Saya selalu berpikir bahwa manajer aset yang baik harus memiliki beberapa mode operasi. Mode-mode ini kemungkinan besar akan menjadi modul sumber terpisah yang mengikuti antarmuka umum. Dua mode dasar operasi adalah:

  • Mode Produksi - semua aset bersifat lokal dan tidak memiliki semua data meta
  • Mode Pengembangan - assests disimpan dalam database (misalnya MySQL, dll) dengan meta data tambahan. Basis data akan menjadi sistem dua tingkat dengan basis data lokal yang menyimpan basis data bersama. Pembuat konten akan dapat mengedit dan memperbarui database bersama dan pembaruan secara otomatis disebarkan ke sistem pengembang / QA. Harus juga dimungkinkan untuk membuat konten placeholder. Karena semuanya ada dalam database, pertanyaan dapat dibuat pada database dan laporan yang dihasilkan untuk menganalisis keadaan produksi.

Anda membutuhkan alat yang dapat mengambil semua assest dari database bersama dan membuat dataset produksi.

Dalam tahun-tahun saya sebagai pengembang, saya belum pernah melihat yang seperti ini, meskipun saya hanya bekerja untuk beberapa perusahaan sehingga pandangan saya tidak benar-benar representatif.

Memperbarui

OK, beberapa suara negatif. Saya akan memperluas desain ini.

Pertama, Anda tidak benar-benar membutuhkan kelas pabrik karena jika Anda punya:

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

Anda tahu tipenya, jadi lakukan saja:

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

tapi kemudian, apa yang saya coba katakan di atas adalah bahwa Anda tidak akan menggunakan nama file eksplisit, tekstur untuk memuat akan ditentukan oleh model tekstur digunakan, sehingga Anda tidak benar-benar membutuhkan nama yang dapat dibaca manusia, itu bisa menjadi nilai integer 32 bit, yang jauh lebih mudah untuk ditangani oleh CPU. Jadi, dalam konstruktor untuk TextureHandle Anda akan memiliki:

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 menggunakan parameter resource_id untuk menemukan lokasi data. Cara melakukannya tergantung pada lingkungan tempat Anda menjalankan:

Dalam Pengembangan: aliran mencari ID dalam database (menggunakan SQL misalnya) untuk mendapatkan nama file dan kemudian membuka file, file tersebut dapat di-cache secara lokal, atau ditarik dari server jika file lokal tidak ada atau kadaluarsa.

Dalam Rilis: aliran melihat ID dalam tabel kunci / nilai untuk mendapatkan offset / ukuran menjadi file besar yang dikemas (seperti file WAD Doom).

Mendesis
sumber
Saya memilih Anda karena Anda menyarankan untuk mengubah semuanya menjadi tabel SQL dengan kunci utama daripada menggunakan VCS nyata. Saya juga mempertimbangkan untuk menggunakan ID buram daripada pengoptimalan prematur nama string. Saya menggunakan string pada dua proyek besar untuk semua aset selain kunci terjemahan, yang kami punya ratusan ribu kunci string yang sangat panjang (dan kemudian hanya untuk port ke konsol). Mereka biasanya dinormalisasi sehingga kita bisa menggunakan perbandingan pointer daripada perbandingan string, tetapi perbandingan string sering didominasi oleh biaya pengambilan memori dan bukan perbandingan sebenarnya.
@ Jo: Saya hanya memberikan SQL sebagai contoh dan kemudian hanya di lingkungan pengembangan, Anda bisa menggunakan VCS. Saya hanya menyarankan database SQL karena Anda kemudian dapat menambahkan informasi tambahan ke objek yang disimpan dan menggunakan fungsi SQL untuk meminta informasi dari database (lebih banyak keuntungan manajemen daripada yang lain). Adapun ID buram sebagai optimasi prematur - beberapa mungkin melihatnya seperti itu saya kira, tapi saya pikir akan lebih mudah untuk memulai dengan itu daripada menunjukkannya di kemudian hari dalam pengembangan. Saya tidak berpikir itu akan banyak mempengaruhi pengembangan jika Anda menggunakan ID atau string.
Mendesing
2

Apa yang saya suka lakukan untuk aset adalah mengatur manajer lumpuh . Terinspirasi oleh mesin Doom, benjolan adalah potongan data yang berisi aset, disimpan dalam file benjolan yang menyatakan nama, panjang, jenis benjolan (bitmap, suara, shader, dll.), Dan jenis konten (file, benjolan lain, di dalam file lump itu sendiri). Saat startup, benjolan ini dimasukkan ke dalam pohon biner, tetapi belum dimuat. Setiap peta (yang juga merupakan benjolan) memiliki daftar dependensi, yang hanyalah nama-nama benjolan yang perlu dikerjakan oleh peta. Benjolan ini, kecuali jika sudah dimuat, dimuat pada saat peta dimuat. Selain itu, gumpalan peta yang bersebelahan dengan peta dimuat, tidak pada saat yang bersamaan, tetapi saat mesin idle karena suatu alasan. Ini dapat membuat peta menjadi mulus, dan tidak ada layar pemuatan.

Metode saya sempurna untuk peta dunia terbuka, tetapi permainan berbasis level tidak akan mendapat manfaat dari kelancaran metode ini. Semoga ini membantu!

Marcus Cramer
sumber