Дипломная (вкр): Создание механики стратегии в реальном времени

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

if (selected && Input.GetMouseButtonDown (1)) {tempTarget = RtsManager.Current.ScreenPointToMapPosition(Input.mousePosition);(tempTarget.HasValue) {(); } }(isActive && Vector3.Distance (target, transform.position) < RelaxDistance) {.Stop ();= false; } }

Передвигается юнит при помощи класса NavMesh. Он отвечает за систему навигации и позволяет создавать объекты, которые могут передвигаться по миру. Он состоит из следующих компонентов:(Navigation Mesh). Это структура данных, которая описывает поверхности в игровом мире, по которым можно передвигаться, и позволяет найти путь из одного места в другое. Она зависит от геометрии уровня.Agent. Данный компонент помогает создавать объекты, которые будут обходить друг друга, если окажутся на пути у друг друга, а также другие препятствия, которые предусмотрены NavMesh.Mesh Link. Компонент позволяет включить в путь точки навигации, которые могут быть не представлены на поверхности.Obstacle. Данная часть структуры данных позволяет описать двигающиеся препятствия, которые объект должен обходить при продвижении по миру игры. Пока препятствие движется, объекты пытаются обойти его, но как только оно остановилось, NavMesh автоматически ограничивает пространство, или если оно заграждает путь, то объект ищет другой маршрут.

Системе навигации нужна информация, чтобы представлять подобное пространство в игре. Оно определяет место, где объект может двигаться и стоять. Затем такие местоположения соединяются в поверхность, которая лежит поверх геометрии всей сцены. Это поверхность называется навигационной сеткой, то есть Navigation Mesh.сохраняет эту поверхность в качестве выпуклых многоугольников. Они являются полезным представлением поверхности, так как между двумя любыми точками многоугольника нет никаких препятствий. В дополнение к границам многоугольников сохраняется информация о том, какие из них являются друг другу соседями. Это позволяет понимать, какая область проходима.

Чтобы найти путь между двумя объектами на сцене, сначала сопоставляются начальный и конечный пункт с ближайшими многоугольниками. Затем начинается поиск с первой точки, с помощью посещений всех нужных полигонов, пока объект не доберется до конечной цели. Отслеживание посещенных полигонов осуществляет алгоритм A*.

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

Логика двигательного управления занимает позицию следующего угла и на основе этого вычисляет желаемое направление и скорость, которые нужны для того, чтобы достичь конечного пункта. Использование предполагаемой скорости может привести к столкновению с другими объектами. Тогда компонент, отвечающий за обход препятствий, выбирает новую скорость, которая должна держать баланс между желаемым направлением и обходом будущих столкновений с другими объектами. Тут используется вычисление столкновений с помощью RVO, то есть reciprocal velocity obstacles. Такой подход берет во внимание реактивное поведение всех объектов, подразумевая, что они делают аналогичные рассуждения об избежании конфликтов.

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

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

Просчитываются все препятствия с помощью компонента NavMesh Obstacle. Он позволяет задать в игре все помехи, которые объект должен учитывать при перемещении по миру, также помогает при построении маршрута. В данном проекте к препятствиям относятся перепады высот.

Строительство базы

При строительстве базы за курсором мыши следует полупрозрачный силуэт здания, чтобы игроку было легче определиться с местом, куда он его может поставить. Здание нельзя сделать на неровных поверхностях или на воде. Все 3D модели являются собранием вершин (Vertices). Такая вершина - пересечение трех и более углов модели. Они считываются при помощи метода класса Mesh.

public bool IsGameObjectSafeToPlace(GameObject go) {verts = go.GetComponent<MeshFilter> ().mesh.vertices;obstacles = GameObject.FindObjectsOfType<NavMeshObstacle> ();cols = new List<Collider> ();(var o in obstacles) {.Add (o.gameObject.GetComponent<Collider> ()); }

Проверяется каждая вершина. Так как они относятся к пространству объекта, то есть к модели объекта «база», с помощью метода TransfromPoint необходимо узнать их позицию в мире игры. Затем проверяются X и Z координаты и столкновение с физической оболочкой объекта.

foreach (var v in verts) {hit;vReal = go.transform.TransformPoint (v);onXAxis = Mathf.Abs (hit.position.x - vReal.x) < 0.5f;onZAxis = Mathf.Abs (hit.position.z - vReal.z) < 0.5f;hitCollider = cols.Any (c => c.bounds.Contains (vReal));


Модель базы должна следовать за курсором мыши. Здесь используется функция ScreenPointToMapPosition, которая отслеживает его движение. Затем место передается в позицию объекта.

var tempTarget = RtsManager.Current.ScreenPointToMapPosition (Input.mousePosition);.position = tempTarget.Value;

Отменить действие игрок может с помощью кнопки Escape. Если он ее нажимает, то объект удаляется.

if (Input.GetKeyDown (KeyCode.Escape)) GameObject.Destroy (active);

Игра должна визуально показывать, возможно ли строить здание или нет. Для этого используется структура Color, и задаются два цвета: красный и зеленый.

Color Red = new Color (1, 0, 0, 0.5f);Green = new Color (0, 1, 0, 0.5f);

Если игрок захочет поставить базу далеко от юнита, то цвет модели станет красным.

if (Vector3.Distance (transform.position, Source.position) > MaxBuildDistance) {.material.color = Red;; }

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

Создание здания.

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

if (RtsManager.Current.IsGameObjectSafeToPlace (gameObject)) {.material.color = Green;(Input.GetMouseButtonDown (0)) {go = GameObject.Instantiate (BuildingPrefab);.transform.position = transform.position;.Credits -= Cost;.AddComponent<Player> ().Info = Info;(this.gameObject);

}

} else {rend.material.color = Red; }

Имитация поведения противника

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

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

Рассматривается два аспекта каждого действия: как оно оценивается и как оно исполняется.

Действия:

- Создание базы;

- Создание юнитов;

- Имитация боя;

Создание базы;

Оценка.

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

if (support == null)= AiSupport.GetSupport (gameObject);(support.Player.Credits < Cost || support.Drones.Count == 0)0;(support.CommandBases.Count * UnitsPerBase <= support.Drones.Count)

return 1;0;

Исполнение

Каждый юнит проверяется на возможность выполнения действия. Ему дается три попытки, чтобы это сделать. Количество попыток выражено в переменной TriesPerDrone. Затем используется метод insideUnitSphere. Он возвращает случайную точку в сфере с радиусом 1. Так как такое расстояние достаточно маленькое, радиус должен быть умножен на число, которое будет более подходящим для возможности строительства. Для позиции здания задается Y величина с помощью метода SampleHeight. Она возвращает высоту карты. И у игрока отнимаются ресурсы за постройку базы.

foreach (var drone in support.Drones)

{(int i = 0; i < TriesPerDrone; i++)

{pos = drone.transform.position;+= Random.insideUnitSphere * RangeFromDrone;.y = Terrain.activeTerrain.SampleHeight (pos);.transform.position = pos;(RtsManager.Current.IsGameObjectSafeToPlace (go)) {.Player.Credits -= Cost;; } } }

Создание юнитов

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

if (bases == 0) return 0;

if (drones >= bases * DronesPerBase) return 0; 1;

Имитация боя

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

if (TimePassed < TimeDelay) return 0;(ai.Drones.Count < DronesRequired) return 0; 1;

Посылается отряд в определенном количестве юнитов. Оно выражено переменной SquadSize.

foreach (var p in RtsManager.Current.Players)

{if (p.IsAi);(int i = 0; i < SquadSize; i++)

{ var drone = ai.Drones [i];(p.Location.position); }; }

Осуществляется механизм поиска цели. Проверяется, есть ли она в игровом мире. Затем рассчитывается расстояние до каждого юнита противника. Если оно меньше, чем максимальная возможность юнита атаковать, то эту цель возможно достичь. Дистанция выражена в переменной AttackRange. При атаке передается компонент ShowUnitInfo, в котором содержится количество очков «здоровья» отдельно взятого юнита.

if (target != null) return;(var p in RtsManager.Current.Players)

{ if (p == player) continue;(var unit in p.ActiveUnits) {(Vector3.Distance (unit.transform.position, transform.position) < AttackRange) { target = unit.GetComponent<ShowUnitInfo>(); return; } } }

Затем идет уменьшение количества очков «здоровья» у вражеских юнитов. После проверки на наличие цели снова измеряется расстояние, потому что юнит отнимает очки непрерывно. Если расстояние между юнитом противника и юнитом «компьютера» станет больше возможного, то атака прекратится. Пока она продолжается, у юнита отнимаются очки «здоровья». Они выражены переменной CurrentHealth, а количество «урона», наносимого каждым юнитом, в AttackDamage.

if (target == null)(Vector3.Distance(target.transform.position, transform.position) > AttackRange) { target = null; return; }.CurrentHealth -= AttackDamage;.Instantiate (ImpactVisual, target.transform.position, Quaternion.identity);

Ресурсы

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

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

В начале необходимо выбрать место, где объект может быть поставлен. Координаты будут содержаться в переменных Xpos и Zpos, где есть координаты X и Z соответственно. Высота объекта остается неизменной, чтобы объект всегда появлялся в том месте, где его могу бы собрать игрок. Затем с помощью двух функций, описанных раннее, проверяется, возможно ли поставить на данных координатах объект, потому что если он занят чем-то другим, то ресурс там собрать невозможно. В конце функция Instantiate воспроизведет объект.

while (check == false) {= Random.Range(RightCorner, LeftCorner);= Random.Range (UpCorner, DownCorner);pos = new Vector2 (Xpos, Zpos);tempTarget = RtsManager.Current.ScreenPointToMapPosition (pos);(tempTarget.HasValue) {(RtsManager.Current.IsGameObjectSafeToPlace (resource)) {check = true; } } }(resource, new Vector3 (Xpos, 9.7f, Zpos), transform.rotation);

Сбор ресурсов

Он осуществляется с помощью компонента Rigidbody. Это главный компонент, который позволяет физическое взаимодействие для игрового объекта. С подключенным Rigidbody на него сразу начнет действовать гравитация. Если есть компонент Collider, то объект также может перемещаться в следствии физических столкновений.не может изменять положение с помощью переписывания позиции, то есть функции Transform, вместо этого стоит прилагать к объекту физические силы, которые будут «толкать» его и физический движок будет все это рассчитывать.

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

Для того, чтобы юнит смог собрать ресурс, используется компонент класса Collider - Trigger. Триггер - это процедура, которая инициирует какое-либо действие, предусмотренное разработчиком. Используя функцию OnTriggerEnter, объект должен лишь коснуться своим компонентом Collider другого объекта, чтобы действие было инициировано. При этом один из них должен обладать свойством Trigger. В данном случае добавляется определенное количество ресурсов к уже имеющимся у игрока, а затем уничтожается объект, чтобы не занимать лишнее место на карте, так как он больше не требуется.

void OnTriggerEnter(Collider collider){(collider.gameObject.tag == "Resource") {.Credits += 20;(collider.gameObject);

}

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


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

Так как карта - одна из самых важных частей интерфейса, а незнание действий противника - один из важных элементов жанра, поэтому они должны быть протестированы в первую очередь. Сначала надо посмотреть, отображаются ли на карте юниты противника, если юнит игрока находится достаточно далеко от них. На карте не отображаются юниты врага, потому что объект игрока стоит на своей начальной точке, откуда не может «увидеть» вражескую базу.

Рисунок 10 - Проверка видимости действий противника

Теперь игрок находится вблизи юнитов противника, поэтому на карте появились красные точки, так как они находятся в его «поле зрения». Также на карте отображаются и базы противника, ведь игрок подошел к его базе достаточно близко.

Рисунок 11 - Проверка видимости противника (2)

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