Разработка архитектуры программы и пользовательского интерфейса
Архитектура игры включает в себя блоки, которые выполняют разные цели внутри программы. В проекте содержатся:
Блок управления основной информацией
Данная часть кода проекта оперирует общей информацией о всех игроках:
- Имя. У каждого игрока должно быть имя, чтобы их удобно было различать;
- Позиция. Начальная точка, в которой игрок начинает матч;
- Цвет. Игроки используют один набор юнитов и баз, поэтому необходимо использовать разные цвета, чтобы их удобно было различать. Также используется на карте пользовательского интерфейса;
- Начальные юниты. Список юнитов, с которыми игрок начинает игру по умолчанию;
- Текущие юниты. Список юнитов, которые есть у игрока в текущий момент времени;
- Текущее количество баз. Список баз, которые есть у игрока в текущий момент времени;
- ИИ. Показывает, человек или искусственный интеллект управляет игроком;
- Ресурсы. Количество ресурсов, которыми владеет игрок в текущий момент времени;
Действия
Эта часть архитектуры содержит в себе действия создания баз и юнитов и возможности сбора ресурсов.
Бой
Действия, которые касаются сражения, отнесены в отдельный блок, потому что это считается важной составляющей и объемной частью механики игры. В данной части содержатся действия, которые отражают суть боевых действий – возможность атаки объекта противника и уничтожение объекта, если у него недостаточно очков «здоровья»[14].
Взаимодействие
Данная часть описывает взаимодействие игрока с виртуальным миром посредством клавиатуры и мыши. Мышь выполняет главную роль в управлении игрой. В этом блоке прописаны возможности клика левой и правой кнопкой мыши. Первая должна выбирать объекты виртуального мира, а также взаимодействовать с интерфейсом пользователя - на панели возможных действий, например. Также прописана возможность «подсветки» игровых объектов при их выборе. Правая кнопка мыши отвечает за перемещение юнитов в пространстве. Клавиатура используется для управления камерой и для выбора нескольких юнитов одновременно.
Имитация поведения противника
В данном блоке описывается возможное поведение игрока, которого контролирует не человек. Имитация строится на оценке самого полезного действия, которое может сделать противник. Они включают в себя постройку здания, создание юнита и атака на игрока. Также сюда включение действие «ничего не делать». Оно нужно для баланса – суть имитации поведения противника не в том, чтобы выиграть живого игрока самым эффективным способом, а в том, чтобы симулировать поведение близкое к нему. И в конце описана оценка каждого действия – какие из них самое необходимое в каждый момент времени.
Интерфейс пользователя
Интерфейс состоит из четырех блоков, которые описывают разную информацию
Рисунок 1 - Схема пользовательского интерфейса
Ресурсы
Окно ресурсов показывает количество ресурсов, которые игрок имеет в текущий момент времени. Оно располагается в правом углу экрана. Игрок не может с ним взаимодействовать.
Рисунок 2 - Окно ресурсов
Карта
Карта – один из самых важных элементов интерфейса в стратегии реального времени. Игрок должен опираться на карту, продумывая свои действия и создавая на их основе тактику. Она показывает всю местность, на которой проходит игра. Некоторые стратегии имеют так называемый «туман войны», то есть вся территория закрыта от взгляда игрока до тех пор, пока он не будет исследовать карту с помощью юнитов. Тогда карта открывается постепенно, учитывая возможную дальность каждого юнита, который по ней передвигается.
В проекте карта открыта, но все объекты противника на карте не помечаются до тех пор, пока игрок не приблизиться к ним на определенное на расстояние. То есть действия противника все равно скрыты, потому что игрок не видит перемещение юнитов и количество и место баз. На карте также отображается прямоугольник белого цвета, он называется «Viewer». Он отображает камеру игрока, то есть то расстояние на карте, которое он просматривает. Объекты разных игроков помечены разным цветом.
Рисунок 3 - Карта
Окно информации о юните
При нажатии на юнита автоматически появляется информация о нем. Она состоит из пяти частей. Первая – изображение юнита, чтобы игроку было легко ориентироваться во время игрового процесса. Также присутствует его имя, потому что в стратегиях обычно существует не один тип войск, поэтому для различия удобно выделять имя крупно.
Затем идет его здоровье. Оно тоже изображается крупно, так как игроку должно быть удобно смотреть во время сражения сколько у какого юнита здоровья, чтобы легче ими управлять. И в конце отображается тот кому юнит принадлежит. Игрок может щелкать и на объекты противника, поэтому так будет легче ему различать.
Рисунок 4 - Окно информации о юните
Окно действий
Окно действий должно отображать все возможные действия, которые игрок может сделать с помощью определенного юнита. По умолчанию это окно является пустым, потому что нажатие на юнита не было произведено. Текущий юнит может производить только базу, поэтому ее изображение стоит слева вверху. Окно действий обычно состоит из девяти панелей.
Рисунок 5 - Окно действий
Реализация ПО
Основная информация
Под основной информацией имеется в виду подготовка частей проекта, которые будут служить базисом для дальнейшей разработки.
Позиция
В объекте «карта» содержатся две начальные позиции обоих игроков. Они стоят там, где они появятся во время начала игры.
Рисунок 6 - Начальные позиции игроков на карте
Чтобы игроки оказались в нужных точках при запуске игры, используется метод Instantiate. Он копирует оригинальный игровой объект и возвращает его аналог. Позиция Location.position - это начальная точка игрока, которая указана в его информации. В данном случае на ней появляется один юнит – все, что есть у игрока в начале игры.
var go = (GameObject)GameObject.Instantiate(u, p.Location.position, p.Location.rotation);
Чтобы сразу показать начальную позицию, объект «камера» должен быть установлен сверху неё с старта игры. Переменная pos задает координаты, где появляется игрок. Затем устанавливается высота, на которой будет объект «камера». Она содержится в переменной Height.
var pos = p.Location.position;
pos.y = Height;
transform.position = pos;
Информация об игроке
Игра оперирует информаций об игроках, чтобы знать их состояние в любой момент времени. Вся информация хранится в отдельном классе. В нем используется библиотека Unity System.Serializable, которая позволяет удобнее ее показывать, выстраивая иерархию и привязывая к определенному объекту, в данном случае – к игроку.
Рисунок 7 - Информация об игроках
Интерфейс
Интерфейс игры состоит из четырех частей: карта, информация о юните, список возможных действий и окно ресурсов.
Карта
Карта показывает, какую область территории видит игрок. Так как он это делает «через» объект «камера», то необходимо преобразовать его координаты в координаты окна, которое будет перемещаться по карте интерфейса.
Вычисляется размер всей карты с помощью двух противоположных углов. Для окошка в интерфейсе используется класс RectTransorm. Он нужен для удобного управления позицией, размером и масштабированием прямоугольников, часто применяется для пользовательского интерфейса.
Затем создается метод, который будет преобразовывать координаты объекта на сцене в координаты окна на карте интерфейса. В случае карты и камеры (Corner.position.z и point.z) в качестве второй координаты берется Z, а в случае окна на карте интерфейса (mapRect.rect.height) – Y. Потому что интерфейс «прикрепляется» к объекту «камера» только при запуске самой игры – в редакторе они расположены на разных осях.
terrainSize = new Vector2 (
Corner2.position.x - Corner1.position.x,
Corner2.position.z - Corner1.position.z);
mapRect = GetComponent<RectTransform> ();
Vector 2 WorldPositionToMap{
var mapPos = new Vector2 (
point.x / terrainSize.x * mapRect.rect.width,
point.z / terrainSize.y * mapRect.rect.height);
return mapPos;
}
void Update () {
ViewPort.position = WorldPositionToMap (Camera.main.transform.position); }
Разделение по цветам
Следующая функция, которую должна выполнять карта – показ юнитов. Игрок должен видеть, что принадлежат ему, а что – противнику. Осуществляется разделение с помощью цвета. Но он не может видеть юнитов противника, если они дальше определенной дистанции от одного из его юнитов. Они должны быть подсвечены цветом своей команды. Для этого используется элемент Unity Image. Он является изображением, которое показывается в графическом интерфейсе.
Создается объект, ему присваивается цвет игрока. И он показывается на карте с помощью метода WorldPositionToMap, который использовался ранее с объектом «камера».
blip = GameObject.Instantiate (Map.Current.BlipPrefab);
var color = GetComponent<Player> ().Info.AccentColor;
blip.GetComponent<Image> ().color = color;
blip.transform.position = Map.Current.WorldPositionToMap (transform.position);
При запуске игры изображение юнитов противника отключено. Поэтому проверяется, как далеко они от объектов игрока. Осуществляется это при помощи структуры Vector3. Она используется для того, чтобы работать с координатами в трехмерном пространстве. Метод Distance возвращает расстояние между двумя объектами. Если это расстояние меньше или равно заданному в коде (переменная VisibleRange), то изображение юнита на карте интерфейса включается. Также он становится видимым в самой игре. Для этого используется класс Renderer. Он отвечает за то, чтобы игрок мог видеть объект на экране.
foreach (var o in oBlips) {
bool active = false;
foreach(var p in pBlips) {
var distance = Vector3.Distance(o.transform.position, p.transform.position);
if (distance <= VisibleRange) {
active = true;
break; } }
o.Blip.SetActive(active);
foreach(var r in o.GetComponentsInChildren<Renderer>()) r.enabled = active;}
Рисунок 8 - Юнит игрока на карте
Окно юнита
Окно юнита содержит его изображение, имя и количество очков «здоровья». С помощью метода SetPic добавляется картинка юнита. Затем показывается текущее число очков «здоровья», максимальное число очков «здоровья», и имя игрока, чтобы легко было различить, кому юнит принадлежит.
InfoManager.Current.SetPic (ProfilePic);
InfoManager.Current.SetLines (
Name,
CurrentHealth + "/" + MaxHealth,
"Owner: " + GetComponent<Player> ().Info.Name);
Рисунок 9 - Окно информации о юните
Окно действий
При каждом нажатии на юнита в окне действий должны быть добавлены кнопки, чтобы игрок мог видеть, что он с помощью данного юнита может сделать в игре. С помощью SetActive активируется игровой объект, то есть кнопка. Затем ей присваивается изображение, чтобы игроку было удобно находить место, куда он может нажать. В конце добавляется действие onClick, то есть условие нажатия на кнопку игроком.
public void AddButton(Sprite pic, Action onClick)
{
Buttons.gameObject.SetActive (true);
Buttons.GetComponent<Image> ().sprite = pic;
actionCalls.Add (onClick);
}
Управление
Выбор юнита
Чтобы подтвердить, что игрок нажал на часть интерфейса, нужен ряд проверок, которые это сделают. Первая – щелкнул ли игрок кнопку вообще. Используется метод класса Input. Он возвращает значение true, если была нажата указанная кнопка мыши. Для левой используется 0, для правой - 1, для средней - 2.
if (!Input.GetMouseButtonDown (0)) return;
Далее проверяется, есть ли объект интерфейса на месте наведении курсора мыши. Если он есть, то надо убедиться, что объект в игровом мире за ним не выберется. Используется класс EventSystem. Он нужен для обработки ввода информации с мыши и клавиатуры. Метод IsPointerOverGameObject возвращает true, если курсор находится поверх 2D элемента.
var es = UnityEngine.EventSystems.EventSystem.current;
if (es != null && es.IsPointerOverGameObject ()) return;
Выбор других объектов должен быть отменен при выборе нового. Исключение составляет нажатие правой или левой кнопки Shift. Они дают возможность игроку выбрать несколько объектов.
if (Selections.Count > 0) {
if (!Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift)) {
foreach(var sel in Selections) {
if (sel != null) sel.Deselect();
}
Selections.Clear(); } }
Далее проверяется, был ли выбран какой-либо объект. Делается это при помощи класса Raycast, который отвечает за «лучевой» поток, работающий с камерой.
Любая точка на камере соответствует линии в пространстве игры. Иногда очень полезно иметь математическое представление подобной линии, и Unity может предоставить это в форме объекта Ray. Ray всегда соответствует точке от «лица» объекта «камера», поэтому класс, который помогает с ней работать, имеет функции ScreenPointToRay и ViewportPointToRay. Данные функции возвращает луч, который содержит начальную точку, и вектор, который содержит направление из этой точки, соответственно.
Raycasting. Наиболее частое использование данного компонента – это осуществлять направленный поток лучей на сцену. Это значит, что камера посылает невидимый «лазерный луч» вдоль Ray до тех пор, он не достигнет коллайдера на сцене. Компонент коллайдер определяет форму объекта для физических столкновений. Коллайдер, который при игре остается невидимым, не должен быть той же формы, что и игровой объект, и обычно даже грубое приближение чаще более эффективно и неразличимо при самой игре. Затем возвращается информация об объекте и фиксируется точка, где данный луч его настиг. Это удобный способ обнаруживать объект с помощью изображения на экране.
Метод RaycastHit выдает положительный результат только при нажатии на тип объекта Collider. Данный тип определяют форму и физическое взаимодействие объекта с игровым миром.
var ray = Camera.main.ScreenPointToRay (Input.mousePosition);
RaycastHit hit;
if (!Physics.Raycast (ray, out hit)) return;
Далее идет проверка на интерактивность объекта. Если у него нет такого компонента, то он не сможет быть выбранным.
var interact = hit.transform.GetComponent<Interactive> ();
if (interact == null) return;
Если все условия выполнены, и игрок может взаимодействовать с объектом, то он выбирается.
Передвижение юнита
Чтобы юнит смог передвинутся на определенный участок на карте, необходима проверка, нажималась ли кнопка, когда курсор мыши наведен на нужную область. Используется метод, который был описан ранее, но с переменной Terrain Collider. Данный вид объекта «обволакивает» всю карту и осуществляет физическое взаимодействие с ней.
public Vector3? ScreenPointToMapPosition(Vector2 point)
{ var ray = Camera.main.ScreenPointToRay (point);
RaycastHit hit;
if (!MapCollider.Raycast (ray, out hit))
return null;
return hit.point; }
Затем определяется: если игрок нажимает на правую кнопку мыши, то юнит двигается в заданную точку. Но юнит может остановиться раньше. Если юнитов будет много и им всем будет дана команда идти в одно и то же место, то для них может не хватить пространства, и они начнут «толкаться». Поэтому вносится переменная RelaxDistance. Измеряется расстояние, и если дистанция меньше этой переменной, то объект может остановиться.
if (selected && Input.GetMouseButtonDown (1)) {
var tempTarget = RtsManager.Current.ScreenPointToMapPosition(Input.mousePosition);
if (tempTarget.HasValue) {
SendToTarget(); } }
if (isActive && Vector3.Distance (target, transform.position) < RelaxDistance) {
agent.Stop ();
isActive = false; } }
Передвигается юнит при помощи класса NavMesh. Он отвечает за систему навигации и позволяет создавать объекты, которые могут передвигаться по миру. Он состоит из следующих компонентов:
NavMesh (Navigation Mesh). Это структура данных, которая описывает поверхности в игровом мире, по которым можно передвигаться, и позволяет найти путь из одного места в другое. Она зависит от геометрии уровня.
NavMesh Agent. Данный компонент помогает создавать объекты, которые будут обходить друг друга, если окажутся на пути у друг друга, а также другие препятствия, которые предусмотрены NavMesh.
Off-Mesh Link. Компонент позволяет включить в путь точки навигации, которые могут быть не представлены на поверхности.
NavMesh Obstacle. Данная часть структуры данных позволяет описать двигающиеся препятствия, которые объект должен обходить при продвижении по миру игры. Пока препятствие движется, объекты пытаются обойти его, но как только оно остановилось, NavMesh автоматически ограничивает пространство, или если оно заграждает путь, то объект ищет другой маршрут.
Системе навигации нужна информация, чтобы представлять подобное пространство в игре. Оно определяет место, где объект может двигаться и стоять. Затем такие местоположения соединяются в поверхность, которая лежит поверх геометрии всей сцены. Это поверхность называется навигационной сеткой, то есть Navigation Mesh.
NavMesh сохраняет эту поверхность в качестве выпуклых многоугольников. Они являются полезным представлением поверхности, так как между двумя любыми точками многоугольника нет никаких препятствий. В дополнение к границам многоугольников сохраняется информация о том, какие из них являются друг другу соседями. Это позволяет понимать, какая область проходима.
Чтобы найти путь между двумя объектами на сцене, сначала сопоставляются начальный и конечный пункт с ближайшими многоугольниками. Затем начинается поиск с первой точки, с помощью посещений всех нужных полигонов, пока объект не доберется до конечной цели. Отслеживание посещенных полигонов осуществляет алгоритм A*.
Последовательность полигонов, которая описывает путь от начала и до конца называется «коридор». Объект должен достигнуть своей цели, всегда направляясь к следующему видимому углу «коридора».
Логика двигательного управления занимает позицию следующего угла и на основе этого вычисляет желаемое направление и скорость, которые нужны для того, чтобы достичь конечного пункта. Использование предполагаемой скорости может привести к столкновению с другими объектами. Тогда компонент, отвечающий за обход препятствий, выбирает новую скорость, которая должна держать баланс между желаемым направлением и обходом будущих столкновений с другими объектами. Тут используется вычисление столкновений с помощью RVO, то есть reciprocal velocity obstacles. Такой подход берет во внимание реактивное поведение всех объектов, подразумевая, что они делают аналогичные рассуждения об избежании конфликтов.
В Unity симуляция происходит с использованием простой динамической модели, которая также учитывает ускорение, чтобы обеспечить более естественное и плавное движение.
Глобальная навигация используется, чтобы найти путь в мире игры. Список полигонов, описывающих путь, представляет собой гибкую структуру данных для управления движением, и он может быть локально скорректирован по мере перемещения объекта. Поэтому локальная навигация выясняет, как эффективнее всего двигаться на следующий полигон без столкновения с другими движущимися объектами.
Просчитываются все препятствия с помощью компонента NavMesh Obstacle. Он позволяет задать в игре все помехи, которые объект должен учитывать при перемещении по миру, также помогает при построении маршрута. В данном проекте к препятствиям относятся перепады высот.
Строительство базы
При строительстве базы за курсором мыши следует полупрозрачный силуэт здания, чтобы игроку было легче определиться с местом, куда он его может поставить. Здание нельзя сделать на неровных поверхностях или на воде. Все 3D модели являются собранием вершин (Vertices). Такая вершина – пересечение трех и более углов модели. Они считываются при помощи метода класса Mesh.
public bool IsGameObjectSafeToPlace(GameObject go) {
var verts = go.GetComponent<MeshFilter> ().mesh.vertices;
var obstacles = GameObject.FindObjectsOfType<NavMeshObstacle> ();
var cols = new List<Collider> ();
foreach (var o in obstacles) {
cols.Add (o.gameObject.GetComponent<Collider> ()); }
Проверяется каждая вершина. Так как они относятся к пространству объекта, то есть к модели объекта «база», с помощью метода TransfromPoint необходимо узнать их позицию в мире игры. Затем проверяются X и Z координаты и столкновение с физической оболочкой объекта.
foreach (var v in verts) {
NavMeshHit hit;
var vReal = go.transform.TransformPoint (v);
bool onXAxis = Mathf.Abs (hit.position.x - vReal.x) < 0.5f;
bool onZAxis = Mathf.Abs (hit.position.z - vReal.z) < 0.5f;
bool hitCollider = cols.Any (c => c.bounds.Contains (vReal));
if (!onXAxis || !onZAxis || hitCollider) {
return false; } }
Модель базы должна следовать за курсором мыши. Здесь используется функция ScreenPointToMapPosition, которая отслеживает его движение. Затем место передается в позицию объекта.
var tempTarget = RtsManager.Current.ScreenPointToMapPosition (Input.mousePosition);
transform.position = tempTarget.Value;
Отменить действие игрок может с помощью кнопки Escape. Если он ее нажимает, то объект удаляется.
if (Input.GetKeyDown (KeyCode.Escape)) GameObject.Destroy (active);
Игра должна визуально показывать, возможно ли строить здание или нет. Для этого используется структура Color, и задаются два цвета: красный и зеленый.
Color Red = new Color (1, 0, 0, 0.5f);
Color Green = new Color (0, 1, 0, 0.5f);
Если игрок захочет поставить базу далеко от юнита, то цвет модели станет красным.
if (Vector3.Distance (transform.position, Source.position) > MaxBuildDistance) {
rend.material.color = Red;
return; }
Зеленым она будет окрашена, если место, куда игрок захочет поставить здание, безопасное. Проверяется это при помощи функции, описанной ниже. Если же существуют препятствия, то здание станет красным.
Создание здания.
Если препятствий нет и игрок нажал на левую кнопку мыши, то базу можно поставить. При этом создается новый объект, которому присваивается позиция курсора мыши. У игрока вычитаются стоимость здания из количества его текущих ресурсов и изменяется информация, так как количество его баз увеличилось на одну. В конце удаляется полупрозрачная модель объекта, которая была визуальным ориентиром для игрока.
if (RtsManager.Current.IsGameObjectSafeToPlace (gameObject)) {
rend.material.color = Green;
if (Input.GetMouseButtonDown (0)) {
var go = GameObject.Instantiate (BuildingPrefab);
go.transform.position = transform.position;
Info.Credits -= Cost;
go.AddComponent<Player> ().Info = Info;
Destroy (this.gameObject);
}
} else { rend.material.color = Red; }
Имитация поведения противника
Искусственный интеллект является симуляцией поведения другого игрока, который существует в игре. Система может быть как небольшим набором правил, так и сложной симуляцией. Главной целью искусственного интеллекта это воспроизводить поведение, которое предложит игроку вызов, который он сможет превзойти.
В данном проекте имитация представляет собой другого игрока, который управляет юнитами на карте. Он должен противостоять живому игроку. Имитация выполнена с помощью метода Сортированного листа. В нем каждое действие оценивается на основе определенных условий, оценка происходит в каждый момент времени игры. Данный метод является серединой между Древом поведений и методом Конечных состояний. Несмотря на то, что оценка происходит у каждого состояния отдельно, решение о том, какое же действие будет происходить следующим, отделено и принимается на основе данных оценок.
Рассматривается два аспекта каждого действия: как оно оценивается и как оно исполняется.
Действия:
- Создание базы;
- Создание юнитов;
- Имитация боя;
- Создание базы;
- Оценка.
Для того, чтобы создать базу, компьютерному игроку надо знать есть ли у него юнит, чтобы это сделать, и хватает ли ему ресурсов. Также он должен понимать, нужна ли ему база вообще. Это вычисляется с помощью количества активных юнитов - если их достаточно много, то ее не стоит строить.
if (support == null)
support = AiSupport.GetSupport (gameObject);
if (support.Player.Credits < Cost || support.Drones.Count == 0)
return 0;
if (support.CommandBases.Count * UnitsPerBase <= support.Drones.Count)
return 1;
return 0;
Исполнение
Каждый юнит проверяется на возможность выполнения действия. Ему дается три попытки, чтобы это сделать. Количество попыток выражено в переменной TriesPerDrone. Затем используется метод insideUnitSphere. Он возвращает случайную точку в сфере с радиусом 1. Так как такое расстояние достаточно маленькое, радиус должен быть умножен на число, которое будет более подходящим для возможности строительства. Для позиции здания задается Y величина с помощью метода SampleHeight. Она возвращает высоту карты. И у игрока отнимаются ресурсы за постройку базы.
foreach (var drone in support.Drones)
{
for (int i = 0; i < TriesPerDrone; i++)
{
var pos = drone.transform.position;
pos += Random.insideUnitSphere * RangeFromDrone;
pos.y = Terrain.activeTerrain.SampleHeight (pos);
go.transform.position = pos;
if (RtsManager.Current.IsGameObjectSafeToPlace (go)) {
support.Player.Credits -= Cost;
return; } } }
Создание юнитов
Оценка 0 идет в двух случаях: если баз нет или юнитов больше, чем того требуется. Чтобы высчитать второе условие, вводится переменная DronesPerBase. Она означает оптимальное число юнитов на каждую базу. Переменная создается для баланса и может быть изменена во время тестирования. Если юнитов больше, чем базы могут себе того позволить, то их создавать больше не нужно. Оценка один, если препятствующих этому условий нет.
if (bases == 0) return 0;
if (drones >= bases * DronesPerBase) return 0;
return 1;
Имитация боя
Сначала проверяется время. Юниты не посылаются сразу, чтобы игрок успел подготовиться. Игра должна проверить, достаточно ли прошло времени от начала игры. Оно выражено переменной TimeDelay. Далее проходит проверка на количество юнитов, потому что посылать мало не слишком эффективно.
if (TimePassed < TimeDelay) return 0;
if (ai.Drones.Count < DronesRequired) return 0;
return 1;
Посылается отряд в определенном количестве юнитов. Оно выражено переменной SquadSize.
foreach (var p in RtsManager.Current.Players)
{ if (p.IsAi)
continue;
for (int i = 0; i < SquadSize; i++)
{ var drone = ai.Drones [i];
SendToTarget (p.Location.position); }
return; }
Осуществляется механизм поиска цели. Проверяется, есть ли она в игровом мире. Затем рассчитывается расстояние до каждого юнита противника. Если оно меньше, чем максимальная возможность юнита атаковать, то эту цель возможно достичь. Дистанция выражена в переменной AttackRange. При атаке передается компонент ShowUnitInfo, в котором содержится количество очков «здоровья» отдельно взятого юнита.
if (target != null) return;
foreach (var p in RtsManager.Current.Players)
{ if (p == player) continue;
foreach (var unit in p.ActiveUnits) {
if (Vector3.Distance (unit.transform.position, transform.position) < AttackRange) { target = unit.GetComponent<ShowUnitInfo>(); return; } } }
Затем идет уменьшение количества очков «здоровья» у вражеских юнитов. После проверки на наличие цели снова измеряется расстояние, потому что юнит отнимает очки непрерывно. Если расстояние между юнитом противника и юнитом «компьютера» станет больше возможного, то атака прекратится. Пока она продолжается, у юнита отнимаются очки «здоровья». Они выражены переменной CurrentHealth, а количество «урона», наносимого каждым юнитом, в AttackDamage.
if (target == null)
if (Vector3.Distance(target.transform.position, transform.position) > AttackRange) { target = null; return; }
target.CurrentHealth -= AttackDamage;
GameObject.Instantiate (ImpactVisual, target.transform.position, Quaternion.identity);
Ресурсы
Управление ресурсами означает механику сбора и управления ими с целью победы над противником. Во многих проектах местоположение, откуда игрок должен собирать ресурс является статическим, так как предполагается, что вокруг него должна быть построена база. Даже если точки с ценным элементом есть в других местах на карте, то игрок должен все равно долго брать оттуда ресурс, чаще всего строя там какое-либо здание.
В данном проекте объект с ресурсом появляется часто в случайно выбранном месте. Чтобы собрать ресурс, игрок должен лишь добраться до него, и это принесет ему совсем немного ценности. Чтобы постоянно обеспечивать себя ресурсами, надо постоянно двигаться по карту в поисках данных объектов.
В начале необходимо выбрать место, где объект может быть поставлен. Координаты будут содержаться в переменных Xpos и Zpos, где есть координаты X и Z соответственно. Высота объекта остается неизменной, чтобы объект всегда появлялся в том месте, где его могу бы собрать игрок. Затем с помощью двух функций, описанных раннее, проверяется, возможно ли поставить на данных координатах объект, потому что если он занят чем-то другим, то ресурс там собрать невозможно. В конце функция Instantiate воспроизведет объект.
while (check == false) {
Xpos = Random.Range(RightCorner, LeftCorner);
Zpos = Random.Range (UpCorner, DownCorner);
Vector2 pos = new Vector2 (Xpos, Zpos);
var tempTarget = RtsManager.Current.ScreenPointToMapPosition (pos);
if (tempTarget.HasValue) {
if (RtsManager.Current.IsGameObjectSafeToPlace (resource)) { check = true; } } }
Instantiate (resource, new Vector3 (Xpos, 9.7f, Zpos), transform.rotation);
Сбор ресурсов
Он осуществляется с помощью компонента Rigidbody. Это главный компонент, который позволяет физическое взаимодействие для игрового объекта. С подключенным Rigidbody на него сразу начнет действовать гравитация. Если есть компонент Collider, то объект также может перемещаться в следствии физических столкновений.
Rigidbody не может изменять положение с помощью переписывания позиции, то есть функции Transform, вместо этого стоит прилагать к объекту физические силы, которые будут «толкать» его и физический движок будет все это рассчитывать.
Чтобы на объект не воздействовали физические силы, но на него все равно можно было бы применять триггеры, будет применяться кинематическое движение. Это свойство, называемое Is Kinematic, которое убирает контроль физического движка над объектом и позволяет ему передвигаться с помощью кода.
Для того, чтобы юнит смог собрать ресурс, используется компонент класса Collider – Trigger. Триггер – это процедура, которая инициирует какое-либо действие, предусмотренное разработчиком. Используя функцию OnTriggerEnter, объект должен лишь коснуться своим компонентом Collider другого объекта, чтобы действие было инициировано. При этом один из них должен обладать свойством Trigger. В данном случае добавляется определенное количество ресурсов к уже имеющимся у игрока, а затем уничтожается объект, чтобы не занимать лишнее место на карте, так как он больше не требуется.
void OnTriggerEnter(Collider collider){
if (collider.gameObject.tag == "Resource") {
player.Credits += 20;
Destroy (collider.gameObject);
}
Тестирование
При тестировании игры в начальной стадии разработки важнее всего проверка функций, которые составляют базис, чтобы в дальнейшем над ними надстраивать остальные механики.
Так как карта – одна из самых важных частей интерфейса, а незнание действий противника – один из важных элементов жанра, поэтому они должны быть протестированы в первую очередь. Сначала надо посмотреть, отображаются ли на карте юниты противника, если юнит игрока находится достаточно далеко от них. На карте не отображаются юниты врага, потому что объект игрока стоит на своей начальной точке, откуда не может «увидеть» вражескую базу.
Рисунок 10 - Проверка видимости действий противника
Теперь игрок находится вблизи юнитов противника, поэтому на карте появились красные точки, так как они находятся в его «поле зрения». Также на карте отображаются и базы противника, ведь игрок подошел к его базе достаточно близко.
Рисунок 11 - Проверка видимости противника (2)
При нажатии на юнита должны заполняться две части интерфейса – информация о самом юните и список его действий. Но когда игрок нажимает левую кнопку мыши где-то в другом месте, оба элемента должны быть пустыми, чтобы ему не было неудобно управлять объектами. В начала игры все должно быть пустым, потому что игрок ничего не выбрал.
Рисунок 12 - Проверка нажатия на юнита
Рисунок 13 - Проверка нажатия на юнита (2)
Также игрок должен иметь возможность собирать ресурсы. Чтобы это сделать, он может найти сферу фиолетового цвета и подойти к ней вплотную, тогда количество его ресурсов увеличиться на двадцать единиц.
Рисунок 14 - Проверка сбора ресурсов
Рисунок 15 - Проверка сбора ресурсов (2)
Выводы
На основании проведенного анализа игрового движка Unity Engine и разработки на нем базиса стратегии в реальном времени можно сделать следующие выводы.
Unity является сложным и комплексным инструментом разработки, который имеет множество функций для создания видеоигр, но при этом является достаточно удобным для небольшой команды разработчиков.
При разработке базиса стратегии в реальном времени использовались основные функции движка: физика трехмерным объектов, навигация, двухмерный интерфейс игрока. Были созданы основные элементы жанра, такие как сбор ресурсов, создание юнитов и строительство. Тестирование показало, что элементы являются рабочими.
БИЗНЕС-МОДЕЛЬ
Обзор рынка видеоигр
Прибыль от сферы видеоигр в США в 2015 году составляла 23,5 миллиарда долларов, в Китае 22,2 миллиарда, в Великобритании 4,2 миллиарда. Доход от всех стран Европы равнялся 20,5 миллиардам. Сумма прибыли во всем остальном мире была примерно 20 миллиардов. Рынок видеоигр рос с конца 90-х годов, и так как они становятся доступнее все большему количеству людей, предсказывается, что рост будет продолжаться.
Рисунок 16 - Размер рынка видеоигр
Цифровые платформы
Понятие цифровая «платформа» включает цифровой сервис, где продаются электронные копии игр.
Steam
Это платформа цифровой дистрибуции, разработанная компанией Valve Corporation, которая предоставляет технические средства защиты авторских прав, возможности сетевой игры, потоковое вещание и сервис схожий с социальными сетями.
Steam предоставляет пользователям возможности установки и автоматического обновления игр, социальные функции, такие как листы друзей, группы, внутриигровой чат и так далее. Отдельная программа предоставляет прикладной интерфейс, с помощью которого разработчики могут интегрировать функции Steam в игру, например, возможность приглашений в сетевую игру, организация матчей, внутриигровые достижения, микротранзакции[15]. Существуют версии для Microsoft Windows, OS X и Linux. Позже были созданы и мобильные версии для iOS, Android и Windows Phone.
Steam считается самым большим сервисом для цифровой продажи видеоигр на персональных компьютерах. К концу 2015 года было зарегистрировано 125 миллионов пользователей, в среднем 12,5 из которых бывают онлайн в каждый момент времени.
Платформа имеет функцию Steam Greenlight. Она помогает любому пользователю загружать свою игру в сервис с последующей целью продажи. Это позволяет независимым разработчикам легче торговать и создавать сообщество вокруг продукта.
Рисунок 17 - Количество пользователей Steam в Европейском регионе
App Store
Это цифровая платформа дистрибуции, созданная компанией Apple Inc. для мобильных систем под управлением операционной системы iOS. Магазин помогает искать и загружать приложения на устройства iPhone, iPad, iPod Touch и Apple Watch.
Мобильная сфера видеоигр растет быстрее всего, поэтому выход на смартфоны и планшеты является целесообразным на сегодняшний день. Он требует значительных изменений не только с технической стороны, но и со стороны игровой механики, но мобильная техника развилась до той степени, что небольшие проекты часто переносятся без потери графической части.
Инструмент разработки, использующийся в проекте курсовой работы, совместим с мобильным-touch платформами - Apple выпустила специальный комплект средств для него. Более существенны будут изменения в механике игры. Все передвижение и взаимодействие сделаны для управления клавиатурой и мышью, но сам жанр игры может быть устроен с помощью touch-управления.
Рисунок 18 - Сравнение пользователей мобильных и станционарных платформ
Модель распространения игры
«Плати сколько хочешь» – это бизнес-модель, при которой механизм ценообразования полностью передан покупающему. Продавец лишь предлагает продукт, тогда как покупатель сам назначает цену. После того, как сделка состоялась, продавец принимает цену, которую заплатили, и не может отозвать товар. Следующие пункты определяют возможный успех при распространении с помощью данной модели:
Такая система повышает продажи, снижая или убирая входной барьер для покупателей
Ког