Unity: Всё, что вы не знали о разработке @unsafecsharp Channel on Telegram

Unity: Всё, что вы не знали о разработке

@unsafecsharp


Авторский канал о разработке в Unity от Alex Silaev (CTO в Zillion Whales). Mushroom Wars 2 моих рук дело.
Рассказываю об интересный кейсах, делюсь лайфхаками, решениями.

Unity: Всё, что вы не знали о разработке (Russian)

Добро пожаловать в канал Unity: Всё, что вы не знали о разработке! Если вы увлечены миром разработки игр в Unity, то этот канал точно для вас. Здесь вы найдете множество полезных советов и интересных кейсов от автора канала, Alex Silaev, который является CTO в компании Zillion Whales и создателем игры Mushroom Wars 2. В канале вы сможете узнать о последних тенденциях в мире разработки в Unity, а также получить лайфхаки и решения от профессионала. Alex Silaev будет делиться своим опытом и знаниями с вами, помогая вам стать успешным разработчиком в Unity. Присоединяйтесь к нам и узнайте всё, что нужно знать о разработке в Unity!

Unity: Всё, что вы не знали о разработке

11 Nov, 12:49


Как свапнуть значение

Представим, что у нас есть 2 переменные a и b, нам нужно поменять их значения местами: a => b, b => a.
Обычно, мы пишем так:

var temp = a;
a = b;
b = temp;


Но есть возможность записать это проще:

(a, b) = (b, a);


По производительности записи идентичны, т.к. второй вариант превращается в первый.

#tips #basics

Unity: Всё, что вы не знали о разработке

12 Oct, 11:36


Quaternion vs Euler

На самом деле довольно интересная тема. Многие (особенно начинающие) не особо понимают в чем разница, т.к. кватернион хрен знает что это, там если умножить на вектор вроде как вектор повернет (но это не точно), а вот euler ваще классная штука (ведь не даром поворот в трансформе 3мя флотами записан (нет)).

Но давайте вспомним (или узнаем) про gimbal lock. Это такое положение осей, когда две оси сложились, а третья не в состоянии повлиять на вращение в нужную нам сторону (на рисунке зеленая и синия ось сложились в одну плоскость, а красная в состоянии вращать только в одном направлении).

Вообще euler angles - это понятное представление осей для человека, где есть понятный поворот x, y, z. Но в играх предпочтительно использовать quaternion, т.к. он не подвержен проблеме gimbal lock.

Другое дело quaternion. В реальности это гиперкомплексные числа.
Если не вдаваться в подробности математики (может стоит?), а остановиться на том, что мы используем в юнити, то вот несколько моментов:
1. Quaternion не может содержать 4 значения 0;
2. Существуют проблемы с точностью float, которые могут приводить к невалидному состоянию;
3. Умножение кватерниона на вектор - повернут вектор;
4. Умножение кватерниона на кватернион - сложение углов;
5. Кватернион можно представить в виде матрицы;
6. Порядок умножения кватернионов важен;
7. Умножение quaternion на inverse(quaternion) вычитает повороты;

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

#quaternion #euler #math

Unity: Всё, что вы не знали о разработке

08 Oct, 08:31


UI Toolkit

Это некое подобие html/css. Почему подобие? Потому что многих стандартных вещей нет, а текущий стандарт css ушел довольно далеко от uss. То есть это примерно как верстать сейчас сайтики под Internet Explorer 6.0, оно вроде называется похоже, но большую часть просто не поддерживает.

Где это использовать?
Я бы рекомендовал это использовать только для editor-tools, еще может быть в каких-нибудь рантайм штук в билде типа дев консоли. Для продакшена еще далеко, да и есть много подводных камней.

Для редактора:
PropertyDrawer. Если в IMGUI нужно использовать два метода, если высота элемента больше стандартной линии, то при использовании UI Toolkit можно использовать только один метод:


VisualElement CreatePropertyGUI(SerializedProperty property) {
...
}


EditorWindow. Для отрисовке в окне существует стандартный метод:


private void CreateGUI() {
...
}


CustomEditor. Для отрисовки редактора для всего скрипта, нужно использовать метод:


VisualElement CreateInspectorGUI() {
...
}


Как это работает?

По сути UIToolkit - это контейнеры, которые можно заполнять объектами, которые сами по себе тоже являются контейнерами. Если знакомы с HTML, то контейнер в UITK - это тег.
В любом варианте из перечисленных выше, у нас есть VisualElement - это рутовый контейнер, в который мы можем вкладывать свои:


Label myLabel;
VisualElement CreateInspectorGUI() {
// Создаем свой контейнер
var myRoot = new VisualElement();
// Добавляем файл со стилями
myRoot.styleSheets.Add(Resources.Load<StyleSheet>("MyStyle"));
// Создаем label
var label = new Label("Hello World");
label.AddToClassList("my-label");
this.myLabel = label;
// Добавляем в иерархию
myRoot.Add(label);
// Возвращаем корневой объект
return myRoot;
}

void UpdateLabel() {
// Просто обновляем текст у объекта
this.myLabel.text = "New text";
}


А в MyStyle.uss:

// Наводим всякие красивости
.my-label {
font-size: 20px;
border-radius: 5px;
background-color: red;
}


Что хорошего?

1. Многопоточность. Я не уверен, что прям все апи многопоточное, но большая часть точно, во всяком случае с тем, что я сталкивался. Это значит, что обновлять данные у элемента можно из потоков.
2. Быстро работает. На самом деле IMGUI работает тоже быстро, если не использовать GUILayout, просто там схема работы "Перемешали логику и визуалку", поэтому чтобы отрисовать элементы - нужно пройтись по всей логике.
3. Визуалка зависит от стилей, а не от настроек элементов в коде.
4. Не нужно тратить CPU на подготовку отрисовки каждый кадр, т.к. структурно меняется все очень редко.

Что плохого?

1. Если структурных изменений очень много, то нужно будет написать пулинг объектов, чтобы переиспользовать элементы, а не создавать их заново.
2. Нет поддержки партиклов и похоже что это будет еще не скоро. На самом деле одна из основных проблем почему UITK не нужно использовать в рантайме.
3. Нет поддержки анимаций. Есть стандартные анимации типа transition, но боюсь что этого недостаточно, чтобы сделать красивые штуки.
4. Нет возможности указать слой, т.е. рисуется все в том порядке, в котором было объявлено.

Резюмирую

Могу сказать, что я использую UITK во всех своих тулзах, т.к. это работает намного быстрее IMGUI и можно добиться красивого результата с меньшими затратами. Но именно для продакшена он не готов.

#uitoolkit #ui

Unity: Всё, что вы не знали о разработке

07 Oct, 05:25


Статические лямбды

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

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


Method(static () => …);


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

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

#basics #staticlambda

Unity: Всё, что вы не знали о разработке

06 Oct, 07:44


checked/unchecked

Встречали ли вы эти слова в коде? Думаю, что вряд ли. А все потому что в основном мы работаем с числами и относимся к ним как к хранилищу. Большинство вообще используют int и float, а если будут хранить большие числа - ну long и double. Для каких-нибудь ГК игр используют BigInteger при необходимости. И на самом деле большинство никогда не задумывается «а что будет если».

Но вернемся к теме. В шарпе по-умолчанию число int.MaxValue + 1 вернет результат без исключения и будет равно int.MinValue. То есть по сути будет ходить по кругу. Иногда это критично для некоторых кейсов, поэтому используют блок checked:


value = int.MaxValue;
checked {
value += 1; // тут мы получим исключение
}


Тем самым можно поймать исключение, которое потом правильно обработать, ну или получить исключение в правильном месте. Ведь где-нибудь мы считали индекс массива, он стал минусовым, а потом где-нибудь совершенно в другом месте упал с ошибкой «чет индекс не попадает в рамки массива». И хорошо, если это индекс массива, а если просто число, которое влияет на логику выполнения и в какой-то момент у вас что-нибудь начнет крутиться в другую сторону?

Аналогично checked существует и unchecked:


value = int.MaxValue;
unchecked {
value += 1; // тут исключения не будет, число будет равно минимальному значению
}


На самом деле хоть unchecked - это поведение по-умолчанию, все равно его можно переопределить параметром компилятора.

Также существуют checked/unchecked операторы для более котороткой записи:

return checked(value + 1);


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

#basics #overflow

Unity: Всё, что вы не знали о разработке

03 Oct, 06:25


StructLayout

На самом деле довольно интересный аттрибут.
Давайте рассмотрим пример:


struct MyStruct {
public int a;
public byte b;
public int c;
public byte d;
}


Размер такой структуры определяется следующим образом:

public int a; // 4 байта
public byte b; // 1 байт
public int c; // 4 байта
public byte d; // 1 байт


Складываем, получаем 4 + 1 + 4 + 1 = 10 байт
Казалось бы, что тут сложного. Не все так просто 😉
Существует такое понятие как Pack size, то есть каким образом будет выровнена в памяти, если простым языком - каждая переменная будет минимум занимать размер pack size, максимум - кратное значение этому размеру:


public byte b; // 4 байта при Pack = 4
public byte b; // 1 байт при Pack = 1


Таким образом размер структуры будет вычисляться так:


public int a; // 4 байта
public byte b; // 4 байта
public int c; // 4 байта
public byte d; // 4 байта


Итого: 16 байт вместо 10 байт

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


public int a; // 4 байта
public int c; // 4 байта
public byte b; // 1 байт
public byte d; // 1 байт


Получается, что теперь будет 10? А вот и снова нет 🙂
Теперь будет 12 байт. Почему так произошло?
Потому что последние два байта будут выровнены до 4х.

Что вообще такое Pack size и где он задается, о котором шла речь?
Это параметр аттрибута StructLayout:

[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct MyStruct {
public int a;
public byte b;
public int c;
public byte d;
}


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

Что не так с паковкой по одному байту и почему не паковать все структуры таким образом по-умолчанию?
Ну, во-первых, это нарушает выравнивание в памяти. Например, если вы захотите после такого сделать Interlocked.Add(ref s.c), то получите краш, т.к. аддрес в памяти у поля c будет не кратным 4, а это приведет к крашу.
Во-вторых, я не знаю аллокатора, который не применяет общее выравнивание аллоцируемых объектов, т.е. в памяти он вероятнее всего будет занимать 12 байт, а не 10.

Что еще есть у StructLayout?

Еще есть Size, которым мы можем ограничить размер структуры до минимально необходимого:

[StructLayout(LayoutKind.Sequential, Size = 10)]
struct MyStruct {
public int a;
public int c;
public byte b;
public byte d;
}


Заметьте, что я специально переставил поля, т.к. если этого не сделать, то будет интересный эффект:
Размер sizeof(MyStruct) вернет нам 13 (т.к. Pack = 4, последний байт будет обрезан), а вот Marshal.SizeOf(s) вернет нам 10, т.к. он берет тип объекта и возвращает сколько нам необходимо было байт, чтобы создать этот инстанс, ведь Marshal.SizeOf принимает именно фактический инстанс объекта.
В любом случае, такого нужно не допускать.

Что про LayoutKind?

Для структур это значение может принимать 2 варианта:
LayoutKind.Sequential - как поля объявлены, так и раскладываем в памяти.
LayoutKind.Explicit - ручное распределение, необходимо указать FieldOffset аттрибут для каждого поля.

С Explicit можно "наслаивать" поля друг на друга, как самый простой вариант:

[StructLayout(LayoutKind.Explicit)]
struct MyStruct {
[FieldOffset(0)]
public int a;
[FieldOffset(4)]
public int c;
[FieldOffset(0)]
public long b;
}


Т.е. положили значения a и c, забрали одно значение b, которое будет содержать 2 int.

#unsafe #structlayout #sizeof

Unity: Всё, что вы не знали о разработке

09 Sep, 11:21


Важно!!!

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

Открыта запись на осень 🍂
Инфа о курсе есть в закрепе.
Но, если вкратце - 10 онлайн занятий по 3 часа. 10 человек. Веду лично. Занимаемся серьезно. Есть даже домашка :)

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

Самое время прокачать свой уровень.

@Voice_labb записаться можно тут.

Unity: Всё, что вы не знали о разработке

09 Sep, 11:21


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

Unity: Всё, что вы не знали о разработке

16 Aug, 10:00


Cache Line и Cache Miss

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

Cache Line. Давайте представим, что у вас есть 2 GameObject: на одном есть скрипт Processor, а на втором скрипт - RAM.
В скрипте RAM давайте объявим массив объектов:


public class RAM : MonoBehaviour {
public object[] objects;
}


А у скрипта Processor одно поле

public class Processor : MonoBehaviour {
public object currentObject;
}


Мы хотим, чтобы процессор обрабатывал какой-то объект, мы можем сделать это двумя путями:
1. Как в примере выше: просто объявляем ссылку на объект;
2. Процессор будет только хранить только информацию о том как этот объект получить, но сам хранить ничего не будет.

Какой вариант будет работать быстрее? Очевидно, что первый.

Вот примерно так и работает Cache Line, где currentObject - это не объект, а просто определенного размера кэш. Он один раз загружается из RAM и используется до тех пор, пока не потребуется какой-то другой участок памяти. Поэтому если читать последовательно (например, из массива), то будет задействован кэш процессора, а не оперативка. Отсюда в названии "Line".
Размер кэш линии зависит от процессора, в основном 32, 64 или 128 байт.

Cache Miss. Это вытекает из первого. По сути это момент, когда мы не попадаем в cache line. Рассмотрим пример:


var arr = new int[1000];
for (int i = 0; i < arr.Length; ++i) {
arr[i] // обращаемся к массиву
}


При первом обращении к массиву мы получаем Cache Miss и загрузку Cache Line, т.к. нам нужно загрузить данные из памяти. Когда мы доходим до N-го элемента, мы снова делаем новую загрузку и снова получаем Cache Miss. Каждая загрузка занимает существенное время, поэтому минимизация количества Cache Miss дает буст в производтельности.
Поэтому код вида:


var arr2 = new int[arr.Length];
for (int i = 0; i < arr.Length; ++i) {
arr[i] // обращаемся к массиву
arr2[i] // обращаемся ко второму массиву
}


Будет постоянно выдавать Cache Miss, т.к. мы загружаем Cache Line заново. На практике вы, конечно, вряд ли заметите разницу, т.к. все же этот процесс довольно быстрый, но при большом количестве обращений в хот частях может дать замедление.

Можно переписать код примерно так:

struct MyStruct {
int a1;
int a2;
}

var arr = new MyStruct[1000];
for (int i = 0; i < arr.Length; ++i) {
arr[i].a1 // обращаемся к массиву и получаем a1
arr[i].a2 // обращаемся к массиву и получаем a2
}


То есть мы просто положили 2 переменные рядом в памяти и теперь мы будем получать Cache Miss как и в первом примере, но в 2 раза чаще, т.к. данных на один элемент у нас теперь х2.

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

Пример:


arr = new int[10]; // Допустим, мы делаем 10 потоков

Thread(int threadIndex) {
++arr[threadIndex]; // обращаемся к своему индексу для каждого потока
}


Такой код занимается увеличением счетчика без блокирования потока. Но тут и есть проблема, которая приведет к тому, что мы потеряем производительность на мердже Cache Line. То есть когда в проц загружается Cache Line - мы по сути делаем "лок" на эту область памяти. И когда второй проц заберет ту же кэш линию - будет конфликт интересов. По факту это, конечно, разрулится, и данные в итоге будут нужные, но вот производительность мы потеряем.

Что делать? Можно просто расширить массив arr с 10 до 10 * CacheLineSize / sizeof(int). Т.е. мы под каждый поток выделяем область памяти, которая никак не пересекается с другим потоком.
Таким образом, первый поток будет писать в arr[0], второй - в arr[1 * CacheLineSize / sizeof(int)] и т.д.
Т.е. при размере кэш линии в 128 байт, у нас размер массива для 10 потоков будет 320, а потоки будут использовать индексы: 0, 32, 64 и т.д. (с шагом 32, где 32 * sizeof(int) = 128 размер кэш линии).

#cache #memory #cachemiss #cacheline

Unity: Всё, что вы не знали о разработке

12 Jun, 20:32


Статья про устройство GPU и о некоторых нюансах производительности шейдеров для продолжающих.

https://telegra.ph/Pochemu-Tvoj-SHejder-Medlennyj-06-12

Автор: @shiko_q
Источник: https://t.me/unity_cg/76092, https://t.me/unity_cg/76093

#rendering #graphics

Unity: Всё, что вы не знали о разработке

30 Apr, 01:39


У нас на всех проектах используется свой гит флоу. И сейчас я расскажу какой именно:

1. Мастер всегда в рабочем состоянии и является основной веткой разработки;
2. Если фича крупная (потребуется больше одного коммита, чтобы ее закрыть), то под такую фичу заводим отдельную ветку “feature/…”, если фича мелкая - заливаем в мастер, проверив перед этим что ничего не сломали (в разрезе того что потрогали);
3. Если фича мелкая, то задачу переводим на qa. Qa проверяют задачу в мастере (если не указано в какой ветке фича);
4. Если фича крупная, то перед тем как отдать фичу в qa - подливаем мастер к себе в ветку;
5. Релизы делаем в отдельных ветках вида “rc/x.y.z”. Почему бы не тегировать? Дело в том, что мы делаем rc ветку за несколько дней до отправки в сторы. Ветка rc - это фичефриз, то есть никаких новых фич туда не заливается, только фикс существующих. Создали rc - qa пошли смотреть полностью билд. Нашли баги - мы их подправляем прям в rc;
6. После отправки билда в сторы, rc вливается в мастер.

Вроде ничего не забыл;) Благодаря такому гит флоу получается, что максимальное количество фич проверяется вместе друг с другом, а не по отдельности. Я встречал разные подходы в разных компаниях, но в итоге мы пришли к этому варианту.

А какой гит флоу у вас на проекте?

#gitflow #git

Unity: Всё, что вы не знали о разработке

27 Apr, 18:35


Самый простой вопрос на собеседовании

И снова про собесы) Как определить попадает ли точка в радиус?
Казалось бы, простой вопрос, но большинство отваливаются на одном из этапов. Диалог получается примерно такой (есть вариации, но направление вопросов у меня всегда одно):
- Вычесть из точки центр
- Это будет вектор, что дальше?
- Взять длину и сравнить с радиусом
- Ок, а как посчитать длину?
- Взять magnitude/Vector2.Distance (тут вопрос зачем тогда первое действие делали)
- Ок, а что внутри у magnitude ну или «как в принципе считается длина вектора»? (По ощущениям тут отваливается процентов 50)
- Теорема Пифагора
- Ок, а почему тогда мы изначально не посчитали квадрат?
- …

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

Кстати, когда я задаю этот вопрос, мне очень и очень стыдно, т.к. он действительно очень простой.
Но после него (если успешно прошли все ответы) я задаю вопрос «ну а если это будет эллипс?»

#interview #unity

Unity: Всё, что вы не знали о разработке

27 Apr, 03:55


bool не является blittable типом

Давайте для начала разберемся что такое blittable типы и чем они отличаются от unmanaged типов.
В документации шарпа будет написано примерно следующее: blittable типы - это такие типы, которые могут содержать blittable типы :)
Это я, конечно, пошутил, но для неподготовленного человека объясню:
Любой примитив (кроме bool) или любая структура, которая содержит примитивы (кроме bool) или blittable структуры.

Unmanaged типы - это неуправляемые GC типы, т.е. структуры, которые содержат примитивы (любые).

То есть по факту получается, что unmanaged и blittable очень близки, но на самом деле сильно разные.
Blittable типы - это такие типы, которые в памяти на любом компутере будут выглядеть одинаково. Отсюда и проблема с bool, который на разных окружениях может занимать 1 (байт), 2 (шорт) или даже 4 байта (т.к. хранится в виде int).

Unmanaged же не гарантируют ровным счетом ничего подобного, да и не нужно ему это совсем.

Эта инфа будет полезна при бинарной сериализации структур.

#unmanaged #blittable #serialization

Unity: Всё, что вы не знали о разработке

26 Apr, 08:56


Как перемещать персонажа по точкам правильно

Многие из вас делали перемещение персонажа по нодам. Например, когда поиск пути вернул массив точек, а нам нужно персонажем пройти по ним.
Обычно такой код выглядит примерно так:


var nextPoint = points[index];
if ((nextPoint - position).sqrMagnitude <= MIN_DISTANCE_SQR) {
++index;
if (index >= points.Length) {
// Мы дошли до конца пути
return;
}
}

position = Vector3.MoveTowards(position, nextPoint, dt * speed);


Логика понятная: дошли до точки - берем следующую и идем к ней, и так пока не дойдем до конца.

Но в таком подходе кроется одна проблема: если персонаж проходит за кадр 1 метр, а расстояние до точки 0.5 метра, то персонаж будет проходить на самом деле меньшее расстояние, чем должен был:


-[]--[]--[]--[]--[]
---------------[] // Этот персонаж дойдет до конца быстрее, чем первый


Что делать?

На самом деле нужно использовать примерно такую логику:


var distance = speed * dt;
while (distance > 0f) {
var nextNodePos = points[index];
var distanceToNextNode = (nextNodePos - currentPos).magnitude;
if (distance >= distanceToNextNode) {
distance -= distanceToNextNode;
currentPos = nextNodePos;
++index;
if (index >= points.Length) break;
continue;
}
var direction = (nextNodePos - currentPos).normalized;
currentPos = direction * distance;
break;
}


Метод HasReached должен проверять "перешли ли мы точку или еще нет". Таким образом, мы "перебрасываем" часть длины, которую мы прошли на новую точку, а если перешли и ее, то еще раз и так пока либо не закончится этот хвост, либо мы не дойдем до конца.
Грубо говоря, если персонаж будет двигаться со скоростью 1000 метров в секунду, а контрольных точек на пути будет много (например, каждый метр), то за секунду он пройдет ровно 1000 метров, а в первом варианте намного меньше.

#unity #algorithms #movement

Unity: Всё, что вы не знали о разработке

25 Apr, 13:34


int Method(IInterface obj) {
...
return obj.Calc();
}

public struct S1 : IInterface {…}
public struct S2 : IInterface {…}

void Update() {
Method(new S1());

Method(new S2());
}

interface IInterface {
int Calc();
}


Чего я только не слышу про этот код на собесах. Тут 2 вопроса:
1. Что не так с этим кодом? Может быть и все так.
2. Как исправить?

И знаете, я вот думаю этот вопрос сделать самым первым на собесе, т.к. я слышу такие ответы:
1. Я бы сделал базовую структуру...
2. Можно сделать ref IInterface
3. Можно поменять struct на class
4. Можно сделать IInterface obj, out int...
5. Поменять struct на class + хранить их в static полях, оттуда забирать когда надо
6. Придумайте свой идиотский вариант

#interview #unity

Unity: Всё, что вы не знали о разработке

19 Apr, 18:52


А вы знали, что юнити из коробки поддерживает дебаг пикселя по клику?)

Для URP надо включить дефайн ENABLE_SHADER_DEBUG_PRINT в проекте (для HDRP, кажется, оно и так будет работать).

Затем добавить в шейдер строчку
// #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/ShaderDebugPrint.hlsl"

А в самом шейдере вызвать
ShaderDebugPrintMouseButtonOver(int2(input.positionSS.xy), ShaderDebugTag('S','m','o', 't'), fragData.smoothness);

И тогда при клике ЛКМ по какому-либо месту на экране, в консоль будет написано
Frame #270497: Smot float4(0.1f, 0.2f, 0.3f, 0.4f) - ну, или что у вас там в вашем цвете.

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

Так можно дебажить свой сложный кастомный шейдер.
Особенно хорошо работает в сочетании с окном Rendering Debugger.

Пост: https://t.me/unity_cg/74386

#rendering #urp

Unity: Всё, что вы не знали о разработке

13 Feb, 20:57


В общем, мы тут с @shiko_q подумали как можно сделать джобу с поддержкой зависимостей на мейн треде. Ну чтобы можно было бы просто делать что-нибудь в главном потоке, но при этом не делать Complete джоб перед этим. И вот что вышло:

https://github.com/chromealex/Unity.MainThreadJob

#jobs #unity #api #hacks

Unity: Всё, что вы не знали о разработке

11 Feb, 09:40


https://youtu.be/w10n9d-uO7k?si=UoifmwqVfr1pRqMr

Разбирались тут со @Mefodei как устроен демо проект. Он уже довольно далеко ушел от того, что в видео, но все равно должно быть интересно;)

#record #mebecs #ecs #demo

Unity: Всё, что вы не знали о разработке

08 Feb, 09:38


Сделал супербыстрые pixel-perfect health-бары для юнитов :)

За реф брал бары из Starcraft II. В принципе получилось по рендеру очень быстро и в 1 проход :) Правда с кастомизацией придется повозиться. Если интересно - задавайте вопросы.

На текущий момент готово:
- Поиск целей
- Поиск пути
- Туман войны
- Отрисовка health-баров
- Группы юнитов

Stay tuned ;)

#rendering #mebecs #ecs

Unity: Всё, что вы не знали о разработке

04 Feb, 22:13


https://youtu.be/EGHdtjmG0Jw

Записал небольшой ролик про то как работает сейчас мой прототип RTS. Тут есть и поиск целей, и туман войны, и поиск пути. Ну и пара тысяч юнитов 🙂
Нет никакой оптимизации графики, на написание и тестирование логики это никак не влияет.

#mebecs #ecs #rts #prototype

Unity: Всё, что вы не знали о разработке

30 Jan, 12:01


https://www.unsafecsharp.com/blog/mebecs-пишем-простую-систему-перезарядки

Написал небольшой пост о том как выглядит простая система на ME.BECS.
Хотелось бы от вас услышать комменты что не понятно, что переусложнено, что можно сделать проще.

#mebecs #tutorial