Спреи / следы от пуль и т.д. для 2D

Пользовательские спреи для двухмерных проектов, подойдут также и для создания следов от пуль, пятен крови и тому подобное. Данный пример, подойдет 2D платформерам и похожим проектам. Использование спреев базируется на специальном пуле объектов, который адаптирован под двухмерные игры. При вызове объекта пула, можно установить базовые значения, такие как: позиция, вращение по Z и масштаб. Можно создать несколько пулов и дополнительно вызвать Update объектов пула из управляющего скрипта, то есть, апдейт в каждом отельном объекте, делается перебором массива, что менее затратно, нежели вызывать апдейт по отдельности.

В проекте создаем папку Resources если таковой нет, а в ней еще одну папку BulletSpray2D. В ней у будут лежать префабы спрайтов, например, "следы от пуль". Смысл создания отдельной папки в том, что вариантов спрайтов может быть несколько. Если говорить точнее, допустим, мы создаем три варианта "следов от пуль" и на их основе будет создан пул объектов, нужного объема.

Так, на префабе спрея, должен быть скрипт:

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

public class Spray2DPoolComponent : MonoBehaviour {

	[SerializeField] private SpriteRenderer spriteRenderer;
	private float timeout;
	private Color color, colorOri;

	public void Initialize()
	{
		timeout = Spray2D.Timeout;
		colorOri = spriteRenderer.color;
		color = colorOri;
		color.a = 0;
	}

	public void ComponentReset()
	{
		spriteRenderer.color = colorOri;
		timeout = Spray2D.Timeout;
	}

	public void ComponentUpdate()
	{
		timeout -= Time.deltaTime;

		if(timeout < 0)
		{
			spriteRenderer.color = Color.Lerp(spriteRenderer.color, color, 0.05f);

			if(spriteRenderer.color.a < 0.1f)
			{
				gameObject.SetActive(false);
				ComponentReset();
			}
		}
	}
}

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



Идем дальше, пишем скрипт создания пула:

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

public static class Spray2DPool {

	private static List<Part> pools;

	struct Part 
	{
		public string name;
		public List<Spray2DPoolComponent> prefab;
		public Transform parent;
	}

	public static void Initialize()
	{
		pools = new List<Part>();
	}

	public static void Create(Spray2DPoolComponent[] sample, string name, int count)
	{
		if(sample == null || name == null) return;
		
		Part p = new Part();
		p.prefab = new List<Spray2DPoolComponent>();
		p.name = name;
		p.parent = new GameObject("Spray2DPool_" + name).transform;

		for(int i = 0; i < sample.Length * count; i++)
		{
			p.prefab.Add(AddObject(sample[Random.Range(0, sample.Length)], name, i, p.parent));
		}

		pools.Add(p);
	}

	static Spray2DPoolComponent AddObject(Spray2DPoolComponent sample, string name, int index, Transform parent)
	{
		Spray2DPoolComponent comp = GameObject.Instantiate(sample) as Spray2DPoolComponent;
		comp.gameObject.name = name + "-" + index;
		comp.transform.parent = parent;
		comp.Initialize();
		comp.gameObject.SetActive(false);
		return comp;
	}

	public static void SprayUpdate(string name)
	{
		if(pools == null || name == null) return;

		foreach(Part part in pools)
		{
			if(string.Compare(part.name, name) == 0)
			{
				foreach(Spray2DPoolComponent comp in part.prefab)
				{
					if(comp.isActiveAndEnabled)
					{
						comp.ComponentUpdate();
					}
				}

				break;
			}
		}
	}

	public static Spray2DPoolComponent GetObject(string name, Vector3 position)
	{
		if(pools == null || name == null) return null;

		Spray2DPoolComponent comp = GetObject_internal(name);

		if(comp == null) return null;

		comp.transform.position = position;
		comp.ComponentReset();
		comp.gameObject.SetActive(true);

		return comp;
	}

	public static Spray2DPoolComponent GetObject(string name, Vector3 position, float zRotation)
	{
		if(pools == null || name == null) return null;

		Spray2DPoolComponent comp = GetObject_internal(name);

		if(comp == null) return null;

		comp.transform.eulerAngles = new Vector3(0, 0, zRotation);
		comp.transform.position = position;
		comp.ComponentReset();
		comp.gameObject.SetActive(true);

		return comp;
	}

	public static Spray2DPoolComponent GetObject(string name, Vector3 position, float zRotation, Vector3 scale)
	{
		if(pools == null || name == null) return null;

		Spray2DPoolComponent comp = GetObject_internal(name);

		if(comp == null) return null;

		comp.transform.eulerAngles = new Vector3(0, 0, zRotation);
		comp.transform.position = position;
		comp.transform.localScale = scale;
		comp.ComponentReset();
		comp.gameObject.SetActive(true);

		return comp;
	}

	static Spray2DPoolComponent GetObject_internal(string name)
	{
		if(pools == null || name == null) return null;

		foreach(Part part in pools)
		{
			if(string.Compare(part.name, name) == 0)
			{
				foreach(Spray2DPoolComponent comp in part.prefab)
				{
					if(!comp.isActiveAndEnabled) return comp;
				}

				return part.prefab[Random.Range(0, part.prefab.Count)];
			}
		}

		return null;
	}
}

Стоит обратить внимание разве что на установки для объекта, которые заточены только под 2D. Еще важный момент, что пул создается на основе массива, это необходимо учитывать, когда устанавливаем количество объектов пула, так как указанное число будет умножено на длину массива.

Переходим к управляющему классу:

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

public class Spray2D : MonoBehaviour {

	[SerializeField] private LayerMask layerMask; // фильтр по маске
	[SerializeField] private string[] tagList; // фильтр по тегам
	[SerializeField] private float zPosition; // позиция спрея по Z
	[SerializeField] private float timeout = 5; // время исчезновения (в секундах)
	[SerializeField] private int sprayCount = 10; // копий для каждого из префаба (если используется, например, три разных спрайта, соответственно умножаем на три)
	[SerializeField] private string sprayPath = "BulletSpray2D"; // папка в Resources со спрайтами
	public static float Timeout { get; private set; }

	void Awake()
	{
		Timeout = timeout;
		Spray2DPool.Initialize();
	}

	void Start()
	{
		Spray2DPool.Create(Resources.LoadAll<Spray2DPoolComponent>(sprayPath), sprayPath, sprayCount); // создаем пул спрайтов
	}

	bool Check(GameObject obj) // проверка объекта на соответствие по: маске слоя или тега
	{
		if(((1 << obj.layer) & layerMask) != 0)
		{
			return true;
		}

		foreach(string t in tagList)
		{
			if(obj.tag.CompareTo(t) == 0) return true;
		}

		return false;
	}

	void LateUpdate()
	{
		if(Input.GetMouseButtonDown(0))
		{
			RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);

			if(hit.collider != null && Check(hit.collider.gameObject))
			{
				Vector3 position = new Vector3(hit.point.x, hit.point.y, zPosition);
				Spray2DPool.GetObject(sprayPath, position, Random.Range(0, 360)); // запрашиваем свободный объект
			}
		}

		Spray2DPool.SprayUpdate(sprayPath); // вызов функции апдейта объектов пула
	}
}

Обратите внимание что тут происходит перебор массива в LateUpdate. Разумеется, необходимости в этом нет, можно сделать, чтобы объекты переходили в "режим ожидания", когда они уходят за приделы видимости камеры например. Однако, конкретно в нашем случаи, предпочтительней, чтобы спрайты исчезали по истечению некоторого времени.

Еще одна деталь.
Если все объекты в пуле "заняты", вместо увеличения его объема, будет использован случайный активный объект.

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

Внимание! Посетители, находящиеся в группе Гости, не могут скачивать файлы.
Тестировалось на: Unity 5.5.1
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика