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

Из минусов - отсутствие safety handler.

Важно: UnsafeList содержит данные о количестве элементов, т.е. относиться к этой структуре нужно как к любому ValueType (оно будет копироваться).

В принципе это касается всех коллекций.

Read More  

Довольно часто мы пишем в коде подобные штуки: direction * deltaTime * 2f 

Фактически мы умножаем Vector direction на float и на какой-нибудь мультипликатор, например, 2. На самом деле лучше писать вот так:

direction * (deltaTime * 2f) 

Т.к. в таком случае операций умножения для вектора будет одна вместо двух.

Read More  

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

https://pastebin.com/6gdiGwde

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

Производительность повышается тем, что при генерации кода C++ мы убираем из кода лишние проверки. Можно использовать в тех местах, где мы уверены, что не будет случайного null или мы точно не выходит за рамки массива. Проще говоря все те исключения, на которые в обычной жизни мы повлиять никак не могли.

Read More  

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

sqrMagnitude внутри: x*x + y*y + z*z

Vector3.Distance внутри: sqrt(x*x + y*y + z*z)

В sqrMagnitude по теореме Пифагора мы вычисляем расстояние между точками, тут все просто.

Но когда мы можем использовать sqrMagnitude? Самое банальное: проверка расстояния, когда каждая точка вычисляет расстояние одинаково. Получается, что корень считать нам нет никакой нужды, если нам нужно найти ближайший объект или, например, определить находится ли юнит в радиусе для выстрела.

Read More  

Мы хотим выполнить несколько итераций (jobCount) и внутри каждой итерации еще по 128 итерации. Внутри всего этого мы хотим посчитать сумму из NativeArray input и положить в массив NativeArray arrSum: 

for (int j = 0; j < jobCount; ++j) {
     for (int i = 0; i < 128; ++i) {
         var sum = arrSum[i];
         sum += input[(j * 128) + i];
         arrSum[i] = sum;
     }
} 

Как видно из примера, массив input намного больше, чем arrSum. Мы думаем-думаем и решаем оптимизировать наш код. Получается примерно следующее: 

for (int i = 0; i < 128; ++i) {
     var sum = arrSum[i];
     for (int j = 0; j < jobCount; ++j) {
         sum += input[(j * 128) + i];
     }
     arrSum[i] = sum;
} 

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

Пример на самом деле плохой, но показывает нам, что нужно проверять код в Burst Inspector.

И да, первый вариант векторизуется и будет работать быстрее.

Read More  

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

Для этого нужно указать sub emitter у основной партикл системы, а все модули основной системы отключить. Мы это использовали для поджигания травы (Кто не видел пост - https://t.me/unsafecsharp/48), чтобы нарисовать огонь для каждой травинки.

Да, весь прикол в том, что вся эта радость будет рисоваться в 1 DrawCall, т.к. партикл система знает все, что нужно о своих подсистемах, за что ей отдельное спасибо.

В итоге мы контролируем огонь через Emit + GetParticles/SetParticles, а что там за огонь - это уже vfxер нарисует, настраивая обычную систему. Имейте ввиду, что ограничение в подсистемах на maxParticles должен быть расчитан на все системы, а не на одну.

Read More  

Если у вас в проекте есть много skinnedmesh анимаций (например, у вас по лесу бегает много животных), то их анимации можно запечь в текстуру, откуда читать шейдером. Такие анимации будут работать довольно с сильной погрешностью, но для объектов окружения этого может быть вполне достаточно. Такое решение намного производительнее, т.к. работает с одной текстурой и укладывается в один DrawCall.

Read More