Unity: Всё, что вы не знали о разработке – Telegram
Unity: Всё, что вы не знали о разработке
1.74K subscribers
40 photos
101 links
Авторский канал о разработке в Unity от Alex Silaev (CTO в Zillion Whales). Mushroom Wars 2 моих рук дело.
Рассказываю об интересный кейсах, делюсь лайфхаками, решениями.
Download Telegram
Для сравнения Unity Object с null можно использовать конструкцию if (obj is null) вместо if (obj == null). А еще если obj - это Unity Object, то оператор == перегружен и проверяет не только фактический null на стороне C#, но и объект на стороне C++. Тот же эффект достигается при использовании ReferenceEquals.

#code
👍14🥱2💩1
Вы можете написать метод GetEnumerator в любой структуре или классе, это позволит использовать конструкцию foreach. Но есть несколько моментов, которые стоит понимать:
* Результат метода должен вернуть структуру или объект, в котором есть метод MoveNext и свойство Current;
* При использовании интерфейса IEnumerable (например, в List<>) при любом использовании foreach или GetEnumerator значение будет запаковано (boxing) и избежать этого уже никак не выйдет.

#code #foreach
👍6💩1🥱1
🥕Ну и задачка, пишите ответы в комменты;)

Что означает конструкция a = *(val1 + val2);?

1. Это не будет компилироваться;
2. Это же простое умножение, можно записать как a = a * (val1 + val2);
3. Это будет работать только в unsafe, двигаем val на index (на сколько - зависит от типа val), получаем данные по указателю;
4. Встречал такое, но точно не знаю как это работает;
5. Свой вариант.

#unsafe
👍8
Можно вызывать internal методы из других assembly, но для этого нужно разрешить другим assembly видеть эти методы. Для этого нужно написать аттрибут InternalsVisibleTo с указанием имени assembly, которая будет видеть internal-методы.
[assembly: InternalsVisibleTo("Friend.Assembly.Name")]

#assembly
👍17🥱3💩1🤨1
🥕Задачка
Вы наверняка знаете, что можно сделать static constructor. Для этого нужно просто написать static MyClass() и сделать там свои грязные делишки.
Но иногда хочется получить static destructor у этого же класса.
На практике я с таким столкнулся, когда в конструкторе я выделял память, которую нужно было где-то удалять.
Вопрос знатокам: каким образом вы бы решили такую проблему? Попробуйте не гуглить;) Своим решением поделюсь чуть позже.
👍5😢1
При сериализации структур в бинарь можно использовать подход *(T*)ptr = value, где ptr - указатель на массив byte, а value - данные, которые мы хотим туда записать. Если вы используете managed массив, то не забудьте использовать fixed.

#unsafe
👍3
Всем привет! Я хотел бы проводить общие созвоны раз в неделю, чтобы пообщаться, рассказать о каких-нибудь проблемах, найти решение.
В какое время и в какой день вам было бы удобно это?
В комментах будет опрос про время.
Final Results
8%
Понедельник
8%
Вторник
4%
Среда
4%
Четверг
9%
Пятница
47%
Суббота
21%
Воскресенье
Метод Array.Resize не проверяет размер массива в сторону уменьшения. То есть всегда будет выделен массив необходимого размера на выходе.

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

#animations #rendering #shaders
👍10👌1🤡1😨1
Партиклы можно использовать в качестве рендера своих спрайтов. Для этого нужно вызвать var count = particleSystem.GetParticles(particlesArr);
изменить массив и вызвать
particleSystem.SetParticles(particlesArr, count);
Таким образом можно контролировать тысячи частиц.

На практике мы с таким столкнулись, когда нам нужно было отрисовать 4 тысячи юнитов на экране телефона, при этом каждый юнит имел по 4 спрайта, т.е. 16к спрайтов на одном экране старенького андроида в 2015м году. Тогда юнити еще толком не умела нормально контролировать партиклы и пришлось писать свои партиклы на плюсах, что повлекло за собой боль с поддержкой этого кода под все платформы (а это были PS4/XBOXONE/Switch/iOS/Android/PC(x86/x64).
И да, в итоге юнити доделали партиклы и мы с радостью избавились от этого кода, но осадочек то остался 😉

#particles #code #rendering
👍13🗿21
🥕Задачка
Представьте игру, где вы управляете большой толпой человечков, отдавая приказы кликом мышки. При этом игра сетевая, т.е. у каждого игрока большая толпа этих человечков. Например, возьмем, 1000 человечков у каждого игрока. Для простоты человечки ходят по прямой и не сталкиваются друг с другом. На карте есть препятствия, которые нужно обходить. Одним кликом вы контролируете сразу всех или выделенную часть.
Вопросы:
1. Каким образом будет устроен поиск пути?
2. Каким образом вы будете синхронизировать позиции человечков между игроками?
👍1🤔1
Как устроен GOAP (или Goal-Oriented Action Planning)?
Все мы знаем деревья поведений или FSM. FSM дает возможность приходить к конкретной цели, основываясь на параметрах, приобретенных при выполнении конкретных состояний, но при этом вы можете находиться в конкретном одном состоянии в определенный момент времени.
В деревьях поведений же мы не ограничены одним состоянием в определенный момент, но сейчас не о них.
Есть еще Utility based AI, но о нем нужно рассказывать отдельно.

GOAP не имеет связей. Это важно. Совсем.
Простыми словами GOAP представляет собой массив простых событий, которые выполняются, если обеспечены их входные параметры, а на выходе дают необходимые эффекты. А имея такие вводные, можно без труда построить граф для достижения необходимого действия.

Как видно из названия, Goal-Oriented или ориентрир - это цель, мы ориентируемся на достижения цели. То есть мы говорим персонажу "будь сыт", а он сам добудет еду и поест. Ну или "построй дом", а он сам добудет необходимые материалы и построит дом.
Пожалуй, на этом примере можно и остановиться.
Допустим, у вас есть 3 простых действия:
1. Собирать камень
2. Рубить дерево
3. Строить дом

Строить дом - это действие, которое как результат выдает "дом построен", но на вход ему необходимо 2 камня и 4 дерева. Для того чтобы добыть 2 камня нужно иметь кирку, а чтобы добыть 4 дерева - нужен топор. Допустим, что топор и кирка у нас уже есть. Мы идем рубить дерево и добывать камень. И повторяем эти действия столько раз, пока не наберется необходимое количество.
Таким образом GOAP - это граф, который строится динамически, когда мы просим дать нам какой-то результат. Он всегда строится исходя из кратчайшего пути, т.е. если 4 дерева будет лежать на складе, то персонаж пойдет именно туда, т.к. рубить ничего не нужно.

А что вы используете в своих проектах?

#algorithms
👍24
Атомарные операции.
int i = 123; // всегда атомарно
long j = 234L; // атомарно на x64, но кому сейчас надо x32?
i++; // никогда не атомарно, т.к. мы читаем данные, увеличиваем, а потом записываем

#threading
👍9🥱7🤔1
Реализация lock, которую я использую в ME.BECS
public static void Lock(ref int lockIndex) {
for (;;) {
if (System.Threading.Interlocked.Exchange(ref lockIndex, 1) == 0) {
break;
}
}
}

public static void Unlock(ref int lockIndex) {
System.Threading.Interlocked.Exchange(ref lockIndex, 0);
}


Мы заводим int поле и используем его в качестве идентификатора для операции блокирования. Другими словами, пока не будет вызван Unlock, второй поток не пройдет через Lock. Из минусов такого подхода - если ваш код между этими вызовами упадет по исключению, то все ожидающие потоки повиснут. Для этого я написал дополнение, которое выходит из цикла с ошибкой, если мы ждем слишком долго. Как я писал выше в одном из постов, лучше вообще избегать блокировки и Interlocked, но в редких случаях без них не обойтись.

#threading
👍3
Рефлексия.
Можно определить отсутствие кода в методе, используя вызов
methodInfo.GetMethodBody().GetILAsByteArray()?.Length <= 2
т.к. по сути там может быть 2 инструкции. Я использую этот метод только в редакторе, т.к. результат работы метода в билде будет зависеть от сборки.

#reflection
👍2🥴1👻1
Существует такой аттрибут Conditional, который дает возможность отключать/включать куски кода по дефайну.
[Conditional("DEBUG")]
void Method() {...}
Метод будет компилироваться только если существует дефайн DEBUG. Метод должен быть void.

#defines
👍23🤔3🥱3🤯1
Многие встречали и даже использовали memcpy. В юнити это UnsafeUtility.MemCpy. Но там есть еще UnsafeUtility.MemMove. Главное отличие в том, что memcpy работает быстрее, т.к. не делает дополнительных проверок на общую область памяти, а вот memmove делает. Проще говоря, не используйте memcpy, если два блока пересекаются, т.к. это приведет к неопределенному результату. Т.е. когда вы копируете данные из одного массива в другой - память никак не пересекается и можно использовать memcpy. Но если же вы хотите "подвинуть" данные в одном массиве, то используйте memmove.

#unsafe
👍8
Дефайны можно объявлять не только в ProjectSettings, но и в файлах csc.rsp. При этом эти файлы могут располагаться как в корне (В папке Assets), так и у каждой ассембли отдельно.
Внутри можно делать ссылки на другие csc.rsp, подробнее можно почитать тут: https://docs.unity3d.com/530/Documentation/Manual/PlatformDependentCompilation.html.

#defines
👍24
Джобы.
Вы, наверное, слышали про IJobParallelForTransform. Это такая джоба, которая с удовольствием даст доступ к массиву трасформов ваших GameObject. Но существует несколько интересных моментов, которые повлияют на производительность:
1. Если вы читаете трасформы и не меняете их - помечайте их как ReadOnly (Запускайте джобу через ScheduleReadOnly), это повлияет на выполнение джобы.
2. Раскидайте элементы по нескольким рутам иерархии, группировав их по 64/128/256 штук. То есть если у вас на сцене в руте 10к GameObject и вы хотите их подвигать, то создайте N пустых GameObject и вложите в них по 64/128/256 ваших объектов. Если этого не сделать, то ваша джоба будет всегда ожидаться главным потоком независимо от отсутствия Complete.

#jobs
👍17🔥4