В какой-то момент движок ДОЛЖЕН специализироваться и знать материал об игре. Я пойду по касательной здесь.
Возьмите ресурсы в РТС. Одна игра может иметь Creditsи Crystalдругую MetalиPotatoes
Вы должны использовать концепции ОО правильно и идти на макс. кода повторного использования. Понятно, что Resourceздесь существует понятие .
Поэтому мы решили, что ресурсы имеют следующее:
- Крюк в основном цикле, чтобы увеличивать / уменьшать себя
- Способ получить текущую сумму (возвращает
int)
- Способ вычитать / складывать произвольно (игроки переводят ресурсы, покупают ....)
Обратите внимание, что это понятие Resourceможет представлять убийства или очки в игре! Это не очень сильно.
Теперь давайте подумаем об игре. Мы можем иметь валюту, имея дело с копейками и добавляя десятичную точку к выводу. Чего мы не можем сделать, так это «мгновенных» ресурсов. Мол, скажем "генерация электросети"
Допустим, вы добавили InstantResourceкласс с похожими методами. Вы теперь (начинаете) загрязнять свой двигатель ресурсами.
Проблема
Давайте снова возьмем пример RTS. Предположим, игрок что-нибудь пожертвует Crystalдругому игроку. Вы хотите сделать что-то вроде:
if(transfer.target == engine.getPlayerId()) {
engine.hud.addIncoming("You got "+transfer.quantity+" of "+
engine.resourceDictionary.getNameOf(transfer.resourceId)+
" from "+engine.getPlayer(transfer.source).name);
}
engine.getPlayer(transfer.target).getResourceById(transfer.resourceId).add(transfer.quantity)
engine.getPlayer(transfer.source).getResourceById(transfer.resourceId).add(-transfer.quantity)
Однако это действительно довольно грязно. Это общее назначение, но грязное. Уже, хотя это налагает, resourceDictionaryчто означает, что теперь ваши ресурсы должны иметь имена! И это на игрока, поэтому вы не можете больше иметь командные ресурсы.
Это «слишком большая» абстракция (я не признаю блестящий пример), вместо этого вы должны достичь точки, когда вы признаете, что в вашей игре есть игроки и кристалл, тогда вы можете просто иметь (например)
engine.getPlayer(transfer.target).crystal().receiveDonation(transfer)
engine.getPlayer(transfer.source).crystal().sendDonation(transfer)
С классом Playerи классом , CurrentPlayerгде CurrentPlayer«S crystalобъект будет автоматически показывать материал на ИЛС для передачи / отправки пожертвований.
Это загрязняет двигатель кристаллом, пожертвованием кристалла, сообщениями в HUD для текущих игроков и всем этим. Это быстрее и проще для чтения / записи / обслуживания (что более важно, так как это не значительно быстрее)
Заключительные замечания
Случай с ресурсами не блестящий. Я надеюсь, что вы все еще можете увидеть суть. Во всяком случае, я продемонстрировал, что «ресурсы не принадлежат движку», поскольку то, что нужно конкретной игре и что применимо ко всем представлениям о ресурсах, это ОЧЕНЬ разные вещи. Обычно вы найдете 3 (или 4) «слоя»
- «Ядро» - это определение движка в учебнике, это граф сцены с перехватчиками событий, он имеет дело с шейдерами и сетевыми пакетами и абстрактным представлением игроков
- «GameCore» - это довольно общий тип игры, но не для всех игр - например, ресурсы в RTS или боеприпасы в FPS. Логика игры начинает просачиваться сюда. Вот где было бы наше раннее представление о ресурсах. Мы добавили эти вещи, которые имеют смысл для большинства ресурсов RTS.
- «GameLogic» ОЧЕНЬ специфичен для самой игры. Вы найдете переменные с именами, такими как
creatureили shipили squad. Использование наследования вы получите классы , которые охватывают все 3 слоя (например , Crystal это Resource который является GameLoopEventListener скажет)
- «Активы» бесполезны для любой другой игры. Возьмем, к примеру, комбинированные сценарии ИИ в Half Life 2, они не будут использоваться в RTS с тем же движком.
Создание новой игры из старого движка
Это ОЧЕНЬ распространено. Фаза 1 состоит в том, чтобы вырвать слои 3 и 4 (и 2, если игра ПОЛНОСТЬЮ другого типа). Предположим, мы делаем RTS из старой RTS. У нас все еще есть ресурсы, только не кристаллы и прочее - поэтому базовые классы в слоях 2 и 1 все еще имеют смысл, все кристаллы, на которые есть ссылки в 3 и 4, могут быть отброшены. Так и делаем. Однако мы можем проверить это как ссылку на то, что мы хотим сделать.
Загрязнение в слое 1
Это может случиться Абстракция и производительность - враги. Например, UE4 предоставляет множество оптимизированных вариантов компоновки (поэтому, если вы хотите, чтобы X и Y написали код, который действительно быстро выполняет X и Y - он знает, что выполняет оба), и в результате ДЕЙСТВИТЕЛЬНО довольно большой. Это не плохо, но это отнимает много времени. Уровень 1 будет определять такие вещи, как «как вы передаете данные в шейдеры» и как вы анимируете вещи. Делать это наилучшим образом для вашего проекта ВСЕГДА хорошо. Просто попробуйте и спланируйте будущее, повторное использование кода - ваш друг, наследуйте там, где это имеет смысл.
Классифицирующие слои
ПОСЛЕДНО (обещаю) не надо слишком бояться слоев. Движок - это архаичный термин из старых дней конвейеров с фиксированной функцией, когда двигатели работали в основном графически (и в результате имели много общего), программируемый конвейер перевернул это с ног на голову, и как таковой «слой 1» стал загрязненным с любыми эффектами, которые разработчики хотели достичь. ИИ был отличительной чертой (из-за множества подходов) двигателей, теперь это ИИ и графика.
Ваш код не должен храниться в этих слоях. Даже знаменитый движок Unreal имеет МНОЖЕСТВО разных версий, каждая из которых специфична для отдельной игры. Есть несколько файлов (отличных от структур данных), которые остались бы без изменений. Это отлично! Если вы хотите создать новую игру из другой, это займет больше 30 минут. Ключ в том, чтобы планировать, знать, какие биты копировать и вставлять, а что оставлять.