Отделение игрового движка от игрового кода в похожих играх с контролем версий

15

У меня есть готовая игра, от которой я хочу отказаться в других версиях. Это были бы похожие игры, с более или менее одинаковым дизайном, но не всегда, в основном все может измениться, иногда мало, иногда крупно.

Я бы хотел, чтобы основной код создавался отдельно от игры, так что если, скажем, я исправлю ошибку, обнаруженную в игре A, то исправление будет присутствовать в игре B.

Я пытаюсь найти лучший способ справиться с этим. Мои первоначальные идеи таковы:

  • Создайте engineмодуль / папку / что угодно, что содержит все, что может быть обобщено и на 100% не зависит от остальной части игры. Это будет включать в себя некоторый код, а также общие ресурсы, которые используются в играх.
  • Поместите этот движок в собственный gitрепозиторий, который будет включен в игры какgit submodule

Часть, с которой я борюсь, это как управлять остальным кодом. Допустим, у вас есть сцена меню, этот код является специфичным для игры, но также большинство из них имеет общий характер и могут быть повторно использованы в других играх. Я не могу поместить это в engine, но перекодировать это для каждой игры было бы неэффективно.

Может быть, использование какой-то разновидности веток git может быть эффективным для управления этим, но я не думаю, что это лучший путь.

У кого-нибудь есть какие-то идеи, опыт, которыми можно поделиться или что-нибудь об этом?

Malharhak
источник
На каком языке работает ваш движок? В некоторых языках есть специальные менеджеры пакетов, которые могут иметь больше смысла, чем использование подмодулей git. Например, NodeJS имеет npm (который может быть нацелен на репозитории Git в качестве источников).
Дэн Кладовая
Ваш вопрос о том, как лучше всего настроить управление общим кодом или как настроить управление полуобобщенным кодом или как создать код, как разработать код или как?
Данк
Это может варьироваться в каждой среде языка программирования, но вы можете рассмотреть не только программное обеспечение Control Version, но также начать с того, как отделить игровой движок от игрового кода (например, пакетов, папок и API) и позже. , примените Control Version.
umlcat 21.09.15
Как создать чистую историю одной папки в одной ветке: выполните рефакторинг вашего движка так, чтобы отдельные (будущие) репо находились в отдельных папках, то есть ваш последний коммит. Затем создайте новую ветку, удалите все, что находится за пределами этой папки, и подтвердите. Затем перейдите к первому коммиту репо и объедините его с вашей новой веткой. Теперь у вас есть ветка только с этой папкой: перетащите ее в другие проекты и / или объедините ее с существующим проектом. Это мне очень помогло с разделением движков по веткам, если ваш код уже отделен. Мне не нужны git-модули.
Барри Стес

Ответы:

13

Создайте модуль двигателя / папку / что угодно, которое содержит все, что может быть обобщено и на 100% не зависит от остальной части игры. Это будет включать в себя некоторый код, а также общие ресурсы, которые используются в играх.

Поместите этот движок в собственный репозиторий git, который будет включен в игры как подмодуль git.

Это именно то, что я делаю, и это работает очень хорошо. У меня есть прикладная среда и библиотека рендеринга, и каждый из них рассматривается как подмодуль моих проектов. Я считаю, что SourceTree полезен, когда дело доходит до подмодулей, поскольку он хорошо управляет ими и не позволит вам ничего забыть, например, если вы обновили подмодуль движка в проекте A, он уведомит вас о необходимости внести изменения в проект B.

С опытом приходит знание того, какой код должен быть в движке, а какой должен быть для каждого проекта. Я полагаю, что если вы даже немного не уверены, что вы держите это в каждом проекте на данный момент. Со временем вы увидите среди своих различных проектов, что остается неизменным, и затем вы можете постепенно учитывать это в коде вашего движка. Другими словами: дублируйте код до тех пор, пока вы не будете почти на 100% уверены, что он не изменяется дискретно для каждого проекта, а затем обобщите его.

Примечание о контроле исходного кода и двоичных файлах

Просто помните, что если вы ожидаете, что ваши двоичные активы будут часто меняться, вы, возможно, не захотите помещать их в текстовый контроль исходных текстов, такой как git. Просто скажу ... есть лучшие решения для двоичных файлов. Самая простая вещь, которую вы можете сделать сейчас, чтобы поддерживать чистоту и производительность вашего репозитория «engine-source», - это иметь отдельный репозиторий «engine-binaries», который содержит только двоичные файлы, которые вы также включаете в свой проект в качестве подмодуля. Таким образом, вы уменьшаете ущерб производительности, нанесенный вашему репозиторию «движок-источник», который постоянно меняется и требует быстрых итераций: коммит, push, pull и т. Д. Системы управления исходным кодом, такие как git, работают с текстовыми дельтами. и, как только вы вводите двоичные файлы, вы вводите огромные дельты с точки зрения текста, что в конечном итоге стоит вашего времени разработки.Приложение GitLab . Google твой друг.

инженер
источник
Они действительно не часто меняются, но мне это интересно. Я ничего не знаю о бинарном версионировании. Какие есть решения?
Малхархак
@Malharhak Отредактировано, чтобы ответить на ваш комментарий.
инженер
@Malharak Вот хорошая информация по этой теме.
инженер
1
+1 за хранение вещей в проекте как можно дольше. Общий код предоставляет большую сложность. Этого следует избегать до тех пор, пока оно не станет абсолютно необходимым.
Гусдор
1
@Malharhak Нет, тем более что ваша цель - хранить «копии» только до тех пор, пока вы не заметите, что указанный код является неизменным и может считаться распространенным. Гусдор повторил это - будьте осторожны - можно легко тратить кучу времени, слишком рано раскладывая вещи, а затем пытаясь найти способы сделать этот код достаточно общим, чтобы оставаться общим, и в то же время достаточно адаптируемым для различных проектов ... много параметров и переключателей и она превращается в уродливый беспорядок , который все еще не то , что вам нужно , потому что вы в конечном итоге изменить его на новый проект в любом случае . Не выносите слишком рано . Иметь терпение.
инженер
6

В какой-то момент движок ДОЛЖЕН специализироваться и знать материал об игре. Я пойду по касательной здесь.

Возьмите ресурсы в РТС. Одна игра может иметь Creditsи Crystalдругую MetalиPotatoes

Вы должны использовать концепции ОО правильно и идти на макс. кода повторного использования. Понятно, что Resourceздесь существует понятие .

Поэтому мы решили, что ресурсы имеют следующее:

  1. Крюк в основном цикле, чтобы увеличивать / уменьшать себя
  2. Способ получить текущую сумму (возвращает int)
  3. Способ вычитать / складывать произвольно (игроки переводят ресурсы, покупают ....)

Обратите внимание, что это понятие 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) «слоя»

  1. «Ядро» - это определение движка в учебнике, это граф сцены с перехватчиками событий, он имеет дело с шейдерами и сетевыми пакетами и абстрактным представлением игроков
  2. «GameCore» - это довольно общий тип игры, но не для всех игр - например, ресурсы в RTS или боеприпасы в FPS. Логика игры начинает просачиваться сюда. Вот где было бы наше раннее представление о ресурсах. Мы добавили эти вещи, которые имеют смысл для большинства ресурсов RTS.
  3. «GameLogic» ОЧЕНЬ специфичен для самой игры. Вы найдете переменные с именами, такими как creatureили shipили squad. Использование наследования вы получите классы , которые охватывают все 3 слоя (например , Crystal это Resource который является GameLoopEventListener скажет)
  4. «Активы» бесполезны для любой другой игры. Возьмем, к примеру, комбинированные сценарии ИИ в Half Life 2, они не будут использоваться в RTS с тем же движком.

Создание новой игры из старого движка

Это ОЧЕНЬ распространено. Фаза 1 состоит в том, чтобы вырвать слои 3 и 4 (и 2, если игра ПОЛНОСТЬЮ другого типа). Предположим, мы делаем RTS из старой RTS. У нас все еще есть ресурсы, только не кристаллы и прочее - поэтому базовые классы в слоях 2 и 1 все еще имеют смысл, все кристаллы, на которые есть ссылки в 3 и 4, могут быть отброшены. Так и делаем. Однако мы можем проверить это как ссылку на то, что мы хотим сделать.


Загрязнение в слое 1

Это может случиться Абстракция и производительность - враги. Например, UE4 предоставляет множество оптимизированных вариантов компоновки (поэтому, если вы хотите, чтобы X и Y написали код, который действительно быстро выполняет X и Y - он знает, что выполняет оба), и в результате ДЕЙСТВИТЕЛЬНО довольно большой. Это не плохо, но это отнимает много времени. Уровень 1 будет определять такие вещи, как «как вы передаете данные в шейдеры» и как вы анимируете вещи. Делать это наилучшим образом для вашего проекта ВСЕГДА хорошо. Просто попробуйте и спланируйте будущее, повторное использование кода - ваш друг, наследуйте там, где это имеет смысл.


Классифицирующие слои

ПОСЛЕДНО (обещаю) не надо слишком бояться слоев. Движок - это архаичный термин из старых дней конвейеров с фиксированной функцией, когда двигатели работали в основном графически (и в результате имели много общего), программируемый конвейер перевернул это с ног на голову, и как таковой «слой 1» стал загрязненным с любыми эффектами, которые разработчики хотели достичь. ИИ был отличительной чертой (из-за множества подходов) двигателей, теперь это ИИ и графика.

Ваш код не должен храниться в этих слоях. Даже знаменитый движок Unreal имеет МНОЖЕСТВО разных версий, каждая из которых специфична для отдельной игры. Есть несколько файлов (отличных от структур данных), которые остались бы без изменений. Это отлично! Если вы хотите создать новую игру из другой, это займет больше 30 минут. Ключ в том, чтобы планировать, знать, какие биты копировать и вставлять, а что оставлять.

Алек Тил
источник
1

Мое личное предложение о том, как обращаться с контентом, который является смесью общего и особенного, состоит в том, чтобы сделать его динамичным. Я возьму ваш экран меню в качестве примера. Если я неправильно понял, что вы просили, дайте мне знать, что вы хотели знать, и я адаптирую свой ответ.

Есть 3 вещи, которые (почти) всегда присутствуют на сцене меню: фон, логотип игры и само меню. Эти вещи обычно отличаются в зависимости от игры. Что вы можете сделать для этого контента, это сделать MenuScreenGenerator в вашем движке, который принимает 3 параметра объекта: BackGround, Logo и Menu. Базовая структура этих трех частей также является частью вашего движка, но ваш движок на самом деле не говорит о том, как эти детали генерируются, а о том, какие параметры вы должны им дать.

Затем в вашем реальном игровом коде вы создаете объекты для BackGround, логотипа и меню и передаете их в MenuScreenGenerator. Опять же, ваша игра сама не обрабатывает то, как генерируется меню, это для движка. Ваша игра должна только сказать движку, как она должна выглядеть и где она должна быть.

По сути, ваш движок должен быть API, чтобы игра показывала, что отображать. Если все сделано правильно, ваш движок должен выполнять тяжелую работу, а ваша игра должна только сообщать движку, какие активы использовать, какие действия предпринять и как выглядит мир.

Nzall
источник