Статьи

Как добавить свои собственные инструменты в редактор Unity

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

В этом руководстве предполагается, что вы уже знаете основы рабочего процесса Unity. Если вы знаете, как создавать объекты, префабы, сцены, перемещаться в редакторе, прикреплять компоненты, тогда все готово!


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

Изменение цвета сетки из окна

Как видите, мы будем создавать окно редактора и палитру цветов, выбор которой мы будем использовать для рисования сетки. Мы также сможем создавать и удалять объекты, привязанные к этой сетке, и отменять такие действия.


Сначала мы научимся пользоваться вещицами. Вот несколько примеров встроенных вещей.

Гизмо используется для перемещения объекта

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

Коробка коллайдеров гизмо

Вот еще одна штуковина, которая позволяет нам увидеть размер BoxCollider прикрепленного к нашему игровому объекту.


Создайте скрипт C #, который мы можем использовать, чтобы нарисовать нашу собственную штуковину для объекта; в качестве примера мы нарисуем простую сетку в редакторе.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
using UnityEngine;
using System.Collections;
 
public class Grid : MonoBehaviour
{
     
    void Start ()
    {
    }
     
    void Update ()
    {
    }
}

Для сетки нам нужно добавить две переменные, ширину и высоту.

01
02
03
04
05
06
07
08
09
10
11
12
13
public class Grid : MonoBehaviour
{
    public float width = 32.0f;
    public float height = 32.0f;
     
    void Start ()
    {
    }
     
    void Update ()
    {
    }
}

Для рисования в редакторе нам нужно использовать OnDrawGizmos вызов OnDrawGizmos , поэтому давайте создадим его.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class Grid : MonoBehaviour
{
    public float width = 32.0f;
    public float height = 32.0f;
     
    void Start ()
    {
    }
     
    void Update ()
    {
    }
     
    void OnDrawGizmos()
    {
    }
}

Чтобы нарисовать сетку, нам нужен набор горизонтальных и вертикальных линий и положение камеры редактора, чтобы мы знали, вокруг какой точки нам следует рисовать нашу сетку. Во-первых, давайте сохраним положение камеры в отдельной переменной.

1
2
3
4
void OnDrawGizmos()
{
    Vector3 pos = Camera.current.transform.position;
}

Как видите, мы можем получить камеру редактора, используя ссылку Camera.current .

Теперь нам понадобятся два цикла для рисования горизонтальных и вертикальных линий.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
void OnDrawGizmos()
{
    Vector3 pos = Camera.current.transform.position;
     
    for (float y = pos.y — 800.0f; y < pos.y + 800.0f; y+= height)
    {
        Gizmos.DrawLine(new Vector3(-1000000.0f, Mathf.Floor(y/height) * height, 0.0f),
                        new Vector3(1000000.0f, Mathf.Floor(y/height) * height, 0.0f));
    }
    
    for (float x = pos.x — 1200.0f; x < pos.x + 1200.0f; x+= width)
    {
        Gizmos.DrawLine(new Vector3(Mathf.Floor(x/width) * width, -1000000.0f, 0.0f),
                        new Vector3(Mathf.Floor(x/width) * width, 1000000.0f, 0.0f));
    }
}

Для рисования линий мы используем Gizmos.DrawLine() . Обратите внимание, что в классе Gizmos есть много других методов API рисования, поэтому можно рисовать такие примитивы, как куб или сфера, или даже их каркас. Вы также можете нарисовать изображение, если вам нужно.

Линии сетки должны быть бесконечно длинными, но float.positiveInfinity и float.negativeInfinity , похоже, плохо работают с рисованием линий, поэтому мы можем просто поставить произвольно большие числа вместо них. Кроме того, количество строк строго зависит от констант, которые мы вводим в определения циклов for ; технически мы не должны оставлять эти константы такими, но это всего лишь тестовый код.

Чтобы увидеть сетку, создайте пустой объект и присоедините к нему наш скрипт:

Сетки

Следующая вещь, которую нужно охватить, это настройка инспектора. Для этого нам нужно создать скрипт редактора. Создайте новый файл C # и назовите его GridEditor . Этот скрипт должен быть помещен в папку редактора ; если у вас его нет, создайте его сейчас.

1
2
3
4
5
6
7
8
using UnityEngine;
using UnityEditor;
using System.Collections;
 
[CustomEditor (typeof(Grid))]
public class GridEditor : Editor
{
}

На этот раз нам также нужно использовать UnityEditor чтобы иметь возможность использовать классы и функции редактора. Чтобы переопределить инспектор по умолчанию для нашего объекта Grid нам нужно добавить атрибут до объявления нашего класса, [CustomEditor (typeof(Grid))] Unity знала, что мы будем настраивать инспектор Grid . Чтобы иметь возможность использовать обратные вызовы редактора, нам нужно наследовать класс Editor а не MonoBehaviour .

Для смены текущего инспектора нам нужно переопределить старый.

1
2
3
4
5
6
public class GridEditor : Editor
{
    public override void OnInspectorGUI()
    {
    }
}

Если вы сейчас проверите инспектора объекта сетки в редакторе, он будет пустым, даже если сам объект имеет несколько открытых членов. Это потому, что, переопределив OnInspectorGUI() мы отказались от инспектора по умолчанию, чтобы вместо него создать собственный.

Инспектор объектов сетки

Прежде чем создавать какие-либо поля, нам нужно получить ссылку на объект, к которому применяется инспектор. На самом деле у нас уже есть ссылка — она ​​называется target — но для удобства мы создадим ссылку на компонент Grid этого объекта. Во-первых, давайте объявим это.

1
2
3
public class GridEditor : Editor
{
    Grid grid;

Мы должны назначить его в OnEnable() которая вызывается, как только инспектор включен.

1
2
3
4
5
6
7
8
public class GridEditor : Editor
{
    Grid grid;
 
    public void OnEnable()
    {
        grid = (Grid)target;
    }

Давайте создадим несколько полей инспектора сейчас. Для этого мы будем использовать классы GUILayout и EditorGUILayout.

1
2
3
4
5
6
7
public override void OnInspectorGUI()
{
    GUILayout.BeginHorizontal();
    GUILayout.Label(» Grid Width «);
    grid.width = EditorGUILayout.FloatField(grid.width, GUILayout.Width(50));
    GUILayout.EndHorizontal();
}

Первая строка, GUILayout.BeginHorizontal(); означает, что мы хотим разместить следующие элементы инспектора рядом друг с другом, слева направо. Как вы можете себе представить, последняя строка, GUILayout.EndHorizontal(); означает, что мы больше не хотим этого делать. Фактические элементы находятся между этими двумя строками. Первый — это простая метка (в нашем случае это будет текст ширины сетки ), а затем рядом с ней мы создаем EditorGUILayout.FloatField который, как вы можете себе представить, представляет собой поле с плавающей точкой. Обратите внимание, что мы присваиваем grid.width значению этого FloatField , а само поле float показывает значение grid.width . Мы также установили его ширину до 50 пикселей.

Посмотрим, будет ли поле добавлено в инспектор:


Теперь давайте добавим еще один элемент в инспектор; на этот раз это будет grid.height .

01
02
03
04
05
06
07
08
09
10
11
12
public override void OnInspectorGUI()
{
    GUILayout.BeginHorizontal();
    GUILayout.Label(» Grid Width «);
    grid.width = EditorGUILayout.FloatField(grid.width, GUILayout.Width(50));
    GUILayout.EndHorizontal();
     
    GUILayout.BeginHorizontal();
    GUILayout.Label(» Grid Height «);
    grid.height = EditorGUILayout.FloatField(grid.height, GUILayout.Width(50));
    GUILayout.EndHorizontal();
}

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

Обратите внимание, что изменения, которые мы вносим в наш новый инспектор, видны только после того, как мы выберем окно Scene View. Чтобы сделать их видимыми после их создания, мы можем вызвать SceneView.RepaintAll() .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public override void OnInspectorGUI()
{
    GUILayout.BeginHorizontal();
    GUILayout.Label(» Grid Width «);
    grid.width = EditorGUILayout.FloatField(grid.width, GUILayout.Width(50));
    GUILayout.EndHorizontal();
     
    GUILayout.BeginHorizontal();
    GUILayout.Label(» Grid Height «);
    grid.height = EditorGUILayout.FloatField(grid.height, GUILayout.Width(50));
    GUILayout.EndHorizontal();
     
    SceneView.RepaintAll();
}

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

Обновленная сетка

Теперь давайте попробуем обработать ввод редактора, как мы это делаем в игре. Любые состояния клавиш или мыши должны быть доступны нам. Чтобы иметь эту функцию, мы должны добавить onSceneGUIDelegate вызов onSceneGUIDelegate к нашему SceneView . Давайте назовем нашу функцию обновления GridUpdate() .

1
2
3
4
5
6
7
8
9
public void OnEnable()
{
    grid = (Grid)target;
    SceneView.onSceneGUIDelegate = GridUpdate;
}
 
void GridUpdate(SceneView sceneview)
{
}

Теперь нам нужно только получить входное Event .

1
2
3
4
void GridUpdate(SceneView sceneview)
{
    Event e = Event.current;
}

Для дальнейшей игры со скриптами редактора нам понадобится игровой объект, который мы сможем использовать. Давайте создадим простой куб и сделаем из него сборный дом.

Куб выровнен по сетке

Вы можете сопоставить размер сетки с кубом или наоборот и совместить ее с сеткой.

Кубики сборные

Как вы можете видеть, в иерархическом представлении текст куба окрашен в синий цвет; это означает, что это связано с префабом. Вы можете увидеть этот сборный в окне проекта.


Теперь мы создадим объект из редактора скрипта. Вернемся к нашему GridEditor.cs и расширим GridUpdate() .

Давайте создадим объект при нажатии клавиши a .

1
2
3
4
5
6
7
8
9
void GridUpdate(SceneView sceneview)
{
    Event e = Event.current;
     
    if (e.isKey && e.character == ‘a’)
    {
        GameObject obj;
    }
}

Как видите, мы просто проверяем, является ли событие изменением состояния клавиши и был ли нажатый символ « a ». Мы также создаем ссылку на наш новый объект. Теперь давайте создадим это.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
void GridUpdate(SceneView sceneview)
{
    Event e = Event.current;
     
    if (e.isKey && e.character == ‘a’)
    {
        GameObject obj;
        if (Selection.activeObject)
        {
            obj = (GameObject)Instantiate(Selection.activeObject);
            obj.transform.position = new Vector3(0.0f, 0.0f, 0.0f);
        }
    }
}

Selection.activeObject — это ссылка на текущий выбранный объект в редакторе. Если какой-либо объект выбран, мы просто клонируем его и меняем позицию клона на (0.0, 0.0, 0.0) .

Давайте проверим, работает ли это. Вы должны помнить одну вещь: наш GridUpdate() перестает работать всякий раз, когда ресурсы повторно импортируются / обновляются, и чтобы повторно включить его, вы должны выбрать объект (например, из представления иерархии), на который ссылается скрипт редактора — в наш пример это объект Grid . Вы также должны помнить, что входные события будут перехвачены, только если выбрано представление Scene.

Новые и старые кубики

Хотя нам удалось клонировать объект, ссылка клонированного объекта на префаб не существует.

CubeClone не связан ни с одним сборным

Как видите, имя куба (клона) отображается простым черным шрифтом, и это означает, что оно не связано с префабом, как исходный куб. Если мы будем дублировать исходный куб вручную в редакторе, клонированный куб будет связан с префабом Куба . Чтобы заставить нас работать таким образом, нам нужно использовать функцию InstantiatePrefab() из класса EditorUtility .

Прежде чем использовать эту функцию, нам нужно получить префаб выбранного объекта. Для этого нам нужно использовать GetPrefabParent() который также принадлежит классу EditorUtility .

01
02
03
04
05
06
07
08
09
10
11
void GridUpdate(SceneView sceneview)
{
    Event e = Event.current;
     
    if (e.isKey&& e.character == ‘a’)
    {
        GameObject obj;
        Object prefab = EditorUtility.GetPrefabParent(Selection.activeObject);
         
        if (prefab)
        {

Мы также можем перестать проверять, существует ли Selection.activeObject , потому что если его нет, то prefab будет равен null , и поэтому мы можем избежать проверки только ссылки на prefab .

Теперь давайте создадим наш префаб и установим его позицию.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
void GridUpdate(SceneView sceneview)
{
    Event e = Event.current;
 
    if (e.isKey && e.character == ‘a’)
    {
        GameObject obj;
        Object prefab = EditorUtility.GetPrefabParent(Selection.activeObject);
         
        if (prefab)
        {
            obj = (GameObject)EditorUtility.InstantiatePrefab(prefab);
            obj.transform.position = new Vector3(0.0f, 0.0f, 0.0f);
        }
    }
}

И это все — давайте проверим, связан ли клонированный куб с префабом сейчас.

Клон кубов связан с сборным

Класс Event не сообщает нам, где находится мышь в мировом пространстве, он только предоставляет координаты мыши в пространстве экрана. Вот как мы конвертируем их, чтобы мы могли получить приблизительное положение мыши в пространстве.

1
2
3
4
5
6
void GridUpdate(SceneView sceneview)
{
    Event e = Event.current;
 
    Ray r = Camera.current.ScreenPointToRay(new Vector3(e.mousePosition.x, -e.mousePosition.y + Camera.current.pixelHeight));
    Vector3 mousePos = r.origin;

Сначала мы используем камеру редактора ScreenPointToRay чтобы получить луч из координат экрана, но, к сожалению, до этого нам нужно преобразовать пространство экрана события в пространство, приемлемое для ScreenPointToRay() .

e.mousePosition содержит положение мыши в координатном пространстве, где верхний левый угол является точкой (0, 0) а нижний правый угол равен (Camera.current.pixelWidth, -Camera.current.pixelHeight) . Нам нужно перевести его в пространство, где нижний левый угол — это (0, 0) а верхний правый — (Camera.current.pixelWidth, Camera.current.pixelHeight) , что довольно просто.

Следующее, что мы должны сделать, это сохранить происхождение луча в нашем векторе mousePos , чтобы он был легко доступен.

Теперь мы можем назначить позицию клона, где находится мышь.

1
2
3
4
5
if (prefab)
{
    obj = (GameObject)EditorUtility.InstantiatePrefab(prefab);
    obj.transform.position = new Vector3(mousePos.x, mousePos.y, 0.0f);
}

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

Много невыровненных кубов

Поскольку мы настроили нашу сетку, было бы стыдно ее не использовать; давайте использовать нашу позицию мыши для выравнивания созданных кубов с сеткой.

1
2
3
4
5
6
7
if (prefab)
{
    obj = (GameObject)EditorUtility.InstantiatePrefab(prefab);
    Vector3 aligned = new Vector3(Mathf.Floor(mousePos.x/grid.width)*grid.width + grid.width/2.0f,
                                  Mathf.Floor(mousePos.y/grid.height)*grid.height + grid.height/2.0f, 0.0f);
    obj.transform.position = aligned;
}

Посмотрите на результат:

Много выровненных кубов

На этом этапе мы удалим объекты программно в редакторе. Мы можем сделать это с помощью DestroyImmediate() . В этом примере давайте более широко используем класс Selection и удаляем все выбранные объекты при нажатии клавиши « d ».

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
if (e.isKey && e.character == ‘a’)
{
    GameObject obj;
    Object prefab = EditorUtility.GetPrefabParent(Selection.activeObject);
     
    if (prefab)
    {
        obj = (GameObject)EditorUtility.InstantiatePrefab(prefab);
        Vector3 aligned = new Vector3(Mathf.Floor(mousePos.x/grid.width)*grid.width + grid.width/2.0f,
                                      Mathf.Floor(mousePos.y/grid.height)*grid.height + grid.height/2.0f, 0.0f);
        obj.transform.position = aligned;
    }
}
else if (e.isKey && e.character == ‘d’)
{
    foreach (GameObject obj in Selection.gameObjects)
        DestroyImmediate(obj);
}

Когда нажата клавиша « d », мы проходим все выбранные объекты и удаляем каждый из них. Конечно, мы также можем нажать клавишу Delete в редакторе, чтобы удалить эти объекты, но тогда они не будут удалены нашим скриптом. Проверьте это в редакторе.


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

Чтобы иметь возможность уничтожить объект, который мы создали в редакторе, нам нужно вызвать Undo.RegisterCreatedObjectUndo() . Он принимает два аргумента: первый — это созданный объект, а второй — имя отмены. Имя действия, которое будет отменено, всегда отображается в меню « Правка» -> «Отменить name .

1
2
3
4
5
6
7
8
if (prefab)
{
    obj = (GameObject)EditorUtility.InstantiatePrefab(prefab);
    Vector3 aligned = new Vector3(Mathf.Floor(mousePos.x/grid.width)*grid.width + grid.width/2.0f,
                                  Mathf.Floor(mousePos.y/grid.height)*grid.height + grid.height/2.0f, 0.0f);
    obj.transform.position = aligned;
    Undo.RegisterCreatedObjectUndo(obj, «Create » + obj.name);
}

Если вы создадите несколько кубов с помощью клавиши, а затем попытаетесь отменить, вы заметите, что все созданные кубы были удалены. Это потому, что все эти созданные кубы вошли в одно событие отмены.


Если мы хотим поместить каждый созданный объект в другое событие отмены и сделать возможным отмену создания их один за другим, нам нужно использовать Undo.IncrementCurrentEventIndex() .

1
2
3
4
5
6
7
8
9
if (prefab)
{
    Undo.IncrementCurrentEventIndex();
    obj = (GameObject)EditorUtility.InstantiatePrefab(prefab);
    Vector3 aligned = new Vector3(Mathf.Floor(mousePos.x/grid.width)*grid.width + grid.width/2.0f,
                                  Mathf.Floor(mousePos.y/grid.height)*grid.height + grid.height/2.0f, 0.0f);
    obj.transform.position = aligned;
    Undo.RegisterCreatedObjectUndo(obj, «Create » + obj.name);
}

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


Чтобы отменить удаление объекта, мы должны использовать Undo.RegisterSceneUndo() . Это очень медленная функция, которая по существу сохраняет состояние сцены, поэтому мы можем позже вернуться в это состояние, выполнив действие отмены. К сожалению, на данный момент это единственный способ вернуть удаленные объекты на сцену.

1
2
3
4
5
6
7
else if (e.isKey && e.character == ‘d’)
{
    Undo.IncrementCurrentEventIndex();
    Undo.RegisterSceneUndo(«Delete Selected Objects»);
    foreach (GameObject obj in Selection.gameObjects)
        DestroyImmediate(obj);
}

Undo.RegisterSceneUndo() принимает только один аргумент, и это имя отмены. После удаления пары кубов с помощью клавиши d вы можете отменить это удаление.


Создайте новый скрипт, и давайте сделаем так, чтобы он расширял EditorWindow вместо Editor . Давайте назовем это GridWindow.cs .

01
02
03
04
05
06
07
08
09
10
using UnityEngine;
using UnityEditor;
using System.Collections;
 
public class GridWindow : EditorWindow
{
    public void Init()
    {
    }
}

Давайте создадим ссылку на наш объект Grid, чтобы мы могли получить к нему доступ из окна.

1
2
3
4
5
6
7
8
9
public class GridWindow : EditorWindow
{
    Grid grid;
     
    public void Init()
    {
        grid = (Grid)FindObjectOfType(typeof(Grid));
    }
}

Теперь нам нужно создать окно, мы можем сделать это из нашего скрипта GridEditor .


В нашем OnInspectorGUI() давайте добавим кнопку, которая создаст GridWindow .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public override void OnInspectorGUI()
{
    GUILayout.BeginHorizontal();
    GUILayout.Label(» Grid Width «);
    grid.width = EditorGUILayout.FloatField(grid.width, GUILayout.Width(50));
    GUILayout.EndHorizontal();
     
    GUILayout.BeginHorizontal();
    GUILayout.Label(» Grid Height «);
    grid.height = EditorGUILayout.FloatField(grid.height, GUILayout.Width(50));
    GUILayout.EndHorizontal();
     
    if (GUILayout.Button(«Open Grid Window», GUILayout.Width(255)))
    {
       GridWindow window = (GridWindow) EditorWindow.GetWindow(typeof(GridWindow));
       window.Init();
    }
     
    SceneView.RepaintAll();
}

Мы используем GUILayout для создания кнопки, мы также устанавливаем имя и ширину кнопки. GUILayout.Button возвращает true при нажатии кнопки, если это так, то мы открываем наше GridWindow .

Вы можете вернуться в редактор и нажать кнопку в нашем инспекторе объектов Grid .

Кнопка, которая открывает окно

Как только вы это сделаете, должно появиться окно GridWindow .

Открытое окно редактора

Прежде чем редактировать что-либо из нашего окна, давайте добавим поле цвета в наш класс Grid , чтобы мы могли редактировать его позже.

1
2
3
4
5
6
public class Grid : MonoBehaviour
{
    public float width = 32.0f;
    public float height = 32.0f;
     
    public Color color = Color.white;

Теперь назначьте Gizmos.color в функции OnDrawGizmos() .

1
2
3
4
void OnDrawGizmos()
{
    Vector3 pos = Camera.current.transform.position;
    Gizmos.color = color;

А теперь давайте вернемся к сценарию GridWindow и создадим там цветовое поле, чтобы мы могли выбрать цвет в окне. Мы можем сделать это с помощью обратного вызова OnGUI() .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public class GridWindow : EditorWindow
{
    Grid grid;
     
    public void Init()
    {
        grid = (Grid)FindObjectOfType(typeof(Grid));
    }
     
    void OnGUI()
    {
        grid.color = EditorGUILayout.ColorField(grid.color, GUILayout.Width(200));
    }
}

Хорошо, теперь вы можете проверить, все ли правильно работает в редакторе:

Изменение цвета сетки из окна

Прямо сейчас мы устанавливаем делегат для получения входных событий из представления сцены, мы используем знак = , который не является хорошим способом сделать это, потому что он переопределяет все другие обратные вызовы. Вместо этого мы должны использовать знак += . Давайте перейдем к нашему сценарию GridEditor.cs и внесем это изменение.

1
2
3
4
5
public void OnEnable()
{
    grid = (Grid)target;
    SceneView.onSceneGUIDelegate += GridUpdate;
}

Нам также необходимо создать OnDisable() вызов OnDisable() чтобы удалить наш GridUpdate() , если мы этого не сделаем, он будет складываться и вызываться несколько раз одновременно.

01
02
03
04
05
06
07
08
09
10
public void OnEnable()
{
    grid = (Grid)target;
    SceneView.onSceneGUIDelegate += GridUpdate;
}
 
public void OnDisable()
{
    SceneView.onSceneGUIDelegate -= GridUpdate;
}

Вот и все для введения в редактор сценариев. Если вы хотите расширить свои знания, по теме в справочнике по Unity Script есть что почитать — вы можете проверить классы Resources , AssetDatabase или FileUtil зависимости от ваших потребностей.

К сожалению, некоторые классы еще не документированы и поэтому подвержены изменениям без работы. Например, класс SceneView и его функции или функция Undo.IncrementCurrentEventIndex() из класса Undo . Если документация не содержит ответов, которые вы ищете, вы можете попробовать поискать через UnityAnswers или Unity Forum .

Спасибо за ваше время!