#if UNITY_EDITOR using System.IO; using UnityEditor; using UnityEngine; using YooAsset.Editor; /// /// 新角色 Prefab:Y-up、底面原点、局部 +Z 正面,不做 +90X。旧资产保留原绑定空间。 /// unity command run_script --file AgentScripts/SetupCharacter.cs --entry SetupCharacter.Run --args "[\"Human\",\"CaoCao\"]" /// public static class SetupCharacter { const string XPbrShaderName = "Test/XPbr"; const string ArtPackage = "Art"; const float TargetHeight = 1.8f; public static string SetupExplorer() { return Run("Human", "Explorer", 1.6f); } public static string SetupCampGuide() { return Run("Human", "CampGuide", 1.55f); } public static string SetupLmCat() { return Run("Animal", "LmCat", 0.42f); } public static string SetupRockBall() { return Run("Animal", "RockBall", 0.55f); } public static string Run(string kind, string actor, float heightMeters = TargetHeight) { if (string.IsNullOrWhiteSpace(actor)) throw new System.ArgumentException("actor required"); if (heightMeters < 0.05f) throw new System.ArgumentException("heightMeters too small"); var root = string.IsNullOrWhiteSpace(kind) ? $"Assets/Bundles/Characters/{actor}" : $"Assets/Bundles/Characters/{kind}/{actor}"; AssetDatabase.Refresh(); var tex = EnsureTextureImports(root, actor); var prefab = EnsureMeshPrefab(root, actor, heightMeters); var art = EnsureArtCollector(); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); return $"{root} tex={tex} prefab={prefab} art={art}"; } static string EnsureTextureImports(string root, string actor) { var fbxPath = $"{root}/{actor}.fbx"; var matPath = $"{root}/{actor}.mat"; var paths = new[] { $"{root}/{actor}_base.png", $"{root}/{actor}_mix.png", }; var ok = 0; foreach (var path in paths) { if (!File.Exists(path)) continue; var importer = AssetImporter.GetAtPath(path) as TextureImporter; if (importer == null) continue; var isMix = path.EndsWith("_mix.png"); importer.textureType = TextureImporterType.Default; importer.sRGBTexture = !isMix; importer.mipmapEnabled = true; importer.alphaIsTransparency = !isMix; importer.SaveAndReimport(); ok++; } if (!File.Exists(fbxPath)) return $"imported={ok} fbx=missing"; var model = AssetImporter.GetAtPath(fbxPath) as ModelImporter; if (model != null) { model.globalScale = 1f; model.useFileScale = false; model.bakeAxisConversion = true; model.materialImportMode = File.Exists(matPath) ? ModelImporterMaterialImportMode.ImportViaMaterialDescription : ModelImporterMaterialImportMode.None; model.meshCompression = ModelImporterMeshCompression.Medium; model.isReadable = false; model.SaveAndReimport(); } return $"imported={ok} fbx=yes"; } static string EnsureMeshPrefab(string root, string actor, float heightMeters) { var fbxPath = $"{root}/{actor}.fbx"; var basePath = $"{root}/{actor}_base.png"; var mixPath = $"{root}/{actor}_mix.png"; var matPath = $"{root}/{actor}.mat"; var prefabPath = $"{root}/{actor}.prefab"; if (!File.Exists(fbxPath)) return "missing fbx"; var baseMap = AssetDatabase.LoadAssetAtPath(basePath); var mixMap = AssetDatabase.LoadAssetAtPath(mixPath); if (baseMap == null) return "missing base png"; var xpbr = Shader.Find(XPbrShaderName); var urpLit = Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard"); var useXpbr = xpbr != null && mixMap != null; var shader = useXpbr ? xpbr : urpLit; var mat = AssetDatabase.LoadAssetAtPath(matPath); if (mat == null) { mat = new Material(shader); AssetDatabase.CreateAsset(mat, matPath); } else { mat.shader = shader; } if (useXpbr) { if (mat.HasProperty("_MainTex")) mat.SetTexture("_MainTex", baseMap); if (mat.HasProperty("_MixTex")) mat.SetTexture("_MixTex", mixMap); if (mat.HasProperty("_BaseColor")) mat.SetColor("_BaseColor", Color.white); if (mat.HasProperty("_Metallic")) mat.SetFloat("_Metallic", 1f); if (mat.HasProperty("_Roughness")) mat.SetFloat("_Roughness", 1f); if (mat.HasProperty("_AO")) mat.SetFloat("_AO", 1f); if (mat.HasProperty("_AlphaTest")) mat.SetFloat("_AlphaTest", 0f); mat.DisableKeyword("_ALPHATEST_ON"); } else if (mat.HasProperty("_BaseMap")) { mat.SetTexture("_BaseMap", baseMap); } EditorUtility.SetDirty(mat); var modelRoot = AssetDatabase.LoadAssetAtPath(fbxPath); if (modelRoot == null) return "fbx not loaded"; var instance = (GameObject)PrefabUtility.InstantiatePrefab(modelRoot); instance.name = actor; foreach (var renderer in instance.GetComponentsInChildren()) renderer.sharedMaterial = mat; instance.transform.localPosition = Vector3.zero; instance.transform.localScale = Vector3.one; instance.transform.localRotation = Quaternion.identity; var bounds = MeshBounds(instance); if (bounds.size.y > 0.01f) instance.transform.localScale = Vector3.one * (heightMeters / bounds.size.y); PrefabUtility.SaveAsPrefabAsset(instance, prefabPath); Object.DestroyImmediate(instance); return prefabPath + (useXpbr ? " (XPbr)" : " (URP Lit)"); } static Bounds MeshBounds(GameObject root) { Bounds bounds = default; var has = false; foreach (var r in root.GetComponentsInChildren()) { if (!has) { bounds = r.bounds; has = true; } else bounds.Encapsulate(r.bounds); } return has ? bounds : new Bounds(root.transform.position, Vector3.one * 0.01f); } static string EnsureArtCollector() { var setting = BundleCollectorSettingData.Setting; setting.ShowPackageView = true; var package = setting.Packages.Find(p => p.PackageName == ArtPackage); if (package == null) package = BundleCollectorSettingData.CreatePackage(ArtPackage); package.PackageDesc = "角色/场景美术"; package.EnableAddressable = true; package.SupportExtensionless = true; package.AutoCollectShaders = true; package.IgnoreRuleName = nameof(NormalIgnoreRule); BundleCollectorSettingData.ModifyPackage(package); var group = package.Groups.Find(g => g.GroupName == "Characters"); if (group == null) group = BundleCollectorSettingData.CreateGroup(package, "Characters"); group.ActiveRuleName = nameof(EnableGroup); group.AssetTags = "art"; BundleCollectorSettingData.ModifyGroup(package, group); const string collectPath = "Assets/Bundles/Characters"; var collector = group.Collectors.Find(c => c.CollectPath == collectPath); if (collector == null) { collector = new BundleCollector { CollectPath = collectPath, CollectorGUID = AssetDatabase.AssetPathToGUID(collectPath), CollectorType = ECollectorType.MainAssetCollector, AddressRuleName = nameof(AddressByFileName), PackRuleName = nameof(PackSeparately), FilterRuleName = nameof(CollectAll), AssetTags = "art", }; BundleCollectorSettingData.CreateCollector(group, collector); } else { collector.CollectorGUID = AssetDatabase.AssetPathToGUID(collectPath); collector.AddressRuleName = nameof(AddressByFileName); collector.PackRuleName = nameof(PackSeparately); collector.FilterRuleName = nameof(CollectAll); BundleCollectorSettingData.ModifyCollector(group, collector); } package.CheckConfigError(); BundleCollectorSettingData.SaveFile(); return $"art collect={collectPath}"; } } #endif