Организация построения юнитов в RTS игре

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


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

На юнитов цепляем скрипт UnitNav, либо измените существующий в соответствии:

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(NavMeshAgent))]

public class UnitNav : MonoBehaviour {

	public Vector3 point;
	public float stopDistance = 10;
	private NavMeshAgent nav;

	void Start () 
	{
		nav = GetComponent<NavMeshAgent>();
		point = transform.position;
	}

	void Update () 
	{
		nav.SetDestination(point);

		if(!nav.hasPath)
		{
			if(FillEntry.unitLook) transform.rotation = Quaternion.Lerp(transform.rotation, FillEntry.unitRot, 3 * Time.deltaTime);
		}
		else
		{
			if(nav.remainingDistance < stopDistance && !FillEntry.unitLook)
			{
				float speed = nav.speed / (nav.speed - 0.1f);
				if(nav.velocity.magnitude < speed) point = transform.position;
			}
		}
	}
}

Скрипт отвечает за позицию объекта и вращение, когда идет построение в отряд.

Теперь добавим пустой объект на сцену и вешаем еще один скрипт FillEntry:

using UnityEngine;
using System.Collections;

public class FillEntry : MonoBehaviour {

	public GameObject[] units;
	public int lineCount = 5;
	public float shift = 5;
	public float activeDistance = 1.5f;
	public Transform arrow;
	public SpriteRenderer arrowSprite;

	private GameObject[,] field;
	private int line_y;
	private Vector2 curHit;
	private float rotAngle;
	private GameObject grid;
	private Vector3 HIT;
	private Vector3 curHIT;

	public static bool unitLook;
	public static Quaternion unitRot;

	void Start () 
	{
		arrowSprite.enabled = false;
		unitLook = false;
	}

	void UnitDestination()
	{
		int id = 0;
		for(int y = 0; y < line_y; y++)
		{
			for(int x = 0; x < lineCount; x++)
			{
				if(id < units.Length)
				{
					units[id].GetComponent<UnitNav>().point = field[x,y].transform.position;
					id++;
				}
			}
		}
	}

	void RotUpdate(Transform target)
	{
		Vector3 curScreenPoint = new Vector3(Input.mousePosition.x, Input.mousePosition.y, Camera.main.transform.position.y);
		Vector3 curTarget = Camera.main.ScreenToWorldPoint(curScreenPoint);
		Vector3 lookPos = curTarget - target.position;
		lookPos.y = 0;
		Quaternion rotation = Quaternion.LookRotation(lookPos);
		target.rotation = rotation;
		unitRot = rotation;
	}

	void ArrowUpdate()
	{
		if(Input.GetMouseButton(1))
		{
			float dis = Vector2.Distance(HIT, curHIT);

			if(dis > activeDistance)
			{
				if(!grid)
				{
					grid = new GameObject();
					grid.transform.position = HIT;
					arrow.transform.position = HIT;
					SetGrid();
				}

				arrowSprite.enabled = true;
			}

			if(grid)
			{
				RotUpdate(arrow);
				
				RotUpdate(grid.transform);
			}
		}
		else
		{
			arrowSprite.enabled = false;
		}
	}

	void Update () 
	{
		RaycastHit hit;
		Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
		if (Physics.Raycast(ray, out hit))
		{
			curHIT = hit.point;

			if(Input.GetMouseButtonDown(1))
			{
				Destroy(grid);
				unitLook = false;
				float a = units.Length;
				float b = lineCount;
				float tmp = a / b;
				line_y = Mathf.CeilToInt(tmp);
				field = new GameObject[lineCount, line_y];
				HIT = hit.point;
			}
			else if(Input.GetMouseButtonUp(1))
			{
				if(grid)
				{
					unitLook = true;
					UnitDestination();
				}
				else
				{
					foreach(GameObject u in units)
					{
						u.GetComponent<UnitNav>().point = hit.point;
					}
				}
			}
		}

		ArrowUpdate();
	}

	void SetGrid()
	{
		float posX = HIT.x - shift * ((float)lineCount / 2);
		float posZ = HIT.z + shift * ((float)line_y / 2);
		float Xreset = posX;
		for(int y = 0; y < line_y; y++)
		{
			posZ -= shift;
			for(int x = 0; x < lineCount; x++)
			{
				posX += shift;
				field[x,y] = new GameObject();
				field[x,y].transform.position = new Vector3(posX, 0, posZ);
				field[x,y].transform.parent = grid.transform;
			}
			posX = Xreset;
		}
	}
}

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

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

shift - сдвиг между позициями в отряде.

activeDistance - расстояние между двумя точками. Первая - после нажатия ПКМ. Вторая - текущая позиция курсора, во время удерживания ПКМ. Иначе говоря, если игрок зажимает ПКМ, ему надо передвинуть курсор, после чего появится указатель. Если игрок просто кликает ПКМ то, выполняется команда следования в указанную точке, без построения.

arrow - трансформ спрайта стрелки.

arrowSprite - здесь указываем Sprite Renderer объекта.

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

Организация построения юнитов в RTS игре

То есть, картинка должна лежать на плоскости ХZ, а указатель развернут стрелкой по оси Z. Делается это просто, создаем пустой объект, закидываем в него спрайт и разворачиваем относительно осям родителя - всё.

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

Скачать проект:

Внимание! Посетители, находящиеся в группе Гости, не могут скачивать файлы.

Комментариев 4

Офлайн
Light 17 сентября 2016
Ленин-Гриб, это уже отдельная тема, если будет возможность, то опубликуем.
Офлайн
Доброго времени. А как можно реализовать систему отрядов и построений? (как в тотал вар или рыцарях чести).
Офлайн
Light 1 сентября 2016
Иван Русский, указывать в виде цели соответствующий объект.
Офлайн
все круто но как сделать так что бы они охотились не на стрелочку а на персонажа
?
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика