Сегодня поговорим о том, как писать скрипты для Unity Editor. Статья рассчитана на тех, кто уже знаком с Unity3D, что-то успел сделать, но еще не решился попробовать писать скрипты для эдитора.
Если коротко — то в режиме эдитора скриптами можно сделать абсолютно всё тоже самое, что и в режиме игры.
Начнем с простого. Допустим, мы хотим в режиме эдитора создать 10 кубиков и расположить их в линию. Для начала давайте упростим задачу и забудем про эдитор, давайте сделаем так, чтобы эти кубики создавались при старте приложения.
public class CreateCubes : MonoBehaviour {
// Use this for initialization
void Start ()
{
Create10Cubes();
}
private void Create10Cubes()
{
Vector3 position = new Vector3(0, 0, 0);
for (int i = 0; i < 10; i++)
{
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.transform.position = position;
position += new Vector3(5, 0, 0);
}
}
}
Теперь попробуем выполнить этот код в режиме эдитора, для этого нужно к коду добавить всего лишь один волшебный атрибут [ContextMenu()] к функции Create10Cubes():
так чтобы код выглядел вот так:
[ContextMenu("CreteCubes")]
private void Create10Cubes()
{
Vector3 position = new Vector3(0, 0, 0);
for (int i = 0; i < 10; i++)
{
GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cube.transform.position = position;
position += new Vector3(5, 0, 0);
}
}
Теперь, если мы нажмем правой кнопкой по заголовку скрипта, там появится пункт CreateCubes, при нажатии на который функция точно также будет выполнена.
Важное замечание: функция, помеченная атрибутом ContextMenu, не должна иметь параметров, вернее, если у нее будут параметры, вы не сможете вызвать таким способом.
Лично я применяю такой способ, когда нужно что-то сделать с группой объектов, например, выключить отбрасывание теней у всех детей объекта, у которых в названии встречается «withoutshadow»:
[ContextMenu("DisableCastShadows")]
private void DisabeCastShadows()
{
Renderer[] renderers = GetComponentsInChildren<Renderer>()
.Where(x => x.name.Contains("withoutshadow"))
.ToArray();
foreach (var r in renderers)
{
r.castShadows = false;
}
}
Вобщем способ хорош для одноразовых действий над кучей объектов — быстренько накидали нужный код в отдельном классе, кинули на нужный объект и тут же удалили этот класс к едрене фене.
Теперь давайте решим следующую задачу: мы хотим запечь occlusion culling. Для этого нам необходимо пометить галочкой Occluder Static все объекты, которые бубдут загораживать другие объекты, и галочкой Occludee Static все, которые будут скрываться за Occluder`ами. То есть нам нужно вычленить все статичные объекты, непрозрачным объкетам поставить обе галки (на самом деле все), прозрачным — только Occludee, а Occluder выключить.
Казалось бы, ну что такого, пробежался по сцене ручками — расставил кому нужно галочки — и все. Но проблема в том, что объектов в сцене может быть много и в процессе развития проекта сцена скорее всего будет меняться. Следить самому за всеми этими галочками — с ума можно сойти. Поэтому мы напишем маленький скриптик, который делает это за нас.
Для начала напишем полезный код, который выполняет нашу работу, а далее оформим это в отдельный виззард:
Задачи здесь две:
1) Найти интересующие нас объекты в сцене;
2) Расставить нужные галочки.
Оформим код в виде отдельной команды, для того чтобы его можно было вызывать из любого места и он не зависел от того, в каком виззарде он будет вызываться. Внимание: файл, содержащий следующий код, необходимо поместить в папку под названием Editor, это нужно для того, чтобы этот код не попал в основной билд:
using UnityEngine;
using UnityEditor;
using System.Linq;
public class SetStaticOclluderFlagsCmd
{
private const int TransparentRenderQueue = 3000;
public void Execute()
{
var scene =Object.FindObjectsOfType(typeof(GameObject));
var transparents = scene.Where(o =>IsStatic(o) && IsTransparent(o)).ToArray();
var occluders = scene.Where(o => IsStatic(o) && !IsTransparent(o)).ToArray();
SceneModeUtility.SetStaticFlags(transparents, (int)StaticEditorFlags.OccluderStatic, false);
SceneModeUtility.SetStaticFlags(transparents, (int)StaticEditorFlags.OccludeeStatic, true);
SceneModeUtility.SetStaticFlags(occluders, (int)StaticEditorFlags.OccluderStatic, true);
SceneModeUtility.SetStaticFlags(occluders, (int)StaticEditorFlags.OccludeeStatic, true);
Debug.Log("SetStaticOclluderFlagsCmd done");
}
private bool IsStatic(Object obj)
{
GameObject gameObject = obj as GameObject;
if (gameObject == null)
return false;
return GameObjectUtility.AreStaticEditorFlagsSet(gameObject, StaticEditorFlags.BatchingStatic);
}
private bool IsTransparent(Object obj)
{
GameObject gameObject = obj as GameObject;
if (gameObject == null ||gameObject.renderer == null)
return false;
return gameObject.renderer.sharedMaterials.Any(x => x.renderQueue == TransparentRenderQueue);
}
}
Здесь мы предполагаем, что статичные объекты уже до этого каким то образом нашли (скорее всего ручками) и отметили их галочкой Static, а значит, в том числе и BatchingStatic.
Теперь давайте оформим отдельный виззард, чтобы можно было удобно вызывать эту команду:
Тут нам пригодится класс EditorWindow.
using UnityEngine;
using UnityEditor;
public class OcclusionCullingUtilites : EditorWindow
{
[MenuItem("Window/OcclusionCullingUtilites")]
static void Init()
{
GetWindow<OcclusionCullingUtilites>();
}
void OnGUI()
{
if(GUILayout.Button("SetStaticFlags"))
{
SetStaticOclluderFlagsCmd cmd = new SetStaticOclluderFlagsCmd();
cmd.Execute();
}
}
}
На этом пока закончим наш обзор, он получился далеко не полным.
В следующих статьях я планирую описать, как можно создавать кастомные инспекторы для ваших классов, как вмешиваться в процесс импорта ассетов, как можно поставить запекать тени на 20-ти уровнях по очереди и получить скриншоты с результатом себе на почту.
Автор: