Есть такая штука, которая позволяет вызывать метод, когда мы не знаем сколько параметров хотим туда передать:

void Method(params object[] arr)

Но есть некоторые особенности, которые нужно понимать:

1. По-умолчанию такой вызов создает новый массив (т.е. триггерит GC, что плохо):

void Method1(params object[] arr)
void Method2(object[] arr)

var arr = new int[10];
Method1(arr); // будет создан массив object[1] и к первому элементу присвоен массив arr
Method2(arr); // будет ошибка компиляции, т.к. object[] не соотвествует типу int[]

2. Если мы передадим точный тип массива, то это не будет создавать новый массив:

void Method1(params int[] arr)
void Method2(int[] arr)

var arr = new int[10];
Method1(arr);
Method2(arr);
// Эти 2 вызова идентичны

3. Любой вызов будет создавать новый массив:

void Method(params int[] arr)

Method(1, 2, 3)
Method(1)

Вывод: избегайте методов с params, особенно если это хот часть.

Read More  

Используйте вместо new T[0]; статичный массив, который не нужно создавать каждый раз.

Read More  

Давайте сначала разберемся что такое Lerp. Это линейная функция, которая задается двумя точками B и C (см изображение), а третье значение t - это как значение между 0 и 1, где 0 - это B, а 1 - это C.

Таким образом получается, что перемещение по линии - это и есть lerp или интерполяция. А вот LerpUnclamped - это экстраполяция, т.е. когда мы не обрезаем значение t до 0..1.

b + (c - b) * clamp01(t);

Т.е. по сути мы сначала сдвигаем сетку координат в ноль (c - b), а потом умножаем получившийся вектор на t, а после сдвигаем получившийся вектор назад.

С Unclamped мы просто не делаем операцию clamp, тем самым можем позволить результату выходить за границы b-c.

Read More  

Если вы не используете параметр <span>out</span>, по которому возвращается значение, то можете писать просто _:

Method(out int notUsed);
Method(out _);
Read More  

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

Я часто встречаю вот такой код:

a += b / 2f;

И мне все время хочется такой код написать так:

a += b * 0.5f;

Еще можно заменять по тому же принципу и любые другие константы. Но что делать, если у нас деление не на константу, а на x? Да все просто, делаем y = 1f / x и используем уже y.

Read More  

Довольно простой вопрос, который решается банальной проверкой (v2.sqrMagnitude <= radius * radius). Мы такой вопрос часто задаем на собесах, но не только для того, чтобы выяснить считает ли человек через квадрат радиуса, а скорее для того, чтобы задать второй вопрос: "а в эллипс?". И вот на этом вопросе люди начинают сами себя закапывать. Кто-то придумывает несуществующие правила и теоремы, кто-то говорит, что мол "да я это не помню, там высшая математика, кому это вообще надо", ну а кто-то предлагает решение.

А самое интересное, что решение довольно простое, которое не требует никаких знаний и формул:

Изменяем Y проверяемой точки на фактор соотношения Rx к Ry, а дальше проверяем на попадание в радиус Rx. То есть мы вытягиваем эллипс таким образом, чтобы он стал кругом и считаем уже относительно круга.

Read More  

Он сравнивает не точное значение, а приблизительное, с учетом погрешности:

var v = new Vector3(0f, 0f, 0f);
var v2 = Vector3.one - Vector3.one;

v == v2
Read More  

Условие всегда ленивое и хочет побыстрее выйти. Если v1 будет true, то что там дальше его не будет интересовать:

if (v1 == true || v2 == true) {...}

Таким образом, если у нас есть такой код:

var v1 = CalcV1();
var v2 = CalcV2();
if (v1 == true || v2 == true) {...}

Выглядит хоть и симпатично, но совершенно непроизводительно.

Лучше писать так:

if (CalcV1() == true || CalcV2() == true) {...}

Естественнно нужно понимать, что CalcV2 вызываться не будет, если CalcV1 вернет true, поэтому не нужно на это расчитывать. Но я надеюсь, что вы это знаете :)

Read More  

Destroy - это маркер, который говорит, что мы хотели бы удалить объект, но это не произойдет моментально. Юнити отложит удаление до конца кадра и он будет удален позже.

Object.Destroy(gameObject
if (gameObject != null) {
     // gameObject is alive
}

DestroyImmediate - это фактическое удаление, т.е. когда нужно удалить объект прямо здесь и сейчас.

Object.DestroyImmediate(gameObject
gameObject // тут он уже помер

Дело в том, что DestroyImmediate ломает юнити флоу, т.к. с точки зрения всех юнити-колбэков они выполнятся моментально, а не в нужном порядке, например, при использовании DefaultExecutionOrder.

При этом DestroyImmediate имеет дополнительный флаг, который позволяет убить ассет из проекта, про что стоит не забывать :)

И ремарка: Object.Destroy/Object.DestroyImmediate могут убить то, что вы туда передаете, т.е. передали компонент - убивается компонент, передали go - убивается go, текстуру - текстура и т.д. Иногда встречаю код вида Object.Destroy(component), при этом явно хотели убивать не компонент, а go.

Read More  

Любой n-мерный массив можно представить в виде одномерного:

arr[x, y] = arr[x * width + y]

И да, это будет быстрее, чем двумерный массив.

Read More  

Довольно часто мне нужно упаковать 2 инта в лонг, или из лога получить 2 инта, ну или в шортов сделать инт и т.д.

Это применяется в основном в каких-нибудь Dictionary (или подобных кейсах) в виде ключей, чтобы не городить структуру, да и работать оно будет быстрее.

void ToInts(long a, out int a1, out int a2) {
     a1 = (int)(a & uint.MaxValue);
     a2 = (int)(a >> 32); 
}

long ToLong(int a1, int a2) {
     long b = a2;
     b = b << 32;
     b = b | (uint)a1;
     return b; 
} 
Read More  

Вы, наверное, не раз сталкивались с таким понятием как сложность алгоритмов. Существует несколько нотаций, которыми можно описать сложность алгоритма, в основном используется О-нотация (или О-большое), т.к. она описывает верхнюю границу сложности алгоритма в зависимости от входных параметров. Например, у вас есть такой метод:

int Method(int n) {
     var sum = 0;
     for (int i = 0; i < n; ++i) {
         sum += i;
     }
     return sum; 
} 

Т.е. мы передаем в метод некое число n, которое обозначает количество итераций цикла внутри метода. Верхняя сложность такого алгоритма будет O(n). При этом если мы добавим в конец метода еще несколько строк:

for (int i = 0; i < 10; ++i) {
    sum += i;
}

то казалось бы, что сложность должна увеличиться на 10 (O(n + 10), но в О-нотации это будет константное время, а значит мы не будем учитывать это в сложности, т.е. сложность все еще останется O(n).

Поэтому нужно понимать, что алгоритм с О-нотацией O(1) (константное время) может на самом деле занимать гораздо больше времени, чем вы расчитываете, это лишь показывает общую сложность алгоритма, но не говорит о его количестве операций.

Вот для сравнения методы со сложностью O(n) и O(1).

Метод O(n):

int Method(int n) {
     var sum = 0;
     for (int i = 0; i < n; ++i) {
         sum += i;
     }
     return sum; 
} 

Метод O(1):

int Method() {
     var sum = 0;
     for (int i = 0; i < 100_000; ++i) {
         sum += i;
     }
     return sum; 
} 

Как видно из примера, любой вызов первого метода с n < 100_000 будет отрабатывать быстрее, чем второй метод с константным временем выполнения. Так что когда вам говорят, что какая-то коллекция работает за константное время на добавление элементов, например, то это совсем не означает что она делает это максимально эффективно.

Read More