Saya akan mulai dengan tidak memikirkan manajer aset . Memikirkan arsitektur Anda dengan istilah yang tidak jelas (seperti "manajer") cenderung membuat Anda secara mental menyapu banyak detail di bawah permadani, dan akibatnya menjadi lebih sulit untuk menyelesaikan suatu solusi.
Fokus pada kebutuhan spesifik Anda, yang tampaknya dilakukan dengan menciptakan mekanisme pemuatan sumber daya yang mengabstraksi penyimpanan asal yang mendasarinya dan memungkinkan untuk diperpanjang dari set tipe yang didukung. Tidak ada yang benar-benar dalam pertanyaan Anda mengenai, misalnya, caching sumber daya yang sudah dimuat - yang baik-baik saja, karena sesuai dengan prinsip tanggung jawab tunggal Anda mungkin harus membangun cache aset sebagai entitas yang terpisah dan menggabungkan dua antarmuka di tempat lain , sewajarnya.
Untuk mengatasi masalah khusus Anda, Anda harus merancang loader agar tidak memuat aset apa pun, melainkan mendelegasikan tanggung jawab tersebut ke antarmuka yang dirancang untuk memuat jenis aset tertentu. Sebagai contoh:
interface ITypeLoader {
object Load (Stream assetStream);
}
Anda bisa membuat kelas baru yang mengimplementasikan antarmuka ini, dengan masing-masing kelas baru yang dirancang untuk memuat jenis data tertentu dari aliran. Dengan menggunakan stream, tipe loader dapat ditulis terhadap antarmuka umum, agnostik penyimpanan, dan tidak harus sulit dikodekan untuk memuat dari disk atau database; ini bahkan akan memungkinkan Anda untuk memuat aset Anda dari aliran jaringan (yang bisa sangat berguna dalam mengimplementasikan hot-reload aset saat game Anda berjalan di konsol dan alat pengeditan Anda pada PC yang terhubung ke jaringan).
Pemuat aset utama Anda harus dapat mendaftarkan dan melacak pemuat khusus tipe ini:
class AssetLoader {
public void RegisterType (string key, ITypeLoader loader) {
loaders[key] = loader;
}
Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}
"Kunci" yang digunakan di sini bisa apa saja yang Anda suka - dan itu tidak harus berupa string, tetapi mudah untuk memulai. Kuncinya akan memperhitungkan bagaimana Anda mengharapkan pengguna untuk mengidentifikasi aset tertentu dan akan digunakan untuk mencari loader yang sesuai. Karena Anda ingin menyembunyikan fakta bahwa implementasinya mungkin menggunakan sistem file atau database, Anda tidak bisa membuat pengguna merujuk ke aset melalui jalur sistem file atau semacamnya.
Pengguna harus merujuk ke aset dengan informasi minimal. Dalam beberapa kasus, hanya nama file saja sudah cukup, tetapi saya telah menemukan bahwa sering diinginkan untuk menggunakan pasangan jenis / nama sehingga semuanya sangat eksplisit. Dengan demikian, pengguna dapat merujuk ke instance bernama salah satu file XML animasi Anda sebagai "AnimationXml","PlayerWalkCycle"
.
Di sini, AnimationXml
akan menjadi kunci di mana Anda mendaftar AnimationXmlLoader
, yang mengimplementasikan IAssetLoader
. Jelas, PlayerWalkCycle
mengidentifikasi aset spesifik. Diberi nama jenis dan nama sumber daya, pemuat aset Anda dapat meminta penyimpanan persistennya untuk byte mentah dari aset itu. Karena kita akan mencapai generalitas maksimum di sini, Anda dapat menerapkan ini dengan melewatkan loader sebagai sarana akses penyimpanan saat Anda membuatnya, memungkinkan Anda untuk mengganti media penyimpanan dengan apa pun yang dapat menyediakan aliran di kemudian hari:
interface IAssetStreamProvider {
Stream GetStream (string type, string name);
}
class AssetLoader {
public AssetLoader (IAssetStreamProvider streamProvider) {
provider = streamProvider;
}
object LoadAsset (string type, string name) {
var loader = loaders[type];
var stream = provider.GetStream(type, name);
return loader.Load(stream);
}
public void RegisterType (string type, ITypeLoader loader) {
loaders[type] = loader;
}
IAssetStreamProvider provider;
Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}
Penyedia aliran yang sangat sederhana hanya akan melihat dalam direktori root aset tertentu untuk subdirektori bernama type
dan memuat byte mentah dari file yang dinamai name
ke dalam aliran dan mengembalikannya.
Singkatnya, apa yang Anda miliki di sini adalah sistem di mana:
- Ada kelas yang tahu cara membaca byte mentah dari beberapa jenis penyimpanan backend (disk, database, aliran jaringan, apa pun).
- Ada kelas yang tahu cara mengubah aliran byte mentah menjadi jenis sumber daya tertentu dan mengembalikannya.
- "Pemuat aset" Anda yang sebenarnya hanya memiliki koleksi di atas yang disebutkan di atas dan tahu cara menyalurkan output dari penyedia aliran ke pemuat jenis khusus dan dengan demikian menghasilkan aset konkret. Dengan memaparkan cara-cara untuk mengonfigurasikan penyedia aliran dan pemuat spesifik-jenis, Anda memiliki sistem yang dapat diperluas oleh klien (atau diri Anda sendiri) tanpa harus mengubah kode pemuat aset yang sebenarnya.
Beberapa peringatan dan catatan akhir:
Kode di atas pada dasarnya adalah C #, tetapi harus diterjemahkan ke bahasa apa saja dengan sedikit usaha. Untuk memfasilitasi ini, saya menghilangkan banyak hal seperti memeriksa kesalahan atau menggunakan dengan benar IDisposable
dan idiom lain yang mungkin tidak berlaku langsung dalam bahasa lain. Itu dibiarkan sebagai pekerjaan rumah bagi pembaca.
Demikian pula, saya mengembalikan aset konkret seperti di object
atas, tetapi Anda dapat menggunakan generik atau templat atau apa pun untuk menghasilkan jenis objek yang lebih spesifik jika Anda suka (Anda harus, senang bekerja dengannya).
Seperti di atas, saya tidak berurusan dengan caching sama sekali di sini. Namun, Anda dapat menambahkan caching dengan mudah dan dengan jenis umum dan konfigurasi yang sama. Cobalah dan lihat!
Ada banyak cara untuk melakukan ini, dan tentu saja tidak ada satu cara atau konsensus, itulah sebabnya Anda belum dapat menemukannya. Saya telah mencoba memberikan kode yang cukup untuk menyampaikan poin-poin spesifik tanpa mengubah jawaban ini menjadi dinding kode yang sangat panjang. Ini sudah sangat lama. Jika Anda memiliki pertanyaan klarifikasi, jangan ragu untuk berkomentar atau menemukan saya di obrolan .