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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user