using System; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEditor.Animations; using UnityEngine; using SynapticAIPro; namespace SynapticPro { /// /// Helper class for advanced animation and motion management /// Handles Mixamo integration, IK setup, and animation asset management /// public static class NexusAnimationHelper { private const string MIXAMO_RIG_PREFIX = "mixamorig:"; private const string HUMANOID_AVATAR_PREFIX = "Avatar_"; /// /// Import and setup Mixamo FBX with automatic Humanoid configuration /// public static string ImportMixamoAnimation(Dictionary parameters) { try { string fbxPath = parameters.GetValueOrDefault("fbxPath", ""); string targetPath = parameters.GetValueOrDefault("targetPath", "Assets/Animations/Mixamo/"); bool createController = parameters.GetValueOrDefault("createController", "true") == "true"; string characterName = parameters.GetValueOrDefault("characterName", "Character"); bool setupIK = parameters.GetValueOrDefault("setupIK", "true") == "true"; if (string.IsNullOrEmpty(fbxPath) || !File.Exists(fbxPath)) { return $"Error: FBX file not found at path: {fbxPath}"; } // Ensure target directory exists if (!AssetDatabase.IsValidFolder(targetPath)) { string[] folders = targetPath.Split('/'); string currentPath = folders[0]; for (int i = 1; i < folders.Length; i++) { if (string.IsNullOrEmpty(folders[i])) continue; string nextPath = currentPath + "/" + folders[i]; if (!AssetDatabase.IsValidFolder(nextPath)) { AssetDatabase.CreateFolder(currentPath, folders[i]); } currentPath = nextPath; } } // Copy FBX to project string fileName = Path.GetFileName(fbxPath); string assetPath = Path.Combine(targetPath, fileName); File.Copy(fbxPath, assetPath, true); AssetDatabase.Refresh(); // Configure as Humanoid ModelImporter importer = AssetImporter.GetAtPath(assetPath) as ModelImporter; if (importer != null) { // Setup for Mixamo importer.animationType = ModelImporterAnimationType.Human; importer.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel; // Animation settings importer.importAnimation = true; importer.animationCompression = ModelImporterAnimationCompression.Optimal; // Optimize for Mixamo importer.optimizeGameObjects = true; importer.optimizeMeshPolygons = true; importer.optimizeMeshVertices = true; // Material settings importer.materialImportMode = ModelImporterMaterialImportMode.ImportStandard; // Apply settings importer.SaveAndReimport(); var result = new Dictionary { ["success"] = true, ["assetPath"] = assetPath, ["characterName"] = characterName }; // Create Animator Controller if requested if (createController) { string controllerPath = CreateAnimatorControllerForMixamo(assetPath, characterName, targetPath); result["controllerPath"] = controllerPath; } // Setup IK if requested if (setupIK) { SetupBasicIK(characterName); result["ikSetup"] = true; } return JsonUtility.ToJson(result); } return "Error: Failed to configure model importer"; } catch (Exception e) { return $"Error importing Mixamo animation: {e.Message}"; } } /// /// Organize and categorize animation clips /// public static string OrganizeAnimationAssets(Dictionary parameters) { try { string sourcePath = parameters.GetValueOrDefault("sourcePath", "Assets/Animations/"); bool autoDetectType = parameters.GetValueOrDefault("autoDetectType", "true") == "true"; bool createFolders = parameters.GetValueOrDefault("createFolders", "true") == "true"; // Find all animation clips string[] guids = AssetDatabase.FindAssets("t:AnimationClip", new[] { sourcePath }); var categories = new Dictionary> { ["Idle"] = new List(), ["Walk"] = new List(), ["Run"] = new List(), ["Jump"] = new List(), ["Attack"] = new List(), ["Death"] = new List(), ["Damage"] = new List(), ["Other"] = new List() }; foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); AnimationClip clip = AssetDatabase.LoadAssetAtPath(path); if (clip != null && autoDetectType) { string category = DetectAnimationType(clip.name); categories[category].Add(clip); // Move to categorized folder if requested if (createFolders) { string targetFolder = Path.Combine(sourcePath, category); if (!AssetDatabase.IsValidFolder(targetFolder)) { AssetDatabase.CreateFolder(sourcePath, category); } string newPath = Path.Combine(targetFolder, Path.GetFileName(path)); if (path != newPath) { AssetDatabase.MoveAsset(path, newPath); } } } } // Generate report var report = new Dictionary { ["totalClips"] = guids.Length, ["categories"] = new Dictionary() }; foreach (var category in categories) { ((Dictionary)report["categories"])[category.Key] = category.Value.Count; } return JsonUtility.ToJson(report); } catch (Exception e) { return $"Error organizing animation assets: {e.Message}"; } } /// /// Setup IK for character /// public static string SetupCharacterIK(Dictionary parameters) { try { string gameObjectName = parameters.GetValueOrDefault("gameObject", ""); bool enableFootIK = parameters.GetValueOrDefault("enableFootIK", "true") == "true"; bool enableHandIK = parameters.GetValueOrDefault("enableHandIK", "false") == "true"; bool enableLookAt = parameters.GetValueOrDefault("enableLookAt", "false") == "true"; float footIKWeight = float.Parse(parameters.GetValueOrDefault("footIKWeight", "1")); float handIKWeight = float.Parse(parameters.GetValueOrDefault("handIKWeight", "1")); GameObject target = GameObject.Find(gameObjectName); if (target == null) { return $"Error: GameObject '{gameObjectName}' not found"; } // Add IK controller component if not exists var ikController = target.GetComponent(); if (ikController == null) { ikController = target.AddComponent(); Undo.RegisterCreatedObjectUndo(ikController, "Add IK Controller"); } // Configure IK settings ikController.enableFootIK = enableFootIK; ikController.enableHandIK = enableHandIK; ikController.enableLookAt = enableLookAt; ikController.footIKWeight = footIKWeight; ikController.handIKWeight = handIKWeight; // Setup IK targets if (enableFootIK) { CreateIKTarget(target.transform, "LeftFootIK", new Vector3(-0.1f, 0, 0)); CreateIKTarget(target.transform, "RightFootIK", new Vector3(0.1f, 0, 0)); } if (enableHandIK) { CreateIKTarget(target.transform, "LeftHandIK", new Vector3(-0.5f, 1.5f, 0.5f)); CreateIKTarget(target.transform, "RightHandIK", new Vector3(0.5f, 1.5f, 0.5f)); } if (enableLookAt) { CreateIKTarget(target.transform, "LookAtTarget", new Vector3(0, 1.6f, 2f)); } EditorUtility.SetDirty(target); return JsonUtility.ToJson(new Dictionary { ["success"] = true, ["gameObject"] = gameObjectName, ["footIK"] = enableFootIK, ["handIK"] = enableHandIK, ["lookAt"] = enableLookAt }); } catch (Exception e) { return $"Error setting up IK: {e.Message}"; } } /// /// Create animation layer mask /// public static string CreateAnimationLayerMask(Dictionary parameters) { try { string maskName = parameters.GetValueOrDefault("maskName", "NewLayerMask"); string savePath = parameters.GetValueOrDefault("savePath", "Assets/Animations/Masks/"); string includeBonesPattern = parameters.GetValueOrDefault("includeBones", ""); string excludeBonesPattern = parameters.GetValueOrDefault("excludeBones", ""); string avatarPath = parameters.GetValueOrDefault("avatarPath", ""); if (!AssetDatabase.IsValidFolder(savePath)) { Directory.CreateDirectory(savePath); AssetDatabase.Refresh(); } // Create avatar mask AvatarMask mask = new AvatarMask(); mask.name = maskName; // Load avatar if specified if (!string.IsNullOrEmpty(avatarPath)) { Avatar avatar = AssetDatabase.LoadAssetAtPath(avatarPath); if (avatar != null) { // Configure body parts based on patterns ConfigureAvatarMaskBodyParts(mask, includeBonesPattern, excludeBonesPattern); } } string assetPath = Path.Combine(savePath, maskName + ".mask"); AssetDatabase.CreateAsset(mask, assetPath); AssetDatabase.SaveAssets(); return JsonUtility.ToJson(new Dictionary { ["success"] = true, ["maskPath"] = assetPath, ["maskName"] = maskName }); } catch (Exception e) { return $"Error creating layer mask: {e.Message}"; } } /// /// Setup animation blend tree /// public static string SetupAdvancedBlendTree(Dictionary parameters) { try { string controllerPath = parameters.GetValueOrDefault("controllerPath", ""); string blendType = parameters.GetValueOrDefault("blendType", "2D"); string parameterX = parameters.GetValueOrDefault("parameterX", "MoveX"); string parameterY = parameters.GetValueOrDefault("parameterY", "MoveY"); string clips = parameters.GetValueOrDefault("clips", ""); // Comma separated paths var controller = AssetDatabase.LoadAssetAtPath(controllerPath); if (controller == null) { return "Error: Animator Controller not found"; } // Create blend tree var rootStateMachine = controller.layers[0].stateMachine; var blendTreeState = rootStateMachine.AddState("BlendTree"); BlendTree blendTree; controller.CreateBlendTreeInController("Movement", out blendTree); blendTreeState.motion = blendTree; // Configure blend type switch (blendType.ToLower()) { case "1d": blendTree.blendType = BlendTreeType.Simple1D; blendTree.blendParameter = parameterX; break; case "2d": blendTree.blendType = BlendTreeType.SimpleDirectional2D; blendTree.blendParameter = parameterX; blendTree.blendParameterY = parameterY; break; case "freeform": blendTree.blendType = BlendTreeType.FreeformDirectional2D; blendTree.blendParameter = parameterX; blendTree.blendParameterY = parameterY; break; } // Add animation clips if (!string.IsNullOrEmpty(clips)) { string[] clipPaths = clips.Split(','); foreach (string clipPath in clipPaths) { AnimationClip clip = AssetDatabase.LoadAssetAtPath(clipPath.Trim()); if (clip != null) { blendTree.AddChild(clip); } } } // Add parameters if not exist if (!controller.parameters.Any(p => p.name == parameterX)) { controller.AddParameter(parameterX, AnimatorControllerParameterType.Float); } if (!string.IsNullOrEmpty(parameterY) && !controller.parameters.Any(p => p.name == parameterY)) { controller.AddParameter(parameterY, AnimatorControllerParameterType.Float); } EditorUtility.SetDirty(controller); AssetDatabase.SaveAssets(); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(controller), ImportAssetOptions.ForceUpdate); return JsonUtility.ToJson(new Dictionary { ["success"] = true, ["blendTreeName"] = "Movement", ["blendType"] = blendType, ["clipCount"] = blendTree.children.Length }); } catch (Exception e) { return $"Error setting up blend tree: {e.Message}"; } } /// /// Retarget animation from one rig to another /// public static string RetargetAnimation(Dictionary parameters) { try { string sourceClipPath = parameters.GetValueOrDefault("sourceClip", ""); string targetAvatarPath = parameters.GetValueOrDefault("targetAvatar", ""); string outputPath = parameters.GetValueOrDefault("outputPath", "Assets/Animations/Retargeted/"); bool adjustRootMotion = parameters.GetValueOrDefault("adjustRootMotion", "true") == "true"; AnimationClip sourceClip = AssetDatabase.LoadAssetAtPath(sourceClipPath); Avatar targetAvatar = AssetDatabase.LoadAssetAtPath(targetAvatarPath); if (sourceClip == null || targetAvatar == null) { return "Error: Source clip or target avatar not found"; } // Create output directory if (!AssetDatabase.IsValidFolder(outputPath)) { Directory.CreateDirectory(outputPath); AssetDatabase.Refresh(); } // Clone animation clip AnimationClip retargetedClip = UnityEngine.Object.Instantiate(sourceClip); retargetedClip.name = sourceClip.name + "_Retargeted"; // Adjust root motion if needed if (adjustRootMotion) { AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(retargetedClip); settings.loopTime = sourceClip.isLooping; settings.keepOriginalPositionY = true; settings.keepOriginalOrientation = true; AnimationUtility.SetAnimationClipSettings(retargetedClip, settings); } string savePath = Path.Combine(outputPath, retargetedClip.name + ".anim"); AssetDatabase.CreateAsset(retargetedClip, savePath); AssetDatabase.SaveAssets(); return JsonUtility.ToJson(new Dictionary { ["success"] = true, ["retargetedClip"] = savePath, ["originalClip"] = sourceClipPath }); } catch (Exception e) { return $"Error retargeting animation: {e.Message}"; } } /// /// Create animation transition presets /// public static string CreateTransitionPreset(Dictionary parameters) { try { string controllerPath = parameters.GetValueOrDefault("controllerPath", ""); string fromState = parameters.GetValueOrDefault("fromState", ""); string toState = parameters.GetValueOrDefault("toState", ""); string presetType = parameters.GetValueOrDefault("presetType", "smooth"); // smooth, instant, crossfade float duration = float.Parse(parameters.GetValueOrDefault("duration", "0.25")); var controller = AssetDatabase.LoadAssetAtPath(controllerPath); if (controller == null) { return "Error: Controller not found"; } var stateMachine = controller.layers[0].stateMachine; var sourceState = FindState(stateMachine, fromState); var destState = FindState(stateMachine, toState); if (sourceState == null || destState == null) { return "Error: States not found"; } var transition = sourceState.AddTransition(destState); // Configure based on preset switch (presetType.ToLower()) { case "instant": transition.duration = 0; transition.offset = 0; transition.hasExitTime = false; break; case "smooth": transition.duration = duration; transition.offset = 0; transition.hasExitTime = true; transition.exitTime = 0.75f; break; case "crossfade": transition.duration = duration * 2; transition.offset = 0.1f; transition.hasExitTime = true; transition.exitTime = 0.5f; break; } EditorUtility.SetDirty(controller); AssetDatabase.SaveAssets(); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(controller), ImportAssetOptions.ForceUpdate); return JsonUtility.ToJson(new Dictionary { ["success"] = true, ["fromState"] = fromState, ["toState"] = toState, ["presetType"] = presetType }); } catch (Exception e) { return $"Error creating transition preset: {e.Message}"; } } /// /// Analyze animation performance /// public static string AnalyzeAnimationPerformance(Dictionary parameters) { try { string targetPath = parameters.GetValueOrDefault("targetPath", "Assets/Animations/"); string[] guids = AssetDatabase.FindAssets("t:AnimationClip", new[] { targetPath }); var report = new Dictionary { ["totalClips"] = guids.Length, ["clips"] = new List>() }; foreach (string guid in guids) { string path = AssetDatabase.GUIDToAssetPath(guid); AnimationClip clip = AssetDatabase.LoadAssetAtPath(path); if (clip != null) { var clipInfo = new Dictionary { ["name"] = clip.name, ["length"] = clip.length, ["frameRate"] = clip.frameRate, ["isHumanMotion"] = clip.humanMotion, ["isLooping"] = clip.isLooping, ["events"] = clip.events.Length, ["approximateSize"] = EstimateAnimationSize(clip) }; ((List>)report["clips"]).Add(clipInfo); } } return JsonUtility.ToJson(report); } catch (Exception e) { return $"Error analyzing animation performance: {e.Message}"; } } // ===== Helper Methods ===== private static string CreateAnimatorControllerForMixamo(string fbxPath, string characterName, string targetPath) { string controllerPath = Path.Combine(targetPath, characterName + "_Controller.controller"); var controller = AnimatorController.CreateAnimatorControllerAtPath(controllerPath); // Create parameters controller.AddParameter("Speed", AnimatorControllerParameterType.Float); controller.AddParameter("Direction", AnimatorControllerParameterType.Float); controller.AddParameter("IsGrounded", AnimatorControllerParameterType.Bool); controller.AddParameter("Jump", AnimatorControllerParameterType.Trigger); // Create default states var stateMachine = controller.layers[0].stateMachine; var idleState = stateMachine.AddState("Idle"); stateMachine.defaultState = idleState; // Try to find and assign idle animation from the FBX var clips = AssetDatabase.LoadAllAssetsAtPath(fbxPath).OfType().ToArray(); foreach (var clip in clips) { if (clip.name.ToLower().Contains("idle")) { idleState.motion = clip; break; } } EditorUtility.SetDirty(controller); AssetDatabase.SaveAssets(); AssetDatabase.ImportAsset(controllerPath, ImportAssetOptions.ForceUpdate); return controllerPath; } private static void SetupBasicIK(string characterName) { // This would normally set up IK components // Actual implementation would depend on the IK solution being used SynLog.Info($"IK setup prepared for {characterName}"); } private static string DetectAnimationType(string clipName) { string lowerName = clipName.ToLower(); if (lowerName.Contains("idle") || lowerName.Contains("stand")) return "Idle"; if (lowerName.Contains("walk")) return "Walk"; if (lowerName.Contains("run") || lowerName.Contains("sprint")) return "Run"; if (lowerName.Contains("jump") || lowerName.Contains("leap")) return "Jump"; if (lowerName.Contains("attack") || lowerName.Contains("punch") || lowerName.Contains("kick")) return "Attack"; if (lowerName.Contains("death") || lowerName.Contains("die")) return "Death"; if (lowerName.Contains("damage") || lowerName.Contains("hit") || lowerName.Contains("hurt")) return "Damage"; return "Other"; } private static GameObject CreateIKTarget(Transform parent, string name, Vector3 localPosition) { GameObject ikTarget = new GameObject(name); ikTarget.transform.SetParent(parent); ikTarget.transform.localPosition = localPosition; // Add visual gizmo component for editor var gizmo = ikTarget.AddComponent(); gizmo.color = name.Contains("Foot") ? Color.green : (name.Contains("Hand") ? Color.blue : Color.yellow); Undo.RegisterCreatedObjectUndo(ikTarget, $"Create {name}"); return ikTarget; } private static void ConfigureAvatarMaskBodyParts(AvatarMask mask, string includeBones, string excludeBones) { // Configure humanoid body parts for (int i = 0; i < (int)AvatarMaskBodyPart.LastBodyPart; i++) { bool include = true; AvatarMaskBodyPart part = (AvatarMaskBodyPart)i; string partName = part.ToString().ToLower(); if (!string.IsNullOrEmpty(excludeBones) && excludeBones.ToLower().Contains(partName)) { include = false; } if (!string.IsNullOrEmpty(includeBones) && !includeBones.ToLower().Contains(partName)) { include = false; } mask.SetHumanoidBodyPartActive(part, include); } } private static AnimatorState FindState(AnimatorStateMachine stateMachine, string name) { foreach (var state in stateMachine.states) { if (state.state.name == name) return state.state; } return null; } private static float EstimateAnimationSize(AnimationClip clip) { // Rough estimation based on curves and keys var bindings = AnimationUtility.GetCurveBindings(clip); int totalKeys = 0; foreach (var binding in bindings) { var curve = AnimationUtility.GetEditorCurve(clip, binding); if (curve != null) { totalKeys += curve.keys.Length; } } // Approximate size in KB (4 bytes per float * keys * 4 values per key) return (totalKeys * 4 * 4) / 1024f; } } /// /// IK Controller component for runtime IK handling /// public class IKController : MonoBehaviour { public bool enableFootIK = true; public bool enableHandIK = false; public bool enableLookAt = false; public float footIKWeight = 1f; public float handIKWeight = 1f; public float lookAtWeight = 1f; public Transform leftFootTarget; public Transform rightFootTarget; public Transform leftHandTarget; public Transform rightHandTarget; public Transform lookAtTarget; private Animator animator; void Start() { animator = GetComponent(); } void OnAnimatorIK(int layerIndex) { if (animator == null) return; // Foot IK if (enableFootIK) { if (leftFootTarget != null) { animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, footIKWeight); animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, footIKWeight); animator.SetIKPosition(AvatarIKGoal.LeftFoot, leftFootTarget.position); animator.SetIKRotation(AvatarIKGoal.LeftFoot, leftFootTarget.rotation); } if (rightFootTarget != null) { animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, footIKWeight); animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, footIKWeight); animator.SetIKPosition(AvatarIKGoal.RightFoot, rightFootTarget.position); animator.SetIKRotation(AvatarIKGoal.RightFoot, rightFootTarget.rotation); } } // Hand IK if (enableHandIK) { if (leftHandTarget != null) { animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, handIKWeight); animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, handIKWeight); animator.SetIKPosition(AvatarIKGoal.LeftHand, leftHandTarget.position); animator.SetIKRotation(AvatarIKGoal.LeftHand, leftHandTarget.rotation); } if (rightHandTarget != null) { animator.SetIKPositionWeight(AvatarIKGoal.RightHand, handIKWeight); animator.SetIKRotationWeight(AvatarIKGoal.RightHand, handIKWeight); animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandTarget.position); animator.SetIKRotation(AvatarIKGoal.RightHand, rightHandTarget.rotation); } } // Look At if (enableLookAt && lookAtTarget != null) { animator.SetLookAtWeight(lookAtWeight); animator.SetLookAtPosition(lookAtTarget.position); } } } /// /// Visual gizmo for IK targets /// public class IKTargetGizmo : MonoBehaviour { public Color color = Color.green; public float size = 0.1f; void OnDrawGizmos() { Gizmos.color = color; Gizmos.DrawWireSphere(transform.position, size); Gizmos.DrawLine(transform.position, transform.position + transform.forward * size * 2); } } }