Автоприцел / выбор цели

В некоторых играх от третьего лица, иногда используется система таргетов. То есть, когда персонаж, после нажатия клавиши «прицеливания», автоматически выбирает ближайшую цель, разворачивается к ней и далее, смотрит только на нее. Игроку остается лишь открыть огонь – «press Х to win», как бы. Такая система хорошо подходит к приключению или ужастику, наподобие ранних серий «Resident Evil», в общем, где камера не следует за игроком или не располагает к обычному типу стрельбы. Попробуем разобраться как сделать нечто подобное.


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

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

Цепляем куда-нибудь, например на персонажа, скрипт TargetSystem:

using UnityEngine;
using System.Collections;

public class TargetSystem : MonoBehaviour {
	
	public Transform player;
	public float range = 15;
	public int enemyLayer = 8;

	public Texture2D aim;
	public float aimSize = 50;

	private GameObject currentTarget;
	private Collider[] colls  = new Collider[0];

	void RandomTarget()
	{
		GameObject[] tmp = new GameObject[0];

		int x = 0;
		foreach (Collider element in colls) 
		{
			if (currentTarget != element.gameObject) x++;
		}
		GameObject[] pureArray = new GameObject[x];
		x = 0;
		foreach (Collider element in colls)
		{
			if (currentTarget != element.gameObject) 
			{
				pureArray[x] = element.gameObject;
				x++;
			}
		}
		tmp = new GameObject[pureArray.Length];
		for(int i = 0; i < tmp.Length; i++)
		{
			tmp[i] = pureArray[i];
		}

		currentTarget = tmp[Random.Range(0, tmp.Length)];
	}

	void PlayerRotate()
	{
		if(currentTarget)
		{
			Vector3 lookPos = currentTarget.transform.position - player.position;
			lookPos.y = 0;
			Quaternion rotation = Quaternion.LookRotation(lookPos);
			player.rotation = Quaternion.Lerp(player.rotation, rotation, 10 * Time.deltaTime);
		}
	}

	void OnGUI()
	{
		if(currentTarget)
		{
			Vector2 tmp = new Vector2(Camera.main.WorldToScreenPoint(currentTarget.transform.position).x, 
			                          Screen.height - Camera.main.WorldToScreenPoint(currentTarget.transform.position).y);

			Vector2 offset = new Vector2(-aimSize/2, -aimSize/2);
			GUI.DrawTexture(new Rect(tmp.x + offset.x, tmp.y + offset.y, aimSize, aimSize), aim);
		}
	}

	void Update()
	{
		if(Input.GetMouseButton(1))
		{
			GetTarget();

			if(colls.Length > 1)
			{
				if(Input.GetKeyDown(KeyCode.LeftShift)) // выбор другой ближайшей цели, исключая текущую - левый шифт
				{
					NearTarget();
				}
				else if(Input.GetKeyDown(KeyCode.LeftControl)) // выбор случайной цели, исключая текущую - левый контрол
				{
					RandomTarget();
				}
			}

			if(colls.Length == 0)
			{
				currentTarget = null;
			}
			else
			{
				float curDist = Vector3.Distance(player.position, currentTarget.transform.position);
				if(curDist > range) currentTarget = null;
			}
		}
		else
		{
			currentTarget = null;
		}

		PlayerRotate();
	}

	void NearTarget()
	{
		if(colls.Length > 0)
		{
			Collider currentCollider = null;
			float dist = Mathf.Infinity;
			
			foreach(Collider coll in colls)
			{
				float currentDist = Vector3.Distance(player.position, coll.transform.position);

				if(currentTarget)
				{
					if(currentDist < dist && currentTarget != coll.gameObject)
					{
						currentCollider = coll;
						dist = currentDist;
					}
				}
				else
				{
					if(currentDist < dist)
					{
						currentCollider = coll;
						dist = currentDist;
					}
				}
			}
			
			currentTarget = currentCollider.gameObject;
		}
	}

	void GetTarget()
	{
		colls = new Collider[0];
		colls = Physics.OverlapSphere(player.position, range, 1 << enemyLayer);

		if(currentTarget) return;

		NearTarget();
	}
}

player - трансформ персонажа, учитывайте что он будет вращаться по оси Y.

range - здесь мы указываем значение, которые получили ранее в радиусе коллайдера сферы.

enemyLayer - слой, который мы присваиваем всем врагам. А номер берем из самого списка слоев, например:

Автоприцел / выбор цели

Тут у нас номер = 8, значит его и указываем в скрипте.

aim - иконка прицела.

aimSize - размеры иконки.

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

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

Офлайн
Light 3 января 2019
maribara, надо с начала рейкастом сделать выборку подходящих объектов, а потом среди них найти ближайший. Если нужен такой скрипт, пиши мне в ЛС.
Офлайн
maribara 3 января 2019
Помогите пожалуйста, как сделать проверку если на пути между игроком и целью есть стена, то не брать её в цель. Не могу понять где именно делать эту проверку((
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика