You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blender导出模型在Unity游戏中无法正常动画的问题排查

问题描述

我正在为Unity游戏《Ultimate Epic Battle Simulator 2》制作带有人形预制动画的角色,使用Blender完成建模与绑定工作。遇到以下问题:

  • 几周前模型在游戏中动画表现正常,现在完全无法播放动画,角色保持初始姿势滑动;移除腿部与髋部的父子关系后,仅髋部可动,四肢仍无动作。
  • 网上下载的模型在游戏中动画正常,但将这类FBX模型导入Blender后,未经编辑直接保存再导出,就会出现同样的动画失效问题。
  • 使用Mixamo进行绑定,此前部分绑定后的模型可正常动画,但目前来自Blender的模型即便经Mixamo绑定也无法正常播放动画。
  • Root motion开关无影响,Unity动画检视器中动画显示正常,但游戏内存在动画失效问题。

疑问:该问题是Blender骨骼层级变更导致的,还是Blender或Unity中存在隐藏设置引发的?

开发者提供的模组代码
private void Update()
{
    for (int i = 0; i < AllMeshes.Length; i++)
    {
        if (AllMeshes[i].Skinned != null/* || AllMeshes[i].MeshRenderer != null*/)
        {
            if (AllMeshes[i].Skinned != null) AllMeshes[i].mats = AllMeshes[i].Skinned.sharedMaterials;
           // if (AllMeshes[i].MeshRenderer != null) AllMeshes[i].mats = AllMeshes[i].MeshRenderer.sharedMaterials;
        }

    }

    if(AllMeshes.Length != LastArraycount)
    {
        if(AllMeshes.Length > LastArraycount)
        {
            for (int i = AllMeshes.Length - (AllMeshes.Length - LastArraycount); i < AllMeshes.Length; i++)
            {
                AllMeshes[i].RenderPriority = Priority.Med;
            }
        }

        LastArraycount = AllMeshes.Length;
    }

    

    if (SeperateSubMeshes)
    {
        SeperateSubMeshes = false;
        List<MeshProps> nm = new List<MeshProps>();
        for (int i = 0; i < AllMeshes.Length; i++)
        {
            GameObject[] newobs = SeparateSubmeshes(AllMeshes[i].Skinned.gameObject);

            for (int s = 0; s < newobs.Length; s++)
            {
                MeshProps nmm = new MeshProps();
                nmm.Skinned = newobs[s].GetComponent<SkinnedMeshRenderer>();
                nm.Add(nmm);
            }

        }

        AllMeshes = nm.ToArray();

    }
}



public GameObject[] SeparateSubmeshes(GameObject sourceObject)
{
    SkinnedMeshRenderer sourceRenderer = sourceObject.GetComponent<SkinnedMeshRenderer>();

    if (sourceRenderer == null)
    {
        Debug.LogWarning("No SkinnedMeshRenderer found on the source object.");
        return null;
    }

    Mesh sourceMesh = sourceRenderer.sharedMesh;
    Material[] sourceMaterials = sourceRenderer.sharedMaterials;

    if (sourceMesh == null || sourceMesh.subMeshCount <= 1)
    {
        Debug.LogWarning("No sub-meshes found to separate.");
        return null;
    }

    List<GameObject> createdObjects = new List<GameObject>();

    for (int i = 0; i < sourceMesh.subMeshCount; i++)
    {
        GameObject newObject = new GameObject(sourceMesh.name + " Submesh "+i, typeof(SkinnedMeshRenderer));
        newObject.transform.SetParent(sourceObject.transform.parent, false);
        newObject.transform.localPosition = Vector3.zero;
        newObject.transform.localRotation = Quaternion.identity;
        newObject.transform.localScale = Vector3.one;

        SkinnedMeshRenderer newRenderer = newObject.GetComponent<SkinnedMeshRenderer>();

        Mesh newMesh = CreateMeshFromSubmesh(sourceMesh, i);
        newRenderer.sharedMesh = newMesh;

      

        newRenderer.sharedMaterials = new Material[] { sourceMaterials[i] };
        newRenderer.bones = sourceRenderer.bones;
        newRenderer.rootBone = sourceRenderer.rootBone;

        createdObjects.Add(newObject);

        //SAVE MESH
        newMesh.name = transform.name + "-"+ newObject.name;

#if UNITY_EDITOR
        AssetDatabase.CreateAsset(newMesh, "Assets"+"/"+SaveMeshFolder+"/"+ newMesh.name);
        AssetDatabase.SaveAssets();
#endif
    }

    return createdObjects.ToArray();
}

private Mesh CreateMeshFromSubmesh(Mesh sourceMesh, int submeshIndex)
{
    if (submeshIndex < 0 || submeshIndex >= sourceMesh.subMeshCount)
    {
        Debug.LogError("Invalid submesh index.");
        return null;
    }

    Mesh newMesh = new Mesh();
    if (sourceMesh.triangles.Length > 150000) newMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;

    int[] sourceTriangles = sourceMesh.GetTriangles(submeshIndex);

    // Collect unique vertex indices from the submesh
    HashSet<int> uniqueIndices = new HashSet<int>(sourceTriangles);
    int[] newIndexRemap = new int[sourceMesh.vertexCount];
    Vector3[] sourceVertices = sourceMesh.vertices;
    Vector2[] sourceUv = sourceMesh.uv;
    Vector3[] sourceNormals = sourceMesh.normals;
    Vector4[] sourceTangents = sourceMesh.tangents;
    BoneWeight[] sourceBoneWeights = sourceMesh.boneWeights;

    // Create new vertex attribute arrays
    Vector3[] vertices = new Vector3[uniqueIndices.Count];
    Vector2[] uv = new Vector2[uniqueIndices.Count];
    Vector3[] normals = new Vector3[uniqueIndices.Count];
    Vector4[] tangents = new Vector4[uniqueIndices.Count];
    BoneWeight[] boneWeights = new BoneWeight[uniqueIndices.Count];

    int newIndex = 0;
    foreach (int vertexIndex in uniqueIndices)
    {
        newIndexRemap[vertexIndex] = newIndex;
        vertices[newIndex] = sourceVertices[vertexIndex];
        uv[newIndex] = sourceUv[vertexIndex];
        normals[newIndex] = sourceNormals[vertexIndex];
        tangents[newIndex] = sourceTangents[vertexIndex];
        boneWeights[newIndex] = sourceBoneWeights[vertexIndex];
        newIndex++;
    }

    // Remap the triangles to the new vertex indices
    int[] triangles = new int[sourceTriangles.Length];
    for (int i = 0; i < sourceTriangles.Length; i++)
    {
        triangles[i] = newIndexRemap[sourceTriangles[i]];
    }

    newMesh.vertices = vertices;
    newMesh.uv = uv;
    newMesh.normals = normals;
    newMesh.tangents = tangents;
    newMesh.boneWeights = boneWeights;
    newMesh.triangles = triangles;
    newMesh.bindposes = sourceMesh.bindposes;

    newMesh.RecalculateBounds();

    return newMesh;
}

private void OnDrawGizmos()
{
    Gizmos.DrawWireCube(transform.position + (Vector3.up * collisionSettings.CollisionHeight * 0.5f), new Vector3(collisionSettings.CollisionWidth, collisionSettings.CollisionHeight, collisionSettings.CollisionWidth));
}
问题分析与解决方案

核心排查方向

  1. Blender导出FBX的关键设置

    • 导出前选中模型和骨骼,按Ctrl+A应用位置、旋转、缩放所有变换,避免导入Unity后出现缩放偏移导致骨骼驱动失效。
    • 导出FBX时,仅勾选选中的对象,取消勾选空物体,防止额外层级干扰骨骼结构;确保骨骼和网格选项处于勾选状态。
    • 严格保持骨骼命名规范:UEBS2依赖标准人形骨骼命名(如Hips、Spine、LeftArm等),不要修改Blender或Mixamo生成的默认骨骼名称,确保骨骼层级与Unity的Humanoid Rig完全匹配。
  2. Unity导入设置修正

    • 导入FBX后,在Rig标签下设置Animation Type为Humanoid,点击Configure检查骨骼映射:必须确保Hips被设为Root骨骼,所有骨骼映射无缺失。
    • 取消勾选Optimize Game Objects选项,UEBS2的模组代码依赖原始骨骼层级,优化后会丢失骨骼引用导致动画无法驱动模型。
  3. 模组代码相关验证
    开发者提供的SeparateSubmeshes方法拆分Submesh时,已复制骨骼和绑定姿势,但需注意:

    • 拆分后的新Mesh需关闭Mesh Compression,避免压缩破坏骨骼权重和绑定数据。
    • 启用SeperateSubMeshes开关后,检查拆分后的SkinnedMeshRenderer的rootBone是否正确指向原始Hips骨骼,确保原始骨骼对象处于激活状态。
  4. Mixamo绑定后的注意事项

    • Mixamo下载FBX时,选择With Skin并同时勾选Animation和Skin选项,不要单独下载骨骼文件。
    • 导入Blender后不要修改骨骼层级,直接应用所有变换后导出FBX,保持骨骼结构的完整性。

快速验证步骤

  • 直接导入网上下载的正常模型到Unity,确认动画正常,作为基准对照。
  • 将该基准模型导入Blender,不做任何编辑,应用所有变换后导出FBX,再导入Unity检查Rig设置,测试动画:若失效则说明Blender导出设置有误,需重新检查导出选项。
  • 在Unity Play模式下,打开场景视图的Gizmos→骨骼选项:若骨骼动但模型不动,说明SkinnedMeshRenderer的骨骼引用或绑定姿势异常;若骨骼也不动,说明动画控制器未正确挂载或骨骼层级不匹配。

内容的提问来源于stack exchange,提问作者The White Tortoise

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 02:27:04