Add prototype gameplay loop with character/battlefield assets and server game APIs.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
#if UNITY_EDITOR
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using YooAsset.Editor;
|
||||
|
||||
/// <summary>
|
||||
/// 新角色 Prefab:Y-up、底面原点、局部 -Z 正面,不做 +90X。
|
||||
/// unity command run_script --file AgentScripts/SetupCharacter.cs --entry SetupCharacter.Run --args "[\"Human\",\"CaoCao\"]"
|
||||
/// </summary>
|
||||
public static class SetupCharacter
|
||||
{
|
||||
const string XPbrShaderName = "Test/XPbr";
|
||||
const string ArtPackage = "Art";
|
||||
const float TargetHeight = 1.8f;
|
||||
|
||||
public static string SetupCaoCao()
|
||||
{
|
||||
return Run("Human", "CaoCao");
|
||||
}
|
||||
|
||||
public static string SetupLmGirl()
|
||||
{
|
||||
return Run("Human", "LmGirl", 1.65f);
|
||||
}
|
||||
|
||||
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<Texture2D>(basePath);
|
||||
var mixMap = AssetDatabase.LoadAssetAtPath<Texture2D>(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<Material>(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<GameObject>(fbxPath);
|
||||
if (modelRoot == null)
|
||||
return "fbx not loaded";
|
||||
|
||||
var instance = (GameObject)PrefabUtility.InstantiatePrefab(modelRoot);
|
||||
instance.name = actor;
|
||||
foreach (var renderer in instance.GetComponentsInChildren<Renderer>())
|
||||
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<Renderer>())
|
||||
{
|
||||
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(PackDirectory),
|
||||
FilterRuleName = nameof(CollectAll),
|
||||
AssetTags = "art",
|
||||
};
|
||||
BundleCollectorSettingData.CreateCollector(group, collector);
|
||||
}
|
||||
else
|
||||
{
|
||||
collector.CollectorGUID = AssetDatabase.AssetPathToGUID(collectPath);
|
||||
collector.AddressRuleName = nameof(AddressByFileName);
|
||||
collector.PackRuleName = nameof(PackDirectory);
|
||||
collector.FilterRuleName = nameof(CollectAll);
|
||||
BundleCollectorSettingData.ModifyCollector(group, collector);
|
||||
}
|
||||
|
||||
package.CheckConfigError();
|
||||
BundleCollectorSettingData.SaveFile();
|
||||
return $"art collect={collectPath}";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user