diff --git a/Packages/net.fushizen.modular-avatar/Editor/EasySetupOutfit.cs b/Packages/net.fushizen.modular-avatar/Editor/EasySetupOutfit.cs new file mode 100644 index 00000000..861231fe --- /dev/null +++ b/Packages/net.fushizen.modular-avatar/Editor/EasySetupOutfit.cs @@ -0,0 +1,80 @@ +using UnityEditor; +using UnityEngine; + +namespace net.fushizen.modular_avatar.core.editor +{ + public class EasySetupOutfit + { + private const int PRIORITY = 49; + + [MenuItem("GameObject/[ModularAvatar] Setup Outfit", false, PRIORITY)] + static void SetupOutfit(MenuCommand cmd) + { + if (!FindBones(cmd.context, + out var avatarRoot, out var avatarHips, out var outfitHips) + ) return; + + var avatarArmature = avatarHips.transform.parent; + var outfitArmature = outfitHips.transform.parent; + + var merge = Undo.AddComponent(outfitArmature.gameObject); + merge.mergeTarget.referencePath = RuntimeUtil.RelativePath(avatarRoot, avatarArmature.gameObject); + merge.InferPrefixSuffix(); + } + + [MenuItem("GameObject/[ModularAvatar] Setup Outfit", true, PRIORITY)] + static bool ValidateSetupOutfit() + { + foreach (var obj in Selection.objects) + { + if (!FindBones(obj, out var _, out var _, out var outfitHips) + || outfitHips.transform.parent.GetComponent() != null) + { + return false; + } + } + + return true; + } + + private static bool FindBones(Object obj, out GameObject avatarRoot, out GameObject avatarHips, + out GameObject outfitHips) + { + avatarHips = outfitHips = null; + var outfitRoot = obj as GameObject; + avatarRoot = outfitRoot != null + ? RuntimeUtil.FindAvatarInParents(outfitRoot.transform)?.gameObject + : null; + if (outfitRoot == null || avatarRoot == null) return false; + + var avatarAnimator = avatarRoot.GetComponent(); + if (avatarAnimator == null) return false; + + avatarHips = avatarAnimator.GetBoneTransform(HumanBodyBones.Hips)?.gameObject; + if (avatarHips == null) return false; + + var outfitAnimator = outfitRoot.GetComponent(); + if (outfitAnimator != null) + { + outfitHips = outfitAnimator.GetBoneTransform(HumanBodyBones.Hips)?.gameObject; + } + + if (outfitHips == null) + { + // Heuristic search - usually there'll be root -> Armature -> (single child) Hips + foreach (Transform child in outfitRoot.transform) + { + if (child.childCount == 1) + { + if (child.GetChild(0).name.Contains(avatarHips.name)) + { + outfitHips = child.GetChild(0).gameObject; + } + } + } + } + + return avatarHips != null && outfitHips != null; + } + } +} \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Editor/EasySetupOutfit.cs.meta b/Packages/net.fushizen.modular-avatar/Editor/EasySetupOutfit.cs.meta new file mode 100644 index 00000000..785c7b10 --- /dev/null +++ b/Packages/net.fushizen.modular-avatar/Editor/EasySetupOutfit.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ce0a55eae40b424084cb91f80f6276c2 +timeCreated: 1664935971 \ No newline at end of file diff --git a/Packages/net.fushizen.modular-avatar/Editor/Inspector/MergeArmatureEditor.cs b/Packages/net.fushizen.modular-avatar/Editor/Inspector/MergeArmatureEditor.cs index 3f143917..41804d43 100644 --- a/Packages/net.fushizen.modular-avatar/Editor/Inspector/MergeArmatureEditor.cs +++ b/Packages/net.fushizen.modular-avatar/Editor/Inspector/MergeArmatureEditor.cs @@ -22,33 +22,7 @@ namespace net.fushizen.modular_avatar.core.editor && string.IsNullOrEmpty(target.prefix) && string.IsNullOrEmpty(target.suffix)) { - // We only infer if targeting the armature (below the Hips bone) - var rootAnimator = RuntimeUtil.FindAvatarInParents(target.transform)?.GetComponent(); - if (rootAnimator == null) return; - - var hips = rootAnimator.GetBoneTransform(HumanBodyBones.Hips); - if (hips == null || hips.transform.parent != target.mergeTargetObject.transform) return; - - // We also require that the attached object has exactly one child (presumably the hips) - if (target.transform.childCount != 1) return; - - // Infer the prefix and suffix by comparing the names of the mergeTargetObject's hips with the child of the - // GameObject we're attached to. - var baseName = hips.name; - var mergeName = target.transform.GetChild(0).name; - - var prefixLength = mergeName.IndexOf(baseName, StringComparison.InvariantCulture); - if (prefixLength < 0) return; - - var suffixLength = mergeName.Length - prefixLength - baseName.Length; - - target.prefix = mergeName.Substring(0, prefixLength); - target.suffix = mergeName.Substring(mergeName.Length - suffixLength); - - if (!string.IsNullOrEmpty(target.prefix) || !string.IsNullOrEmpty(target.suffix)) - { - RuntimeUtil.MarkDirty(target); - } + target.InferPrefixSuffix(); } } } diff --git a/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarMergeArmature.cs b/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarMergeArmature.cs index 39cb00b8..940e007c 100644 --- a/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarMergeArmature.cs +++ b/Packages/net.fushizen.modular-avatar/Runtime/ModularAvatarMergeArmature.cs @@ -22,6 +22,7 @@ * SOFTWARE. */ +using System; using System.Collections.Generic; using UnityEngine; #if UNITY_EDITOR @@ -186,5 +187,36 @@ namespace net.fushizen.modular_avatar.core return correspondingParent.Find(prefix + xform.name + suffix); } + + public void InferPrefixSuffix() + { + // We only infer if targeting the armature (below the Hips bone) + var rootAnimator = RuntimeUtil.FindAvatarInParents(transform)?.GetComponent(); + if (rootAnimator == null) return; + + var hips = rootAnimator.GetBoneTransform(HumanBodyBones.Hips); + if (hips == null || hips.transform.parent != mergeTargetObject.transform) return; + + // We also require that the attached object has exactly one child (presumably the hips) + if (transform.childCount != 1) return; + + // Infer the prefix and suffix by comparing the names of the mergeTargetObject's hips with the child of the + // GameObject we're attached to. + var baseName = hips.name; + var mergeName = transform.GetChild(0).name; + + var prefixLength = mergeName.IndexOf(baseName, StringComparison.InvariantCulture); + if (prefixLength < 0) return; + + var suffixLength = mergeName.Length - prefixLength - baseName.Length; + + prefix = mergeName.Substring(0, prefixLength); + suffix = mergeName.Substring(mergeName.Length - suffixLength); + + if (!string.IsNullOrEmpty(prefix) || !string.IsNullOrEmpty(suffix)) + { + RuntimeUtil.MarkDirty(this); + } + } } } \ No newline at end of file