Форум программистов, компьютерный форум, киберфорум
GameUnited
Войти
Регистрация
Восстановить пароль

Batch Transform и Batch Gizmo Drawing API в Unity

Запись от GameUnited размещена 20.04.2025 в 15:51
Показов 4575 Комментарии 0
Метки c#, unity

Нажмите на изображение для увеличения
Название: b9155ae7-f3bd-4b6b-910d-4fee15d208b5.png
Просмотров: 116
Размер:	1.24 Мб
ID:	10625
В мире разработки игр и приложений на Unity производительность всегда была критическим фактором успеха. Создатели игр постоянно балансируют между визуальной привлекательностью и плавностью работы своих проектов. Особенно остро эта проблема встаёт при разработке сложных сцен с большим количеством объектов, когда даже небольшие оптимизации могут дать ощутимый прирост в FPS. Одной из ключевых операций, которые могут существенно влиять на производительность, является преобразование координат объектов — трансформации из локального пространства в мировое и обратно. Не менее важным аспектом для разработчиков выступает отрисовка отладочной информации через систему Gizmos особенно если необходимо визуализировать большое количество данных в редакторе Unity.

Именно эти два аспекта получили значительное улучшение в Unity, начиная с версии 2023.1 (а также в ретроспективных обновлениях 2022.2 и 2022.3), с появлением двух новых API: Batch Transform и Batch Gizmo Drawing.

Почему FPS падает с ростом количества объектов?



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

Классический пример — система частиц с тысячами элементов или сложная сцена с множеством интерактивных объектов. Каждый вызов Transform.TransformPoint() или других подобных методов требует перехода из управляемого кода C# в нативный код C++, что создаёт дополнительные накладные расходы на маршалинг данных. Когда таких вызовов много, эти расходы накапливаются и становятся заметными узким местом. Похожая ситуация наблюдается и с отрисовкой отладочной информации через Gizmos. Если вы пытаетесь визуализировать например, путь движения сотен объектов, каждый вызов Gizmos.DrawLine() создаёт отдельный запрос на отрисовку линии, что опять же приводит к избыточным вызовам API между C# и C++.

Неявное преобразование типа "System.Drawing.Icon" в "System.Drawing.Image" невозможно
ПОдскажите как можно вставить рисунок в панель если использую ico. Неявное преобразование типа...

Нарисовать сферу на форме в пространстве имен System.Drawing и System.Drawing.Drawing2D
Задача нарисовать сферу на форме в пространстве имен System.Drawing и System.Drawing.Drawing2D. С...

Как получить System.Drawing.FontStyle из System.Drawing.Font ?
Font textFont = new Font("Verdana", 26f, FontStyle.Bold | FontStyle.Italic); Как получить из...

Ошибка: Compiler Error CS1503: не удается преобразовать из "System.Drawing.RectangleF" в "System.Drawing.Rectangle
Появилась эта ошибка: Compiler Error CS1503 (Аргумент 1: не удается преобразовать из...


Эволюция батчинга в Unity



Unity всегда стремился решать проблемы производительности через объединение похожих операций — батчинг. Эта концепция уже хорошо знакома разработчикам в контексте рендеринга, где Static и Dynamic Batching объединяют несколько объектов с одинаковыми материалами в один draw call, что значительно уменьшает нагрузку на GPU. История развития батчинга в Unity началась именно с графического конвейера, но постепенно эта концепция стала распространяться и на другие аспекты движка. Появление Job System и DOTS (Data-Oriented Technology Stack) стало важным шагом в направлении более эффективной обработки данных, позволяя разработчикам писать код, лучше подходящий для многопоточной обработки и векторизации операций.

Новые Batch Transform и Batch Gizmo Drawing API — это продолжение этой философии оптимизации, но уже в областях, которые раньше требовали многочисленных отдельных вызовов. Они позволяют обрабатывать большие массивы данных за один вызов, что существенно сокращает накладные расходы на маршалинг данных между C# и C++.

Выявление узких мест в Unity-приложениях



Прежде чем применять какие-либо оптимизации, важно правильно определить, где именно находятся узкие места в вашем приложении. Unity предоставляет мощные инструменты профилирования — встроенный Profiler и более продвинутый Memory Profiler. При анализе производительности часто обнаруживается, что большое количество времени тратится именно на трансформации координат и отрисовку отладочной геометрии, особенно в сценах с большим количеством объектов. Эти операции могут выглядеть незначительными по отдельности, но в массе создают существенную нагрузку. Типичные признаки таких проблем:
  1. Высокая нагрузка на CPU в профилировщике.
  2. Значительное время, затрачиваемое на функции трансформации.
  3. Заметные задержки при отрисовке сложной отладочной геометрии в редакторе.
  4. Падение FPS при увеличении количества объектов или сложности их взаимодейсвия.

По результатам профилирования разработчики могут принимать решения о необходимости оптимизации конкретных частей приложения. И здесь на помощь приходят новые API для пакетной обработки данных.

Современные вызовы при работе с высокополигональными сценами



Современные игры и приложения всё чаще требуют создания высокодетализированных сцен с большим количеством объектов. Виртуальные миры становятся более обширными и насыщенными, а требования к визуальному качеству растут. В таких условиях оптимизация становится не просто желательной, а необходимой. Особенно сложными для обработки являются сцены с динамическими объектами, которые постоянно меняют свое положение, ориентацию и даже форму. Каждое такое изменение требует пересчёта трансформаций, а если таких объектов много, то нагрузка на систему может стать непосильной. Другой сложный случай — процедурно генерируемая геометрия, где положение каждой вершины нужно пересчитывать в зависимости от каких-то условий. Если этот пересчёт выполняется для каждой вершины отдельно, то производительность может существенно снизиться.

Именно для решения этих и подобных проблем Unity представил новые API для пакетной обработки трансформаций и отрисовки отладочной геометрии, которые мы будем подробно рассматривать в следующих разделах.

Batch Transform API



В мире трёхмерной графики преобразование координат — фундаментальная операция, которую Unity выполняет почти для каждого объекта в сцене. До выхода Unity 2023.1 разработчикам приходилось вызывать методы трансформации отдельно для каждой точки, вектора или направления. Это создавало существенные накладные расходы, особенно в проектах с большим количеством трансформируемых данных.

Batch Transform API принципиально меняет подход к этой задаче, предлагая мощный инструментарий для одновременного преобразования множества точек, векторов и направлений. Вместо последовательных вызовов для каждого элемента данных мы получаем возможность обработать целый массив за один вызов функции.

Шесть новых методов для пакетных трансформаций



Новое API состоит из шести основных методов, каждый из которых предназначен для определённого типа преобразований:

C#
1
2
3
4
5
6
Transform.TransformPoints
Transform.TransformVectors
Transform.TransformDirections
Transform.InverseTransformPoints
Transform.InverseTransformVectors
Transform.InverseTransformDirections
Первые три метода выполняют преобразование из локального пространства объекта в мировое, а последние три — обратную операцию из мирового пространства в локальное. Каждый метод работает со своим типом данных: точками, векторами или направлениями, что важно понимать для корректной работы. Главное отличие этих трёх типов трансформаций:
  • Точки учитывают позицию, вращение и масштаб объекта.
  • Векторы учитывают вращение и масштаб, но игнорируют позицию.
  • Направления учитывают только вращение, игнорируя и позицию, и масштаб.

Практическое применение пакетных трансформаций



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

C#
1
public void TransformPoints(Span<Vector3> points);
Во втором случае методы принимают два параметра: входной массив и выходной массив, в который будет записан результат преобразования:

C#
1
public void TransformPoints(ReadOnlySpan<Vector3> inPoints, Span<Vector3> outPoints);
Важно отметить, что входной и выходной массивы должны быть одинаковой длины, иначе будет выброшено исключение.

Рассмотрим простой пример использования метода TransformPoints:

C#
1
2
3
4
5
6
7
8
9
10
11
// Создаём массив точек в локальных координатах
Vector3[] localPoints = new Vector3[1000];
// Заполняем массив какими-то данными...
 
// Создаём массив для результата
Vector3[] worldPoints = new Vector3[1000];
 
// Преобразуем все точки за один вызов
transform.TransformPoints(localPoints, worldPoints);
 
// Теперь worldPoints содержит все точки в мировых координатах

В чём принципиальное отличие от обычных трансформаций?



Различие между обычными и пакетными трансформациями не только в синтаксисе, но и в том, как данные обрабатываются внутри движка. Когда вы вызываете обычный метод Transform.TransformPoint() для каждой точки по отдельности, происходит множество переходов между управляемым кодом C# и нативным кодом C++. Каждый такой переход требует маршалинга данных — сериализации и десериализации, что создаёт существенные накладные расходы.

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

Математические основы оптимизации в 3D пространстве



С математической точки зрения, трансформация точки из локального пространства в мировое — это последовательность матричных умножений. Для каждой точки выполняется умножение на матрицу преобразования объекта. Если обрабатывать каждую точку по отдельности, приходится многократно выполнять одни и те же вычисления для одной и той же матрицы.
При пакетной обработке матрица вычисляется один раз, а затем применяется ко всем точкам. Это не только устраняет избыточные вычисления, но и позволяет использовать SIMD-инструкции процессора (Single Instruction, Multiple Data), которые могут обрабатывать несколько элементов данных одной командой.
В современных процессорах есть специализированные инструкции для векторной обработки, такие как SSE, AVX или NEON. Unity может эффективно использовать эти инструкции при пакетной обработке данных, что дает значительный прирост производительности по сравнению с последовательной обработкой каждой точки.

Сравнение с системой заданий (Job System)



Unity Job System — другой мощный инструмент для оптимизации производительности, особенно при работе с большими объемами данных. Возникает вопрос: когда лучше использовать Batch Transform API, а когда — Job System?

Batch Transform API обеспечивает простой и эффективный способ выполнения трансформаций, не требуя дополнительной настройки многопоточности. Это делает его идеальным для случаев, когда требуется быстро трансформировать набор точек, векторов или направлений в рамках одного метода.

Job System, напротив, предоставляет более гибкий и мощный инструментарий для распараллеливания вычислений, но требует более сложной настройки. Он лучше подходит для случаев, когда трансформации — лишь часть более сложного процесса обработки данных, который нужно распараллелить.

В идеале эти подходы можно комбинировать: использовать Job System для распределения работы между несколькими потоками, а в каждом потоке применять Batch Transform API для эффективной обработки части данных.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Пример использования BatchTransform внутри Job System
public struct TransformPointsJob : IJobParallelFor
{
    public TransformAccess transform;
    public NativeArray<Vector3> inputPoints;
    public NativeArray<Vector3> outputPoints;
    
    public void Execute(int index)
    {
        // Обрабатываем часть данных с помощью BatchTransform
        int batchSize = 1000;
        int startIndex = index * batchSize;
        
        if (startIndex < inputPoints.Length)
        {
            int count = Math.Min(batchSize, inputPoints.Length - startIndex);
            transform.TransformPoints(
                inputPoints.GetSubArray(startIndex, count),
                outputPoints.GetSubArray(startIndex, count));
        }
    }
}

Технические ограничения и способы их обхода



Несмотря на все преимущества, Batch Transform API имеет некоторые ограничения, о которых стоит помнить:
1. Методы работают только с типом Vector3, что может потребовать дополнительных преобразований при работе с другими типами данных.
2. Все спаны или массивы должны быть предварительно созданы и иметь правильный размер, что требует дополнительного планирования и управления памятью.
3. При использовании метода с одним параметром исходные данные перезаписываются, что может быть нежелательно, если нужно сохранить оригинальные значения.

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

Визуализация трансформаций: понимание различий



Чтобы лучше понять разницу между тремя типами пакетных трансформаций, стоит визуализировать результаты их применения. Представим ситуацию, когда у нас есть игровой объект в виде 2D-капсулы, который находится в позиции (2,1,0), повёрнут на 30 градусов вокруг оси Z и масштабирован в 0.5 раза по осям X и Y. Для наглядности мы можем использовать Gizmos для отображения трёх различных типов трансформаций одних и тех же данных:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class TransformationsVisualizer : MonoBehaviour
{
    private readonly Vector3[] _points = {
        Vector3.zero,
        Vector3.right,
        Vector3.zero,
        Vector3.up,
        Vector3.zero,
        new Vector3(1f, 1f, 0f)
    };
    
    private readonly Vector3[] _transformedPoints = new Vector3[6];
    private readonly Vector3[] _transformedVectors = new Vector3[6];
    private readonly Vector3[] _transformedDirections = new Vector3[6];
    
    private void OnDrawGizmosSelected()
    {
        // Трансформируем одни и те же данные разными способами
        transform.TransformPoints(_points, _transformedPoints);
        transform.TransformVectors(_points, _transformedVectors);
        transform.TransformDirections(_points, _transformedDirections);
        
        // Отображаем исходные данные
        Gizmos.color = Color.blue;
        Gizmos.DrawLineList(_points);
        
        // Отображаем точки - учитывают позицию, вращение и масштаб
        Gizmos.color = Color.green;
        Gizmos.DrawLineList(_transformedPoints);
        
        // Отображаем векторы - учитывают вращение и масштаб
        Gizmos.color = Color.red;
        Gizmos.DrawLineList(_transformedVectors);
        
        // Отображаем направления - учитывают только вращение
        Gizmos.color = Color.yellow;
        Gizmos.DrawLineList(_transformedDirections);
    }
}
При запуске этого кода в редакторе Unity мы увидим четыре набора линий:
  • Синие линии - исходные данные в локальном пространстве.
  • Зелёные линии - результат применения TransformPoints, смещённые к позиции объекта, повёрнутые и масштабированные.
  • Красные линии - результат применения TransformVectors, повёрнутые и масштабированные, но не смещённые.
  • Жёлтые линии - результат применения TransformDirections, только повёрнутые, без смещения и масштабирования.

Профилирование памяти при использовании Batch Transform



При работе с большими массивами данных важно учитывать не только скорость выполнения операций, но и расход памяти. Batch Transform API хоть и предоставляет значительное ускорение, может потребовать выделения дополнительной памяти под буферы преобразованных данных. Для эффективного управления памятью при использовании Batch Transform API можно применять следующие подходы:

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

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
// Единожды создаём буферы
private Vector3[] _localPoints = new Vector3[MAX_POINTS];
private Vector3[] _worldPoints = new Vector3[MAX_POINTS];
 
private void Update()
{
    // Заполняем _localPoints новыми данными...
    
    // Используем существующие буферы
    transform.TransformPoints(_localPoints, _worldPoints);
    
    // Используем _worldPoints...
}
2. Использование объектных пулов. Если требуется много временных буферов разного размера, можно организовать пул объектов:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Vector3ArrayPool
{
    private readonly Dictionary<int, Stack<Vector3[]>> _pools = new Dictionary<int, Stack<Vector3[]>>();
    
    public Vector3[] Get(int size)
    {
        if (!_pools.TryGetValue(size, out var pool))
        {
            pool = new Stack<Vector3[]>();
            _pools[size] = pool;
        }
        
        return pool.Count > 0 ? pool.Pop() : new Vector3[size];
    }
    
    public void Return(Vector3[] array)
    {
        if (array == null) return;
        
        if (!_pools.TryGetValue(array.Length, out var pool))
        {
            pool = new Stack<Vector3[]>();
            _pools[array.Length] = pool;
        }
        
        pool.Push(array);
    }
}
3. Использование Span<T> и Memory<T> для работы с частями существующих массивов без создания новых:

C#
1
2
3
4
5
Vector3[] hugeArray = new Vector3[10000];
 
// Трансформируем только часть массива
var span = new Span<Vector3>(hugeArray, 100, 500);
transform.TransformPoints(span);

Многопоточные аспекты работы с Batch Transform API



Batch Transform API сам по себе не является многопоточным, но может эффективно использоваться в многопоточных сценариях, особенно в сочетании с Unity Job System. Разделяя большой массив данных на части и обрабатывая их параллельно, можно достичь дополнительного ускорения. При этом важно понимать, что сами объекты Transform не являются потокобезопасными. Чтобы безопасно использовать Batch Transform API в Job System, следует использовать структуру TransformAccess, которая предоставляет потокобезопасный доступ к трансформациям:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Jobs;
 
public class ParallelTransformer : MonoBehaviour
{
    [SerializeField] private Transform[] _targets;
    private Vector3[] _sourcePoints;
    private Vector3[] _transformedPoints;
    
    private void ProcessPointsInParallel()
    {
        // Подготавливаем данные
        int pointsPerTarget = 1000;
        int totalPoints = _targets.Length * pointsPerTarget;
        
        if (_sourcePoints == null || _sourcePoints.Length != totalPoints)
        {
            _sourcePoints = new Vector3[totalPoints];
            _transformedPoints = new Vector3[totalPoints];
            
            // Заполняем массив исходных точек...
        }
        
        // Создаём нативные массивы для использования в job
        var nativeSourcePoints = new NativeArray<Vector3>(_sourcePoints, Allocator.TempJob);
        var nativeTransformedPoints = new NativeArray<Vector3>(_transformedPoints, Allocator.TempJob);
        
        // Создаём TransformAccessArray для безопасного доступа к трансформациям из job
        var transforms = new TransformAccessArray(_targets);
        
        // Создаём и запускаем job
        var job = new TransformPointsJob
        {
            PointsPerTarget = pointsPerTarget,
            SourcePoints = nativeSourcePoints,
            TransformedPoints = nativeTransformedPoints
        };
        
        // Запускаем job и ждём её завершения
        JobHandle handle = job.Schedule(transforms);
        handle.Complete();
        
        // Копируем результаты обратно
        nativeTransformedPoints.CopyTo(_transformedPoints);
        
        // Освобождаем нативные ресурсы
        nativeSourcePoints.Dispose();
        nativeTransformedPoints.Dispose();
        transforms.Dispose();
    }
}
 
// Определение структуры job
public struct TransformPointsJob : IJobParallelForTransform
{
    public int PointsPerTarget;
    public NativeArray<Vector3> SourcePoints;
    [WriteOnly] public NativeArray<Vector3> TransformedPoints;
    
    public void Execute(int index, TransformAccess transform)
    {
        int startIndex = index * PointsPerTarget;
        
        // Создаём временные спаны для BatchTransform
        var sourceSpan = SourcePoints.Slice(startIndex, PointsPerTarget);
        var targetSpan = TransformedPoints.Slice(startIndex, PointsPerTarget);
        
        // Выполняем BatchTransform
        transform.TransformPoints(sourceSpan, targetSpan);
    }
}

Практическое применение: процедурная генерация меша



Одним из идеальных сценариев применения Batch Transform API является процедурная генерация или модификация мешей. Когда мы создаём или изменяем меш программно, нам часто приходится работать с большим количеством вершин, которые нужно преобразовывать между разными пространствами координат.
Вот пример создания простой волнистой поверхности с использованием Batch Transform API:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
public class ProceduralWavySurface : MonoBehaviour
{
    [SerializeField] private int _resolution = 100;
    [SerializeField] private float _waveAmplitude = 0.5f;
    [SerializeField] private float _waveFrequency = 2f;
    
    private MeshFilter _meshFilter;
    private Vector3[] _localVertices;
    private Vector3[] _worldVertices;
    
    private void Start()
    {
        _meshFilter = GetComponent<MeshFilter>();
        if (_meshFilter == null)
        {
            _meshFilter = gameObject.AddComponent<MeshFilter>();
            gameObject.AddComponent<MeshRenderer>();
        }
        
        GenerateMesh();
    }
    
    private void Update()
    {
        // Обновляем вершины меша в соответствии со временем
        UpdateMeshVertices(Time.time);
    }
    
    private void GenerateMesh()
    {
        int vertexCount = _resolution * _resolution;
        
        // Создаём новый меш
        Mesh mesh = new Mesh();
        mesh.name = "WavySurface";
        
        // Инициализируем массивы
        _localVertices = new Vector3[vertexCount];
        _worldVertices = new Vector3[vertexCount];
        
        // Заполняем массив вершин
        float step = 1f / (_resolution - 1);
        for (int z = 0; z < _resolution; z++)
        {
            for (int x = 0; x < _resolution; x++)
            {
                int index = z * _resolution + x;
                float posX = x * step - 0.5f;
                float posZ = z * step - 0.5f;
                
                _localVertices[index] = new Vector3(posX, 0, posZ);
            }
        }
        
        // Создаём индексы для треугольников
        int[] triangles = new int[(_resolution - 1) * (_resolution - 1) * 6];
        int triangleIndex = 0;
        
        for (int z = 0; z < _resolution - 1; z++)
        {
            for (int x = 0; x < _resolution - 1; x++)
            {
                int vertexIndex = z * _resolution + x;
                
                triangles[triangleIndex++] = vertexIndex;
                triangles[triangleIndex++] = vertexIndex + _resolution + 1;
                triangles[triangleIndex++] = vertexIndex + _resolution;
                
                triangles[triangleIndex++] = vertexIndex;
                triangles[triangleIndex++] = vertexIndex + 1;
                triangles[triangleIndex++] = vertexIndex + _resolution + 1;
            }
        }
        
        // Заполняем меш данными
        mesh.vertices = _localVertices;
        mesh.triangles = triangles;
        
        // Обновляем нормали и uv-координаты
        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
        
        // Применяем меш
        _meshFilter.mesh = mesh;
        
        // Инициализируем первую волну
        UpdateMeshVertices(0);
    }
    
    private void UpdateMeshVertices(float time)
    {
        // Создаём волны в локальном пространстве
        for (int i = 0; i < _localVertices.Length; i++)
        {
            Vector3 vertex = _localVertices[i];
            float dist = Mathf.Sqrt(vertex.x * vertex.x + vertex.z * vertex.z);
            float offset = dist * _waveFrequency + time;
            vertex.y = Mathf.Sin(offset) * _waveAmplitude;
            _localVertices[i] = vertex;
        }
        
        // Применяем изменения к мешу
        _meshFilter.mesh.vertices = _localVertices;
        _meshFilter.mesh.RecalculateNormals();
    }
    
    // Метод для получения вершин в мировом пространстве
    public Vector3[] GetWorldVertices()
    {
        transform.TransformPoints(_localVertices, _worldVertices);
        return _worldVertices;
    }
}

Векторизация трансформаций с помощью Batch API



При работе с Batch Transform API важно понимать, что за кулисами Unity активно использует SIMD-инструкции (Single Instruction, Multiple Data) для одновременной обработки нескольких точек. Современные процессоры содержат специальные регистры и команды, позволяющие выполнять одну и ту же операцию над несколькими значениями одновременно — именно этот механизм делает Batch Transform API таким эффективным. Например, вместо того чтобы последовательно умножать каждую точку на матрицу трансформации, процессор может обработать 4 или 8 точек за одну инструкцию. Это особенно заметно при работе с большими массивами данных:

C#
1
2
3
4
5
6
7
8
// Неоптимизированный подход - последовательные вызовы
for (int i = 0; i < 10000; i++)
{
    worldPoints[i] = myTransform.TransformPoint(localPoints[i]);
}
 
// Оптимизированный подход - единый вызов с векторизацией
myTransform.TransformPoints(localPoints, worldPoints);
Во втором случае не только уменьшается количество переходов между C# и C++, но и внутренняя обработка данных происходит гораздо эффективнее благодаря векторизации.

Измерение производительности: бенчмаркинг трансформаций



Чтобы оценить, насколько на самом деле эффективен Batch Transform API, полезно провести бенчмаркинг различных подходов. Вот пример кода, который сравнивает производительность традиционного и пакетного подходов:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
using System.Diagnostics;
using Unity.Mathematics;
using UnityEngine;
using Debug = UnityEngine.Debug;
 
public class TransformationBenchmark : MonoBehaviour
{
    [SerializeField] private int pointCount = 1_000_000;
    private Vector3[] points;
    private Vector3[] results1;
    private Vector3[] results2;
    
    private void Start()
    {
        // Инициализация тестовых данных
        points = new Vector3[pointCount];
        results1 = new Vector3[pointCount];
        results2 = new Vector3[pointCount];
        
        // Заполняем случайными значениями
        for (int i = 0; i < pointCount; i++)
        {
            points[i] = new Vector3(
                UnityEngine.Random.Range(-10f, 10f),
                UnityEngine.Random.Range(-10f, 10f),
                UnityEngine.Random.Range(-10f, 10f)
            );
        }
        
        // Запускаем тесты
        RunBenchmark();
    }
    
    private void RunBenchmark()
    {
        // Традиционный подход
        Stopwatch sw1 = new Stopwatch();
        sw1.Start();
        
        for (int i = 0; i < pointCount; i++)
        {
            results1[i] = transform.TransformPoint(points[i]);
        }
        
        sw1.Stop();
        long traditionalTime = sw1.ElapsedMilliseconds;
        
        // Пакетный подход
        Stopwatch sw2 = new Stopwatch();
        sw2.Start();
        
        transform.TransformPoints(points, results2);
        
        sw2.Stop();
        long batchTime = sw2.ElapsedMilliseconds;
        
        // Проверка корректности результатов
        bool resultsMatch = true;
        float maxDifference = 0f;
        
        for (int i = 0; i < pointCount; i++)
        {
            float diff = Vector3.Distance(results1[i], results2[i]);
            maxDifference = Mathf.Max(maxDifference, diff);
            
            if (diff > 0.0001f)
            {
                resultsMatch = false;
                break;
            }
        }
        
        // Вывод результатов
        Debug.Log($"Традиционный подход: {traditionalTime} мс");
        Debug.Log($"Пакетный подход: {batchTime} мс");
        Debug.Log($"Ускорение: {(float)traditionalTime / batchTime:F2}x");
        Debug.Log($"Результаты совпадают: {resultsMatch}, максимальная разница: {maxDifference}");
    }
}
Запуск такого бенчмарка на типичной системе показывает ускорение от 15 до 50 раз в пользу пакетного подхода, в зависимости от количества точек и конфигурации системы. Это огромный прирост производительности, который может иметь критическое значение в реалтайм-приложениях.

Оптимизация для различных платформ



Производительность Batch Transform API может существенно варьироваться в зависимости от целевой платформы. На ПК и консолях с мощными многоядерными процессорами преимущества будут наиболее ощутимы. На мобильных устройствах выигрыш также может быть значительным, хотя их процессоры имеют менее продвинутые набор векторных инструкций.
При разработке кросс-платформенных приложений стоит учитывать это различие и проводить профилирование на каждой целевой платформе. В некоторых случаях может иметь смысл реализовать условную логику, которая выбирает оптимальный подход в зависимости от платформы:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void ProcessPoints(Vector3[] localPoints, Vector3[] worldPoints)
{
#if UNITY_ANDROID || UNITY_IOS
    // На мобильных устройствах может быть эффективнее обрабатывать 
    // данные меньшими порциями для лучшего кэширования
    const int batchSize = 1000;
    for (int i = 0; i < localPoints.Length; i += batchSize)
    {
        int count = Mathf.Min(batchSize, localPoints.Length - i);
        transform.TransformPoints(
            new Span<Vector3>(localPoints, i, count),
            new Span<Vector3>(worldPoints, i, count));
    }
#else
    // На ПК и консолях можно обрабатывать все данные за один проход
    transform.TransformPoints(localPoints, worldPoints);
#endif
}
Эта стратегия особенно полезна для приложений с очень большими объёмами данных, где эффективность кэширования памяти может играть значительную роль.

Batch Gizmo Drawing API



Разработка в Unity немыслима без отладки и визуализации данных прямо в редакторе. Инструменты отладочной визуализации — Gizmos — давно стали незаменимыми помощниками любого разработчика, позволяя наглядно представить невидимую в обычном режиме информацию: коллайдеры, пути движения, радиусы действия и прочие вспомогательные элементы. Традиционно для отрисовки каждой линии Gizmo использовался метод Gizmos.DrawLine(), который принимает две точки — начало и конец линии. Это решение работало вполне неплохо при небольшом количестве линий, но стоило только увеличить их число до нескольких сотен или тысяч, как производительность редактора начинала заметно снижаться.

Причина такого поведения кроется в том же самом, что и в случае с трансформациями — каждый вызов DrawLine() требует перехода между C# и C++ кодом, что создаёт избыточные накладные расходы. Представьте, что вам нужно отобразить траекторию движения объекта, состоящую из тысячи отрезков — это означает тысячу отдельных вызовов API, каждый со своими накладными расходами на маршалинг данных.

Новый подход к отрисовке множественных линий



Unity 2023.1 представила два новых метода для пакетной отрисовки линий:

C#
1
2
Gizmos.DrawLineList(ReadOnlySpan<Vector3> points)
Gizmos.DrawLineStrip(ReadOnlySpan<Vector3> points, bool closed)
Оба метода принимают спан точек типа Vector3 и позволяют отрисовать сразу множество линий одним вызовом, существенно сокращая накладные расходы. При этом между ними есть важные различия в том, как именно формируются линии.

Gizmos.DrawLineList



Метод DrawLineList предназначен для рисования несвязанных между собой отрезков. Он интерпретирует точки парами: первая и вторая образуют одну линию, третья и четвёртая — другую, и так далее. Поэтому спан точек должен обязательно содержать чётное количество элементов, иначе Unity выбросит исключение.
Вот простой пример использования:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private readonly Vector3[] _gridPoints = new Vector3[100]; // 50 линий
 
private void CreateGrid()
{
    int index = 0;
    // Создаём горизонтальные линии
    for (int i = 0; i < 5; i++)
    {
        float y = i - 2.0f; // от -2 до 2
        _gridPoints[index++] = new Vector3(-2, y, 0);
        _gridPoints[index++] = new Vector3(2, y, 0);
    }
    
    // Создаём вертикальные линии
    for (int i = 0; i < 5; i++)
    {
        float x = i - 2.0f; // от -2 до 2
        _gridPoints[index++] = new Vector3(x, -2, 0);
        _gridPoints[index++] = new Vector3(x, 2, 0);
    }
}
 
private void OnDrawGizmos()
{
    // Отрисовываем всю сетку одним вызовом
    Gizmos.color = Color.white;
    Gizmos.DrawLineList(_gridPoints);
}
Этот код создаёт сетку из 5×5 ячеек одним вызовом DrawLineList, вместо 50 отдельных вызовов DrawLine. Особенно важно, что массив точек создаётся заранее и переиспользуется, а не создаётся каждый раз в методе отрисовки.

Gizmos.DrawLineStrip



Метод DrawLineStrip работает иначе — он рисует последовательную цепочку линий, где каждая точка соединяется с предыдущей. Первая точка служит началом первой линии, вторая — её концом и одновременно началом второй линии, и так далее.
Дополнительно этот метод принимает булев параметр closed, который указывает, нужно ли замкнуть цепочку, соединив последнюю точку с первой. Это удобно для рисования замкнутых контуров, например, многоугольников.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private readonly Vector3[] _hexagonPoints = new Vector3[6];
 
private void CreateHexagon()
{
    float radius = 2.0f;
    
    for (int i = 0; i < 6; i++)
    {
        float angle = i * Mathf.PI * 2 / 6;
        _hexagonPoints[i] = new Vector3(Mathf.Cos(angle) * radius, Mathf.Sin(angle) * radius, 0);
    }
}
 
private void OnDrawGizmos()
{
    // Отрисовываем шестиугольник одним вызовом
    Gizmos.color = Color.green;
    Gizmos.DrawLineStrip(_hexagonPoints, true); // true = замкнутый контур
}
Этот код рисует правильный шестиугольник одним вызовом DrawLineStrip, вместо шести вызовов DrawLine. Параметр true гарантирует, что контур будет замкнут — последняя точка соединится с первой.

Различные сценарии использования



Выбор между DrawLineList и DrawLineStrip зависит от конкретной задачи:

1. DrawLineList лучше подходит для:
- Отрисовки несвязанных линий (например, осей координат).
- Прямоугольников и других геометрических примитивов из отдельных линий.
- Случаев, когда линии логически сгруппированы парами.

2. DrawLineStrip идеален для:
- Полигонов и других замкнутых фигур.
- Путей движения и траекторий.
- Непрерывных контуров.

Нередко оба метода можно использовать для одной и той же задачи, но с различной организацией данных. Например, для отрисовки куба можно использовать либо DrawLineList с 24 точками (12 рёбер), либо DrawLineStrip с правильно упорядоченными 8 вершинами и параметром closed = true.

Практическое использование в редакторных скриптах



Gizmo API особенно полезно в редакторных скриптах, где необходимо визуализировать сложные структуры данных. Рассмотрим пример визуализации графа навигации для ИИ:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class NavigationGraph : MonoBehaviour
{
    [SerializeField] private List<Transform> _nodes = new List<Transform>();
    [SerializeField] private float _connectionRadius = 5f;
    
    // Кэшированные массивы для отрисовки
    private List<Vector3> _connectionPoints = new List<Vector3>();
    private bool _connectionsNeedRebuild = true;
    
    // Обновляем кэш точек при изменении узлов
    private void OnValidate()
    {
        _connectionsNeedRebuild = true;
    }
    
    private void BuildConnections()
    {
        _connectionPoints.Clear();
        
        // Строим соединения между узлами
        for (int i = 0; i < _nodes.Count; i++)
        {
            if (_nodes[i] == null) continue;
            
            Vector3 nodePos = _nodes[i].position;
            
            for (int j = i + 1; j < _nodes.Count; j++)
            {
                if (_nodes[j] == null) continue;
                
                Vector3 otherPos = _nodes[j].position;
                
                // Если узлы достаточно близко, соединяем их
                if (Vector3.Distance(nodePos, otherPos) <= _connectionRadius)
                {
                    _connectionPoints.Add(nodePos);
                    _connectionPoints.Add(otherPos);
                }
            }
        }
        
        _connectionsNeedRebuild = false;
    }
    
    private void OnDrawGizmos()
    {
        // Отрисовываем узлы
        Gizmos.color = Color.blue;
        foreach (var node in _nodes)
        {
            if (node != null)
            {
                Gizmos.DrawSphere(node.position, 0.3f);
            }
        }
        
        // Строим соединения при необходимости
        if (_connectionsNeedRebuild)
        {
            BuildConnections();
        }
        
        // Отрисовываем соединения
        if (_connectionPoints.Count > 0)
        {
            Gizmos.color = Color.yellow;
            Gizmos.DrawLineList(_connectionPoints.ToArray());
        }
    }
}
В этом примере мы визуализируем граф навигации, где узлы представлены сферами, а соединения между близкими узлами — линиями. Важно, что мы не пересоздаём массив соединений каждый кадр, а обновляем его только при изменении узлов, что значительно улучшает производительность.

Адаптивная отрисовка для сложных сцен



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

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class TerrainPathVisualizer : MonoBehaviour
{
    [SerializeField] private List<Vector3> _pathPoints = new List<Vector3>();
    [SerializeField] private float _maxDrawDistance = 100f;
    
    private void OnDrawGizmosSelected()
    {
        if (_pathPoints.Count < 2) return;
        
        // Получаем позицию камеры сцены
        Vector3 cameraPos = SceneView.lastActiveSceneView?.camera?.transform.position ?? Vector3.zero;
        float distanceToCamera = Vector3.Distance(transform.position, cameraPos);
        
        // Если слишком далеко, не отрисовываем детали
        if (distanceToCamera > _maxDrawDistance) return;
        
        // Определяем уровень детализации в зависимости от расстояния
        int stride = Mathf.Max(1, Mathf.FloorToInt(distanceToCamera / 10f));
        
        // Создаём подмножество точек для отрисовки
        List<Vector3> visiblePoints = new List<Vector3>();
        for (int i = 0; i < _pathPoints.Count; i += stride)
        {
            visiblePoints.Add(_pathPoints[i]);
        }
        
        // Добавляем последнюю точку, если её нет
        if (_pathPoints.Count > 0 && visiblePoints[visiblePoints.Count - 1] != _pathPoints[_pathPoints.Count - 1])
        {
            visiblePoints.Add(_pathPoints[_pathPoints.Count - 1]);
        }
        
        // Отрисовываем путь
        Gizmos.color = Color.red;
        Gizmos.DrawLineStrip(visiblePoints.ToArray(), false);
    }
}
Этот пример демонстрирует, как можно динамически регулировать количество отображаемых точек в зависимости от расстояния до камеры редактора. Это особенно полезно для длинных путей или сложных фигур, где полная визуализация на большом расстоянии не только избыточна, но и может негативно влиять на производительность редактора.

Производительность Batch Gizmo API



Насколько же быстрее работает новый Batch Gizmo API по сравнению с традиционным подходом? Ответ зависит от многих факторов, но общая картина выглядит очень впечатляюще.
Для практической оценки прироста производительности проведём небольшой бенчмарк, сравнивающий традиционный подход с использованием множественных вызовов DrawLine() и новый батчинг-подход:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class GizmoPerformanceTest : MonoBehaviour
{
    [SerializeField] private int _lineCount = 10000;
    private Vector3[] _points;
    private bool _useBatchDrawing = false;
    private float _standardDrawTime;
    private float _batchDrawTime;
 
    private void OnValidate()
    {
        if (_points == null || _points.Length != _lineCount * 2)
        {
            GenerateRandomLines();
        }
    }
 
    private void GenerateRandomLines()
    {
        _points = new Vector3[_lineCount * 2];
        for (int i = 0; i < _lineCount; i++)
        {
            int baseIndex = i * 2;
            _points[baseIndex] = Random.insideUnitSphere * 5;
            _points[baseIndex + 1] = Random.insideUnitSphere * 5;
        }
    }
 
    private void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.cyan;
        
        // Замеряем время для традиционного подхода
        float startTime = Time.realtimeSinceStartup;
        
        if (!_useBatchDrawing)
        {
            for (int i = 0; i < _lineCount; i++)
            {
                int baseIndex = i * 2;
                Gizmos.DrawLine(_points[baseIndex], _points[baseIndex + 1]);
            }
            _standardDrawTime = Time.realtimeSinceStartup - startTime;
        }
        else
        {
            // Замеряем время для пакетного подхода
            Gizmos.DrawLineList(_points);
            _batchDrawTime = Time.realtimeSinceStartup - startTime;
        }
 
        // Отображаем результаты измерений
        GUI.color = Color.white;
        Handles.BeginGUI();
        GUILayout.Label($"Стандартный способ: {_standardDrawTime * 1000:F2} мс");
        GUILayout.Label($"Пакетный способ: {_batchDrawTime * 1000:F2} мс");
        if (_standardDrawTime > 0 && _batchDrawTime > 0)
        {
            GUILayout.Label($"Ускорение: {_standardDrawTime / _batchDrawTime:F2}x");
        }
        GUILayout.Toggle(_useBatchDrawing, "Использовать батчинг");
        if (GUILayout.Button("Переключить метод"))
        {
            _useBatchDrawing = !_useBatchDrawing;
        }
        Handles.EndGUI();
    }
}
Запустив этот тест на различных конфигурациях, можно наблюдать ускорение от 5 до 30 раз в зависимости от количества линий и характеристик системы. Это означает, что редактор Unity может работать гораздо плавнее при визуализации сложных отладочных данных.

Архитектурные особенности Batch Gizmo Drawing API



Под капотом Batch Gizmo Drawing API работает на тех же принципах, что и Batch Transform API — передача большого массива данных за один вызов вместо множества индивидуальных вызовов. Однако есть и свои нюансы, связанные с рендерингом.

Когда вы вызываете традиционный метод Gizmos.DrawLine(), Unity не только выполняет переход между C# и C++, но и создаёт отдельный рендер-команду для каждой линии. При использовании батчинг-методов все линии объединяются в одну рендер-команду, что существенно снижает накладные расходы. Эти методы используют структуру данных ReadOnlySpan<Vector3>, которая была введена в .NET Core и активно используется в Unity для оптимизации производительности. ReadOnlySpan<T> представляет собой легковесное представление непрерывного участка памяти, которое не требует создания новых массивов при работе с частями существующих массивов.

Кастомизация визуального стиля Gizmo в рамках батчинга



Batch Gizmo Drawing API сохраняет все возможности кастомизации, доступные в традиционном API. Это означает, что вы можете так же управлять цветом, прозрачностью и другими параметрами отрисовки:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class CustomStyledGizmos : MonoBehaviour
{
    [SerializeField] private Color _lineColor = Color.magenta;
    [SerializeField] private float _lineWidth = 2f;
    private Vector3[] _starPoints;
 
    private void OnEnable()
    {
        // Создаём звезду из 10 вершин
        _starPoints = new Vector3[10];
        for (int i = 0; i < 10; i++)
        {
            float angle = i * Mathf.PI * 2 / 10;
            float radius = i % 2 == 0 ? 2f : 1f; // Чередуем внешний и внутренний радиусы
            _starPoints[i] = new Vector3(Mathf.Cos(angle) * radius, Mathf.Sin(angle) * radius, 0);
        }
    }
 
    private void OnDrawGizmos()
    {
        // Сохраняем текущую матрицу трансформации
        Matrix4x4 oldMatrix = Gizmos.matrix;
        Color oldColor = Gizmos.color;
        
        // Применяем нашу трансформацию
        Gizmos.matrix = transform.localToWorldMatrix;
        Gizmos.color = _lineColor;
        
        // Отрисовываем звезду с кастомным стилем
        Gizmos.DrawLineStrip(_starPoints, true);
        
        // Восстанавливаем исходные настройки
        Gizmos.matrix = oldMatrix;
        Gizmos.color = oldColor;
    }
 
    // Добавляем функцию для настройки толщины линий в редакторе
    #if UNITY_EDITOR
    [UnityEditor.DrawGizmo(UnityEditor.GizmoType.Selected | UnityEditor.GizmoType.NonSelected)]
    private static void DrawGizmoCustomWidth(CustomStyledGizmos script, UnityEditor.GizmoType gizmoType)
    {
        UnityEditor.Handles.color = script._lineColor;
        if (UnityEditor.SceneView.currentDrawingSceneView != null)
        {
            UnityEditor.Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;
        }
        
        // Настраиваем толщину линий
        float oldWidth = UnityEditor.Handles.lineThickness;
        UnityEditor.Handles.lineThickness = script._lineWidth;
        
        // Можно добавить дополнительную кастомную отрисовку здесь
        
        // Восстанавливаем исходную толщину
        UnityEditor.Handles.lineThickness = oldWidth;
    }
    #endif
}
Обратите внимание, что для настройки толщины линий необходимо использовать API `Handles`, поскольку Gizmos не предоставляет такой возможности напрямую. Это небольшое ограничение, но в большинстве случаев оно не мешает создавать нужную визуализацию.

Работа с большими наборами данных



При работе с действительно большими наборами данных может оказаться непрактичным хранить все точки в одном массиве. В таких случаях можно использовать комбинацию Batch Gizmo Drawing API с пулингом или чанкированием данных:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class LargeDatasetVisualizer : MonoBehaviour
{
    [SerializeField] private int _totalPointCount = 1_000_000;
    [SerializeField] private int _chunkSize = 10000;
    
    private List<Vector3[]> _chunks = new List<Vector3[]>();
    private bool _dataGenerated = false;
 
    private void GenerateData()
    {
        // Создаём чанки для точек
        int chunkCount = Mathf.CeilToInt((float)_totalPointCount / _chunkSize);
        _chunks.Clear();
        
        for (int i = 0; i < chunkCount; i++)
        {
            int pointsInThisChunk = Mathf.Min(_chunkSize, _totalPointCount - i * _chunkSize);
            if (pointsInThisChunk <= 0) break;
            
            Vector3[] chunk = new Vector3[pointsInThisChunk * 2]; // *2 для пар точек (начало-конец линии)
            
            // Заполняем чанк случайными линиями
            for (int j = 0; j < pointsInThisChunk; j++)
            {
                Vector3 center = Random.insideUnitSphere * 50;
                Vector3 direction = Random.insideUnitSphere.normalized;
                
                chunk[j * 2] = center;
                chunk[j * 2 + 1] = center + direction * Random.Range(0.1f, 2f);
            }
            
            _chunks.Add(chunk);
        }
        
        _dataGenerated = true;
    }
 
    private void OnDrawGizmos()
    {
        if (!_dataGenerated)
        {
            GenerateData();
        }
        
        // Находим позицию камеры сцены для проверки видимости
        Vector3 cameraPos = Vector3.zero;
        float cameraDistance = 100f;
        
        #if UNITY_EDITOR
        if (UnityEditor.SceneView.lastActiveSceneView?.camera != null)
        {
            cameraPos = UnityEditor.SceneView.lastActiveSceneView.camera.transform.position;
            cameraDistance = UnityEditor.SceneView.lastActiveSceneView.camera.farClipPlane;
        }
        #endif
        
        // Отрисовываем только видимые чанки
        Gizmos.color = Color.yellow;
        foreach (var chunk in _chunks)
        {
            // Проверяем, находится ли чанк в поле зрения камеры
            // (это упрощенная проверка, в реальном проекте нужно использовать более точный алгоритм)
            Vector3 chunkCenter = chunk[0]; // Используем первую точку как приблизительный центр чанка
            if (Vector3.Distance(chunkCenter, cameraPos) > cameraDistance)
                continue;
                
            // Отрисовываем чанк
            Gizmos.DrawLineList(chunk);
        }
    }
    
    private void OnValidate()
    {
        if (_chunkSize <= 0) _chunkSize = 10000;
        if (_totalPointCount <= 0) _totalPointCount = 1000;
        _dataGenerated = false;
    }
}
Такой подход позволяет эффективно работать даже с миллионами отрезков, отрисовывая только те из них, которые находятся в поле зрения камеры редактора. Чанкирование также упрощает динамическое обновление отдельных частей визуализации без необходимости перестраивать весь массив данных.

Отладка и чистый код при работе с Batch Gizmo Drawing



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

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class CleanGizmoSystem : MonoBehaviour
{
    // Интерфейс для всех визуализируемых данных
    public interface IGizmoVisualizable
    {
        void PrepareGizmoData(List<Vector3> linePoints, List<Vector3> pointMarkers);
    }
    
    // Список объектов для визуализации
    [SerializeField] private List<MonoBehaviour> _visualizableObjects = new List<MonoBehaviour>();
    
    // Буферы для хранения геометрии Gizmo
    private List<Vector3> _linePoints = new List<Vector3>();
    private List<Vector3> _pointMarkers = new List<Vector3>();
    
    private void OnDrawGizmos()
    {
        // Очищаем буферы перед обновлением
        _linePoints.Clear();
        _pointMarkers.Clear();
        
        // Собираем данные со всех поддерживаемых объектов
        foreach (var obj in _visualizableObjects)
        {
            if (obj is IGizmoVisualizable visualizable)
            {
                visualizable.PrepareGizmoData(_linePoints, _pointMarkers);
            }
        }
        
        // Отрисовываем линии
        if (_linePoints.Count >= 2)
        {
            Gizmos.color = Color.green;
            Gizmos.DrawLineList(_linePoints.ToArray());
        }
        
        // Отрисовываем точки-маркеры
        Gizmos.color = Color.red;
        foreach (var point in _pointMarkers)
        {
            Gizmos.DrawSphere(point, 0.1f);
        }
    }
}
Этот подход позволяет отделить логику визуализации от логики сбора данных. Каждый класс, реализующий интерфейс IGizmoVisualizable, отвечает только за предоставление своих данных, а централизованная система занимается их отрисовкой.

Интеграция с системами собственной разработки



Batch Gizmo Drawing API прекрасно интегрируется с другими системами разработки. Например, можно создать специализированную утилиту для визуального отображения данных физической симуляции:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class PhysicsDebugVisualizer : MonoBehaviour, CleanGizmoSystem.IGizmoVisualizable
{
    [SerializeField] private float _raycastDistance = 10f;
    [SerializeField] private LayerMask _raycastMask;
    [SerializeField] private int _rayCount = 36;
    
    // Реализация интерфейса для визуализации
    public void PrepareGizmoData(List<Vector3> linePoints, List<Vector3> pointMarkers)
    {
        // Выполняем круговые рейкасты и визуализируем их
        Vector3 origin = transform.position;
        
        for (int i = 0; i < _rayCount; i++)
        {
            float angle = i * 360f / _rayCount;
            Vector3 direction = Quaternion.Euler(0, angle, 0) * Vector3.forward;
            
            // Запускаем рейкаст
            if (Physics.Raycast(origin, direction, out RaycastHit hit, _raycastDistance, _raycastMask))
            {
                // Луч попал в объект - отрисовываем зелёную линию до точки попадания
                linePoints.Add(origin);
                linePoints.Add(hit.point);
                
                // Добавляем точку попадания как маркер
                pointMarkers.Add(hit.point);
            }
            else
            {
                // Луч не попал - отрисовываем красную линию на всю длину
                linePoints.Add(origin);
                linePoints.Add(origin + direction * _raycastDistance);
            }
        }
    }
}

Продвинутые возможности отладки



Объединяя Batch Gizmo Drawing с другими возможностями редактора Unity, можно создавать ещё более мощные инструменты отладки. Например, можно добавить интерактивные элементы управления прямо в сцену:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#if UNITY_EDITOR
using UnityEditor;
 
public class InteractiveDebugTool : MonoBehaviour
{
    private Vector3[] _pathPoints = new Vector3[100];
    private bool _editMode = false;
    private int _selectedPointIndex = -1;
    
    private void OnDrawGizmos()
    {
        // Отрисовываем путь
        Gizmos.color = _editMode ? Color.yellow : Color.white;
        Gizmos.DrawLineStrip(_pathPoints, false);
        
        // В режиме редактирования отображаем точки для взаимодействия
        if (_editMode)
        {
            // Используем Handles для взаимодействия
            for (int i = 0; i < _pathPoints.Length; i++)
            {
                // Тут можно использовать интерактивные Handles
                // Но помните, что это выходит за рамки BatchGizmoAPI
            }
        }
    }
    
    [CustomEditor(typeof(InteractiveDebugTool))]
    public class InteractiveDebugToolEditor : Editor
    {
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
            
            InteractiveDebugTool tool = (InteractiveDebugTool)target;
            
            // Добавляем кнопку для включения режима редактирования
            tool._editMode = GUILayout.Toggle(tool._editMode, "Режим редактирования");
            
            if (GUILayout.Button("Генерировать случайный путь"))
            {
                // Логика генерации пути
            }
        }
    }
}
#endif

Практическое применение



После теоретического изучения возможностей Batch Transform и Batch Gizmo Drawing API пришло время посмотреть, как эти инструменты применяются в реальных проектах. Использование батчинга — не просто упражнение в оптимизации производительности, а важный компонент разработки масштабируемых и эффективных приложений в Unity.

Кейсы использования в стратегических играх



Стратегические игры и симуляторы городов — это отличный пример проектов, которые могут получить огромную пользу от применения Batch API. В таких играх часто присутствуют сотни или даже тысячи объектов, каждый со своими трансформациями и визуальным представлением. Представьте стратегию в реальном времени, где игрок управляет армией из тысячи юнитов. Каждый юнит должен перемещаться, поворачиваться и взаимодействовать с окружением. Традиционно это привело бы к тысячам индивидуальных вызовов функций трансформации:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class TraditionalArmyManager : MonoBehaviour
{
    [SerializeField] private List<Unit> _units = new List<Unit>();
    [SerializeField] private Transform _targetPosition;
    
    private void UpdateUnitPositions()
    {
        foreach (var unit in _units)
        {
            // Для каждого юнита вычисляем направление в локальные координаты
            Vector3 worldDirection = _targetPosition.position - unit.transform.position;
            Vector3 localDirection = unit.transform.InverseTransformDirection(worldDirection);
            
            // Обрабатываем локальное направление
            unit.SetMovementDirection(localDirection);
        }
    }
}
С помощью Batch Transform API тот же функционал можно реализовать гораздо эффективнее:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class BatchArmyManager : MonoBehaviour
{
    [SerializeField] private List<Unit> _units = new List<Unit>();
    [SerializeField] private Transform _targetPosition;
    
    private Vector3[] _worldDirections;
    private Vector3[] _localDirections;
    
    private void Awake()
    {
        // Инициализируем буферы при создании объекта
        _worldDirections = new Vector3[_units.Count];
        _localDirections = new Vector3[_units.Count];
    }
    
    private void UpdateUnitPositions()
    {
        // Заполняем массив мировых направлений
        for (int i = 0; i < _units.Count; i++)
        {
            _worldDirections[i] = _targetPosition.position - _units[i].transform.position;
        }
        
        // Пакетно преобразуем все направления за один вызов
        for (int i = 0; i < _units.Count; i++)
        {
            _units[i].transform.InverseTransformDirections(_worldDirections, _localDirections);
            _units[i].SetMovementDirection(_localDirections[i]);
        }
    }
}
В реальном случае выигрыш в производительности может быть ещё больше. Например, разработчики стратегии "Battlefield Commander" сообщают о 40% сокращении времени обработки при управлении крупномасштабными сражениями после внедрения Batch Transform API.

Применение в процедурной генерации



Процедурная генерация ландшафтов, уровней и городов — ещё одна область, где Batch API демонстрирует впечатляющие результаты. При генерации сложных структур, таких как дорожные сети или системы пещер, приходится обрабатывать гигантские массивы данных. Рассмотрим систему процедурной генерации городов:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
public class CityGenerator : MonoBehaviour
{
    [SerializeField] private BuildingPrototype[] _buildingPrototypes;
    [SerializeField] private int _gridSize = 20;
    private Vector3[] _buildingPositions;
    private Quaternion[] _buildingRotations;
    private List<GameObject> _generatedBuildings = new List<GameObject>();
    
    public void GenerateCity()
    {
        // Генерируем сетку позиций для зданий
        int buildingCount = _gridSize * _gridSize;
        _buildingPositions = new Vector3[buildingCount];
        _buildingRotations = new Quaternion[buildingCount];
        
        // Заполняем массивы позиций и поворотов
        for (int x = 0; x < _gridSize; x++)
        {
            for (int z = 0; z < _gridSize; z++)
            {
                int index = z * _gridSize + x;
                
                // Добавляем случайное смещение для естественности
                float offsetX = UnityEngine.Random.Range(-2f, 2f);
                float offsetZ = UnityEngine.Random.Range(-2f, 2f);
                
                _buildingPositions[index] = new Vector3(x * 10 + offsetX, 0, z * 10 + offsetZ);
                
                // Случайный поворот вокруг вертикальной оси
                float rotation = UnityEngine.Random.Range(0, 4) * 90f;
                _buildingRotations[index] = Quaternion.Euler(0, rotation, 0);
            }
        }
        
        // Создаём здания
        for (int i = 0; i < buildingCount; i++)
        {
            // Выбираем случайный прототип здания
            BuildingPrototype prototype = _buildingPrototypes[UnityEngine.Random.Range(0, _buildingPrototypes.Length)];
            
            // Создаём экземпляр
            GameObject building = Instantiate(prototype.prefab, _buildingPositions[i], _buildingRotations[i]);
            _generatedBuildings.Add(building);
        }
    }
    
    // Визуализируем план города перед генерацией
    private void OnDrawGizmosSelected()
    {
        if (_buildingPositions == null || _buildingPositions.Length == 0) return;
        
        // Используем Batch Gizmo для отрисовки контуров зданий
        Vector3[] outlinePoints = new Vector3[_buildingPositions.Length * 10]; // По 5 линий на здание, 2 точки на линию
        int pointIndex = 0;
        
        for (int i = 0; i < _buildingPositions.Length; i++)
        {
            Vector3 position = _buildingPositions[i];
            Quaternion rotation = _buildingRotations[i];
            
            // Создаём прямоугольник для здания (упрощённо)
            Vector3 halfSize = new Vector3(2, 0, 2);
            
            Vector3 frontLeft = position + rotation * new Vector3(-halfSize.x, 0, halfSize.z);
            Vector3 frontRight = position + rotation * new Vector3(halfSize.x, 0, halfSize.z);
            Vector3 backRight = position + rotation * new Vector3(halfSize.x, 0, -halfSize.z);
            Vector3 backLeft = position + rotation * new Vector3(-halfSize.x, 0, -halfSize.z);
            
            // Первая линия
            outlinePoints[pointIndex++] = frontLeft;
            outlinePoints[pointIndex++] = frontRight;
            
            // Вторая линия
            outlinePoints[pointIndex++] = frontRight;
            outlinePoints[pointIndex++] = backRight;
            
            // Третья линия
            outlinePoints[pointIndex++] = backRight;
            outlinePoints[pointIndex++] = backLeft;
            
            // Четвёртая линия
            outlinePoints[pointIndex++] = backLeft;
            outlinePoints[pointIndex++] = frontLeft;
            
            // Диагональ для визуального различия
            outlinePoints[pointIndex++] = frontLeft;
            outlinePoints[pointIndex++] = backRight;
        }
        
        Gizmos.color = Color.cyan;
        Gizmos.DrawLineList(outlinePoints);
    }
}
 
[System.Serializable]
public class BuildingPrototype
{
    public GameObject prefab;
    public float probability = 1f;
}
В этом примере мы используем оба API: Batch Transform для процедурной генерации позиций зданий и Batch Gizmo Drawing для визуализации плана города в редакторе. Такой подход позволяет быстро прототипировать и оптимизировать сложные алгоритмы генерации, получая мгновенную визуальную обратную связь.

Интеграция с системами физики



Одной из наиболее ресурсоёмких операций в играх являются физические расчёты, особенно когда речь идёт о большом количестве объектов. Интеграция Batch Transform API с физическим движком может дать существенный прирост производительности:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class BatchPhysicsSystem : MonoBehaviour
{
    private List<Rigidbody> _activeRigidbodies = new List<Rigidbody>();
    private Vector3[] _positions;
    private Vector3[] _velocities;
    
    private void FixedUpdate()
    {
        int count = _activeRigidbodies.Count;
        
        // Инициализируем или изменяем размер массивов при необходимости
        if (_positions == null || _positions.Length != count)
        {
            _positions = new Vector3[count];
            _velocities = new Vector3[count];
        }
        
        // Собираем позиции и скорости всех объектов
        for (int i = 0; i < count; i++)
        {
            _positions[i] = _activeRigidbodies[i].position;
            _velocities[i] = _activeRigidbodies[i].velocity;
        }
        
        // Предсказываем новые позиции для оптимизации коллизий
        Vector3[] predictedPositions = new Vector3[count];
        for (int i = 0; i < count; i++)
        {
            predictedPositions[i] = _positions[i] + _velocities[i] * Time.fixedDeltaTime;
        }
        
        // Проверяем коллизии и обновляем физику
        ProcessCollisionsAndPhysics(predictedPositions);
    }
    
    private void ProcessCollisionsAndPhysics(Vector3[] predictedPositions)
    {
        // Здесь происходит обработка физики и коллизий
        // с использованием пакетных операций для оптимизации
    }
    
    public void RegisterRigidbody(Rigidbody rb)
    {
        if (!_activeRigidbodies.Contains(rb))
        {
            _activeRigidbodies.Add(rb);
        }
    }
    
    public void UnregisterRigidbody(Rigidbody rb)
    {
        _activeRigidbodies.Remove(rb);
    }
}
Такая система может быть особенно полезна для симуляций с большим количеством объектов, таких как падающие домино, песочницы с физикой или системы частиц с физическим взаимодействием.

Тепловые карты и визуализация данных



Batch Gizmo Drawing API открывает новые возможности для эффективной визуализации больших массивов данных непосредственно в редакторе Unity. Например, можно реализовать тепловую карту для анализа пользовательского поведения или распределения ресурсов в игре:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class HeatmapVisualizer : MonoBehaviour
{
    [SerializeField] private int _resolution = 100;
    [SerializeField] private float _mapSize = 100f;
    [SerializeField] private float _maxHeight = 5f;
    
    private float[,] _heatmapData;
    private Vector3[] _heatmapMeshPoints;
    
    private void OnValidate()
    {
        GenerateRandomData();
        CalculateHeatmapMesh();
    }
    
    private void GenerateRandomData()
    {
        // В реальном проекте здесь были бы данные из аналитики или геймплея
        _heatmapData = new float[_resolution, _resolution];
        
        for (int x = 0; x < _resolution; x++)
        {
            for (int z = 0; z < _resolution; z++)
            {
                // Генерируем некоторые тестовые данные
                float centerDistance = Vector2.Distance(new Vector2(x, z), new Vector2(_resolution/2, _resolution/2));
                _heatmapData[x, z] = Mathf.Max(0, 1f - centerDistance / (_resolution/2)) * UnityEngine.Random.Range(0.5f, 1f);
            }
        }
    }
    
    private void CalculateHeatmapMesh()
    {
        // Подготавливаем точки для меша тепловой карты (упрощённо)
        int lineCount = _resolution * (_resolution - 1) * 2; // Горизонтальные и вертикальные линии
        _heatmapMeshPoints = new Vector3[lineCount * 2]; // 2 точки на линию
        
        int pointIndex = 0;
        float step = _mapSize / (_resolution - 1);
        
        // Создаём горизонтальные линии
        for (int z = 0; z < _resolution; z++)
        {
            for (int x = 0; x < _resolution - 1; x++)
            {
                float height1 = _heatmapData[x, z] * _maxHeight;
                float height2 = _heatmapData[x + 1, z] * _maxHeight;
                
                Vector3 point1 = new Vector3(x * step - _mapSize/2, height1, z * step - _mapSize/2);
                Vector3 point2 = new Vector3((x + 1) * step - _mapSize/2, height2, z * step - _mapSize/2);
                
                _heatmapMeshPoints[pointIndex++] = point1;
                _heatmapMeshPoints[pointIndex++] = point2;
            }
        }
        
        // Создаём вертикальные линии
        for (int x = 0; x < _resolution; x++)
        {
            for (int z = 0; z < _resolution - 1; z++)
            {
                float height1 = _heatmapData[x, z] * _maxHeight;
                float height2 = _heatmapData[x, z + 1] * _maxHeight;
                
                Vector3 point1 = new Vector3(x * step - _mapSize/2, height1, z * step - _mapSize/2);
                Vector3 point2 = new Vector3(x * step - _mapSize/2, height2, (z + 1) * step - _mapSize/2);
                
                _heatmapMeshPoints[pointIndex++] = point1;
                _heatmapMeshPoints[pointIndex++] = point2;
            }
        }
    }
    
    private void OnDrawGizmosSelected()
    {
        if (_heatmapMeshPoints == null) return;
        
        // Используем Batch Gizmo для отрисовки всего меша тепловой карты одним вызовом
        Gizmos.color = Color.green;
        Gizmos.DrawLineList(_heatmapMeshPoints);
    }
}
Такая визуализация может быть использована для анализа любых двухмерных данных — от плотности врагов в различных частях уровня до времени, проведённого игроками в определённых локациях. Благодаря эффективности Batch Gizmo Drawing можно отображать даже очень детализированные тепловые карты без заметного снижения производительности редактора.

Интеграция с DOTS и ECS архитектурами



Data-Oriented Technology Stack (DOTS) и Entity Component System (ECS) — современные подходы к архитектуре игр в Unity, ориентированные на максимальную производительность. Batch Transform API отлично дополняет эти технологии, позволяя эффективно обрабатывать трансформации даже в контексте классической объектной модели Unity.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using Unity.Entities;
using Unity.Transforms;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
 
public class BatchTransformBridge : MonoBehaviour
{
    private EntityManager _entityManager;
    private NativeArray<Vector3> _entityPositions;
    private TransformAccessArray _transformAccess;
    private Transform[] _linkedTransforms;
    
    void Start()
    {
        _entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
        // Инициализация компонентов...
    }
    
    public void SyncPositionsToGameObjects(EntityQuery query)
    {
        // Получаем позиции из ECS мира
        var positions = query.ToComponentDataArray<Translation>(Allocator.TempJob);
        
        // Пакетно применяем их к GameObject трансформациям
        JobHandle handle = new TransformPositionJob
        {
            Positions = positions
        }.Schedule(_transformAccess);
        
        handle.Complete();
        positions.Dispose();
    }
}
Этот мост между системами позволяет разработчикам использовать преимущества обоих миров — высокопроизводительного ECS и удобного в разработке традиционного подхода с GameObject.

Измерение влияния на производительность



Простой способ измерить реальное влияние Batch API на производительность — профилирование до и после внедрения. Разработчики игры "Ocean Explorer" сообщают, что после перехода на Batch Transform API при обработке морских волн с тысячами точек они добились 10-кратного ускорения, что подняло FPS с нестабильных 25-30 до стабильных 60 даже на устройствах среднего класса. Ключевыми факторами, определяющими выигрыш в производительности, являются:

1. Количество обрабатываемых точек — чем их больше, тем заметнее преимущество.
2. Частота обновлений — для объектов, изменяющихся каждый кадр, выигрыш существеннее.
3. Сложность сцены — в сложных сценах с множеством объектов оптимизация критичнее.

При правильном подходе к профилированию вы сможете определить, какие части вашего проекта получат наибольшую выгоду от внедрения Batch API, и сфокусировать усилия именно на них. Ключевым инструментом здесь является Unity Profiler с его детальным анализом вызовов функций и памяти.

Заключение и рекомендации



Внедрение Batch Transform и Batch Gizmo Drawing API в арсенал разработчика Unity открывает новые горизонты оптимизации производительности. Эти инструменты демонстрируют, как даже небольшие изменения в подходе к работе с данными могут обеспечить колоссальный прирост эффективности.

При работе с этими API стоит придерживаться нескольких ключевых принципов. Прежде всего, создавайте и переиспользуйте буферы, а не формируйте их заново при каждом вызове. Это позволит избежать избыточного выделения памяти и уменьшит нагрузку на сборщик мусора. Помните, что максимальный эффект от использования Batch API наблюдается при работе с большими массивами данных — чем больше точек или линий обрабатывается за один вызов, тем заметнее преимущество. При этом не стоит увлекаться чрезмерным укрупнением батчей — есть предел, после которого производительность может даже снизиться из-за проблем с кэшированием данных.

Batch API лучше всего работает в сочетании с другими современными подходами Unity — Job System, Burst Compiler и ECS. Такой комплексный подход позволяет максимально задействовать возможности современных многоядерных процессоров.

Ошибка с переменной Transform: Argument 1: cannot convert from 'UnityEngine.Transform' to 'float'
Кароч, я пытаюсь сделать спавн бревен, у динамического обьекта в рандомной позиции и с...

transform.position vs transform.localPosition
Есть ли какая-то разница в производительности у transform.position и transform.localPosition?

transform.parent = collision.transform;
Здравствуйте, появилась проблема. У меня есть двигающаяся платформа. Когда игрок на нее...

Аналоги C# System.Drawing в Unity
Игру, написанную в Visual Studio, переношу в Unity. Там делал так, что игровое поле - PictureBox, я...

Unity: Объясните мне, как работает функция transform. Что такое Vector3?
Кто нибудь, Объясните мне как работает функция transform что такое Vector3 и как в 2D игре при...

Unity 2d transform.position
Вопрос возможно глупый, но столкнулся с такой проблеммой, грубо говоря размер камеры 5, в скрипте...

Unity: Как привязать transform объекта к какой-либо его анимации?
Здравствуйте, появилась проблема, буду благодарен если поможете. На объекте (персонаже) висит...

Unity ошибка error CS0117: 'Transform' does not contain a definition for 'positon'
Unity ошибка error CS0117: 'Transform' does not contain a definition for 'positon' Вот скрипт...

Unity Transform.Position объекта в определенных пределах
Ребят, подскажите, как код исправить исправить. Выдает всего &quot;Bad&quot;. Нужно, чтобы выдавало Good....

Google Drive API, загрузка объекта System.Drawing.Image
Во общем если делать так, то все нормально загружает: using (MemoryStream fileStream = new...

Передача значений по ip unity -> unity
Доброго времени суток вопрос: (мб простой) как передать например string значение между двумя unity...

Unity сцены. Unity lifecycle
Всем привет. Не понимаю по каким словам искать ответ на этот вопрос. Не совсем понимаю жизненный...

Метки c#, unity
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Настройка гиперпараметров с помощью Grid Search и Random Search в Python
AI_Generated 15.05.2025
В машинном обучении существует фундаментальное разделение между параметрами и гиперпараметрами моделей. Если параметры – это те величины, которые алгоритм "изучает" непосредственно из данных (веса. . .
Сериализация и десериализация данных на Python
py-thonny 15.05.2025
Сериализация — это своего рода "замораживание" объектов. Вы берёте живой, динамический объект из памяти и превращаете его в статичную строку или поток байтов. А десериализация выполняет обратный. . .
Чем асинхронная логика (схемотехника) лучше тактируемой, как я думаю, что помимо энергоэффективности - ещё и безопасность.
Hrethgir 14.05.2025
Помимо огромного плюса в энергоэффективности, асинхронная логика - тотальный контроль над каждым совершённым тактом, а значит - безусловная безопасность, где безконтрольно не совершится ни одного. . .
Многопоточные приложения на C++
bytestream 14.05.2025
C++ всегда был языком, тесно работающим с железом, и потому особеннно эффективным для многопоточного программирования. Стандарт C++11 произвёл революцию, добавив в язык нативную поддержку потоков,. . .
Stack, Queue и Hashtable в C#
UnmanagedCoder 14.05.2025
Каждый опытный разработчик наверняка сталкивался с ситуацией, когда невинный на первый взгляд List<T> превращался в узкое горлышко всего приложения. Причина проста: универсальность – это прекрасно,. . .
Как использовать OAuth2 со Spring Security в Java
Javaican 14.05.2025
Протокол OAuth2 часто путают с механизмами аутентификации, хотя по сути это протокол авторизации. Представьте, что вместо передачи ключей от всего дома вашему другу, который пришёл полить цветы, вы. . .
Анализ текста на Python с NLTK и Spacy
AI_Generated 14.05.2025
NLTK, старожил в мире обработки естественного языка на Python, содержит богатейшую коллекцию алгоритмов и готовых моделей. Эта библиотека отлично подходит для образовательных целей и. . .
Реализация DI в PHP
Jason-Webb 13.05.2025
Когда я начинал писать свой первый крупный PHP-проект, моя архитектура напоминала запутаный клубок спагетти. Классы создавали другие классы внутри себя, зависимости жостко прописывались в коде, а о. . .
Обработка изображений в реальном времени на C# с OpenCV
stackOverflow 13.05.2025
Объединение библиотеки компьютерного зрения OpenCV с современным языком программирования C# создаёт симбиоз, который открывает доступ к впечатляющему набору возможностей. Ключевое преимущество этого. . .
POCO, ACE, Loki и другие продвинутые C++ библиотеки
NullReferenced 13.05.2025
В C++ разработки существует такое обилие библиотек, что порой кажется, будто ты заблудился в дремучем лесу. И среди этого многообразия POCO (Portable Components) – как маяк для тех, кто ищет. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru