Рисунок 2.2.3 - Интерфейс уровней
Рисунок 2.2.4 - Меню паузы
Правильное планирование алгоритмов позволит избежать многих ошибок в
реализации. В соответствии с разработанной механикой игры надо разработать
алгоритмы, реализующие эту механику.
Башни будут строится в определённых местах карты. Для этого надо
разработать места для постройки. При нажатии на них, если выбрана какая-либо
башня для постройки и достаточно денег, будет построена выбранная башня. Если
башня не выбрана или недостаточно денег, башня не будет построена. Блок-схема
алгоритма представлена на рисунке 2.3.1.
Рисунок 2.3.1 - Блок-схема алгоритма постройки башен.
Противники будут двигаться по определённому маршруту. Маршрут в игре
будет представлен в виде извивающегося пути, а в коде в виде списока маршрутных
точек. Противники создаются в первой точке маршрута и идут к следующей, до тех
пор, пока не достигнут конца или не будут уничтожены. При достижении конечной
точки у игрока отнимается 1 жизнь. Блок-схема алгоритма представлена на рисунке
2.3.2.
Рисунок 2.3.2 - Блок-схема алгоритма передвижения противников
У каждой башни будет определённый радиус поражения, в котором она может
поражать цели. При появлении в нём противника, он записывается в список целей.
Если противник уходит за радиус, он удаляется из списка. Башня последовательно
стреляет в цели, при уничтожении цели она переключается на следующую в списке
цель. Блок-схема алгоритма представлена на рисунке 2.3.3.
Рисунок 2.3.3 - Блок-схема алгоритма стрельбы
При выстреле создаётся снаряд, который целенаправленно летит в цель. При
попадании в цель он уничтожается и у противника отнимаются очки здоровья. Если
здоровье упадёт до 0, противник погибает.
3. РЕАЛИЗАЦИЯ
В выбранной среде разработки поддерживаются 2 языка программирования:
JavaScript и C#. Был выбран язык C#, так как он предоставляет больше
возможностей для ООП.
В Unity код представляется в виде скриптов. Скрипты в Unity- классы со
своими полями и методами. Большинство скриптов активно используют функцию
Update, которая вызывается при каждой смене кадров на экране. Благодаря это
функции можно реализовать множество алгоритмов, работающих в реальном времени.
Описание переменных приведены в виде таблице 3.1
Таблица 3.1 Описание переменных скрипта ShootEnemies
|
Название переменной |
Тип переменной |
Назначение |
|
enemiesInRange |
List<GameObject> |
Список целей башни |
|
lastShotTime |
Float |
Время последнего выстрела, используется для определения времени следующего выстрела |
|
monsterData |
MonsterData |
Информация о башне, представленная в виде отдельного класса |
|
Target |
GameObject |
Цель для стрельбы |
|
minimalEnemyDistance |
Float |
Используется для нахождения минимального расстояние до последней точки маршрута. |
|
distanceToGoal |
Float |
Используется для хранения расстояния до конечной точки маршрута текущего противника, сравнивается с minimalEnemyDistance |
|
Direction |
Vector3 |
Используется определения направления стрельбы, чтобы поворачивать башню в сторону цели |
|
bulletPrefab |
GameObject |
Используется для хранения прототипа снаряда. |
|
startPosition |
Vector3 |
Начальная позиция снаряда |
|
targetPosition |
Vector3 |
|
|
newBullet |
GameObject |
Создаваемый снаряд |
|
Animator |
Animator |
Используется для хранения анимаций и воспроизведения их |
|
audioSource |
AudioSource |
Используется для хранения и воспроизведения звука выстрела |
Для каждого объекта можно создать компонент Collider. На Рисунке 3.1.1
показан компонент CircleCollider, который задаёт круглую коллизионную область
определённого радиуса.
Рисунок 3.1.1 - Компонент CircleCollider
Если в эту область заходит объект с компонентом Collider, у этих объектов
вызывается функция void OnTriggerEnter2D с передаваемой ссылкой на объект,
который зашёл в область. Эта функция используется для заполнения списка целей.
Сначала у объекта проверяется тэг (ключевое слово для разделения объектов на
типы) «Enemy» (Рисунок 3.1.2), чтобы другие башни в радиусе на попадали в
список.
Рисунок 3.1.2 - Установка тэга «Enemy» для противника
Когда объекта областями коллизий расходятся, вызывается функция void OnTriggerExit2D с ссылкой на объект, который вышел из области коллизий. Эта функция применяется для удаления врагов из списка целей. Когда враг выходит из радиуса, он удаляется из списка целей.
Выбор цели производится в функции Update().Цель выбирается из списка целей EnemyinRange. Если он пуст, то переменной target присваивается значение null. Если нет, то переменной target присваивается ссылка на противника, который находится ближе других к последней точке маршрута.
Если переменная target не равна null, то вызывается функция Shoot.
Функция Shoot создаёт объект bullet и присваивает ей следующие параметры:
) 1)target. Будет присвоена текущая цель.
) startPosition. Будет присвоена позиция башни, обозначающая начальное положение снаряда.
) targetPosition. Будет присвоена позиция цели.
После создание объекта bullet, он начинает двигаться к цели. Между башней и целью проводится линия. Движение по линии осуществляется с помощью функции Lerp класса Vector3. Конечная точка полёта меняется при движении цели
Функция Lerp возвращает значение типа Vector3.
У функции Lerp 3 входных параметра:
) Начальная точка
) Конечная точка
) Параметр, показывающий положение между начальной и конечной
точками. Параметр принимает значение от 0 до 1. В данном случае этот параметр
принимает значение timeInterval * speed / distance, где timeInterval - время,
прошедшее с момента выстрела, speed - скорость, distance- расстояние до цели.
Когда позиции снаряда и цели будут равны, у цели ищется наследственный
компонент класса healthbar(Рисунок 3.1.3) . В нём находится переменная
currenthealth, отвечающая за текущие очки здоровья цели. Из неё вычитается
значения равное количеству урона снаряда. Если у цели осталось меньше 0 очков
здоровья, она уничтожается, игроку начисляется награда. У всех башен, которые
имели в списке целей уничтоженного врага, вызывается функция OnEnemyDestroy с
ссылкой на уничтоженный объект, которая убирает эту цель из списка.
Рисунок 3.1.3 - Объект класса Enemy
После попадания в цель снаряд уничтожается.
Для того чтобы скрипт выполнял заложенную в него функцию, его надо привязать к какому-либо объекту. Для работоспособности башни были написаны 2 скрипта:
) ShootEnemies.cs. Отвечает за стрельбу по противникам.
) MonsterData.cs. Хранит в себе информацию о башне и её улучшениях.
Привязка скриптов производится путём добавления компонента Script для
объекта, как показано на рисунке 3.1.4
Рисунок 3.1.4 - Компоненты Script объекта Monster
Как показано на рисунке 3.1.4 для скриптов надо задать начальные значения. Для скрипта MonsterData надо задать размер массива с уровнями башни. У каждого уровня есть стоимость Cost, визуализация башни Vizualization, тип снаряда Bullet, скорость стрельбы Fire Rate.
Визуализация башни Vizualization представлена в виде объекта с
параметрами показанном на рисунке 3.1.5
Рисунок 3.1.5 - Объект Monster
Компонент Sprite Render отвечает за обрисовку спрайта башни на экране. В параметрах компонента Sprite Render можно задать сам спрайт, прозрачность, материал.
Компонент Animator отвечает за воспроизведение анимации. В параметре
Controller надо указывать файл типа Animator Controller. Этот файл отвечает за
последовательность действий при анимации как показанно на рисунке 3.1.6
Рисунок 3.1.6 - Интерфейс Animator Controller
После создания башни, она переходит в состояние Idle, в которой он просто
ничего не делает. Если башня стреляет, она переходит в состояние Shooting и
выполняется анимация, привязанная к состоянию Shooting (Рисунок 3.1.7)
Рисунок 3.1.7 - Интерфейс состояния Shooting
Интерфейс создания анимации показан на рисунке 3.1.8. Здесь можно
задавать действия, совершаемые над объектом. Анимация зацикленная, то есть
после последнего кадра идёт первый.
Рисунок 3.1.8 - Интерфейс создания анимации
Тип снаряда Bullet представляется в виде объекта с привязанным к нему скриптом Bullet Behavior. Параметры объекта показаны на рисунке 3.1.9
Рисунок - 3.1.9 Объект Bullet
В скрипте Bullet Behavior есть следующие параметры:
) Speed. Скорость полёта снаряда.
) Damage. Урон, наносимый противнику при попадании.
) Target. Цель, в которую летит снаряд.
) Start Position. Начальное положение снаряда.
) Target Position. Позиция текущей цели.
Были настроены места постройки башен. На рисунке 3.1.10 показаны
параметра объекта Openspot, отвечающего за места постройки
Рисунок 3.1.10 - Объект Openspot
У привязанного скрипта Place Monster есть 2 параметра:
) Monster Prefab. Ссылка на выбранную игроком башню.
) Monster. Ссылка на текущую построенную башню.
Маршрут движения противников представлен в виде вспомогательных объектов
Waypoint, у которых только один компонент Transform, отвечающий за
расположение.
Рисунок 3.1.11 - Объект Waypoint
Объект Road отвечает за хранение маршрута и за создание противников. Параметры объекта Road показаны на рисунке 3.1.12
Рисунок 3.1.12 - Объект Road
Привязанный скрипт SpawnEnemy имеет следующие параметры:
) Waves. Массив типа Wave, где хранится информацию о волнах противников. В классе Wave указаны ссылки на противников, интервал их появления и максимальное число.
) Time Between Waves. Время между волнами.
) WayPoints. Массив маршрутных точек.
Интерфейс игры в Unity реализуется с помощью компонента Canvas. Далее на
него выносятся элементы, которые необходимы для интерфейса. Canvas
располагается вне игрового поля в редакторе, но при запуске игры Canvas
масштабируется под разрешение и расположится поверх игрового поля
Для главного меню создана отдельная сцена. Она включает себя стартовое меню и меню выбора уровней. Они объединены в компоненты типа Panel, чтобы было легче переключатся между ними.
В стартовом меню всего 2 кнопки.
) Play- кнопка перехода на меню выбора уровней. К ней привязан скрипт Maintochoose.cs. При нажатии на кнопку, из скрипта вызывается функция void toggle(), которая скрывает стартовое меню и делает видимым меню выбора уровней (Рисунок 3.2.1).
) Exit - кнопка выхода из игры. К ней привязан скрипт Exit.cs. При
нажатии на кнопку, из скрипта вызывается функция void QuitGame(), которая
закрывает приложение.
Рисунок 3.2.1 - Интерфейс стартового меню
В меню выбора уровней реализованы кнопки перехода на уровни и кнопка возврата в стартовое меню(Рисунок 3.2.2).
Все кнопки перехода работают одинаково. К ним привязаны скрипты, имеющих функцию void cl1ick(), которая срабатывают при нажатии на кнопку. Функция void cl1ick() загружает сцену с уровнем, соответствующий кнопке.
К кнопке назад привязан скрипт ChooseToMain.cs, имеющий функцию void
toggle(), которая скрывает меню выбора уровней и делает видимым стартовое меню
Рисунок 3.2.2 - Интерфейс выбора уровней
Для отображения информации, сверху расположены 3 компонента Text. В GameManager хранится сама информация и ссылки на компоненты Text.Каждый кадр эта информация обновляется.
К кнопкам выбора башен привязан скрипт tower_type_1.cs, хранящий в себе ссылку на тип башни, который закреплён за кнопкой. При нажатии на кнопку, скрипт передаёт тип башни GameManager.С помощью этого есть возможность переключения между типами башен.
Так же в левом нижнем углу есть кнопка паузы. К ней привязан скрипт
Pause.cs. Реализованная в нём функция void togglePause() останавливает время в
игре и показывает меню паузы(Рисунок 3.2.3).
Рисунок 3.2.3 - Интерфейс уровня
Кнопка Resume использует тот же скрипт, что и кнопка паузы. Функция void togglePause() возобновляет время и скрывает меню паузы.
Функция void togglePause() в зависимости от Time.timeScale, обозначающим масштаб времени, выполняет разные функции. Если время не остановлено, то она останавливает время и показывает меню паузы, иначе возобновляет игру и скрывают меню паузы (Рисунок 3.2.4).
(Time.timeScale != 0f)
{.SetActive(true);.timeScale = 0f;
}
{.SetActive(false);.timeScale = 1f;
}
Рисунок 3.2.4 - Интерфейс меню паузы
4. ТЕСТИРОВАНИЕ
Существует множество методов тестирования ПО
Модульное тестирование - тестирование отдельных модулей разрабатываемой программы отдельно от других модулей, в целях изоляции от воздействия других модулей.
Игра, разработанная в Unity, состоит из множества скриптов. Их всех надо
проверить на работоспособность. Для этого лучше всего подойдёт модульное
тестирование.
Функциональное тестирование программы на соответствие функциональным требованиям.
Чтобы проверить функции, которым должна соответствовать игра, необходимо
применить функциональное тестирование для тестирования игры целиком. Эти два
метода позволят протестировать игру с внешней и с внутренней стороны игры.
Каждый модуль (скрипт) тестировался отдельно. Модульное тестирование проводилось во время реализации игры для более быстрого исправления появляющихся ошибок. Скрипты в финальной их версии работоспособны и выполняют возложенную на них функцию.
4.2.2 Результаты функционального тестирования
Каждая функция тестировалась согласно функциональным требованиям приложения.
В качестве тестировщиков выбраны независимые лица из числа одногруппников и знакомых. Был выдан список функций, требующие проверки.
Результаты функционального тестирования приведено в таблице 4.1
Таблица 4.1 - Результаты функционального тестирования
|
Функция |
Результат |
|
Постройка башни |
При нажатии на место постройки башня строится, если выбрана башня |
|
Выбор типа башни |
Кнопки выбора типов башен работают корректно, выбирается нужный тип башни |
|
Генерация противников |
Противники генерируются в начальной точке маршрута. |
|
Движение противников |
Противники движутся плавно, но разворот происходит резко |
|
Стрельба башен |
Все башни стреляют в нужные цели, башни разворачиваются в сторону цели. Снаряды попадая в цель отнимают у них очки здоровья. |
|
Уничтожение противника |
Если здоровье противника опускается ниже 0, то он уничтожается и счётчик золота увеличивается. |
|
Достижение противниками конечной точки маршрута |
При достижении конечной точки противник уничтожается и у игрока отнимается одна жизнь |
|
Пауза |
Кнопка паузы выполняет свою функцию, корректно останавливает игру и показывает меню паузы |
|
Выбор уровней |
Кнопки выбора уровней загружают нужные уровни. |