Я б почав, не замислюючись про менеджера з активів . Роздумуючи про свою архітектуру в нечітко визначених термінах (як-от "менеджер"), як правило, ви дозволяєте подумки підмітати багато деталей під килимом, і, отже, стає складніше вирішувати рішення.
Зосередьтеся на ваших конкретних потребах, що, як видається, пов'язане зі створенням механізму завантаження ресурсів, який абстрагує базове сховище походження та дозволяє розширити підтримуваний набір типів. У вашому питанні насправді немає нічого, що стосується, наприклад, кешування вже завантажених ресурсів - це чудово, тому що, дотримуючись принципу єдиної відповідальності, ви, ймовірно, повинні будувати кеш активів як окрему сутність та об’єднувати два інтерфейси в іншому місці , по мірі необхідності.
Щоб вирішити вашу конкретну проблему, слід розробити завантажувач таким чином, щоб він не здійснював завантаження будь-яких активів, а делегував цю відповідальність інтерфейсам, призначеним для завантаження конкретних типів активів. Наприклад:
interface ITypeLoader {
object Load (Stream assetStream);
}
Ви можете створювати нові класи, що реалізують цей інтерфейс, при цьому кожен новий клас призначений для завантаження певного типу даних із потоку. Використовуючи потік, завантажувач типів може записуватися на загальний інтерфейс, що зберігає агностики, і не потрібно сильно кодувати для завантаження з диска або бази даних; це навіть дозволить вам завантажувати свої активи з мережевих потоків (що може бути дуже корисно при здійсненні гарячого перезавантаження активів, коли ваша гра працює на консолі та ваші інструменти для редагування на підключеному до мережі ПК).
Ваш головний навантажувач активів повинен мати можливість реєструвати та відстежувати ці типові навантажувачі:
class AssetLoader {
public void RegisterType (string key, ITypeLoader loader) {
loaders[key] = loader;
}
Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}
Використовуваний тут "ключ" може бути будь-яким, що вам подобається - і він не повинен бути рядком, а з них легко почати. Ключ буде визначати те, як ви очікуєте від користувача ідентифікації певного ресурсу, і він буде використаний для пошуку відповідного завантажувача. Оскільки ви хочете приховати той факт, що в реалізації може бути використана файлова система або база даних, у вас не може бути користувачів, що посилаються на активи шляхом шляху до файлової системи або чогось подібного.
Користувачі повинні посилатися на актив із мінімальним рівнем інформації. У деяких випадках достатньо лише одного імені файлу, але я виявив, що часто бажано використовувати пару типів / імен, щоб все було дуже явним. Таким чином, користувач може посилатися на названий екземпляр одного з ваших XML-файлів анімації як "AnimationXml","PlayerWalkCycle"
.
Тут AnimationXml
буде ключ, під яким ви зареєструвались AnimationXmlLoader
, який реалізує IAssetLoader
. Очевидно, PlayerWalkCycle
визначає конкретний актив. Враховуючи ім'я типу та ім’я ресурсу, завантажувач активів може запитувати його постійне сховище для необроблених байтів цього ресурсу. Оскільки ми маємо на увазі максимальну загальність, ви можете реалізувати це, передаючи завантажувачу засіб доступу до пам’яті, коли ви створюєте його, дозволяючи замінити носій пам’яті будь-чим, що може подати потік пізніше:
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>();
}
Дуже простий постачальник потоків просто загляне у вказаний кореневий каталог активів для підкаталогу з іменем type
та завантажить необроблені байти файла, названого name
у потік, і поверне його.
Коротше кажучи, тут у вас є система, де:
- Є клас, який вміє читати необроблені байти з якогось резервного сховища (диск, база даних, мережевий потік і все, що завгодно).
- Існують класи, які знають, як перетворити необроблений потік байтів у певний вид ресурсу та повернути його.
- Ваш фактичний "завантажувач активів" просто має колекцію вищезазначених даних і знає, як передавати висновок постачальника потоків у специфічний навантажувач і таким чином отримувати конкретний актив. Розкриваючи способи налаштування постачальника потоку та навантажувачів, що відповідають типу, у вас є система, яку можна розширити клієнтами (або вами), не змінюючи фактичний код завантажувача активів.
Деякі застереження та заключні нотатки:
Вищенаведений код в основному є C #, але повинен бути перекладений майже на будь-яку мову з мінімальними зусиллями. Щоб полегшити це, я пропустив багато речей, таких як перевірка помилок або правильне використання IDisposable
та інші ідіоми, які можуть не застосовуватися безпосередньо іншими мовами. Вони залишаються як домашнє завдання для читача.
Так само я повертаю конкретний актив, як object
зазначено вище, але ви можете використовувати дженерики або шаблони або будь-що інше, щоб створити більш конкретний тип об'єкта, якщо вам подобається (вам слід, приємно працювати).
Як вище, тут я взагалі не займаюся кешуванням. Однак ви можете додати кешування легко та з однаковим загалом та налаштованістю. Спробуйте і подивіться!
Існує багато-багато і безліч способів зробити це, і, звичайно, немає жодного способу чи консенсусу, через що ви не змогли його знайти. Я намагався надати достатньо коду, щоб отримати конкретні точки, не перетворюючи цю відповідь на болісно довгу стіну коду. Це вже надзвичайно довго, як є. Якщо у вас є уточнюючі питання, не соромтесь прокоментувати або знайти мене в чаті .