Кодогенерация в этих ваших юнити

не хотите мемы с вотермарками -- так я тоже не хочу
не хотите мемы с вотермарками -- так я тоже не хочу

Мы засунули монитор тебе в монитор, а потом MTV скатился в кал.

Ну и что такое это ваша кодогенерация? Всё просто. Это код, который пишет код. Это как интернет, карри (и currying тоже, да) или унитаз -- сначала ты думаешь "а нахуя оно мне надо", а потом жить без этого не можешь.

И вот где-где, а в Юнити без неё жить точно невозможно, потому что на каждом шагу приходится пользоваться магическими строками:
- Названия стейтов и параметров в аниматоре (аниматор вообще сильно повышает риск суицида)
- Названия инпутов, что во встроенной системе, что в каком-нибудь Rewired
- Названия сцен
- Названия слоёв, тэгов, и ещё целой кучи разного говна

"Ну строки и строки", скажешь ты, -- "и чё"? LayerMask.GetMask("Dfeault"), вот чё. Или иди попробуй через два месяца вспомни, как называется триггер для мощной атаки в аниматоре -- "Attack Heavy", "Attack_Heavy", "AttackHeavy", я на тебя посмотрю.

Ну ты понял, короче. А теперь, вот как это делается: в Юнити есть замечательный такой аттрибут (не спрашивайте, какого хрена сишарповская версия декораторов так называется -- я не знаю): [UnityEditor.InitializeOnLoadMethod].

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

А теперь попробуем запилить генератор, который выводит все слои в константы.

Для начала, создадим новый класс и засунем его в какой-нибудь стрёмный неймспейс, чтобы случайно где не надо к нему не обратиться

namespace Utils.Codegen { public static class LayersGenerator { } }

Теперь напишем метод, который возвращает массив всех слоёв.

private static string[] GetLayerNames () { string[] layers = new string[32]; for(int i = 0; i < 32; i++) { layers[i] = LayerMask.LayerToName(i); } return layers; }

Тут всё просто -- в Юнити есть 32 слоя, поэтому мы и проходимся от 0 до 32 и записываем в массив результаты метода LayerMask.LayerToName, который возвращает названия слоя по индексу.

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

[InitializeOnLoadMethod] private static void Generate () { string path = $"{Application.dataPath}/Scripts/Generated/Layers.cs"; string code = $@"public static class Layers {{ { String.Join("\n", GetLayerNames().Select(layerName => $"const string {layerName} = \"{layerName}\";")) } }}"; // Записываем код в файл System.IO.File.WriteAllText(path, code); }

Первая строка говорит сама за себя -- создаём переменную path, в которую записываем путь к файлу, который будем создавать. В данном случае = /Assets/Scripts/Generated/Layers.cs

А вот на String.Join("\n", GetLayerNames().Select(_ => $"const string {_} = {_};")) я остановлюсь поподробнее. Тут мы сначала преобразуем массив слоёв в массив из строк вида "const string %НАЗВАНИЕ_СЛОЯ% = "%НАЗВАНИЕ_СЛОЯ%"" через LINQовский Select, а потом объединяем всё это в одную большую строку раздёленную символом переноса строк -- "\n".

Не видишь проблему? А она есть, как суслик. Даже целых два суслика.

Кодогенерация в этих ваших юнити

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

Ну-ка по-быстрому её решим.

String.Join("\n", GetLayerNames() .Where(layerName => layerName != "") .Select(layerName => $"const string {layerName.Replace(" ", "_").ToUpper()} = \"{layerName}\";")

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

Теперь наш файл выглядит вот так и компилится как надо
Теперь наш файл выглядит вот так и компилится как надо

Для копипаста, выглядит всё в результате вот так:

using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEditor; namespace Utils.Codegen { public static class LayersGenerator { // Записываем private static string[] GetLayerNames () { string[] layers = new string[32]; for(int i = 0; i < 32; i++) { layers[i] = LayerMask.LayerToName(i); } return layers; } [InitializeOnLoadMethod] private static void Generate () { string path = $"{Application.dataPath}/Scripts/Layers.cs"; string code = $@"public static class Layers {{ { String.Join("\n", GetLayerNames() .Where(layerName => layerName != "") .Select(layerName => $"const string {layerName.Replace(" ", "_").ToUpper()} = \"{layerName}\";") ) } }}"; // Записываем код в файл System.IO.File.WriteAllText(path, code); } } }

Такие дела. Мораль думайте сами.

2121
17 комментариев

Или иди попробуй через два месяца вспомни, как называется триггер для мощной атаки в аниматоре — "Attack Heavy", "Attack_Heavy", "AttackHeavy"

Если у вас нет стайлгайдов для таких вещей, то это печально.

2

а) не всегда спасает
б) в стайл гайд всё равно нужно лезть
ц) всё-таки намного лучше, когда интеллисенс сам предлагает варианты

5

Ещё райдер сильно облегчает эту задачу, он интелсенсит и слои и теги.

1

это единственное его отличие от VC или есть ещё что то полезное для юнити?

Комментарий недоступен

Ну совсем спасти не спасёт, но вот от тупых очепяток защитит. И сразу забьёт тревогу, если нужный стейт или параметр удалили/переименовали.

Но в целом аниматор я выбросил в мусорку, купил Animancer и доволен как слон.

2

- Названия стейтов и параметров в аниматоре (аниматор вообще сильно повышает риск суицида)- Названия инпутов, что во встроенной системе, что в каком-нибудь Rewired- Названия сцен- Названия слоёв, тэгов, и ещё целой кучи разного говна

Интересно что ты перечислил всё чего в проекте я практически не касаюсь:
Аниматор - использую только для возможности вывода анимации ну и деревья,
Слои - только некоторые объекты в Игноррейкаст,
Название сцен -сцена обычно одна 

Тэги???? если кто то их использует то нам не по пути, разве что CameraMain и то правильнее обращаться на прямую.  

Статью целиком не читал, но прочитав начало ещё раз убедился в своей правоте.