Fix MaleExplorer setup with shadow material and editor diagnostics.

Improve FBX/prefab import setup, CharacterSpawner, and add M3 inspect/rebuild helpers.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
udx
2026-09-21 17:23:40 +08:00
co-authored by Cursor
parent 6550b8df4b
commit 10d9b98637
18 changed files with 509 additions and 51 deletions
+87 -26
View File
@@ -64,6 +64,10 @@ public static class SetupM3Character
var model = AssetImporter.GetAtPath(FbxPath) as ModelImporter;
if (model != null)
{
// Blender 按米导出;FBX 文件单位常带 0.01,useFileScale 会把角色缩成 ~1cm
model.globalScale = 1f;
model.useFileScale = false;
model.bakeAxisConversion = true;
// XPbr:保留 externalObjects 材质重映射;无 .mat 时再 None
model.materialImportMode = File.Exists(MatPath)
? ModelImporterMaterialImportMode.ImportViaMaterialDescription
@@ -154,36 +158,93 @@ public static class SetupM3Character
foreach (var renderer in instance.GetComponentsInChildren<Renderer>())
renderer.sharedMaterial = mat;
// 落地参考盘
var ground = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
ground.name = "ShadowDisc";
ground.transform.SetParent(instance.transform, false);
ground.transform.localPosition = new Vector3(0f, 0.02f, 0f);
ground.transform.localScale = new Vector3(0.7f, 0.015f, 0.7f);
Object.DestroyImmediate(ground.GetComponent<Collider>());
var gMat = new Material(urpLit != null ? urpLit : shader);
if (gMat.HasProperty("_BaseColor")) gMat.SetColor("_BaseColor", new Color(0.12f, 0.12f, 0.14f, 1f));
if (gMat.HasProperty("_Color")) gMat.color = new Color(0.12f, 0.12f, 0.14f, 1f);
ground.GetComponent<MeshRenderer>().sharedMaterial = gMat;
ground.GetComponent<MeshRenderer>().shadowCastingMode = ShadowCastingMode.Off;
// Blender/混元 FBX:身高常在本地 Z。转到 Unity Y-up,并面向 -Z(相机常见朝向)。
UprightBlenderMesh(instance);
// 粗略归一化身高约 1.8m
var bounds = new Bounds(instance.transform.position, Vector3.zero);
var renderers = instance.GetComponentsInChildren<Renderer>();
foreach (var r in renderers)
{
if (r.name == "ShadowDisc") continue;
bounds.Encapsulate(r.bounds);
}
if (bounds.size.y > 0.01f)
{
var scale = 1.8f / bounds.size.y;
instance.transform.localScale = Vector3.one * scale;
}
// 落地参考盘(扶正定标后再放)
PlaceShadowDisc(instance, urpLit != null ? urpLit : shader);
PrefabUtility.SaveAsPrefabAsset(instance, PrefabPath);
Object.DestroyImmediate(instance);
return PrefabPath;
return PrefabPath + (useXpbr ? " (XPbr)" : " (URP Lit)");
}
static void UprightBlenderMesh(GameObject instance)
{
instance.transform.localPosition = Vector3.zero;
instance.transform.localScale = Vector3.one;
// 混元/Blender:头在 +Z。Unity 用 +90°X 扶正;Y=0 使正面朝向相机(-Z)。
instance.transform.localRotation = Quaternion.Euler(90f, 0f, 0f);
var bounds = EncapsulateMeshBounds(instance);
if (bounds.size.y < 0.001f)
{
if (bounds.size.z >= bounds.size.x)
instance.transform.localRotation = Quaternion.Euler(90f, 0f, 0f);
else
instance.transform.localRotation = Quaternion.Euler(0f, 0f, 90f);
bounds = EncapsulateMeshBounds(instance);
}
if (bounds.size.y > 0.001f)
instance.transform.localScale = Vector3.one * (1.8f / bounds.size.y);
bounds = EncapsulateMeshBounds(instance);
instance.transform.position = new Vector3(0f, -bounds.min.y, 0f);
}
static Bounds EncapsulateMeshBounds(GameObject root)
{
Bounds bounds = default;
var has = false;
foreach (var r in root.GetComponentsInChildren<Renderer>())
{
if (r.name == "ShadowDisc") continue;
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 void PlaceShadowDisc(GameObject instance, Shader shader)
{
var existing = instance.transform.Find("ShadowDisc");
if (existing != null)
Object.DestroyImmediate(existing.gameObject);
const string shadowMatPath = ActorRoot + "/" + ActorName + "_Shadow.mat";
var gMat = AssetDatabase.LoadAssetAtPath<Material>(shadowMatPath);
if (gMat == null)
{
gMat = new Material(shader);
AssetDatabase.CreateAsset(gMat, shadowMatPath);
}
else gMat.shader = shader;
if (gMat.HasProperty("_BaseColor")) gMat.SetColor("_BaseColor", new Color(0.12f, 0.12f, 0.14f, 1f));
if (gMat.HasProperty("_Color")) gMat.color = new Color(0.12f, 0.12f, 0.14f, 1f);
// 不透明,避免 URP 透明洋红
if (gMat.HasProperty("_Surface")) gMat.SetFloat("_Surface", 0f);
gMat.DisableKeyword("_SURFACE_TYPE_TRANSPARENT");
gMat.renderQueue = -1;
EditorUtility.SetDirty(gMat);
var ground = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
ground.name = "ShadowDisc";
ground.transform.SetParent(instance.transform, false);
var bounds = EncapsulateMeshBounds(instance);
var localY = instance.transform.InverseTransformPoint(new Vector3(0f, bounds.min.y + 0.02f, 0f)).y;
ground.transform.localPosition = new Vector3(0f, localY, 0f);
ground.transform.localRotation = Quaternion.identity;
ground.transform.localScale = new Vector3(0.55f, 0.012f, 0.55f);
Object.DestroyImmediate(ground.GetComponent<Collider>());
var mr = ground.GetComponent<MeshRenderer>();
mr.sharedMaterial = gMat;
mr.shadowCastingMode = ShadowCastingMode.Off;
mr.receiveShadows = false;
}
static string EnsureBillboardPrefab()