Простая автоматическая турель

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

Итак, турель состоит из нескольких частей. Башня, которая вращается на некой опоре, и собственно пушки. Модели могут быть разными, мы рассмотрим примитивный образец, вот такого вида:


Очень простая модель, с двумя пушками.

Для каждой пушки нам нужно создать дочерний пустой объект:


Это точки, откуда будут вылетать снаряды.

В центре башни также понадобится пустой дочерний объект:


Это вспомогательный объект, из которого будет вылетать поисковый луч, обратите внимание, что данный объект необходимо установить по центру между пушками, используя оси Х и Y, а по Z ставим на ноль, чтобы длина луча соответствовала настройкам скрипта.

Теперь, для варианта с снарядом, как он должен выглядеть:

Простая автоматическая турель

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

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]

public class TurretBullet : MonoBehaviour {

	[SerializeField] private float damage = 10;
	[SerializeField] private float bulletSpeed = 5;
	private LayerMask layer;

	public void SetBullet(LayerMask layerMask, Vector3 direction)
	{
		layer = layerMask;
		Rigidbody body = GetComponent<Rigidbody>();
		body.useGravity = false;
		body.velocity = direction * bulletSpeed;
		transform.forward = direction;
	}
	
	void OnTriggerEnter(Collider other)
	{
		if(!other.isTrigger)
		{
			if(((1 << other.gameObject.layer) & layer) != 0)
			{
				other.GetComponent<UnitHP>().Adjust(-damage);
			}

			Destroy(gameObject);
		}
	}
}

Очень простой класс, где мы можем регулировать скорость снаряда и наносимый урон.

Класс которому мы передаем урон, может быть, например, таким:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UnitHP : MonoBehaviour {

	[SerializeField] private float health = 100;

	public float currentHealth
	{
		get{ return health; }
	}

	public void Adjust(float value)
	{
		health += value;

		if(health <= 0)
		{
			Destroy(gameObject);
		}
	}
}

Тут вообще сложностей нет никаких.

Далее, основной скрипт, который цепляем на турель:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(SphereCollider))]

public class Turret : MonoBehaviour {

	[SerializeField] private TurretBullet bulletPrefab; // префаб снаряда, если его нет, то будет выбран режим стрельбы "лучом"
	[SerializeField] private float fireRate = 1; // скорострельность
	[SerializeField] private float smooth = 1; // сглаживание движения башни
	[SerializeField] private float rayOffset = 1; // делаем поисковый луч, немного больше области поиска
	[SerializeField] private float damage = 10; // повреждение (при стрельбе "лучом")
	[SerializeField] private Transform[] bulletPoint; // точки, откуда ведется стрельба
	[SerializeField] private Transform turretRotation; // объект вращения, башня турели
	[SerializeField] private Transform center; // центр между пушками, для поискового луча
	[SerializeField] private LayerMask layerMask; // фильтр коллайдеров по маске слоя
	[Header("Лимиты по осям башни:")]
	[SerializeField] private bool useLimits;
	[SerializeField] [Range(0, 180)] private float limitY = 50;
	[SerializeField] [Range(0, 180)] private float limitX = 30;
	private SphereCollider turretTrigger;
	private Transform target;
	private Vector3 offset;
	private int index;
	private float curFireRate;
	private Quaternion defaultRot = Quaternion.identity;

	void Awake()
	{
		turretTrigger = GetComponent<SphereCollider>();
		turretTrigger.isTrigger = true;
		offset = turretTrigger.center;
		curFireRate = fireRate;
		turretTrigger.enabled = true;
		enabled = false;
	}

	void OnTriggerEnter(Collider other)
	{
		if(CheckLayerMask(other.gameObject, layerMask))
		{
			target = other.transform;
			turretTrigger.enabled = false;
			enabled = true;
		}
	}

	Transform FindTarget() // возвращает ближайшую цель
	{
		Collider[] colliders = Physics.OverlapSphere(transform.position + offset, turretTrigger.radius, layerMask);

		Collider currentCollider = null;
		float dist = Mathf.Infinity;

		foreach(Collider coll in colliders)
		{
			float currentDist = Vector3.Distance(transform.position + offset, coll.transform.position);

			if(currentDist < dist)
			{
				currentCollider = coll;
				dist = currentDist;
			}
		}

		return (currentCollider != null) ? currentCollider.transform : null;
	}

	Vector3 CalculateNegativeValues(Vector3 eulerAngles)
	{
		eulerAngles.y = (eulerAngles.y > 180) ? eulerAngles.y - 360 : eulerAngles.y;
		eulerAngles.x = (eulerAngles.x > 180) ? eulerAngles.x - 360 : eulerAngles.x;
		eulerAngles.z = (eulerAngles.z > 180) ? eulerAngles.z - 360 : eulerAngles.z;
		return eulerAngles;
	}

	bool Search() // разворот башни на цель
	{
		if(rayOffset < 0) rayOffset = 0;
		float dist = Vector3.Distance(transform.position + offset, target.position);
		Vector3 lookPos = target.position - turretRotation.position;
		Debug.DrawRay(turretRotation.position, center.forward * (turretTrigger.radius + rayOffset));
		Vector3 rotation = Quaternion.Lerp(turretRotation.rotation, Quaternion.LookRotation(lookPos), smooth * Time.deltaTime).eulerAngles;

		if(useLimits)
		{
			rotation = CalculateNegativeValues(rotation);
			rotation.y = Mathf.Clamp(rotation.y, -limitY, limitY);
			rotation.x = Mathf.Clamp(rotation.x, -limitX, limitX);
		}

		rotation.z = 0;
		turretRotation.eulerAngles = rotation;

		if(dist > turretTrigger.radius + rayOffset)
		{
			target = null;
			return false;
		}

		if(IsRaycastHit(center)) return true;

		return false;
	}

	bool CheckLayerMask(GameObject obj, LayerMask layers)
	{
		if(((1 << obj.layer) & layers) != 0)
		{
			return true;
		}

		return false;
	}

	bool IsRaycastHit(Transform point)
	{
		RaycastHit hit;
		Ray ray = new Ray(point.position, point.forward);
		if(Physics.Raycast(ray, out hit, turretTrigger.radius + rayOffset))
		{
			if(CheckLayerMask(hit.transform.gameObject, layerMask))
			{
				return true;
			}
		}

		return false;
	}

	void Shot()
	{
		if(!Search()) return;

		curFireRate += Time.deltaTime;
		if(curFireRate > fireRate)
		{
			Transform point = GetPoint();
			curFireRate = 0;

			if(bulletPrefab != null)
			{
				TurretBullet bullet = Instantiate(bulletPrefab, point.position, Quaternion.identity) as TurretBullet;
				bullet.SetBullet(layerMask, point.forward);
			}
			else if(IsRaycastHit(point))
			{
				target.GetComponent<UnitHP>().Adjust(-damage);
			}
		}
	}

	void Choice()
	{
		curFireRate = fireRate;

		target = FindTarget();

		turretRotation.rotation = Quaternion.Lerp(turretRotation.rotation, defaultRot, smooth * Time.deltaTime);

		if(Quaternion.Angle(turretRotation.rotation, defaultRot) == 0)
		{
			turretRotation.rotation = defaultRot;
			turretTrigger.enabled = true;
			enabled = false;
		}
	}

	Transform GetPoint()
	{
		if(index == bulletPoint.Length-1) index = 0; else index++;
		return bulletPoint[index];
	}

	void LateUpdate()
	{
		if(target != null) Shot(); else Choice();
	}
}

Скрипт автоматом добавит Sphere Collider, настраиваем радиус сферы, это будет область поиска целей. Если в игре используются объекты/юниты без компонента Rigidbody, то его нужно добавить на саму турель и поставить галочку Is Kinematic, чтобы срабатывал триггер турели.

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

У вас нет доступа!
Тестировалось на: Unity 5.5.0

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

Офлайн
Andrejus 5 января 2017
Как всегда на высшем уровне.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика