欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > 【Unity动态换装骨骼合并】

【Unity动态换装骨骼合并】

2025/4/20 23:41:28 来源:https://blog.csdn.net/qq_32174013/article/details/147304095  浏览:    关键词:【Unity动态换装骨骼合并】

文章目录

  • 换装骨骼合并
    • 前言
    • 换装的实现原理
    • 人物模型
    • 动态加载

换装骨骼合并

前言

前司有个换装需求,把身上的衣服当做部件来制作,每个单独的裙子、手头、翅膀、帽子上都完整的骨骼部分(即主骨骼),然后有特殊需求的部分会加上额外骨骼。

换装的实现原理

换装是游戏里面常见的模块,现在已经有非常多的游戏实装了换装的模块,一般来说,换装的核心实现就是以下几种方案:

  • 材质替换(材质或贴图)
  • 网格替换(Mesh
  • 骨骼绑定部件替换
  • 模型替换

还有其余的一些方法(通过框架比如Universal Multiplayer Avatar等),我下面使用的是主要是第三种方法即使用部件替换。材质替换适用于那种简单的皮肤换色,换纹理,比较朴素(糊弄)的方法,有点是性能消耗少;网格替换使用的也不多,不适合灵活部件的替换,一般会使用合并的方法减少Draw CallCombineMesh或者自己写)

人物模型

  • 使用网格替换和部件的,都需要基于同一套骨骼或者共享同一骨骼树(如果第三方的部件替换也需要遵循此要求),下面以简单的例子举例:

人物
这是一个人物,如果我们不需要动态添加部件,那么人物的节点信息可以简单的设置成如下(不考虑性能)
节点信息组件
可以看到,SkinnedMeshRenderer蒙皮骨骼节点的Root Bone指向的是root节点,组件
所以这个人物换装的思路就很明确了,因为是公用的一组骨骼,而且本身部件上不带自己的骨骼,所以需要的时候直接SetActive就能达到换装的效果,通过捣鼓配置就能达成换装的目的(ScriptObject啥的),当场景中人物过多的时候,可以执行网格合并减少DC,但是如果组件是从网上下载的(比如通过WebRequest下载部件,然后动态的一个个加上去,而且部件自身带了完整的骨骼,就会稍微麻烦一些)

动态加载

  • 动态加载要做的比上面静态放置的多几个步骤,去掉或者合并自带的骨骼(没有更好),将骨骼减少到一条主骨骼(比如body身体上的部分)。这样做的好处有很多,一是减少了动画控制器的数量,如果你要这几个组件同时做动画,不处理的情况下,有几个部件就需要创建几个动画控制器(虽然可以复用),会增加内存、CPU的调用,Animation.Update会消耗不少性能,还有一个问题就是可能会不协调穿帮(因为是部件组成,会存在部件接缝衔接不上的情况,你可以实际中试试)
  1. 如果是下载的,需要初始化到物体身上,放到父节点下面
  2. 编写脚本进行动态合并:
public List<GameObject> Equips = new List<GameObject>(); // 在外面的面板上可以看到对应的组件
Dictionary<string, Transform> m_MainBonesDic = new Dictionary<string, Transform>(); // 记录骨骼// 初始化主骨骼
private void InitMainBones()
{m_MainBonesDic.Clear();Transform outtran = null;var childtrans = GetComponentsInChildren<Transform>();for (int i = 0; i < childtrans.Length; ++i){var tran = childtrans[i];if (!m_MainBonesDic.TryGetValue(tran.name, out outtran))m_MainBonesDic[tran.name] = tran;}
}// 讲物体全部放到父节点下面后(组件的原点信息最好都是0点)
private void WearParts(List<GameObject> Equips)
{for (int i = 0; i < Equips.Count; i++){var equip = Equips[i];CombineSourceBones(equip);}
}// 合并组件的骨骼到一条骨骼身上
public void CombineSourceBones(GameObject sourceClothing)
{sourceClothing.transform.SetParent(transform);var skinnedMeshRenderers = sourceClothing.GetComponentsInChildren<SkinnedMeshRenderer>(true);foreach (var sourceRenderer in skinnedMeshRenderers){sourceRenderer.bones = TranslateTransforms(sourceRenderer.bones);sourceRenderer.rootBone = GetBoneByName(sourceRenderer.rootBone.name);UpdateSkinMeshMagicaClothInfo(sourceClothing); // (如果有Magic Cloth,可以在这里处理)}// 如果原来的部件身上都带有完整骨骼,需要销毁DestroyRootBones(sourceClothing);sourceClothing.transform.localPosition = Vector3.zero;sourceClothing.transform.localRotation = Quaternion.identity;
}// 举个例子
private void DestroyRootBones(GameObject sourceClothing)
{var rootbone = sourceClothing.transform.Find("Bip"); // 或者其他名字if (rootbone == null)rootbone = sourceClothing.transform.Find("Root");if (rootbone != null)Destroy(rootbone.gameObject);
}private Transform GetSourceBoneParentInMainBones(Transform sourceBone, out Transform curCheckBoneParent)
{var sourceboneparent = sourceBone.parent;var mainbone = GetBoneByName(sourceboneparent.name);if (mainbone == null){return GetSourceBoneParentInMainBones(sourceboneparent, out curCheckBoneParent);}curCheckBoneParent = sourceBone;return mainbone;
}private Transform GetBoneByName(string boneName)
{if (m_MainBonesDic.TryGetValue(boneName, out Transform tran)){return tran;}return null;
}
  1. 你可以在Start里面执行初始化和穿装备,半途更新需要的话就封装一个接口调用,这样的话很方便别人调用。

实际效果如何就不展示了,如果你的项目需要,可以到具体的场景中比较效果。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词