Raycast в окне редактора + меню

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


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

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

[ExecuteInEditMode]

public class BuilderSetup : MonoBehaviour {

	public Vector2 position = new Vector2(10, 10); // позиция меню
	public float width = 250; // ширина
	public float height = 400; // высота
	public string prefabsPath = "Prefabs"; // путь в папке Resources

	// данные переменные используются менюшкой
	[HideInInspector] public Transform[] prefabs;
	[HideInInspector] public string[] prefabsNames;
	[HideInInspector] public int index;
	[HideInInspector] public bool showButton, project2D;
	[HideInInspector] public string tagField;
	[HideInInspector] public LayerMask layerMask;

	public void LoadResources()
	{
		prefabs = Resources.LoadAll<Transform>(prefabsPath);

		prefabsNames = new string[prefabs.Length];
		for(int i = 0; i < prefabs.Length; i++)
		{
			prefabsNames[i] = prefabs[i].name;
		}

		index = 0;
	}

	public void InstantiatePrefab(Vector3 position)
	{
		Instantiate(prefabs[index], position, Quaternion.identity);
	}
}

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

Raycast в окне редактора + меню

Теперь, класс функций:

#if UNITY_EDITOR
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(BuilderSetup))]

public class BuilderEditor : Editor {

	public override void OnInspectorGUI()
	{
		DrawDefaultInspector();
		BuilderSetup t = (BuilderSetup)target;
		GUILayout.Label("Обновить массив префабов:", EditorStyles.boldLabel);
		if(GUILayout.Button("Load Resources")) t.LoadResources();
	}

	LayerMask LayerMaskField(LayerMask layerMask) // вывод маски
	{
		List<string> layers = new List<string>();
		List<int> layerNumbers = new List<int>();

		for(int i = 0; i < 32; i++)
		{
			string layerName = LayerMask.LayerToName(i);

			if(layerName != "")
			{
				layers.Add(layerName);
				layerNumbers.Add(i);
			}
		}

		int maskWithoutEmpty = 0;

		for(int i = 0; i < layerNumbers.Count; i++)
		{
			if(((1 << layerNumbers[i]) & layerMask.value) > 0) maskWithoutEmpty |= (1 << i);
		}

		maskWithoutEmpty = EditorGUILayout.MaskField(maskWithoutEmpty, layers.ToArray());

		int mask = 0;

		for(int i = 0; i < layerNumbers.Count; i++)
		{
			if((maskWithoutEmpty & (1 << i)) > 0) mask |= (1 << layerNumbers[i]);
		}

		layerMask.value = mask;

		return layerMask;
	}

	void OnSceneGUI()
	{
		BuilderSetup t = (BuilderSetup)target;

		HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive)); // отмена выбора объекта ЛКМ в окне редактора

		string showButtonText = (t.showButton) ? "Скрыть меню" : "Развернуть меню";
		string toggleText = (t.project2D) ? "Raycast в двухмерном режиме" : "Трехмерный режим Raycast-а";

		if(Event.current.button == 0 && Event.current.type == EventType.mouseDown)
		{
			if(t.project2D)
			{
				RaycastHit2D hit = Physics2D.Raycast(Camera.current.ScreenToWorldPoint(new Vector2(Event.current.mousePosition.x, 
					SceneView.currentDrawingSceneView.camera.pixelHeight - Event.current.mousePosition.y)), Vector2.zero, Mathf.Infinity, t.layerMask);

				if(hit.collider != null && hit.collider.tag.CompareTo(t.tagField) == 0)
				{
					t.InstantiatePrefab(hit.point);
				}
			}
			else
			{
				RaycastHit hit;
				Ray ray = Camera.current.ScreenPointToRay(new Vector2(Event.current.mousePosition.x, 
					SceneView.currentDrawingSceneView.camera.pixelHeight - Event.current.mousePosition.y));

				if(Physics.Raycast(ray, out hit, Mathf.Infinity, t.layerMask) && hit.collider.tag.CompareTo(t.tagField) == 0)
				{
					t.InstantiatePrefab(hit.point);
				}
			}
		}

		Handles.BeginGUI();
		GUILayout.BeginArea(new Rect(t.position.x, t.position.y, t.width, t.height)); // вырезаем область в окне сцены

		if(GUILayout.Button(showButtonText)) t.showButton = !t.showButton;

		if(t.showButton)
		{
			GUILayout.TextArea("Справка:\n" +
				"Для установки выбранного префаба, ставим курсор в нужное место, затем ЛКМ");

			GUILayout.BeginHorizontal();
			GUILayout.TextField("Выбор префаба: ");
			t.index = EditorGUILayout.Popup(t.index, t.prefabsNames);
			GUILayout.EndHorizontal();

			GUILayout.Box("Опции рейкаста:", GUILayout.ExpandWidth(true));

			GUILayout.BeginHorizontal();
			GUILayout.TextField("Фильтр по тегу: ");
			if(t.tagField.Trim() == string.Empty) t.tagField = "Untagged";
			t.tagField = EditorGUILayout.TagField(t.tagField);
			GUILayout.EndHorizontal();

			GUILayout.BeginHorizontal();
			GUILayout.TextField("Фильтр по маске: ");
			t.layerMask = LayerMaskField(t.layerMask);
			GUILayout.EndHorizontal();


			t.project2D = EditorGUILayout.ToggleLeft(toggleText, t.project2D, EditorStyles.textField);

			GUILayout.Label("© 2017 NULLcode Studio", EditorStyles.centeredGreyMiniLabel);	
		}

		GUILayout.EndArea();
		Handles.EndGUI();
	}
}
#endif

Этот скрипт просто оставляем в папке с другими, добавлять на сцену не нужно. Чтобы вызвать функции класса надо выбрать первый скрипт, который мы ранее добавили на сцену. То есть, они работают в связке.



Как видно по скриншотам, тут можно переключать рейкаст в 2D или 3D режим. Пример достаточно наглядный.

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

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

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

Офлайн
Aulexian 24 февраля 2017
Приветствую! Немного не по теме.
Есть тривиальная и частая для многих задача - чтобы physics.raycast, на котором замешана логика взаимодействия с миром, попадал и в UI, не проходя насквозь. Эдакий унифицированный рейкаст.
Так вот, есть ли какие-то способы, кроме конечно костыля в виде пускания первоочерёдно graphic.raycast, и при непопадании уже physics.raycast? Не критично, но всё-таки.
Онлайн
Light 25 февраля 2017
Aulexian, есть возможность получить данные с системы UI. Для этого на сцене, на объекте EventSystems удаляем стандартный скрипт Standalone Input Module, а вместо него цепляем другой:

using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;

public class ExtendedStandaloneInputModule : StandaloneInputModule
{
	public PointerEventData GetPointerEventData(int id /* -1 ЛКМ, -2 ПКМ, -3 Колесико */)
	{
		return GetLastPointerEventData(id);
	}
}

Теперь, чтобы получить данные, используем код:

using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;

public class TestScript : MonoBehaviour {

	void Update ()
	{
		var inst = EventSystem.current.currentInputModule as ExtendedStandaloneInputModule;
		if (inst == null) return;

		var data = inst.GetPointerEventData(-1);
		if (data == null) return;

		if (data.pointerEnter != null)
		{
			Debug.Log("Мышь над: " + data.pointerEnter + " | Позиция: " + data.position);
		}
	}
}
Офлайн
Aulexian 28 февраля 2017
Light, благодарю. Подправил своё и всё стало прекрасно.

Теперь в своём UpdateManager по срабатыванию OnMouseButtonDown запускаю нужную функцию.
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Яндекс.Метрика