Скрипты для редактора в Unity3D

в 7:49, , рубрики: editorscript, game development, unity3d, Программирование

Сегодня поговорим о том, как писать скрипты для 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);
        }
    }

Скрипты для редактора в Unity3D

Теперь, если мы нажмем правой кнопкой по заголовку скрипта, там появится пункт 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 выключить.

Казалось бы, ну что такого, пробежался по сцене ручками — расставил кому нужно галочки — и все. Но проблема в том, что объектов в сцене может быть много и в процессе развития проекта сцена скорее всего будет меняться. Следить самому за всеми этими галочками — с ума можно сойти. Поэтому мы напишем маленький скриптик, который делает это за нас.

Для начала напишем полезный код, который выполняет нашу работу, а далее оформим это в отдельный виззард:

Скрипты для редактора в Unity3D

Задачи здесь две:

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.

По поводу реализации метода IsTransparent().

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

Теперь давайте оформим отдельный виззард, чтобы можно было удобно вызывать эту команду:

Тут нам пригодится класс 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-ти уровнях по очереди и получить скриншоты с результатом себе на почту.

Автор:

Источник

* - обязательные к заполнению поля