diff --git a/Editor/Core.cs b/Editor/Core.cs index 1e437da..c282620 100644 --- a/Editor/Core.cs +++ b/Editor/Core.cs @@ -49,6 +49,12 @@ namespace gay.lilyy.SoldAvatarBootstrap AssetDatabase.DeleteAsset(definition.FXLayerPath); AssetDatabase.Refresh(); } + var directory = System.IO.Path.GetDirectoryName(definition.FXLayerPath); + if (!AssetDatabase.IsValidFolder(directory)) + { + System.IO.Directory.CreateDirectory(directory); + AssetDatabase.Refresh(); + } var newController = AnimatorController.CreateAnimatorControllerAtPath(definition.FXLayerPath); if (originalGuid != null && System.IO.File.Exists(metaPath)) { @@ -70,8 +76,23 @@ namespace gay.lilyy.SoldAvatarBootstrap { var allLayerGroups = LayerGroup.Instances.ToList(); + + var definitionsToProcess = new HashSet { definition }; + var definitionsToExpand = new Queue(definitionsToProcess); + while (definitionsToExpand.Count > 0) + { + var current = definitionsToExpand.Dequeue(); + foreach (var extended in current.Extends) + { + if (definitionsToProcess.Add(extended)) + { + definitionsToExpand.Enqueue(extended); + } + } + } + var layerGroups = allLayerGroups - .Where(lg => lg.TargetDefinitions.Contains(definition)) + .Where(lg => lg.TargetDefinitions.Any(definitionsToProcess.Contains)) .ToList(); // Start overall timer @@ -81,6 +102,11 @@ namespace gay.lilyy.SoldAvatarBootstrap GameObject root = null; foreach (var candidate in GameObject.FindObjectsOfType(true)) { + if (PrefabUtility.IsPartOfPrefabAsset(candidate.gameObject)) + { + continue; + } + if (definition.IsApplicable(candidate.gameObject)) { AvatarLogger.LogInfo($"Found suitable avatar: {candidate.gameObject.name}"); @@ -94,6 +120,12 @@ namespace gay.lilyy.SoldAvatarBootstrap return; } + // If the avatar is part of a prefab instance, use the instance root so we create overrides. + if (PrefabUtility.IsPartOfPrefabInstance(root)) + { + root = PrefabUtility.GetNearestPrefabInstanceRoot(root); + } + // Skip if the root GameObject or the component itself is disabled if (!root.activeSelf || !root.gameObject.activeInHierarchy) { @@ -177,7 +209,8 @@ namespace gay.lilyy.SoldAvatarBootstrap AvatarLogger.LogInfo($"Average per layer group: {(processedCount > 0 ? totalLayerGroupTime / processedCount : 0)}ms"); // replace the animator controller - var animatorControllers = root.GetComponent().baseAnimationLayers + var descriptor = root.GetComponent(); + var animatorControllers = descriptor.baseAnimationLayers .ToList(); animatorControllers.RemoveAll(layer => layer.type == VRCAvatarDescriptor.AnimLayerType.FX); animatorControllers.Add(new VRCAvatarDescriptor.CustomAnimLayer @@ -185,7 +218,12 @@ namespace gay.lilyy.SoldAvatarBootstrap type = VRCAvatarDescriptor.AnimLayerType.FX, animatorController = assets.fx }); - root.GetComponent().baseAnimationLayers = animatorControllers.ToArray(); + descriptor.baseAnimationLayers = animatorControllers.ToArray(); + + if (PrefabUtility.IsPartOfPrefabInstance(root)) + { + PrefabUtility.RecordPrefabInstancePropertyModifications(descriptor); + } } } } \ No newline at end of file diff --git a/Editor/Definition.cs b/Editor/Definition.cs index c8892a1..868c28d 100644 --- a/Editor/Definition.cs +++ b/Editor/Definition.cs @@ -11,9 +11,12 @@ namespace gay.lilyy.SoldAvatarBootstrap public static IEnumerable Instances => instances; + + public abstract string DisplayName { get; } public abstract string SystemName { get; } public abstract string FXLayerPath { get; } + public virtual AvatarDefinition[] Extends => new AvatarDefinition[0]; public abstract bool IsApplicable(GameObject avatarRoot); }