Система выбора юнитов [RTS]

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


Итак, добавляем новый Canvas и далее к нему:

Image для нашей рамки, убираем галочку Raycast Target и ставим пресет как на скриншоте:

Система выбора юнитов [RTS]

Button - шаблон для иконок, настраиваем размер, убираем с него текст и спрайт по умолчанию:


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

На все юниты, которые можно выбрать, вешаем скрипт MainUnit:

using UnityEngine;
using System.Collections;

public class MainUnit : MonoBehaviour {

	public int iconID = 1;
	public GameObject icon;
	
	void Start()
	{
		RTSSelect.unit.Add(gameObject);
	}

	void OnDestroy() // удаление иконки, если объект уничтожен
	{
		Destroy(icon);
	}
}

Здесь мы указываем id иконки, при этом имена иконок должны иметь такой вид: 1,2,3.. и т.п. Суть в том, что поиск будет производиться по имени, поэтому оно должно соответствовать номеру. И тут у нас еще храниться сама иконка icon, если объект выбран.

На Canvas вешаем скрипт RTSSelect:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.EventSystems;

public class RTSSelect : MonoBehaviour {

	public static List<GameObject> unit; // массив всех юнитов, которых мы можем выделить
	public static List<GameObject> unitSelected; // массив выделенных юнитов

	public Image mainImage; // рамка, которой будем выделять юниты
	public RectTransform iconButton; // шаблон кнопки
	public ScrollRect scrollRect; // бэкграунд
	public int offset = 5; // расстояние между иконками
	public Sprite[] icons; // все спрайты иконок
	public int maxIcons = 4; // сколько иконок будет по горизонтали в один ряд

	private bool draw, isLock;
	private Rect rect;
	private int line;
	private Vector2 startPos, endPos;
	private Color original, clear;

	void Awake()
	{
		unit = new List<GameObject>();
		unitSelected = new List<GameObject>();
		original = mainImage.color;
		clear = original;
		clear.a = 0;
		mainImage.color = clear;
		scrollRect.content.sizeDelta = new Vector2(0, 1);
		iconButton.anchoredPosition = new Vector2(0, iconButton.sizeDelta.y);
		isLock = false;
		SetEventTrigger();
	}

	void SetEventTrigger() // настройка функций UI триггера
	{
		EventTrigger et = scrollRect.GetComponent<EventTrigger>();
		
		var t = new EventTrigger.TriggerEvent();
		t.AddListener(delegate {{Lock();}});
		et.triggers.Add(new EventTrigger.Entry{callback = t, eventID = EventTriggerType.PointerEnter});
		
		t = new EventTrigger.TriggerEvent();
		t.AddListener(delegate {{UnLock();}});
		et.triggers.Add(new EventTrigger.Entry{callback = t, eventID = EventTriggerType.PointerExit});
	}

	void Lock()
	{
		isLock = true;
	}

	void UnLock()
	{
		isLock = false;
	}
	
	void BuildRectContent() // настройка размера окна с контентом по высоте
	{
		float height = (iconButton.sizeDelta.y + offset) * line;
		scrollRect.content.sizeDelta = new Vector2(0, height);
	}

	void BuildIcons() // создание иконок
	{
		BuildRectContent();
		float sizeX = iconButton.sizeDelta.x + offset;
		float sizeY = iconButton.sizeDelta.y + offset;
		float posX = -sizeX * maxIcons / 2 - sizeX / 2;
		float posY = sizeX / 2;
		float Xreset = posX;
		int i = 0;
		for(int y = 0; y < line; y++)
		{
			posY -= sizeY;
			for(int x = 0; x < maxIcons; x++)
			{
				posX += sizeX;
				RectTransform b = Instantiate(iconButton) as RectTransform;
				b.SetParent(scrollRect.content);
				b.localScale = Vector3.one;
				b.anchoredPosition = new Vector2(posX, posY);
				unitSelected[i].GetComponent<MainUnit>().icon = b.gameObject;
				string id = unitSelected[i].GetComponent<MainUnit>().iconID.ToString();
				foreach(Sprite img in icons)
				{
					if(img.name == id) b.GetComponent<Button>().image.sprite = img;
				}
				SetButton(b.GetComponent<Button>(), unitSelected[i]);

				i++;
				if(unitSelected.Count == i) return;
			}
			posX = Xreset;
		}
	}

	void SetButton(Button button, GameObject obj)
	{
		button.onclick.AddListener(() => {GetUnit(obj);});
	}

	void GetUnit(GameObject obj) // выделить только указанный юнит
	{
		Deselect();
		unitSelected = new List<GameObject>();
		unitSelected.Add(obj);
		DestroyIcons();
		Select();
	}

	void DestroyIcons()
	{
		for(int j = 0; j < scrollRect.content.childCount; j++)
		{
			Destroy(scrollRect.content.GetChild(j).gameObject);
		}
	}

	void Select()
	{
		if(unitSelected.Count > 0)
		{
			for(int j = 0; j < unitSelected.Count; j++)
			{
				// делаем что-либо с выделенными объектами
				unitSelected[j].GetComponent<MeshRenderer>().material.color = Color.red;
			}

			line = Mathf.CeilToInt((float)unitSelected.Count / (float)maxIcons);
			BuildIcons();
		}
	}

	void Deselect()
	{
		for(int j = 0; j < unitSelected.Count; j++)
		{
			// отменяем то, что делали с объектоми
			if(unitSelected[j]) unitSelected[j].GetComponent<MeshRenderer>().material.color = Color.white;
		}
	}
	
	void CheckRect()
	{
		for(int j = 0; j < unit.Count; j++)
		{
			// трансформируем позицию объекта из мирового пространства, в пространство экрана
			if(unit[j])
			{
				Vector2 objPos = new Vector2(Camera.main.WorldToScreenPoint(unit[j].transform.position).x, 
				                             Screen.height - Camera.main.WorldToScreenPoint(unit[j].transform.position).y);
				
				if(rect.Contains(objPos)) // проверка, находится-ли текущий объект в рамке
				{
					unitSelected.Add(unit[j]);
				}
			}
		}
	}
	
	void Update()
	{
		if(Input.GetMouseButtonDown(0) && !isLock)
		{
			Deselect();
			DestroyIcons();
			unitSelected = new List<GameObject>();
			startPos = Input.mousePosition;
			mainImage.color = clear;
			rect = new Rect();
			scrollRect.content.sizeDelta = new Vector2(0, 1);
			draw = true;
		}
		
		if(Input.GetMouseButtonUp(0) && draw)
		{
			draw = false;
			CheckRect();
			Select();
		}
		
		if(draw)
		{
			endPos = Input.mousePosition;
			if(startPos == endPos) return;

			mainImage.color = Color.Lerp(mainImage.color, original, 10 * Time.deltaTime);

			rect = new Rect(Mathf.Min(endPos.x, startPos.x),
			                Screen.height - Mathf.Max(endPos.y, startPos.y),
			                Mathf.Max(endPos.x, startPos.x) - Mathf.Min(endPos.x, startPos.x),
			                Mathf.Max(endPos.y, startPos.y) - Mathf.Min(endPos.y, startPos.y)
			                );

			mainImage.rectTransform.sizeDelta = new Vector2(rect.width, rect.height);

			mainImage.rectTransform.anchoredPosition = new Vector2(rect.x + mainImage.rectTransform.sizeDelta.x / 2, 
			                                                       Mathf.Max(endPos.y, startPos.y) - mainImage.rectTransform.sizeDelta.y / 2);
		}
		else
		{
			mainImage.color = Color.Lerp(mainImage.color, clear, 10 * Time.deltaTime);
		}
	}
}

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

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

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

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

Офлайн
Keltis 5 июля 2017
Здравствуйте
В прикреплённом проекте файл с классом RTSSelect назван SelectObjects, из-за чего Unity выдаёт ошибку и не может найти файл. Исправьте, пожалуйста.
Спасибо за ваш труд, уроки замечательные.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика