/// <summary> /// Implementation of ScriptedImporter.OnImportAsset /// </summary> /// <param name="ctx"> /// This argument contains all the contextual information needed to process the import /// event and is also used by the custom importer to store the resulting Unity Asset. /// </param> public override void OnImportAsset(AssetImportContext ctx) { var spriteLib = ScriptableObject.CreateInstance <SpriteLibraryAsset>(); spriteLib.name = System.IO.Path.GetFileNameWithoutExtension(assetPath); var sourceAsset = UnityEditorInternal.InternalEditorUtility.LoadSerializedFileAndForget(assetPath); if (sourceAsset?.Length > 0) { var sourceLibraryAsset = sourceAsset[0] as SpriteLibrarySourceAsset; if (sourceLibraryAsset != null) { foreach (var cat in sourceLibraryAsset.library) { spriteLib.AddCategoryLabel(null, cat.name, null); foreach (var entry in cat.overrideEntries) { spriteLib.AddCategoryLabel(entry.spriteOverride, cat.name, entry.name); } } } if (!string.IsNullOrEmpty(sourceLibraryAsset.primaryLibraryID)) { var primaryAssetPath = AssetDatabase.GUIDToAssetPath(sourceLibraryAsset.primaryLibraryID); if (primaryAssetPath != assetPath) { ctx.DependsOnArtifact(AssetDatabase.GUIDToAssetPath(sourceLibraryAsset.primaryLibraryID)); m_PrimaryLibrary = AssetDatabase.LoadAssetAtPath <SpriteLibraryAsset>(primaryAssetPath); } } } ctx.AddObjectToAsset("SpriteLib", spriteLib, IconUtility.LoadIconResource("Sprite Library", "Icons/Light", "Icons/Dark")); }
public override void OnImportAsset(AssetImportContext ctx) { ScriptableObjectVariant variantBase = ScriptableObject.CreateInstance <ScriptableObjectVariant>(); ctx.AddObjectToAsset(nameof(variantBase), variantBase); ctx.SetMainObject(variantBase); if (Origin == default) { return; } string dependencyPath = AssetDatabase.GUIDToAssetPath(Origin); if (string.IsNullOrEmpty(dependencyPath)) { return; } ctx.DependsOnArtifact(new GUID(Origin)); // We need to reload the dependency from the asset database or else it doesn't know we depend on it. var dependency = AssetDatabase.LoadAssetAtPath <ScriptableObject>(dependencyPath); ScriptableObject variant = Instantiate(dependency); var overrideData = string.IsNullOrEmpty(Json) ? null : JsonConvert.DeserializeObject <OverrideData>(Json); if (overrideData?.Overrides.Count > 0) { using var so = new SerializedObject(variant); foreach (KeyValuePair <string, JToken> dataOverride in overrideData.Overrides) { SerializedProperty property = so.FindProperty(dataOverride.Key); if (property == null) { Debug.LogWarning($"{dataOverride.Key} no longer exists in {variant}"); continue; } SetProperty(property, dataOverride.Value); } so.ApplyModifiedPropertiesWithoutUndo(); } variantBase.Variant = variant; variant.name = Path.GetFileNameWithoutExtension(assetPath); ctx.AddObjectToAsset(nameof(variant), variant); }
public override void OnImportAsset(AssetImportContext ctx) { var graphAsset = ScriptableObject.CreateInstance <SubGraphAsset>(); var subGraphPath = ctx.assetPath; var subGraphGuid = AssetDatabase.AssetPathToGUID(subGraphPath); graphAsset.assetGuid = subGraphGuid; var textGraph = File.ReadAllText(subGraphPath, Encoding.UTF8); var messageManager = new MessageManager(); var graphData = new GraphData { isSubGraph = true, assetGuid = subGraphGuid, messageManager = messageManager }; MultiJson.Deserialize(graphData, textGraph); try { ProcessSubGraph(graphAsset, graphData); } catch (Exception e) { graphAsset.isValid = false; Debug.LogException(e, graphAsset); } finally { if (messageManager.AnyError()) { graphAsset.isValid = false; foreach (var pair in messageManager.GetNodeMessages()) { var node = graphData.GetNodeFromId(pair.Key); foreach (var message in pair.Value) { MessageManager.Log(node, subGraphPath, message, graphAsset); } } } messageManager.ClearAll(); } Texture2D texture = Resources.Load <Texture2D>("Icons/sg_subgraph_icon@64"); ctx.AddObjectToAsset("MainAsset", graphAsset, texture); ctx.SetMainObject(graphAsset); var metadata = ScriptableObject.CreateInstance <ShaderSubGraphMetadata>(); metadata.hideFlags = HideFlags.HideInHierarchy; metadata.assetDependencies = new List <UnityEngine.Object>(); AssetCollection assetCollection = new AssetCollection(); MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection); foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.IncludeInExportPackage)) { // this sucks that we have to fully load these assets just to set the reference, // which then gets serialized as the GUID that we already have here. :P var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key); if (!string.IsNullOrEmpty(dependencyPath)) { metadata.assetDependencies.Add( AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(UnityEngine.Object))); } } } ctx.AddObjectToAsset("Metadata", metadata); // declare dependencies foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.SourceDependency)) { ctx.DependsOnSourceAsset(asset.Key); // I'm not sure if this warning below is actually used or not, keeping it to be safe var assetPath = AssetDatabase.GUIDToAssetPath(asset.Key); // Ensure that dependency path is relative to project if (!string.IsNullOrEmpty(assetPath) && !assetPath.StartsWith("Packages/") && !assetPath.StartsWith("Assets/")) { Debug.LogWarning($"Invalid dependency path: {assetPath}", graphAsset); } } // NOTE: dependencies declared by GatherDependenciesFromSourceFile are automatically registered as artifact dependencies // HOWEVER: that path ONLY grabs dependencies via MinimalGraphData, and will fail to register dependencies // on GUIDs that don't exist in the project. For both of those reasons, we re-declare the dependencies here. if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency)) { ctx.DependsOnArtifact(asset.Key); } } }
public override void OnImportAsset(AssetImportContext ctx) { var oldShader = AssetDatabase.LoadAssetAtPath <Shader>(ctx.assetPath); if (oldShader != null) { ShaderUtil.ClearShaderMessages(oldShader); } List <PropertyCollector.TextureInfo> configuredTextures; string path = ctx.assetPath; AssetCollection assetCollection = new AssetCollection(); MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection); var textGraph = File.ReadAllText(path, Encoding.UTF8); var graph = new GraphData { messageManager = new MessageManager(), assetGuid = AssetDatabase.AssetPathToGUID(path) }; MultiJson.Deserialize(graph, textGraph); graph.OnEnable(); graph.ValidateGraph(); Shader shader = null; #if VFX_GRAPH_10_0_0_OR_NEWER if (!graph.isOnlyVFXTarget) #endif { // build the shader text // this will also add Target dependencies into the asset collection var text = GetShaderText(path, out configuredTextures, assetCollection, graph); #if UNITY_2021_1_OR_NEWER // 2021.1 or later is guaranteed to have the new version of this function shader = ShaderUtil.CreateShaderAsset(ctx, text, false); #else // earlier builds of Unity may or may not have it // here we try to invoke the new version via reflection var createShaderAssetMethod = typeof(ShaderUtil).GetMethod( "CreateShaderAsset", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.ExactBinding, null, new Type[] { typeof(AssetImportContext), typeof(string), typeof(bool) }, null); if (createShaderAssetMethod != null) { shader = createShaderAssetMethod.Invoke(null, new Object[] { ctx, text, false }) as Shader; } else { // method doesn't exist in this version of Unity, call old version // this doesn't create dependencies properly, but is the best that we can do shader = ShaderUtil.CreateShaderAsset(text, false); } #endif if (graph.messageManager.nodeMessagesChanged) { foreach (var pair in graph.messageManager.GetNodeMessages()) { var node = graph.GetNodeFromId(pair.Key); MessageManager.Log(node, path, pair.Value.First(), shader); } } EditorMaterialUtility.SetShaderDefaults( shader, configuredTextures.Where(x => x.modifiable).Select(x => x.name).ToArray(), configuredTextures.Where(x => x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray()); EditorMaterialUtility.SetShaderNonModifiableDefaults( shader, configuredTextures.Where(x => !x.modifiable).Select(x => x.name).ToArray(), configuredTextures.Where(x => !x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray()); } UnityEngine.Object mainObject = shader; #if VFX_GRAPH_10_0_0_OR_NEWER ShaderGraphVfxAsset vfxAsset = null; if (graph.hasVFXTarget) { vfxAsset = GenerateVfxShaderGraphAsset(graph); if (mainObject == null) { mainObject = vfxAsset; } else { //Correct main object if we have a shader and ShaderGraphVfxAsset : save as sub asset vfxAsset.name = Path.GetFileNameWithoutExtension(path); ctx.AddObjectToAsset("VFXShaderGraph", vfxAsset); } } #endif Texture2D texture = Resources.Load <Texture2D>("Icons/sg_graph_icon"); ctx.AddObjectToAsset("MainAsset", mainObject, texture); ctx.SetMainObject(mainObject); foreach (var target in graph.activeTargets) { if (target is IHasMetadata iHasMetadata) { var metadata = iHasMetadata.GetMetadataObject(); if (metadata == null) { continue; } metadata.hideFlags = HideFlags.HideInHierarchy; ctx.AddObjectToAsset($"{iHasMetadata.identifier}:Metadata", metadata); } } var sgMetadata = ScriptableObject.CreateInstance <ShaderGraphMetadata>(); sgMetadata.hideFlags = HideFlags.HideInHierarchy; sgMetadata.assetDependencies = new List <UnityEngine.Object>(); foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.IncludeInExportPackage)) { // this sucks that we have to fully load these assets just to set the reference, // which then gets serialized as the GUID that we already have here. :P var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key); if (!string.IsNullOrEmpty(dependencyPath)) { sgMetadata.assetDependencies.Add( AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(UnityEngine.Object))); } } } ctx.AddObjectToAsset("SGInternal:Metadata", sgMetadata); // declare dependencies foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.SourceDependency)) { ctx.DependsOnSourceAsset(asset.Key); // I'm not sure if this warning below is actually used or not, keeping it to be safe var assetPath = AssetDatabase.GUIDToAssetPath(asset.Key); // Ensure that dependency path is relative to project if (!string.IsNullOrEmpty(assetPath) && !assetPath.StartsWith("Packages/") && !assetPath.StartsWith("Assets/")) { Debug.LogWarning($"Invalid dependency path: {assetPath}", mainObject); } } // NOTE: dependencies declared by GatherDependenciesFromSourceFile are automatically registered as artifact dependencies // HOWEVER: that path ONLY grabs dependencies via MinimalGraphData, and will fail to register dependencies // on GUIDs that don't exist in the project. For both of those reasons, we re-declare the dependencies here. if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency)) { ctx.DependsOnArtifact(asset.Key); } } }
public override void OnImportAsset(AssetImportContext ctx) { var width = 64; var mipmapEnabled = true; var textureFormat = TextureFormat.RGBA32; var srgbTexture = true; // Mark all input textures as dependency to the CubemapArray. // This causes the CubemapArray to get re-generated when any input texture changes or when the build target changed. for (var n = 0; n < m_Cubemaps.Count; ++n) { var source = m_Cubemaps[n]; if (source != null) { var path = AssetDatabase.GetAssetPath(source); #if UNITY_2020_1_OR_NEWER ctx.DependsOnArtifact(path); #else ctx.DependsOnSourceAsset(path); #endif } } #if !UNITY_2020_1_OR_NEWER // This value is not really used in this importer, // but getting the build target here will add a dependency to the current active buildtarget. // Because DependsOnArtifact does not exist in 2019.4, adding this dependency on top of the DependsOnSourceAsset // will force a re-import when the target platform changes in case it would have impacted any texture this importer depends on. var buildTarget = ctx.selectedBuildTarget; #endif // Check if the input textures are valid to be used to build the texture array. var isValid = Verify(ctx, false); if (isValid) { // Use the texture assigned to the first slice as "master". // This means all other textures have to use same settings as the master texture. var sourceTexture = m_Cubemaps[0]; width = sourceTexture.width; textureFormat = sourceTexture.format; var sourceTexturePath = AssetDatabase.GetAssetPath(sourceTexture); var textureImporter = (TextureImporter)AssetImporter.GetAtPath(sourceTexturePath); mipmapEnabled = textureImporter.mipmapEnabled; srgbTexture = textureImporter.sRGBTexture; } CubemapArray cubemapArray; try { // Create the texture array. // When the texture array asset is being created, there are no input textures added yet, // thus we do Max(1, Count) to make sure to add at least 1 slice. cubemapArray = new CubemapArray(width, Mathf.Max(1, m_Cubemaps.Count), textureFormat, mipmapEnabled, !srgbTexture); } catch (System.Exception e) { Debug.LogException(e); ctx.LogImportError($"Import failed '{ctx.assetPath}'.", ctx.mainObject); isValid = false; textureFormat = TextureFormat.RGBA32; cubemapArray = new CubemapArray(width, Mathf.Max(1, m_Cubemaps.Count), textureFormat, mipmapEnabled, !srgbTexture); } cubemapArray.wrapMode = m_WrapMode; cubemapArray.filterMode = m_FilterMode; cubemapArray.anisoLevel = m_AnisoLevel; if (isValid) { // If everything is valid, copy source textures over to the texture array. for (int face = 0; face < 6; face++) { for (var n = 0; n < m_Cubemaps.Count; ++n) { var source = m_Cubemaps[n]; Graphics.CopyTexture(source, face, cubemapArray, face + (n * 6)); } } } else { // If there is any error, copy a magenta colored texture into every slice. // I was thinking to only make the invalid slice magenta, but then it's way less obvious that // something isn't right with the texture array. Thus I mark the entire texture array as broken. var errorTexture = new Cubemap(width, textureFormat, mipmapEnabled); try { for (var face = 0; face < 6; ++face) { var errorPixels = errorTexture.GetPixels((CubemapFace)face); for (var n = 0; n < errorPixels.Length; ++n) { errorPixels[n] = Color.magenta; } errorTexture.SetPixels(errorPixels, (CubemapFace)face); } errorTexture.Apply(); for (int face = 0; face < 6; face++) { for (var n = 0; n < cubemapArray.cubemapCount; ++n) { Graphics.CopyTexture(errorTexture, face, cubemapArray, face + (n * 6)); } } } finally { DestroyImmediate(errorTexture); } } // this should have been named "MainAsset" to be conform with Unity, but changing it now // would break all existing CubemapArray assets, so we don't touch it. cubemapArray.Apply(false, !m_IsReadable); ctx.AddObjectToAsset("CubemapArray", cubemapArray); ctx.SetMainObject(cubemapArray); if (!isValid) { // Run the verify step again, but this time we have the main object asset. // Console logs should ping the asset, but they don't in 2019.3 beta, bug? Verify(ctx, true); } }
/// <inheritdoc /> public override void OnImportAsset(AssetImportContext ctx) { if (ctx == null) { return; } var outputFilePath = Path.Combine(Path.GetDirectoryName(ctx.assetPath), Path.GetFileNameWithoutExtension(ctx.assetPath) + ".cs"); var src = File.ReadAllText(ctx.assetPath); TemplateGenerator generator; switch (importerVersion) { case ImporterVersion.Stable: generator = new TemplateGenerator(); break; case ImporterVersion.Beta: { generator = UnityDataHost <Object> .CreateInstance(embeddedData, additionalTypes); break; } default: throw new InvalidOperationException(); } if (generator.ProcessTemplate(ctx.assetPath, src, ref outputFilePath, out string dst)) { if (!removeIfEmptyGeneration || dst.Length > 0) { File.WriteAllText(outputFilePath, dst); } EditorUtils.Once(() => { // TODO(bengreenier): is there a less expensive way to force it to reload scripts/appdomain? AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate); }); } else { for (var i = 0; i < generator.Errors.Count; i++) { var err = generator.Errors[i]; Debug.LogError(err.ToString()); } } // core asset import var asset = new TextAsset(src); ctx.AddObjectToAsset("text", asset); ctx.SetMainObject(asset); // setup data dependency if (embeddedData != null && importerVersion == ImporterVersion.Beta) { var dataPath = AssetDatabase.GetAssetPath(embeddedData); var dataGuid = AssetDatabase.GUIDFromAssetPath(dataPath); ctx.DependsOnArtifact(dataGuid); // needed to enforce the dependency AssetDatabase.LoadAssetAtPath <Object>(dataPath); } }
public override void OnImportAsset(AssetImportContext ctx) { var width = 8; var height = 8; var mipmapEnabled = true; var textureFormat = TextureFormat.ARGB32; //var srgbTexture = true; // Check if the input textures are valid to be used to build the texture array. var isValid = Verify(ctx, false); if (isValid) { // Use the texture assigned to the first slice as "master". // This means all other textures have to use same settings as the master texture. var sourceTexture = m_Textures[0]; width = sourceTexture.width; height = sourceTexture.height; textureFormat = sourceTexture.format; var sourceTexturePath = AssetDatabase.GetAssetPath(sourceTexture); var textureImporter = (TextureImporter)AssetImporter.GetAtPath(sourceTexturePath); mipmapEnabled = textureImporter.mipmapEnabled; //srgbTexture = textureImporter.sRGBTexture; } // Create the texture array. // When the texture array asset is being created, there are no input textures added yet, // thus we do Max(1, Count) to make sure to add at least 1 slice. var texture3D = new Texture3D(width, height, Mathf.Max(1, m_Textures.Count), textureFormat, mipmapEnabled);//, !srgbTexture); texture3D.wrapMode = m_WrapMode; texture3D.filterMode = m_FilterMode; texture3D.anisoLevel = m_AnisoLevel; if (isValid) { // If everything is valid, copy source textures over to the texture array. for (var n = 0; n < m_Textures.Count; ++n) { var source = m_Textures[n]; Graphics.CopyTexture(source, 0, texture3D, n); } } else { // If there is any error, copy a magenta colored texture into every slice. // I was thinking to only make the invalid slice magenta, but then it's way less obvious that // something isn't right with the texture array. Thus I mark the entire texture array as broken. var errorPixels = new Color32[width * height]; for (var n = 0; n < errorPixels.Length; ++n) { errorPixels[n] = Color.magenta; } var texture3DPixels = new Color32[width * height * texture3D.depth]; for (var n = 0; n < texture3D.depth; ++n) { System.Array.Copy(errorPixels, 0, texture3DPixels, width * height * n, errorPixels.Length); } texture3D.SetPixels32(texture3DPixels); texture3D.Apply(); } // Mark all input textures as dependency to the texture array. // This causes the texture array to get re-generated when any input texture changes or when the build target changed. for (var n = 0; n < m_Textures.Count; ++n) { var source = m_Textures[n]; if (source != null) { var path = AssetDatabase.GetAssetPath(source); #if UNITY_2020_1_OR_NEWER ctx.DependsOnArtifact(path); #else ctx.DependsOnSourceAsset(path); #endif } } #if !UNITY_2020_1_OR_NEWER // This value is not really used in this importer, // but getting the build target here will add a dependency to the current active buildtarget. // Because DependsOnArtifact does not exist in 2019.4, adding this dependency on top of the DependsOnSourceAsset // will force a re-import when the target platform changes in case it would have impacted any texture this importer depends on. var buildTarget = ctx.selectedBuildTarget; #endif // this should have been named "MainAsset" to be conform with Unity, but changing it now // would break all existing Texture3DAtlas assets, so we don't touch it. ctx.AddObjectToAsset("Texture3D", texture3D); ctx.SetMainObject(texture3D); if (!isValid) { // Run the verify step again, but this time we have the main object asset. // Console logs should ping the asset, but they don't in 2019.3 beta, bug? Verify(ctx, true); } }
public override void OnImportAsset(AssetImportContext ctx) { try { var sceneWithBuildConfiguration = SceneWithBuildConfigurationGUIDs.ReadFromFile(ctx.assetPath); // Ensure we have as many dependencies as possible registered early in case an exception is thrown EditorEntityScenes.AddEntityBinaryFileDependencies(ctx, sceneWithBuildConfiguration.BuildConfiguration); EditorEntityScenes.DependOnSceneGameObjects(sceneWithBuildConfiguration.SceneGUID, ctx); var config = BuildConfiguration.LoadAsset(sceneWithBuildConfiguration.BuildConfiguration); var scenePath = AssetDatabaseCompatibility.GuidToPath(sceneWithBuildConfiguration.SceneGUID); var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive); try { EditorSceneManager.SetActiveScene(scene); var settings = new GameObjectConversionSettings(); settings.SceneGUID = sceneWithBuildConfiguration.SceneGUID; if (!sceneWithBuildConfiguration.IsBuildingForEditor) { settings.ConversionFlags |= GameObjectConversionUtility.ConversionFlags.IsBuildingForPlayer; } settings.BuildConfiguration = config; settings.AssetImportContext = ctx; settings.FilterFlags = WorldSystemFilterFlags.HybridGameObjectConversion; WriteEntitySceneSettings writeEntitySettings = new WriteEntitySceneSettings(); if (config != null && config.TryGetComponent <DotsRuntimeBuildProfile>(out var profile)) { if (config.TryGetComponent <DotsRuntimeRootAssembly>(out var rootAssembly)) { writeEntitySettings.Codec = Codec.LZ4; writeEntitySettings.IsDotsRuntime = true; writeEntitySettings.BuildAssemblyCache = new BuildAssemblyCache() { BaseAssemblies = rootAssembly.RootAssembly.asset, PlatformName = profile.Target.UnityPlatformName }; settings.FilterFlags = WorldSystemFilterFlags.DotsRuntimeGameObjectConversion; //Updating the root asmdef references or its references should re-trigger conversion ctx.DependsOnArtifact(AssetDatabase.GetAssetPath(rootAssembly.RootAssembly.asset)); foreach (var assemblyPath in writeEntitySettings.BuildAssemblyCache.AssembliesPath) { ctx.DependsOnArtifact(assemblyPath); } } } var sectionRefObjs = new List <ReferencedUnityObjects>(); var sectionData = EditorEntityScenes.ConvertAndWriteEntityScene(scene, settings, sectionRefObjs, writeEntitySettings); WriteAssetDependencyGUIDs(sectionRefObjs, sectionData, ctx); foreach (var objRefs in sectionRefObjs) { DestroyImmediate(objRefs); } } finally { EditorSceneManager.CloseScene(scene, true); } } // Currently it's not acceptable to let the asset database catch the exception since it will create a default asset without any dependencies // This means a reimport will not be triggered if the scene is subsequently modified catch (Exception e) { Debug.Log($"Exception thrown during SubScene import: {e}"); } }
public override void OnImportAsset(AssetImportContext ctx) { var importLog = new AssetImportErrorLog(ctx); string path = ctx.assetPath; AssetCollection assetCollection = new AssetCollection(); MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection); var textGraph = File.ReadAllText(path, Encoding.UTF8); var graph = new GraphData { messageManager = new MessageManager(), assetGuid = AssetDatabase.AssetPathToGUID(path) }; MultiJson.Deserialize(graph, textGraph); graph.OnEnable(); graph.ValidateGraph(); UnityEngine.Object mainObject = null; #if VFX_GRAPH_10_0_0_OR_NEWER if (!graph.isOnlyVFXTarget) #endif { // build shaders mainObject = BuildAllShaders(ctx, importLog, assetCollection, graph); } #if VFX_GRAPH_10_0_0_OR_NEWER ShaderGraphVfxAsset vfxAsset = null; if (graph.hasVFXTarget) { vfxAsset = GenerateVfxShaderGraphAsset(graph); if (mainObject == null) { mainObject = vfxAsset; } else { //Correct main object if we have a shader and ShaderGraphVfxAsset : save as sub asset vfxAsset.name = Path.GetFileNameWithoutExtension(path); ctx.AddObjectToAsset("VFXShaderGraph", vfxAsset); } } #endif Texture2D texture = Resources.Load <Texture2D>("Icons/sg_graph_icon"); ctx.AddObjectToAsset("MainAsset", mainObject, texture); ctx.SetMainObject(mainObject); foreach (var target in graph.activeTargets) { if (target is IHasMetadata iHasMetadata) { var metadata = iHasMetadata.GetMetadataObject(); if (metadata == null) { continue; } metadata.hideFlags = HideFlags.HideInHierarchy; ctx.AddObjectToAsset($"{iHasMetadata.identifier}:Metadata", metadata); } } var sgMetadata = ScriptableObject.CreateInstance <ShaderGraphMetadata>(); sgMetadata.hideFlags = HideFlags.HideInHierarchy; sgMetadata.assetDependencies = new List <UnityEngine.Object>(); foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.IncludeInExportPackage)) { // this sucks that we have to fully load these assets just to set the reference, // which then gets serialized as the GUID that we already have here. :P var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key); if (!string.IsNullOrEmpty(dependencyPath)) { sgMetadata.assetDependencies.Add( AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(UnityEngine.Object))); } } } List <GraphInputData> inputInspectorDataList = new List <GraphInputData>(); foreach (AbstractShaderProperty property in graph.properties) { // Don't write out data for non-exposed blackboard items if (!property.isExposed) { continue; } // VTs are treated differently if (property is VirtualTextureShaderProperty virtualTextureShaderProperty) { inputInspectorDataList.Add(MinimalCategoryData.ProcessVirtualTextureProperty(virtualTextureShaderProperty)); } else { inputInspectorDataList.Add(new GraphInputData() { referenceName = property.referenceName, propertyType = property.propertyType, isKeyword = false }); } } foreach (ShaderKeyword keyword in graph.keywords) { // Don't write out data for non-exposed blackboard items if (!keyword.isExposed) { continue; } var sanitizedReferenceName = keyword.referenceName; if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON")) { sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty); } inputInspectorDataList.Add(new GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true }); } sgMetadata.categoryDatas = new List <MinimalCategoryData>(); foreach (CategoryData categoryData in graph.categories) { // Don't write out empty categories if (categoryData.childCount == 0) { continue; } MinimalCategoryData mcd = new MinimalCategoryData() { categoryName = categoryData.name, propertyDatas = new List <GraphInputData>() }; foreach (var input in categoryData.Children) { GraphInputData propData; // Only write out data for exposed blackboard items if (input.isExposed == false) { continue; } // VTs are treated differently if (input is VirtualTextureShaderProperty virtualTextureShaderProperty) { propData = MinimalCategoryData.ProcessVirtualTextureProperty(virtualTextureShaderProperty); inputInspectorDataList.RemoveAll(inputData => inputData.referenceName == propData.referenceName); mcd.propertyDatas.Add(propData); continue; } else if (input is ShaderKeyword keyword) { var sanitizedReferenceName = keyword.referenceName; if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON")) { sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty); } propData = new GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true }; } else { var prop = input as AbstractShaderProperty; propData = new GraphInputData() { referenceName = input.referenceName, propertyType = prop.propertyType, isKeyword = false }; } mcd.propertyDatas.Add(propData); inputInspectorDataList.Remove(propData); } sgMetadata.categoryDatas.Add(mcd); } // Any uncategorized elements get tossed into an un-named category at the top as a fallback if (inputInspectorDataList.Count > 0) { sgMetadata.categoryDatas.Insert(0, new MinimalCategoryData() { categoryName = "", propertyDatas = inputInspectorDataList }); } ctx.AddObjectToAsset("SGInternal:Metadata", sgMetadata); // declare dependencies foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.SourceDependency)) { ctx.DependsOnSourceAsset(asset.Key); // I'm not sure if this warning below is actually used or not, keeping it to be safe var assetPath = AssetDatabase.GUIDToAssetPath(asset.Key); // Ensure that dependency path is relative to project if (!string.IsNullOrEmpty(assetPath) && !assetPath.StartsWith("Packages/") && !assetPath.StartsWith("Assets/")) { importLog.LogWarning($"Invalid dependency path: {assetPath}", mainObject); } } // NOTE: dependencies declared by GatherDependenciesFromSourceFile are automatically registered as artifact dependencies // HOWEVER: that path ONLY grabs dependencies via MinimalGraphData, and will fail to register dependencies // on GUIDs that don't exist in the project. For both of those reasons, we re-declare the dependencies here. if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency)) { ctx.DependsOnArtifact(asset.Key); } } }
public override void OnImportAsset(AssetImportContext ctx) { var oldShader = AssetDatabase.LoadAssetAtPath <Shader>(ctx.assetPath); if (oldShader != null) { ShaderUtil.ClearShaderMessages(oldShader); } List <PropertyCollector.TextureInfo> configuredTextures; string path = ctx.assetPath; AssetCollection assetCollection = new AssetCollection(); MinimalGraphData.GatherMinimalDependenciesFromFile(assetPath, assetCollection); var textGraph = File.ReadAllText(path, Encoding.UTF8); var graph = new GraphData { messageManager = new MessageManager(), assetGuid = AssetDatabase.AssetPathToGUID(path) }; MultiJson.Deserialize(graph, textGraph); graph.OnEnable(); graph.ValidateGraph(); Shader shader = null; #if VFX_GRAPH_10_0_0_OR_NEWER if (!graph.isOnlyVFXTarget) #endif { // build the shader text // this will also add Target dependencies into the asset collection var text = GetShaderText(path, out configuredTextures, assetCollection, graph); #if UNITY_2021_1_OR_NEWER // 2021.1 or later is guaranteed to have the new version of this function shader = ShaderUtil.CreateShaderAsset(ctx, text, false); #else // earlier builds of Unity may or may not have it // here we try to invoke the new version via reflection var createShaderAssetMethod = typeof(ShaderUtil).GetMethod( "CreateShaderAsset", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.ExactBinding, null, new Type[] { typeof(AssetImportContext), typeof(string), typeof(bool) }, null); if (createShaderAssetMethod != null) { shader = createShaderAssetMethod.Invoke(null, new Object[] { ctx, text, false }) as Shader; } else { // method doesn't exist in this version of Unity, call old version // this doesn't create dependencies properly, but is the best that we can do shader = ShaderUtil.CreateShaderAsset(text, false); } #endif ReportErrors(graph, shader, path); EditorMaterialUtility.SetShaderDefaults( shader, configuredTextures.Where(x => x.modifiable).Select(x => x.name).ToArray(), configuredTextures.Where(x => x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray()); EditorMaterialUtility.SetShaderNonModifiableDefaults( shader, configuredTextures.Where(x => !x.modifiable).Select(x => x.name).ToArray(), configuredTextures.Where(x => !x.modifiable).Select(x => EditorUtility.InstanceIDToObject(x.textureId) as Texture).ToArray()); } UnityEngine.Object mainObject = shader; #if VFX_GRAPH_10_0_0_OR_NEWER ShaderGraphVfxAsset vfxAsset = null; if (graph.hasVFXTarget) { vfxAsset = GenerateVfxShaderGraphAsset(graph); if (mainObject == null) { mainObject = vfxAsset; } else { //Correct main object if we have a shader and ShaderGraphVfxAsset : save as sub asset vfxAsset.name = Path.GetFileNameWithoutExtension(path); ctx.AddObjectToAsset("VFXShaderGraph", vfxAsset); } } #endif Texture2D texture = Resources.Load <Texture2D>("Icons/sg_graph_icon"); ctx.AddObjectToAsset("MainAsset", mainObject, texture); ctx.SetMainObject(mainObject); foreach (var target in graph.activeTargets) { if (target is IHasMetadata iHasMetadata) { var metadata = iHasMetadata.GetMetadataObject(); if (metadata == null) { continue; } metadata.hideFlags = HideFlags.HideInHierarchy; ctx.AddObjectToAsset($"{iHasMetadata.identifier}:Metadata", metadata); } } var sgMetadata = ScriptableObject.CreateInstance <ShaderGraphMetadata>(); sgMetadata.hideFlags = HideFlags.HideInHierarchy; sgMetadata.assetDependencies = new List <UnityEngine.Object>(); foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.IncludeInExportPackage)) { // this sucks that we have to fully load these assets just to set the reference, // which then gets serialized as the GUID that we already have here. :P var dependencyPath = AssetDatabase.GUIDToAssetPath(asset.Key); if (!string.IsNullOrEmpty(dependencyPath)) { sgMetadata.assetDependencies.Add( AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(UnityEngine.Object))); } } } List <MinimalCategoryData.GraphInputData> inputInspectorDataList = new List <MinimalCategoryData.GraphInputData>(); foreach (AbstractShaderProperty property in graph.properties) { // Don't write out data for non-exposed blackboard items if (!property.isExposed) { continue; } // VTs are treated differently if (property is VirtualTextureShaderProperty virtualTextureShaderProperty) { inputInspectorDataList.Add(MinimalCategoryData.ProcessVirtualTextureProperty(virtualTextureShaderProperty)); } else { inputInspectorDataList.Add(new MinimalCategoryData.GraphInputData() { referenceName = property.referenceName, propertyType = property.propertyType, isKeyword = false }); } } foreach (ShaderKeyword keyword in graph.keywords) { // Don't write out data for non-exposed blackboard items if (!keyword.isExposed) { continue; } var sanitizedReferenceName = keyword.referenceName; if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON")) { sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty); } inputInspectorDataList.Add(new MinimalCategoryData.GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true }); } sgMetadata.categoryDatas = new List <MinimalCategoryData>(); foreach (CategoryData categoryData in graph.categories) { // Don't write out empty categories if (categoryData.childCount == 0) { continue; } MinimalCategoryData mcd = new MinimalCategoryData() { categoryName = categoryData.name, propertyDatas = new List <MinimalCategoryData.GraphInputData>() }; foreach (var input in categoryData.Children) { MinimalCategoryData.GraphInputData propData; // Only write out data for exposed blackboard items if (input.isExposed == false) { continue; } // VTs are treated differently if (input is VirtualTextureShaderProperty virtualTextureShaderProperty) { propData = MinimalCategoryData.ProcessVirtualTextureProperty(virtualTextureShaderProperty); inputInspectorDataList.RemoveAll(inputData => inputData.referenceName == propData.referenceName); mcd.propertyDatas.Add(propData); continue; } else if (input is ShaderKeyword keyword) { var sanitizedReferenceName = keyword.referenceName; if (keyword.keywordType == KeywordType.Boolean && keyword.referenceName.Contains("_ON")) { sanitizedReferenceName = sanitizedReferenceName.Replace("_ON", String.Empty); } propData = new MinimalCategoryData.GraphInputData() { referenceName = sanitizedReferenceName, keywordType = keyword.keywordType, isKeyword = true }; } else { var prop = input as AbstractShaderProperty; propData = new MinimalCategoryData.GraphInputData() { referenceName = input.referenceName, propertyType = prop.propertyType, isKeyword = false }; } mcd.propertyDatas.Add(propData); inputInspectorDataList.Remove(propData); } sgMetadata.categoryDatas.Add(mcd); } // Any uncategorized elements get tossed into an un-named category at the top as a fallback if (inputInspectorDataList.Count > 0) { sgMetadata.categoryDatas.Insert(0, new MinimalCategoryData() { categoryName = "", propertyDatas = inputInspectorDataList }); } ctx.AddObjectToAsset("SGInternal:Metadata", sgMetadata); // declare dependencies foreach (var asset in assetCollection.assets) { if (asset.Value.HasFlag(AssetCollection.Flags.SourceDependency)) { ctx.DependsOnSourceAsset(asset.Key); // I'm not sure if this warning below is actually used or not, keeping it to be safe var assetPath = AssetDatabase.GUIDToAssetPath(asset.Key); // Ensure that dependency path is relative to project if (!string.IsNullOrEmpty(assetPath) && !assetPath.StartsWith("Packages/") && !assetPath.StartsWith("Assets/")) { Debug.LogWarning($"Invalid dependency path: {assetPath}", mainObject); } } // NOTE: dependencies declared by GatherDependenciesFromSourceFile are automatically registered as artifact dependencies // HOWEVER: that path ONLY grabs dependencies via MinimalGraphData, and will fail to register dependencies // on GUIDs that don't exist in the project. For both of those reasons, we re-declare the dependencies here. if (asset.Value.HasFlag(AssetCollection.Flags.ArtifactDependency)) { ctx.DependsOnArtifact(asset.Key); } } }