Серия «Space Turret: Defense Point»

6

Space Turret: Defense Point - Часть 6. Локализация

Всем привет!

Когда мы только начинали проект, у меня уже тогда была хитрая идея для локализации.

Зачем нужны тонны текстов и подсказок, различных шрифтов, поддержка кириллицы, мучение с подборкой размера текстов... Это всё не нужно, если будут иконки! Вот он, секрет успешной локализации, - думал я! Человечество уже опубликовало тысячи мобильных игр, придумало всевозможные иконки под всё что угодно!

Воодушевившись этой идеей, мы начали работу над интерфейсом. Минималистичный дизайн, простые иконки и никакого текста!


Пример интерфейса

Но радость была недолгой. Как только мы дошли до дизайна окон, мы поняли, что немного текста добавить придется.


Интерфейс окна улучшений турели

Все только самое необходимое и нужное, минимум информации. :D

Тогда я задумался над системой локализации. Потому, что чем раньше ее сделать, тем меньше ошибок мы допустим при переводе на новые языки.

На тот момент у меня были общие знания по переводу, я где то видел использование файлов xml. Но идти по общему шаблону мне не хотелось. Я был уверен, что смогу придумать что то еще. Ведь у нас редактор Unity, наверняка в нем есть механизмы хранения данных... Например, ScriptableObject. В голове крутилась идея, что он может отлично подойти, главное - правильно организовать сами данные. Как обычно, я загрузил голову перед сном, а на утро уже родилась идея :)

Какие параметры нам могут пригодиться? Как минимум - шрифт, размер текста и набор уникальных идентификаторов. А также название языка и системный идентификатор языка, чтобы по нему потом выбрать локализацию в соотв. с настройками языка на телефоне.


Пример профиля локализации меню

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


Пример идентификаторов текста enum

Пример функции определения текста

После этого остается сделать простой менеджер локализаций, который будет хранить доступные профили, и скрипт, который мы добавим на все тексты. По заданному типу идентификатора скрипт будет считывать текст из текущего профиля локализации. Также, мы добавим в скрипт размер текста (Нормальный, Меньше, Мелкий), чтобы устанавливать необходимый размер шрифта. Готово!


Пример использования

Какие плюсы использования ScriptableObject?

Если бы мы делали локализацию на основе файлов XML, то нам пришлось бы их размещать в папке Resources, это может плохо сказаться на скорости загрузки приложения (при большом кол-ве файлов в Resources). Ограничение связано с тем, что работа с файлами доступна, только если сами файлы лежат в открытом источнике.

Наши же профили ScriptableObject мы можем размешать в любой части проекта и на скорость загрузки они будут влиять гораздо меньше.

В случае, если нам нужно добавить новую локализацию - нам будет визуально проще править все тексты и меньше шансов, что мы где то забудем перевести текст.


Помните, гораздо проще внедрить локализацию на ранних этапах разработки, когда Вы еще не забыли, на каких элементах есть тексты, в каких окнах и скрытых позициях. Также выработаете навык автоматического добавления нужных идентификаторов при появлении новых текстов :) Учтите, что новые идентификаторы текстов можно добавлять только в конец enum, иначе все текущие настройки текстов в сцене "сдвинутся".


На этом пока всё, о чем будет следующая статья - я еще не придумал, но обязательно что то придумаю :)

Спасибо, что дочитали!


Ниже я оставлю ссылки на примеры скриптов, может кому то понадобятся

Профиль ScriptableObject

Менеджер локализаций

Скрипт для текстов


Предыдущие статьи:

Часть 5. Игровой ИИ и с чего начать

Часть 4. Противники (продолжение)

Часть 3. Противники

Часть 2. Генератор волн противников

Часть 1. История разработки

Показать полностью 7
7

Space Turret: Defense Point - Часть 5. Игровой ИИ и с чего начать

Всем привет!

С чего начинается искусственный интеллект (ИИ)? А начинается он с объекта, которым ИИ будет управлять. В зависимости от того, как Вы спроектируете свой объект, будет зависеть сложность его управления.

Мы с самого начала разделили наши объекты на разрушаемые и неразрушаемые. Для разрушаемых объектов сделали простой скрипт Health, который позволял задавать Жизнь и имел функцию AddDamage(float damage), через которую наносился урон объекту. Если у объекта заканчивалась Жизнь, он деактивировался.


Противники у нас разрушаемые, но кроме параметра Жизнь, им нужны были еще параметры  Скорость передвижения, Скорость поворота, Наносимый урон, Дальность атаки и Точность попадания. И еще Уровень противника, от которого будут зависеть некоторые показатели.

Поэтому, на базе скрипта Health мы сделали новый скрипт SpaceShip, в котором мы могли хранить такие настройки.


Пример настройки фрегата

Для чего нужно наследоваться от класса Health?

В игре есть пули, ракеты и лазеры, которые при попадании наносят урон. Так как у нас только один класс отвечает за разрушаемость, то нам достаточно проверить, есть ли скрипт Health у объекта, куда попала пуля. И если есть - нанести урон.


Сейчас хочу сказать, что общая структура противников получилась очень удобной и понятной. Если нам нужны были какие то дополнительные параметры для корабля, то мы уже создавали новый класс на базе SpaceShip (например, для авианосца нужна ссылка на объект-истребитель, а также два параметра - лимит истребителей и задержка между их запуском). Параметры корабля инициализировались в скрипте Генератора противников, перед выходом корабля из варпа.

Ну что, теперь у нас уже есть некоторые параметры для нашего ИИ.


На чем сделать ИИ?

Изначально, я очень хотел сделать ИИ на базе нейронных сетей, чтобы корабли сами выстраивали свою стратегию полета, уклонялись от снарядов и вообще, становились сильнее с каждой игрой. К сожалению, мне не хватило знаний, чтобы формализовать и перенести такие действия на нейронные сети. Хотя как работают нейронные сети и как проводить их обучение - я знаю :)

Раз нейронные сети отпадают, то оставался вариант - машина состояний (state machine). Такой вариант хорошо вписывался в общую концепцию поведения кораблей, потому что любое поведение кораблей можно было разделить на составляющие - подлететь к базе, отлететь от базы, приблизиться на определенную дистанцию, лететь к выбранной точке А, повернуться стороной и т.д.


Визуализация двух состояний истребителя

Оставалось выбрать, как реализовать машину состояний:

1. писать свой "движок" для состояний;

2. использовать какой то готовый "движок".


Перед тем, как что то выбрать, сначала надо изучить доступные варианты. В интернете мы нашли много различных готовых "движков", а также подробных инструкций, как сделать свой движок. Но при этом, мы нашли информацию, что в Unity уже есть встроенная машина состояний - это Аниматор. Изначально, задача аниматора в Unity - это проигрывание разных состояний анимации персонажа - бег, ходьба, атака и тд. Но при необходимости, можно добавить выполнение кода в состояние. Вот она, лазейка :)

Многие скажут, что написать движок для машины состояний легко, и будут правы! Игру мы пишем под мобилки, где как раз процессоры многоядерные.

Получается, мы можем писать логику ИИ практически без всякой нагрузки на главный процесс игры! Это же какая оптимизация получается! :) Только при разработке кода, нужно учитывать, что код выполняется параллельно, и если мы будем менять какие то общие переменные, то нужно делать это безопасно, с использованием блокировок.


Состояния аниматора Unity

Чтобы использовать код в состояниях, нужно написать скрипт, который наследуется от класса StateMachineBehaviour. Мы постарались сделать некий удобный шаблон, который использовали во всех подобных скриптах.


public class StateTemplate : StateMachineBehaviour
{
[Header("Parameters")]
//тут можно объявить всякие параметры
[Header("State settings")]
public string onExitDeactivate = "stateA";
public string onFinishActivate = "stateB";  //приватные переменные

public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)

{
//вызывается при каждом входе в состояние
//через animator.GetComponent можно получить доступ к скриптам объекта
}
public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
//пока состояние активно, постоянно вызывается
//если нужно выйти из состояния, вызываем animator.SetBool(onFinishActivate, true);
}
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
//вызывается при каждом выходе из состояния
animator.SetBool(onExitDeactivate, false);
}
}
Каждое состояние у нас связано с определенной переменной типа bool в аниматоре.
Когда нам нужно выйти из состояния А, мы активируем переменную stateB, и у нас состояние переходит в состояние B. При этом, переменная stateA сразу деактивируется. Функция OnStateUpdate вызывается либо каждый кадр, либо каждый "физический тик", в зависимости от настроек аниматора.

В функции OnStateEnter обычно кешируются различные классы противника, проводится инициализация состояния, рассчитываются точки маршрута и т.д.

Также важно учесть, что внутри функций нельзя объявлять переменные, иначе при параллельной работе значения будут перезатираться. Все переменные объявляются как атрибуты класса и будут принадлежать одному противнику (владельцу аниматора). Это особенность аниматора Unity.


Разработка ИИ

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

1. Истребители будут иметь два состояния - в одном состоянии они приближаются к станции на определенную дистанцию, а потом переходят во второе состояние и отлетают в случайном направлении и потом всё повторяется.

2. Фрегаты также имеют два состояния, сначала они приближаются к станции на некоторую дистанцию, а потом переходят во второе состояние, где летают влево-вправо перед станцией.

3. Крейсеры и Авианосцы также имеют два состояния - подлетают к станции и после этого поворачиваются одной из сторон к станции и открывают огонь.


В дальнейшем, мы улучшили поведение истребителей. Потому что изначально, каждый из них летал самостоятельно, кто то нападал, другой в это время отлетал. Получалось какое то месиво. Этот тип кораблей стал самым ненавистным в игре :) Мы хотели их как то сгруппировать, чтобы игроку было удобнее и зрелищнее их уничтожать. И тут мне попадается статья о симуляции полета стаи птиц под названием Boids. Увидев эту симуляцию, мы просто загорелись желанием реализовать подобное поведение у истребителей! Через несколько вечеров доработка была готова!

Теперь каждый истребитель присваивался группе. "Голова" группы выбирал куда лететь - на станцию или от станции, все остальные члены группы следовали за ним с помощью алгоритма Boids.


Состояния ИИ истребителя

Теперь у истребителей было три состояния:

1. Boids - если истребитель не "голова" группы, он остается в этом состоянии. Здесь происходит расчет движения корабля в стае. В случае, если текущая "голова" группы будет уничтожена, следующий за ним в группе корабль становится новой "головой" группы.

2. Fly Back - это состояние для "головы" группы, истребитель выбирает случайное направление и удаляется от станции.

3. Chase - это также состояние для "головы" группы, истребитель приближается к станции.


Демонстрация нового ИИ истребителей

Результаты оказались просто потрясающими! Новое поведение истребителей стало гораздо зрелищнее и приятнее :)


Если кому то будет интересно, как именно писать код для состояний, то вот пример скрипта, который случайно поворачивает Крейсер влево или вправо и открывает стрельбу по игроку NetRotateSide.cs


В завершение статьи хочу сказать, что разработка ИИ оказалась одной из самых интересных этапов написания игры. У нас получилось сделать гибкую систему, которую легко можно расширить и добавить новые фичи. Надеюсь, что статья поможет кому то с выбором основы под ИИ :)

В следующей статье я расскажу о том, как мы делали локализацию.

Если у Вас есть идеи поведения противников - делитесь!

Всем хорошего кодинга!


Предыдущие статьи:

Часть 4. Противники (продолжение)

Часть 3. Противники

Часть 2. Генератор волн противников

Часть 1. История разработки


Ссылка игры в Google Play: Space Turret: Defense Point

Показать полностью 3 1
7

Space Turret: Defense Point - Часть 4. Противники (продолжение)

Всем привет!

Во многих играх Боссы занимают весьма важную роль. Их прохождение гарантирует, что игрок приобрел некоторые навыки, смог развиться до определенного уровня и уже хорошо разбирается в механике игры. Поэтому мы подготовили 5 различных боссов под каждую кампанию. Итак, встречайте! :)


Крейсер Паршивых Пиратов

Пираты, отчаявшись захватить станцию, предпринимают свою последнюю попытку. Использовав практически все ресурсы, они разрабатывают особый Крейсер.

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


Авианосец Кровавых Пиратов

Кровавые Пираты смогли дальше продвинуться в развитии космических технологий и построили Авианосец.

Этот корабль дополнительно оборудован ракетными установками. Пока истребители будут отвлекать противника, Авианосец сможет нанести критические повреждения станции. Современные маневровые двигатели обеспечивают отличное уклонение от снарядов противника.


Линкор Наемников

Наемники обладают самыми совершенными военными технологиями в области кораблестроения.

Именно они смогли построить первый Линкор, благодаря которому добились своей жестокой известности. На текущий момент это самый большой, мощный и хорошо вооруженный корабль. На своем борту он имеет целых 10 тяжелых ионных орудий, вмонтированных в корпус корабля. Также имеется ангар для истребителей, вместимостью до 50 кораблей! Еще ни одна станция не смогла отразить нападение Линкора!


Дредноут Враждебной Империи

Пираты, Наемники... Их мощь и сила никогда не сравнятся с силой Империи! Обладая всеми ресурсами галактики, они смогли построить Дредноут!

Это самый совершенный и разрушительный тип кораблей, который получилось построить у человечества. Совершенная броня, огромные генераторы, громадные ангары, тяжелые ионные орудия и торпедные установки - гарантия победы Дредноута в любой битве.


Флагман Зоргов

Вселенная таит в себе много опасностей, но появление Флагмана Зоргов - было абсолютной неожиданностью.

Именно этот противник является "мозгом" всей расы Зоргов. Способен выпускать огромный луч разрушающей энергии, расщепляющий всё на своем пути. Имеет на борту множество различных защитных систем. Одна из них - особый щит, который защищает Флагман от любых повреждений. Щит поддерживается специальными Дронами-Защитниками. Это Абсолютное оружие, настоящий Армагеддон всего живого!


На этом статья подходит к концу. Было потрачено множество вечеров на проектирование и разработку этих боссов, особенно на Флагмана Зоргов. Мы не стали раскрывать все фишки Флагмана, так что надеемся, он сможет удивить тех, кто до него дойдет :)

В следующей статье я попробую рассказать о глубоком внутреннем мире противников, как мы организовали внутренние структуры и как проектировали их интеллект.

Если у Вас есть вопросы или замечания - пишите, с радостью отвечу :)


Предыдущие статьи:

Часть 3. Противники

Часть 2. Генератор волн противников

Часть 1. История разработки


Ссылка игры в Google Play: Space Turret: Defense Point

Показать полностью 4
16

Space Turret: Defense Point - Часть 3. Противники

Всем привет!

Сегодня я расскажу и покажу то, что большинство игроков не сможет увидеть в игре. Мы очень долго старались это скрывать, практически целый год (10 месяцев). Это те вещи, над которыми мы работали ночами, куда вкладывали максимум усилий. Более того, мне пришлось вложить часть своей души в одну из вещей, чтобы сделать ее ошеломительной.

Итак, речь пойдет о противниках и я их покажу крупным планом!


Истребители

Самый первый противник, которого встречает игрок - это истребитель.

Данный корабль относится к легкому классу кораблей. Это маневренный, легкий и очень быстрый противник. Имеет очень слабую броню и простое плазменное вооружение. Сами по себе они не могут причинить какой-либо серьезный ущерб станции, но если они собираются в стаю, то всё может поменяться. В основном используются в качестве разведки, также могут быть использованы в качестве поддержки крейсеров и авианосцев.


Фрегаты

После того, как игрок успешно отбивается от нескольких стай истребителей, пираты отправляют в подмогу корабли среднего класса - фрегаты.

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


Крейсеры

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

Данный вид противника относится к тяжелому классу кораблей. Они были специально сконструированы для осады крупных станций. Их особый сплав корпуса способен выдержать прямые попадания ионного орудия. На борту у себя имеют две тяжелые турели. Единственным их слабым местом является скорость - они очень медленные. Но будьте уверены, несмотря на это, нескольких крейсеров будет достаточно, чтобы разнести всю вашу станцию.


Взрыватели

Когда у пиратов дело доходит до жареного, они применяют абсолютное оружие - взрывателей.

Главная задача этих противников - разнести всё на Вашей станции. На своем борту они несут несколько десятков тонн взрывчатки. Имеют очень толстую броню, чтобы гарантировать доставку своего груза до цели. Для этого корабля был разработан особый двигатель, который способен выдавать огромную мощь. Но для того, чтобы запустить этот двигатель в режиме постоянного ускорения, ему необходимо сначала зарядиться. Именно это является единственным слабым местом Взрывателей, т.к. в процессе зарядки двигателя корабли полностью неподвижны.


Авианосцы

Только однажды одному из шпионов ценой собственной жизни удалось получить секретную информацию о разработке пиратов - Авианосцах.

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


Зорги

Последняя кампания сильно отличается от всех предыдущих. В ней Игроку предстоит встретиться с новой расой противников - Зоргами. Это роботизированные, бездушные и безжалостные противники, которые разоряют всё на своем пути. О них мало что известно, кроме того, что первые контакты с Зоргами произошли в секторе Альфа.


Дроны-Разведчики

Эти противники также быстры и маневренны, как истребители, но они гораздо сильнее. Каждый дрон обладает штурмовым лазером, который может нанести неплохой ущерб станции. Нападают в огромных количествах. При обнаружении врага дроны вызывают подкрепление.


Дроны поддержки

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

При уничтожении дрона, его ядро преобразует внутреннюю структуру и распадается на более мелкие части - дронов-разведчиков.


Штурмовые дроны

Это самые тяжелые боевые единицы Зоргов. Их корпус состоит из неизвестного сплава, которому практически невозможно нанести урон. Их лазерное оружие гораздо больше, чем у других дронов. При уничтожении, ядро дрона также преобразует внутреннюю структуру и распадается на нескольких дронов поддержки.


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


Предыдущие статьи:

Часть 2

Часть 1


Ссылка игры в Google Play: Space Turret: Defense Point

Показать полностью 8 1
19

Space Turret: Defense Point - Часть 2. Генератор волн противников

Всем привет!

Наверное в большинстве игр, где есть враги, каждый разработчик задается вопросом "Как создать генератор волн противников? Чтобы было удобно и легко настраивать?"

В интернете врятли получится найти подробный ответ. В большинстве случаев результат будет "Задайте массив врагов и генерируйте их с задержкой".

Но сегодня я попробую подробно описать один из вариантов генерации противников.

Для начала, надо понимать, что нам нужно от генератора:

1. Игроку требуется пройти раунд;

2. Каждый раунд состоит из набора волн;

3. Каждая волна состоит из противников одного типа;

4. Генератор волн должен генерировать новую волну при определенных условиях;

5. Расположение противников генерируется случайно в некоторой области игрового пространства.


Распутывать этот клубок лучше всего с конца.


Расположение противников

В нашей игре мы определили некую область (большой куб), внутри которой могут генерироваться противники.

Каждая новая волна будет создавать врагов только в какой то малой части этой области (малый куб). Размеры этой малой части определяются случайным образом, но при этом, они не выходят за рамки большого куба.


Пример генерации малого куба

Как можно расположить противников внутри малого куба? Тут может быть множество вариантов, но лучше всего начать с простых:

1. Задать случайные позиции;

2. Задать одинаковые позиции (все противники появляются из одной точки);

3. Задать позиции по горизонтали слева направо (или справа налево);

4. Задать позиции по вертикали сверху вниз (или снизу вверх).


Схематичные примеры расположения, вид спереди

При этом, в вариантах 3 и 4 можно дополнительно отражать расположение по горизонтали или вертикали относительно центра малого куба. Схематично это выглядит так:

Между генерацией противников можно указать задержку. Это положительно скажется на производительности, т.к. одновременное создание множества объектов требует значительного времени и пользователь может заметить такие лаги. Зная количество противников, можно рассчитать расстояние между точками (Количество противников будет случайным в диапазоне, но об этом чуть ниже).

Итого, из примитивных вариантов генераций у нас уже получается хороший набор :)

В игре такие способы расположения выглядят весьма неплохо!


Пример горизонтального отражения

Пример горизонтального и одновременно вертикального отражения

Когда генерировать новую волну?

На самом деле, это достаточно сложный вопрос, от которого напрямую зависит, насколько интересно будет играть. В большинстве игр типа Tower Defense, волны генерируются через определенное время. Такой вариант нас не устроил, т.к. в этом случае противников может накопиться очень много и игра начнет тормозить.

Поэтому мы придумали другой вариант: мы будем начинать генерировать новые волны, когда текущее кол-во противников опустится ниже определенного минимума. Останавливать генерацию новых волн мы будем, когда кол-во противников превысит определенный максимум.

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

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


Указание типа противника

Для каждой волны нам нужно знать, какого противника нужно генерировать и в каком количестве. Для этого нам достаточно указать ссылку на объект-противника (префаб) и задать минимальное и максимальное количество. Еще можно указать уровень противника. Это некий показатель, от которого будут зависеть урон, жизнь, скорость передвижения и другие параметры противника.


Если постараться это всё обобщить, то получится следующая структура для волны противников:

Данные генерации
-генерация волны: последовательная или параллельная;
-начальная задержка перед генерацией волны.
Данные расположения
-тип расположения противников: один из шести видов;
-задержка между генерацией;
-размер малого куба (относительно большого);
-позиция малого куба (относительно большого);
-горизонтальное отражение: да\нет;
-вертикальное отражение: да\нет.
Данные противника
-ссылка на объект (префаб);
-уровень противника;
-минимальное и максимальное количество.

Настройка раунда

Для хранения списка волн мы решили использовать специальный объект в Unity - ScriptableObject. Данный объект представляет собой что то типа профиля настроек.


Пример заполнения настройки раунда

Тестирование

После создания десяти раундов с постепенным увеличением сложности, мы успешно протестировали их. И после этого сделали сборку игры для тестировщиков :) Почти все тестировщики попросили сделать раунды более сложными, т.к. в текущих раундах было очень мало волн (5-10) и они быстро кончались.

Вот тут началось самое интересное. Мы осознали, что нам очень сложно управлять такой подробной настройкой. Она хоть и оказалась очень гибкой, но при этом на ее изменение ушло бы очень много времени. Особенно если учесть, что мы планировали 3 кампании по 20 раундов.

Пришлось думать, как можно это упростить.


Упрощение

Через несколько дней ко мне в голову пришла светлая мысль: игрокам не важно, как расположены противники, в каком месте и в какой последовательности они появляются. Поэтому мы можем использовать случайны числа! Выбирать случайный тип корабля, случайное количество и т.д. Но при этом оставалась проблема с нарастающей сложностью, ведь надо было как-то высчитывать, сколько кораблей должно быть в волне, сколько всего в раунде, как они будут изменяться...

Но вскоре и эта проблема была решена. Я придумал использовать игровые очки!

Когда система генерирует противника, противник будет использовать некоторое количество очков. Различные типы противников будут использовать различное количество очков, например:

Истребитель - 1 очко;

Фрегат - 2 очка;

Крейсер - 4 очка и т.д.

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

Например, 1 раунд - 30 очков, 2 раунд - 35 очков и т.д.

Раунд считается завершенным, когда потрачены все очки раунда.

Так как раунд состоит из волн, то  каждая волна также будет "запрашивать" некоторое случайное количество очков и на полученные очки генерировать противников. К примеру, от 5 до 10 очков на волну. Если у волны будет 10 очков, то на них можно создать 10 истребителей или 5 фрегатов. Чтобы в первом раунде не появились сразу мощные противники - крейсеры, то можно указать номер раунда, после которого волна будет доступна для генерации. Также, с каждым новым раундом, очки для волн будут увеличиваться.


Теперь, если всё обобщить, то для настройки одной кампании потребуется:

Настройка раундов
-количество раундов в кампании;
-количество очков первого раунда;
-инкремент очков раунда за каждый раунд;
Настройка волн
-количество очков волны;
-инкремент очков волны за каждый раунд;
Настройка генерации
-уровень противника;
-инкремент уровня противника за каждый раунд;
-минимальное и максимальное кол-во активных противников;
-инкремент минимального и максимального кол-ва противников за каждый раунд;
-ограничение лимита минимального и максимального кол-ва активных противников;
-список типов кораблей.

Пример заполнения профиля кампании

В упрощенной системе нам уже не понадобится куча настроек для каждого типа противника, поэтому выберем самое необходимое:

Настройка типов противников
-ссылка на объект-противника (префаб);
-минимальный номер раунда, в котором появляется волна;
-требуемые очки за одного противника;
-минимальное количество кораблей;
-максимальное количество кораблей (опционально);
-минимальная задержка между генерацией противников;
-максимальная задержка между генерацией противников.

Пример заполнения профиля типа противника

После того, как мы внедрили новую систему генерации, настраивать уровни стало гораздо проще и быстрее! Но оставалась одна проблема: т.к. мы использовали случайные числа, то у нас каждый раз генерировались разные последовательности противников при повторном прохождении раунда. Чтобы решить эту проблему, нам нужна была последовательность случайных чисел, которую всегда можно было бы повторить. Тут нам очень помог системный генератор случайных чисел System.Random. Этот генератор чисел будет каждый раз повторять псевдослучайную последовательность чисел, если каждый раз задавать ему одно и то же начальное значение. После внедрения System.Random, наш генератор волн стал выглядеть идеально! :)

А самое главное - мы смогли получить позитивные отзывы от тестировщиков!


На этой приятной ноте я завершаю вторую статью, спасибо всем, кто дочитал :) Надеюсь, эта статья поможет тем, кто хочет сделать свой генератор волн.

В следующей части попробую рассказать о противниках, которые используются в игре, об их особенностях и основных настройках.


P.S.

Я намеренно не выкладываю примеры кода, чтобы не усложнять повествование. Очень надеюсь, что понимая структуру данных для генератора, многие смогут самостоятельно написать код для его работы :) Но если возникнут затруднения или будут какие то вопросы - конечно, пишите :)


Ссылка игры в Google Play: Space Turret: Defense Point

Показать полностью 8
37

Space Turret: Defense Point - История разработки, часть 1

Всем привет!

Меня зовут Михаил, и я бы хотел сделать несколько постов о процессе разработки своей игры.

Игру пишу с другом из Германии, оба очень любим разрабатывать игры. Работаем айтишниками, в свободные вечера работаем над игрой на платформе Unity.

Зарождение

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

Несколько дней думали над геймплеем, смотрели примеры игр, искали аналоги, отмечали плюсы и минусы. И в итоге решили сделать космический шутер-оборону станции, где игрок управляет турелями, уничтожает врагов, получает награды и улучшает свое вооружение.

В голове мы себе представляли игру как некий тайм киллер, в который можно поиграть 5-10 минут и немного отвлечься от своих дел.


Геймплей, который мы себе представляли на тот момент

От идеи к реализации

После обсуждения базовых механик приступили к реализации.

Достаточно долго думали над управлением камерой. В итоге сделали два варианта:

1. Фискированное управление. Поворот камеры напрямую зависит от положения курсора на экране. Например, если курсор находится в самой левой части экрана, то камера повернута влево на 90 градусов. и наоборот.

Пример такой работы как раз на видео ниже, с кубиком.

2. Свободное управление. Камера может свободно вращаться во все стороны. Скорость вращения будет зависеть от положения курсора на экране. Чем ближе курсор к краю экрана, тем сильнее скорость поворота в эту сторону. Пример управления в первом ролике. В дальнейшем был выбран именно этот вариант.


Из прошлых разработок у нас уже был скрипт для турели, которая могла целиться в заданный объект и стрелять по нему.

Для того, чтобы заставить турель целиться туда, куда указывает курсор на экране, пришлось применить хитрость :)

Дальше нужно было добавить врагов. Но перед тем, как их добавлять, нужно придумать, откуда они будут появляться. Исследовав несколько популярных фильмов на эту тему, было решено, что враги будут прилетать из варпа. Эффект должен быть красивым, поэтому было потрачено не мало времени на поиск того, какие варпы нам понравились.

В итоге наткнулись на такой ролик с обалденным эффектом варпа! (качество не айс, зато оригинал)

Но чтобы реализовать такой эффект, нужно знать шейдеры. И следующие несколько дней я искал различные туториалы, изучал примеры, даже разбирался с кодом мобильных шейдеров в Unity.

В итоге, получилось воспроизвести похожий эффект!

На самом деле, изучение шейдеров в дальнейшем очень пригодилось. С их помощью удалось значительно улучшить графику под мобильные устройства, с учетом различных нюансов геймплея.


После разработки варпа были добавлены враги, которые летели только вперед. На тот момент еще не было никакого ИИ и нам очень хотелось проверить производительность на устройстве и удобство управления.


Пример тестирования производительности графики и наведения турели на цель.

60 ФПС очень воодушевляло, был некий "запас" прочности для дальнейшей разработки. Правда, нас немного смутило, что "крестики", куда прицеливается турель, немного смещены от направления самих кораблей.

Курсоры и наведение на цель

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

В интернете существует много разных алгоритмов, отличающихся точностью результата и скоростью выполнения.

Для нашей игры мы сделали отдельный менеджер, задача которого была отобразить рамку вокруг корабля и точку попадания. Менеджер работал замечательно, пока корабли не поравнялись со станцией. Чем ближе находился корабль, тем сильнее у него съезжала точка наведения относительно направления.


Пример со смещенной точкой наведения

Пример желаемого расположения точки наведения

После нескольких дней анализа кода, вывода кучи отладочной информации на экран и серфинга интернета, была найдена причина такого поведения! Имя ей - камера!

Дело в том, что из-за особенностей алгоритма и точности типа float, точка пересечения корабля с пулей немного смещалась относительно направления корабля.


Схематическое изображение смещения прицела

При этом, если смотреть на цель как бы из ствола турели - то всё было хорошо.

Но вид то у нас от третьего лица! И если смотреть из другой точки, то это смещение становилось сразу заметным.

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

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

Да, на этот расчет тратятся ресурсы процессора, но когда на экране рассчитывается 50 точек пересечения, +- несколько дополнительных точек погоды не сделают :)


На этом первая часть статьи заканчивается, спасибо, что дочитали до конца!


В следующей части расскажу подробно о генераторе волн противников, как мы его настраивали и что нас ожидало...


P.S.

Если у Вас есть какие-либо вопросы, пишите, с радостью отвечу :)

Первый релиз игры состоялся в начале декабря 2019 года. На разработку первой версии ушло 4 месяца.

Ссылка игры в Google Play: Space Turret: Defense Point

Показать полностью 5 4
Отличная работа, все прочитано!