О лучших принципах организации вашего проекта в Godot Engine
Я довольно долго работаю с Godot Engine и готов представить вам свой список лучших принципов, которые помогут вам организовать ваш проект.
Организация файлов
Godot Engine предоставляет вам полную свободу в хранении файлов вашего проекта. Большая сила - большая ответственность. Я называю папки в snake_style. Почему? Много было мною видано проектов, где файлы хранятся в PascalCase. Конечно, любой разработчик сам волен выбирать, однако, похоже, Хуан и другие предполагают именно вариант с маленькой буквы. Ассеты по умолчанию хранятся в папке /addons/, многие плагины завязаны именно на эту папку. Значит, подразумевается в наименовании папок именно мой стиль.
Какие же папки есть в моих проектах:
/addons/ - здесь хранятся все плагины, мои и не очень.
/assets/ - папка для хранения всех "сырых ресурсов" - оригинальных картинок, музыки, звуков, иконок, 3D-моделей. У меня они хранятся по следующим путям соответственно: /assets/images/, /assets/music/, /assets/sounds/, /assets/icons/, /assets/models/.
/resources/ - здесь хранятся все внутренние ресурсы Godot`a. Дело в том, что движок позволяет хранить различные ресурсы, например, тайлсеты, шейдеры и шейдер-материалы, стили и темы, и др. которые можно использовать во многих сценах. Редактируя эти ресурсы, вы редактируете их во всех сценах. Важно! Годо позволяет хранить вам ресурсы локально, для одной сцены, позволительно их не хранить в файловой системе проекта, чтоб не создавать лишних сущностей, но это уж вам решать.
/scenes/ - здесь хранятся вообще все сцены игры. Сцены меню, уровни, префабы, сцены интерфейса, вспомогательные сцены и все остальное.
/scripts/ - папка для хранения скриптов. Для каждой фичи я создаю отдельную папку. На самом деле, эту идею я подсмотрел у разработчиков Space Station 14. Это очень удобно, раскрывая папку вы легко можете найти то, что вам нужно отредактировать. Вопрос, что делать, если весь код для игры у вас написан на C#? Для начала рекомендую работать в едином namespace-е. Вы все также можете создавать папки, однако предлагаю вам называть их уже в PascalCase. Это соотносится в свою очередь с кодстайлом самого шарпа. Также, для каждой фичи вы можете создавать отдельный namespace.
/localization/ - папка для хранения локализационных файлов. Вы можете держать там json-файлы, конфиги или .csv файлы, в зависимости от того, как вы работаете с локализацией в вашем проекте. Самый каноничный способ - хранить локализацию в таблицах. Этот способ поддерживается движком, что называется, "из коробки".
Вы сами можете модифицировать эту рекомендуемую систему под себя. Например, у меня также хранятся в отдельных папках диалоги и конфиги проекта.
Игровое состояние
Я храню единое игровое состояние в словаре. Над словарем находится обëртка в виде статичного класса, который помогает сохранить в него нужные вам перемены, устанавливать состояние напрямую и выгружать его. Такое устройство поможет вам легко создавать сохранения - словарь легко конвертируется в JSON или YAML, который вы можете свободно сохранить в системе игрока. При загрузке сохранения вы легко заменяете словарь.
ООП и классы в Godot Engine
В движке есть зачатки ООП, однако очень слабые. Например, отсутствуют интерфейсы и инкапсуляция. Единственное, что хорошо поддерживает Годо - наследование.
Обычно я создаю для множества однотипных объектов базовый класс, который реализует все необходимое для создания нужного поведения у дочерних объектов.
Какой кодстайл? Давайте следовать кодстайлу самого Godot`a! В целом, он поступает как язык Python. Все функции, которые должны быть "скрыты", начинаются с нижнего подчеркивания. Наименование классов же, в свою очередь, оформляется в PascalCase. Очень неудобно, что в Годоте нельзя, взглянув на код, понять, какие функции нод, созданные именно движком, используются. Для этого в C# я использую синтаксический сахар в виде бесполезных атрибутов типо [GodotInternal] или [Signal].
В Годоте нет встроенного нормального способа отлавливания ошибок типо throw/catch, а используются... константы? Это не очень удобно, но терпимо. Я бы предложил вам делать также.
Одно из преимуществ GDScript - поддержка динамической типизации. Однако, это не всегда удобно. Я расскажу вам, почему нужно стараться использовать статическую типизацию:
1. Во-первых, Годот использует еë и сам. Просто взгляните в документацию.
2. Вы всегда понимаете какие конкретно переменные вам нужно передать в метод и не боитесь сломать его поведение.
3. Внутренний редактор кода Годо помогает вам, предлагая методы и поля класса, понимая его тип.
4. Повышает читаемость кода.
Глобальные ноды
Глобальные ноды могут показаться изначально неудобным рудиментом, однако, разобравшись, вы поймете, что они очень хороши и подходят движку.
Эти классы загружаются при запуске игры и всегда хранятся в памяти. Они могут использоваться для того функционала, что нужен игре постоянно.
Вот лишь ограниченный список того, для чего глобальные ноды хорошо подходят:
- Музыка. Если вам нужна музыка на задний фон, чтобы она играла постоянно, плавно переходила между треками при смене локации.
- Инициализация игры. Правильно проиницируйте игровое состояние, загрузите актуальные настройки и локализационные файлы.
- Предзагрузка уровней. Загружайте уровни заранее.
- Глобальный передатчик сигналов
- Синхронизатор для вашей мультиплеерной игры
Функциональные ноды
Если вам не нужны какие-то функции в игре постоянно, и вы не хотите навешивать их на глобальные ноды, используйте функциональные ноды.
Под функциональными нодами я подразумеваю ноды, которые "фоном" висят в сцене, которые делают лишь полезную работу и обеспечивают функционал других нод, но не отображаются игроку, не видны им и вообще напрямую не нужны. Можно подразумевать, что они лишь "свойства" сцены.
На такие ноды удобно навешивать функционал внутриигрового интерфейса. Я использую эти ноды также для глобальной камеры, камеры мини-карты и для диалоговой системы.
Для создания таких нод хорошо использовать концепцию "сами в себе". Не привязывайте их к определенным нодам на уровне, не создавайте абстракции, которые будут работать лишь в определенных условиях. Для таких случаев лучше писать скрипт для конкретной сцены.
Строгая иерархия vs слабая иерархия
Я предостерегаю вас от использования строгой иерархии в основных уровнях! Godot Engine предоставляет вам достаточно гибкую систему иерархии нод, но... Система дублирования, к которой вы будете прибегать довольно часто не очень удобна. К тому же, для разных уровней оптимальная организация их может быть не всегда одинаковой. Не портьте настроение геймдизайнеру или самому себе, когда неправильно поставленная в иерархию нода будет ломать весь уровень. Избавляйтесь от излишней привязанности нод к друг другу.
Однако, где я рекомендую использовать строгую и понятную иерархию, так это в префабах и меню. В префабах - для упрощения организации кода. В меню - потому что вы не сможете без достаточного геморроя создать даже простое меню, расставляя ноды интерфейса вразнобой.
Здесь лишь некоторые рекомендуемые принципы. Не зацикливайтесь на них, как говорит сам Хуан - делайте как вам удобно. Разработка игр в любом случае подразумевает быдлокодинг и большое количество взаимосвязей.
Продолжение следует...