#if UNITY_EDITOR using System; using System.IO; using System.Linq; using UnityEditor; using UnityEditor.Animations; using UnityEngine; public static class SetupExplorer { const string Root = "Assets/Bundles/Characters/Human/Explorer"; const string FbxPath = Root + "/Explorer.fbx"; const string IdleFbxPath = Root + "/Explorer@Idle.fbx"; const string WalkFbxPath = Root + "/Explorer@Walk.fbx"; const string BaseTexPath = Root + "/Explorer_base.png"; const string MixTexPath = Root + "/Explorer_mix.png"; const string MatPath = Root + "/Explorer.mat"; const string ControllerPath = Root + "/Explorer.controller"; const string PrefabPath = Root + "/Explorer.prefab"; const float TargetHeight = 1.6f; public static string Run() { AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); SetupTexture(BaseTexPath, true); SetupTexture(MixTexPath, false); var modelImporter = (ModelImporter)AssetImporter.GetAtPath(FbxPath); if (modelImporter == null) return "FAIL: ModelImporter not found for " + FbxPath; modelImporter.globalScale = 1.0f; modelImporter.useFileScale = false; modelImporter.bakeAxisConversion = true; modelImporter.materialImportMode = ModelImporterMaterialImportMode.None; modelImporter.animationType = ModelImporterAnimationType.Human; modelImporter.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel; modelImporter.SaveAndReimport(); var avatar = AssetDatabase.LoadAssetAtPath(FbxPath); Debug.Log($"[Explorer] Avatar generated: {avatar != null && avatar.isValid}"); SetupAnimationFbx(IdleFbxPath, avatar, "Idle"); SetupAnimationFbx(WalkFbxPath, avatar, "Walk"); var mat = AssetDatabase.LoadAssetAtPath(MatPath); var shader = Shader.Find("Test/XPbr") ?? Shader.Find("Universal Render Pipeline/Lit"); if (mat == null) { mat = new Material(shader) { name = "Explorer" }; AssetDatabase.CreateAsset(mat, MatPath); } else { mat.shader = shader; } var baseTex = AssetDatabase.LoadAssetAtPath(BaseTexPath); var mixTex = AssetDatabase.LoadAssetAtPath(MixTexPath); if (baseTex != null) { mat.SetTexture("_MainTex", baseTex); mat.SetTexture("_BaseMap", baseTex); } if (mixTex != null) { mat.SetTexture("_MixTex", mixTex); } mat.SetColor("_BaseColor", Color.white); mat.SetColor("_Color", Color.white); EditorUtility.SetDirty(mat); AssetDatabase.SaveAssetIfDirty(mat); var controller = AssetDatabase.LoadAssetAtPath(ControllerPath); if (controller == null) { controller = AnimatorController.CreateAnimatorControllerAtPath(ControllerPath); } if (controller.parameters.All(p => p.name != "isMoving")) controller.AddParameter("isMoving", AnimatorControllerParameterType.Bool); if (controller.parameters.All(p => p.name != "speed")) controller.AddParameter("speed", AnimatorControllerParameterType.Float); if (controller.parameters.All(p => p.name != "Motion")) controller.AddParameter("Motion", AnimatorControllerParameterType.Int); var idleClip = LoadClip(IdleFbxPath); var walkClip = LoadClip(WalkFbxPath); var rootStateMachine = controller.layers[0].stateMachine; foreach (var s in rootStateMachine.states.ToArray()) rootStateMachine.RemoveState(s.state); var idleState = rootStateMachine.AddState("Idle"); idleState.motion = idleClip; rootStateMachine.defaultState = idleState; var walkState = rootStateMachine.AddState("Walk"); walkState.motion = walkClip; // Transition: Idle -> Walk var toWalk = idleState.AddTransition(walkState); toWalk.hasExitTime = false; toWalk.duration = 0.15f; toWalk.AddCondition(AnimatorConditionMode.If, 0, "isMoving"); // Transition: Walk -> Idle var toIdle = walkState.AddTransition(idleState); toIdle.hasExitTime = false; toIdle.duration = 0.15f; toIdle.AddCondition(AnimatorConditionMode.IfNot, 0, "isMoving"); // Also add Motion transitions for ExplorerLocomotion compatibility var toWalkMotion = idleState.AddTransition(walkState); toWalkMotion.hasExitTime = false; toWalkMotion.duration = 0.15f; toWalkMotion.AddCondition(AnimatorConditionMode.Equals, 1, "Motion"); var toIdleMotion = walkState.AddTransition(idleState); toIdleMotion.hasExitTime = false; toIdleMotion.duration = 0.15f; toIdleMotion.AddCondition(AnimatorConditionMode.Equals, 0, "Motion"); EditorUtility.SetDirty(controller); AssetDatabase.SaveAssetIfDirty(controller); var fbxPrefab = AssetDatabase.LoadAssetAtPath(FbxPath); var instance = (GameObject)PrefabUtility.InstantiatePrefab(fbxPrefab); instance.name = "Explorer"; foreach (var r in instance.GetComponentsInChildren(true)) { r.sharedMaterial = mat; } var animator = instance.GetComponent(); if (animator == null) animator = instance.AddComponent(); animator.runtimeAnimatorController = controller; if (avatar != null) animator.avatar = avatar; animator.applyRootMotion = false; // Add Locomotion Controller var ctrlType = AppDomain.CurrentDomain.GetAssemblies() .Select(a => a.GetType("HotUpdate.Gameplay.CampExplorerController")) .FirstOrDefault(t => t != null); if (ctrlType != null) { if (instance.GetComponent(ctrlType) == null) instance.AddComponent(ctrlType); } // Add CapsuleCollider var col = instance.GetComponent(); if (col == null) col = instance.AddComponent(); col.center = new Vector3(0, 0.8f, 0); col.radius = 0.35f; col.height = 1.6f; // Set layer 29 foreach (var t in instance.GetComponentsInChildren(true)) { t.gameObject.layer = 29; } var renderers = instance.GetComponentsInChildren(true); if (renderers.Length > 0) { var b = renderers[0].bounds; foreach (var r in renderers.Skip(1)) b.Encapsulate(r.bounds); float currentH = b.size.y; if (currentH > 0.01f) { float scale = TargetHeight / currentH; instance.transform.localScale = Vector3.one * scale; } } PrefabUtility.SaveAsPrefabAsset(instance, PrefabPath); UnityEngine.Object.DestroyImmediate(instance); AssetDatabase.SaveAssets(); return $"SUCCESS: Authored {PrefabPath} with Avatar={avatar != null}, Idle={idleClip != null}, Walk={walkClip != null}"; } static void SetupTexture(string path, bool isSrgb) { var importer = (TextureImporter)AssetImporter.GetAtPath(path); if (importer == null) return; importer.textureType = TextureImporterType.Default; importer.sRGBTexture = isSrgb; importer.alphaIsTransparency = false; importer.mipmapEnabled = true; importer.maxTextureSize = 1024; importer.textureCompression = TextureImporterCompression.Compressed; importer.SaveAndReimport(); } static void SetupAnimationFbx(string path, Avatar sourceAvatar, string clipName) { var importer = (ModelImporter)AssetImporter.GetAtPath(path); if (importer == null) return; importer.globalScale = 1.0f; importer.importAnimation = true; importer.animationType = ModelImporterAnimationType.Human; if (sourceAvatar != null && sourceAvatar.isValid) { importer.avatarSetup = ModelImporterAvatarSetup.CopyFromOther; importer.sourceAvatar = sourceAvatar; } else { importer.avatarSetup = ModelImporterAvatarSetup.CreateFromThisModel; } var clip = new ModelImporterClipAnimation { name = clipName, takeName = "Armature|Scene", loopTime = true, loopPose = true, lockRootRotation = true, lockRootHeightY = true, lockRootPositionXZ = true }; importer.clipAnimations = new[] { clip }; importer.SaveAndReimport(); } static AnimationClip LoadClip(string path) { var assets = AssetDatabase.LoadAllAssetsAtPath(path); foreach (var a in assets) { if (a is AnimationClip c && !c.name.StartsWith("__preview__")) return c; } return null; } } #endif