Files

236 lines
8.9 KiB
C#

#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<Avatar>(FbxPath);
Debug.Log($"[Explorer] Avatar generated: {avatar != null && avatar.isValid}");
SetupAnimationFbx(IdleFbxPath, avatar, "Idle");
SetupAnimationFbx(WalkFbxPath, avatar, "Walk");
var mat = AssetDatabase.LoadAssetAtPath<Material>(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<Texture2D>(BaseTexPath);
var mixTex = AssetDatabase.LoadAssetAtPath<Texture2D>(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<AnimatorController>(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<GameObject>(FbxPath);
var instance = (GameObject)PrefabUtility.InstantiatePrefab(fbxPrefab);
instance.name = "Explorer";
foreach (var r in instance.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
r.sharedMaterial = mat;
}
var animator = instance.GetComponent<Animator>();
if (animator == null) animator = instance.AddComponent<Animator>();
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<CapsuleCollider>();
if (col == null) col = instance.AddComponent<CapsuleCollider>();
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<Transform>(true))
{
t.gameObject.layer = 29;
}
var renderers = instance.GetComponentsInChildren<Renderer>(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