236 lines
8.9 KiB
C#
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 |