Реалистичная акустика выстрелов в шутерах
Статья-рассуждение о создании системы процедурной реверберации и отражений
Мастер-класс по работе с кодом в ChatGPT
На мастер-классе Александр Хилько показывает, как можно умело использовать нейросети в своей ежедневной работе
Мастер-класс по работе с C#
Мастер-класс с Александром Хилько на тему языка программирования C# и основ работе с ним в среде Unity.
Код в проекте
Привет. Расскажу про то, как устроен код в проекте, чего придерживался, что пришлось менять. Тут есть, о чем поговорить. Я поделюсь своим опытом.
Разграничения
Как уже писал в предыдущей статье, пространства имен – это отличный способ разграничить островки игровой логики в проекте в крупном масштабе. На более низком уровне идет разбиение на классы. Помните, Божественный объект – это антипаттерн. К слову, один заказчик немного разбирался в программировании и всерьез хотел, чтобы мы делали именно божественные объекты в играх.
Тогда еще совет. Вы – специалист своего дела, который знает технические подробности реализации проекта, и это ваша обязанность – настоять на правильном обоснованном техническом решении.
Альманах
Не забываем про абстракцию. Если того требует ситуация, сначала делаем абстрактный класс или интерфейс, потом более конкретную реализацию. Не беспокойтесь, никогда не поздно начать извлекать методы и выделять классы.
Интерфейс или абстрактный класс? Абстрактный класс уместней использовать при очевидной общности наследников, интерфейс же – лишь модель поведения, которую могут реализовать объекты, не имеющие ничего общего между собой.
Совет. Учи рефакторинг, это реально полезно. Учи горячие клавиши в среде программирования, это реально полезно. На новом месте работы я научился многому у старших коллег, и в то же время мне тоже было что рассказать и показать им.
Пример абстракции. Когда главный герой знакомится с новым видом противника, то игроку показывается описание нового врага и его особенности, характерные черты поведения. То же самое происходит с новыми подобранными игроком свитками. Логично сделать некую основу, в которой будут меняться детали. Поэтому данные о свитке и данные о новом противнике унаследованы от общего класса от ScriptableObject. Страница описания противника, и страница описания свитка тоже имеют сходство, но в описании противника мы еще показываем список характерных черт, а в свитках есть только описание.
Только спустя пару лет практики программирования начинаешь понимать принципы SOLID и ООП по-настоящему. Не просто так в каждом ролике, в каждой статье или интервью с разработчиками повторяется как мантра: каждая механика – это отдельный островок, который работает независимо от других; удаляя один компонент, другой не должен сломаться… Старайтесь придерживаться принципов программирования, это в любом случае дешевле обойдется, чем потом переделывать все, поправляя одно, ломая тем самым другое.
Совет. Не переставайте менять именование полей, методов и классов до тех пор, пока не станет максимально удовлетворять вас. Именование должно лаконично передавать суть и все. Классы именуются как имя существительное, методы как глагол. События привычно писать со слова On (OnHit, OnLoaded, OnError), но методы, которые на них подписываются, должны передавать свою суть (GetDamage, Unfade, SetDefaultValues). Помните, большую часть времени вы прежде всего читаете код, нежели непосредственно пишите его. Решиться на изменения в коде гораздо легче, если хорошо владеть IDE (например, переименование и извлечение метода).
Совет. Используйте ключевое слово field в атрибутах, оно позволяет воспринимать свойства как поля, отображая их в инспекторе, но все еще инкапсулировать данные. Не пренебрегайте ограничением доступа к данным. Этот трюк сокращает количество строчек в 2 раза, Карл! Этот десяток строк мог выглядеть как 20 строк.
Спаун противников
Тут вроде все просто. Враги появляются по волнам. Волны, количество противников и период их создания во время волны определяется в SpawnerData. Они могут появляться на протяжении всей волны, могут в начале волны или только в конце; по одному в секунду или сразу скопом.
С самого начала я не до конца продумал как именно будут появляться противники на арене, а я очень хотел внести какую-то оригинальность в моей игре. Поэтому мне пришлось переопределить архитектуру этой части кода. Теперь точка появления и логика этого события делегируются в отдельный класс SpawnDealer. Он стал отвечать за то, как именно и где появится противник. Например, арахниды появляются с помощью червя Нидуса, если такого нет на сцене, сначала появится червь, проиграется анимация, а уж потом появится противник из него. Духи появляются только в темных участках арены, а демоны в точках, где есть огонь (факел, костер, пожар). Некоторые противники появляются с помощью молний, только на участках арены под открытым небом.
Красными точками отмечены темные места для появления Духов, а белыми точки под открытым небом для создания молнии и спауна противников из них.
Урон и здоровье
Эту часть игры тоже пришлось переписывать после первых попыток. Итогом стали абстрактный класс Health и его наследники HealthPart и HealthParent. Предполагалось, что попадание в разные части тела (HealthPart) дает множитель на получаемый урон, передавая значения на HealthParent, который у противника есть в единственном экземпляре. Это решение из общей папки с кодовой базой, на самом деле в проекте используется только HealthParent.
Так как в игре есть стихийный урон и различные мета-данные нанесения урона (с какой стороны прилетел удар, точка удара, физическая сила удара, префабы попадания), я сделал структуру HitData, а также DamageDealer для удобства заполнения этих данных.
Совет. Старайтесь подписываться на события (если это не дочерние компоненты), нежели напрямую завязывать внешние объекты между собой. Это уменьшит связность проекта, позволит убирать или добавлять что-либо на сцену, не ломаю остальное.
Завершение
Понимаешь диаграммы классов? Что-то стоит поправить? Что думаешь? Есть польза от написанного? Критикуй, пиши, прочтем. Я очень надеюсь, что делаю какое-то полезное дело, в надежде повысить свою и вашу грамотность в написании кода.
Разработчик | Проект | YouTube | e-mail | Поддержка
Структура проекта
Привет, коллеги. Пора переходить от описания девлога к описанию проекта. В этой статье я расскажу про структуру проекта:
- Порядок файлов и папок, почему именно так, какие еще варианты бывают
- Общая архитектура кода
- Советы по организации кода в проектах
- Порядок инициализации
Файловый порядок
В целом мы на работе использовали два подхода по организации файлов в проекте: сортировать в папки по типу файлов или по смысловой общности (аля ассет или игровая механика). Все скрипты лежат в одной папе, а все текстуры в другой? Это сортировка по типу файлов. Если же все текстуры, материалы, анимации и скрипты относятся к одной игровой механике и лежат в папке с игровой механикой, то это второй тип порядка в проекте.
Долгое время мы придерживались первого варианта, но на практике рано или поздно приходишь к следующим выводам:
- В процессе разработки ты работаешь не с файлами как таковыми, а с игровой механикой (с файлами, которые к ней относятся). Жутко неудобно искать скрипт в одной папке, править шейдер в другой папке, далеко от первой. Ты вроде работаешь над одним кирпичиком проекта (над одной игровой механикой), а файлы раскиданы по всему проекту. Это жутко неудобно.
- Если ты скачиваешь готовые ассеты с Ассет Стора, то они скачиваются именно в выше изложенном виде – отдельная папка игровой механики, внутри которой все, что к ней относится: скрипты, текстуры, анимации, звуки.
- Так и так ты используешь оба подхода, но в разных масштабах. Глобально папки сформированы по игровым механикам, но внутри этих папок есть папки Scripts, Sources, Prefabs.
Совет. С самого начала и до конца работы на игрой придерживайтесь какого-то порядка и не переставайте его соблюдать. Несортированные файлы и непродуманная архитектура накапливается и с каждым разом и все больше усложняет процесс разработки. Тебе попросту больше не хочется в этом всем разбираться. Книга Роберта Мартина «Чистый код», как мне показалось, прежде всего учит придерживаться порядка. А какой именно порядок, это уже вам решать. Придерживайтесь.
Структура кода
Все Unity-проекты – это монолитное приложение. Внутри же мы можем использовать различные подходы, например, MVC и его разновидности или ECS (в Unity встроен DOTS).
Совет. Не забывайте пользоваться пространствами имен, очень помогает организовать работу кода. Еще вариант, к которому я не склонился – это AssemblyDefinition. Как по мне, подобный способ неудобен из-за чувствительной настройки. А еще ты можешь долгое время не понимать почему не компилируется код, и кто на кого должен ссылаться. Звучит как отговорка, но я не пришел к этому способу, просто использую пространства имен.
В проекте есть общее пространство имен IndieBroGames, внутри которого есть остальные. Например, IndieBroGames.Common содержит в себе мою накопленную базу скриптов, база формировалась в процессе работы над другими проектами, включает мои наработки, измененные версии других скриптов и чужого опыта. Это пространство имен не имеет зависимостей от конкретного проекта и используется в каждом проекте в одностороннем порядке.
А есть пространство имен IndieBroGames.Soulskiller, к которому относятся скрипты этой игры. Это пространство имен может использовать скрипты из IndieBroGames. Common. Здесь формируется конкретная реализация игровых механик, относящихся только к этой игре.
Под IndieBroGames.Soulskiller лежат остальные пространства имен, которые отсортированы… по игровым механикам, как папки, в которых и лежат эти скрипты. Например, поведение противников основано на машине состояний (об этом поговорим чуть позже), где машина состояний находится в пространстве имен IndieBroGames.Common.StateMachineSystem, а состояния противников унаследованы от абстрактного класса State.
Порядок инициализации
Начало начал в игре – это первая сцена для инициализации ядра из пространства имен IndieBroGames. Common. По сути Core – это точка, в которой инициализируются все основные системы, а также это фасад для доступа ко всем этим системам.
Ядро включает систему сохранения, локализацию, асинхронный загрузчик сцен, механику паузы и некоторые другие вещи.
Вторая сцена – инициализация ядра проекта и выбор предварительных настроек – языка локализации, громкости и вибрации.
Ядро проекта, по аналогии, содержит в себе вещи, к которым нужен доступ в процессе игры. Только те вещи, которые относятся к этому проекту.
После всех инициализаций запускается сцена с убежищем главного героя, охотника на нечисть.
Завершение
Что не так сказал? Какие бывают еще способы упорядочить проект? Мысли, критика? Пиши, прочтем.
Разработчик | Проект | YouTube | e-mail | Поддержка
Особенности игры и игры-вдохновители
Привет, коллеги. Расскажу про игровой цикл и на какие игры ориентировался в процессе.
Об игре и вдохновителях
Игра будет по сути ареной, в которой противники нападают волнами. По завершении одной волны, игрок активирует следующую на определенной точке на арене. Примерами игр по волнам могут быть Killing Floor (pc), Only One (android), They are Billions (pc). С каждым разом противников будет больше и разнообразней. Придется учитывать их особенности и расставлять приоритеты.
В свое время мне очень нравилась игра Painkiller (pc). Да, она была не самой оригинальной в плане игровых механик (хотя там были карты таро и специфичная пушка у героя, а у каждого оружия альтернативный огонь), но какая там была атмосфера! Мрачные текстуры подземелий и заброшенных зданий в совокупности с отлично подобранными звуками, отлетающие в виде тряпичных кукол противники и награда – душа поверженного противника!
Противники
Противники в игре – главная особенность проекта. Каждый противник имеет свою собственную модель поведения. Я хотел прежде всего сделать каждого противника индивидуальным, они не должны отличаться друг от друга только очками здоровья и скорости. Об особенностях поведения я расскажу в отдельной статье, как реализовано и какие особенности бывают.
Механики
Начнем с перемещения героя. Оно здесь простое, охотник может передвигаться с помощью джойстика, огибая тучи противников и занимая более выгодную позицию. На арене много дорог и тропинок, узких мест, где можно ускользнуть от погони.
Боевые особенности. Герой применяет автоматические способности, как только противник приблизится достаточно близко. Подошел близко? Получи удар мечом. Еще далеко, но на расстоянии выстрела? Луки к бою! Много вражин? Граната решит проблему. Помимо автоматических способностей есть магия, которую игрок выбирает куда применить, перетаскивая иконку способности в точку на арене. Магия перезаряжается. Но так же есть свитки, которые выпадают из противников. Свитки по своей сути – одноразовая магия без выбора точки применения. Например, самолечение или удар молнии в одного из случайных противников.
С каждым днем я понемногу начал сомневаться в том, что делаю что-то необычное, новое, а не очередную игру, в которой нет ничего, чего вы не видели в других играх. Очень надеюсь, что это не так. В моих силах придумать незаезженную магическую способность и поведение врагов.
Убежище
Это наше Лобби. Место, в котором охотник подготавливается к следующему забегу. Здесь можно изучить свитки, прочитать в Альманахе особенности поведения противников, выбрать снаряжение и повысить навыки главного героя. Знакомясь в процессе боя с новыми противниками, вы пополняете страницы Альманаха, в котором описывается поведение и особенности вновь изученного противника.
Итогом
Пусть это будет фишкой девлога, я буду определять пользу и итоги как для себя, так и для читателей отдельно.
Написав все это, я скомпоновал в своей голове краткое описание игры, еще раз прошелся по механикам и… кажется все не так плохо, игра потенциально может удивить заядлого игрока, порадовать новыми игровыми механиками или дать новое представление уже знакомым механикам.
Для вас же итогом будет пару советов. Старайтесь делать свои игры самобытными, индивидуальными, иначе какой смысл в таком творчестве. Многие большие игры, которые сейчас выпускают, очень шаблонные и привычные игрокам. Цвет зеленый-синий-золотой для противников и оружия, аванпосты и собирательство ресурсов, награда за проделанную работу. Пусть лучше сам процесс приносит удовольствие, чем награда после рутины.
В играх часто сменяют темп. Например, горячие бои сменяются спокойным повествованием и диалогами, напряжение в хорроре сменяется время от времени безопасными местами. Думаю, это хорошая практика, игра не так быстро приедается игроку. Безопасность между волнами и жаркие бои, разнообразие поведения каждого противника и сочетание противников на арене повысят многогранность игры.
Немного пользы от статьи. Как запомнить названия планет? Джоел поможет. "Мама всегда запрещала мне, юному следопыту, учить названия планет" (Меркурий, Венера, Земля...)
Вопросы, мнение, критика? Пиши, что думаешь.
Какие магические способности реже всего встречаются? Я бы сказал, что большинство игр стараются воздействовать магией напрямую на противника. Я бы хотел как-нибудь сделать игру про ведьму, которая побеждает непрошенных гостей Темного леса косвенным путем, влияя на природу или на снаряжение противников, на их восприятие мира.
Разработчик | Проект | YouTube | Поддержка