Управление импортерами с помощью скриптов
Вы можете использовать скрипты C# для взаимодействия со встроенными импортерами Unity, или для создания импортера для добавления поддержки файлов, которые не поддерживаются Unity.
Сценарии со встроенными импортерами
Используйте обратные вызова в AssetPostprocessor класс, чтобы добавить собственное поведение до или после того, как Unity запустит импорт своими встроенными импортёрами. Вы можете менять настройки импорта, анализировать импортированные ассеты или динамически создавать новые прямо в процессе импорта. См. Ссылка на поддерживаемый тип ассета для полного списка встроенных импортеров доступных.
Ниже приведен пример скрипта AssetPostprocessor, который изменяет параметры импорта текстуры перед ее импортом, а затем применяет красный цвет к текстуре после импорта:
using UnityEngine;
using UnityEditor;
public class CustomTextureImporter : AssetPostprocessor
{
// Increment the version number, when the AssetPostprocessors code/behavior is changed
static readonly uint k_Version = 0;
public override uint GetVersion() { return k_Version; }
void OnPreprocessTexture()
{
// Get a reference to the TextureImporter
TextureImporter importer = assetImporter as TextureImporter;
// Customize settings
importer.mipmapEnabled = false;
importer.textureType = TextureImporterType.Default;
importer.maxTextureSize = 512;
importer.wrapMode = TextureWrapMode.Repeat;
Debug.Log($"Texture '{assetPath}' has had its import settings changed in OnPreProcessTexture.");
}
void OnPostprocessTexture(Texture2D texture)
{
// Set a red color tint to the texture
Color tintColor = new(1.0f, 0.5f, 0.5f, 1.0f);
// Get the texture's pixels
Color[] pixels = texture.GetPixels();
for (int i = 0; i < pixels.Length; i++)
{
// Apply the tint color
pixels[i] *= tintColor;
}
// Set the modified pixels back to the texture
texture.SetPixels(pixels);
// Apply the changes to the texture
texture.Apply();
// Log the change
Debug.Log($"Texture '{texture.name}' has been tinted with a red color in OnPostProcessTexture.");
}
}
Чтобы использовать этот пример, поместите его в новый файл скрипта где-то в папке Assets вашего проекта, а затем добавьте новую текстуру в папку Assets. Unity затем применяет настройки к текстуре, как показано на следующем изображении:
Создание пользовательских импортеров
Чтобы добавить собственную поддержку форматов файлов, которые не поддерживаются Unity, вы можете использовать ScriptedImporter для написания пользовательских импортеров ассетов в C#.
Скриптовый импортёр — это класс, наследующий абстрактный класс ScriptedImporter и имеет [ScriptedImporter] атрибут. Это регистрирует ваш пользовательский импортер для обработки одного или нескольких расширений файлов. Когда Unity обнаруживает файл, который соответствует зарегистрированным расширениям файлов как новый или измененный, он вызывает метод OnImportAsset вашего таможенного импортера.
Важно: Скриптовые импортеры не могут обрабатывать расширение файла, которое Unity уже обрабатывает. Вы можете использовать overrideExts параметр, чтобы переопределить это поведение и добавить расширение файла для существующего импортёра. Список файлов, поддерживаемых Unity изначально, см. Ссылка на поддерживаемый тип ассета.
После добавления скрипта ScriptedImporter в проект, вы можете использовать его так же, как и любой другой тип файла, поддерживаемый Unity. Дополнительные сведения см. в Введение в импорт активов.
Создание скриптового импортера
Следующий пример кода импортирует файлы ассетов с расширением cube в префаб с примитивом куба в качестве основного ассета и материалом и цветом по умолчанию. Затем он присваивает свое положение из значения, прочитанного из файла ассета:
using UnityEngine;
using UnityEditor.AssetImporters;
using System.IO;
// The importer is registered with Unity's asset pipeline by placing the ScriptedImporter attribute on the
// CubeImporter class. The CubeImporter class implements the abstract ScriptedImporter base class.
[ScriptedImporter(1, "cube")]
public class CubeImporter : ScriptedImporter
{
public float m_Scale = 1;
// The ctx argument contains both input and output data for the import event
public override void OnImportAsset(AssetImportContext ctx)
{
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
var position = JsonUtility.FromJson<Vector3>(File.ReadAllText(ctx.assetPath));
cube.transform.position = position;
cube.transform.localScale = new Vector3(m_Scale, m_Scale, m_Scale);
// 'cube' is a GameObject and is automatically converted into a prefab.
// Only the 'Main Asset' is eligible to become a prefab.
ctx.AddObjectToAsset("main obj", cube);
ctx.SetMainObject(cube);
var material = new Material(Shader.Find("Standard"));
material.color = Color.red;
// Assets must be assigned a unique identifier string consistent across imports.
ctx.AddObjectToAsset("my Material", material);
// Assets that are not passed into the context as import outputs must be destroyed.
var tempMesh = new Mesh();
DestroyImmediate(tempMesh);
}
}
Дополнительные сведения см. в документации AssetImporters.ScriptedImporter API.
Создание настраиваемого окна параметров импорта
Чтобы сделать собственное окно настроек импорта для вашего скриптового импортёра, объявите класс, наследующий ScriptedImporterEditor и украсить его [CustomEditor] атрибут. Например:
using UnityEditor;
using UnityEditor.AssetImporters;
using UnityEditor.SceneManagement;
using UnityEngine;
[CustomEditor(typeof(CubeImporter))]
public class CubeImporterEditor: ScriptedImporterEditor
{
public override void OnInspectorGUI()
{
var colorShift = new GUIContent("Color Shift");
var prop = serializedObject.FindProperty("m_ColorShift");
EditorGUILayout.PropertyField(prop, colorShift);
base.ApplyRevertGUI();
}
}
Импортные зависимости и детерминизм
При создании пользовательских скриптов AssetPostprocessor или ScriptedImporter убедитесь, что ваш код импорта является детерминированным. Детерминированный импортер всегда производит один и тот же вывод из одного и того же входа и зависимостей. Незарегистрированные зависимости или недетерминированный код могут привести к тому, что Asset Pipeline будет кэшировать неправильные результаты, что приведет к непоследовательным сборкам на различных машинах или при изменении активного объекта сборки.
Зависимости импорта реестров
Если ваш код импорта считывает из внешних файлов или зависит от других состояний среды, которые не охватываются зависимостями автоматического импорта Unity, вы должны зарегистрировать эти зависимости. В противном случае Unity не будет повторно импортировать ассет, если эти зависимости изменятся. Для активного объекта сборки, используемого для импорта, считайте selectedBuildTarget на импорте context вместо того, чтобы отслеживать платформу вручную.
Для файловых зависимостей используйте context.DependsOnArtifact для их регистрации. Если файл конфигурации изменяется, Unity автоматически реимпортирует зависимый ресурс.
public class ConfigDependentPostprocessor : AssetPostprocessor
{
void OnPreprocessTexture()
{
// Register the dependency before reading the asset
context.DependsOnArtifact("Assets/Config/TextureImportConfig.asset");
// Read config and apply settings...
}
}
Для логики импорта, зависящей от активного объекта сборки (например, сжатия или путей для конкретной платформы), избегайте констант времени компиляции, таких как #if UNITY_EDITOR_WIN. Используйте проверки времени выполнения для объекта сборки Unity, для которого осуществляется импорт. Подход зависит от того, используется ли ScriptedImporter или AssetPostprocessor:
- Скриптовый импортер: Чтение
selectedBuildTargetизAssetImportContext, переданных вOnImportAsset. - Постпроцессор актива: Использовать то же свойство на экземпляре
context.
Доступ к selectedBuildTarget регистрирует зависимость импорта от этой цели сборки, поэтому Unity реимпортирует, когда цель, относящаяся к импорту, изменяется.
using UnityEditor;
using UnityEngine;
public class PlatformAwarePostprocessor : AssetPostprocessor
{
void OnPreprocessTexture()
{
switch (context.selectedBuildTarget)
{
case BuildTarget.StandaloneWindows64:
// Apply settings for Windows standalone imports
break;
case BuildTarget.StandaloneOSX:
// Apply settings for macOS standalone imports
break;
default:
break;
}
}
}
Повысить версии импортера и постпроцессора
При изменении поведения файла ScriptedImporter или AssetPostprocessorвыполните бумп его версии, чтобы аннулировать ранее кэшированные результаты импорта. Если вы не выполните бумп версии, то при следующем запуске импортера он может повторно использовать неверные результаты импорта.
- Скриптовые импортеры: Увеличение версии аргумента атрибута
[ScriptedImporter]при каждом изменении кода импортера. - Постпроцессоры активов: Переопределяет
GetVersionи возвращает новое значение при изменении кодаAssetPostprocessor.
CustomTextureImporter — пример пользовательского импортёра в разделе Создание скриптов для встроенных импортёров демонстрирует реализацию GetVersion, которую можно использовать как образец для постпроцессоров.
Избегайте недетерминистического кода
Настраиваемый код импорта, основанный на времени, случайных числах, неупорядоченных коллекциях или асинхронном времени, дает различные результаты при каждом запуске.
- Операции, основанные на времени: Не используйте свойства, такие как
DateTime.UtcNow, для маркировки ассетов во время импорта. Если вам нужно регистрировать время импорта, записывайте в файл журнала вне конвейера ассетов. - Неупорядоченные коллекции: Итерация по коллекции
DictionaryилиHashSetсоздает элементы в неопределенном порядке. Используйте явно сортируемые коллекции, такие какListили массивы, чтобы гарантировать последовательный порядок обработки. - Операции, зависящие от времени: Не полагайтесь на асинхронный порядок завершения, планирование потоков, доступность сети или производительность файла ввода-вывода. Например, если импортер создает несколько задач
asyncдля обработки субактивов, сортируйте или синхронизируйте задачи перед объединением их результатов, а не полагайтесь на порядок, в котором они заканчиваются.
class OrderedCollectionProcessor : AssetPostprocessor
{
void OnPostprocessModel(GameObject model)
{
var materialList = new List<Material>();
// Populate list...
// Sort explicitly by a deterministic property
materialList.Sort((a, b) => string.Compare(a.name, b.name, System.StringComparison.Ordinal));
foreach (var material in materialList)
{
// Modification order is now deterministic
}
}
}