一、另一种思路
另一种更换人物服饰模型的方式(合Mesh),参看此篇博客:
二、模型
人物模型的显示所依靠的是骨骼与其上的蒙皮。就类似于我们人体组成部分,有骨头,有皮肤(当然还有器官、神经等等)。
如果我们需要做出动作的话,就要控制自身各部位做出相应行为。那么模型也是如此,人物动画系统将会控制各关键骨骼的运动,引擎会为我们补全中间骨骼的运动轨迹,及骨骼上的蒙皮形变过程。
可以看到,所有的模型可以分为两个部分,骨骼树和模型。我们模型就是绑在自身的骨骼树上的,换句话说,骨骼树中包含了当前模型上所需要的所有骨骼结点。
三、SkinnedMeshRenderer
在unity中将这骨骼与蒙皮整合成了一个组件——SkinnedMeshRenderer。
当然,SkinnedMeshRenderer的功能是非常强大的,本文只介绍换装所需用到的几项:
1、RootBone
这是该模型绑定所对应骨骼的根骨骼这在Inspector视窗下是可以直观看到的,到然后也是允许通过拖动来改变参数
2、bones
这是该模型所绑定的所有骨骼,这在Inspector视窗下是无法看见的,只能通过代码的形式进行获得,当然,它是允许通过代码对骨骼进行修改的。
gameObject.GetComponent<SkinnedMeshRenderer>().bones
这里你所获得的就是由所有绑定的骨骼所组成的数组。数组数据以RootBone为根结点,深度优先进行遍历所得。
四、换装的实现思路(本文将人物拆分为了五个部位,头发,头,上身,下身,脚)
把所有用到的服饰模型像拼积木一样全部绑到一套骨骼树上,哪个部位需要更换,就把对应的部位拆下来绑上替换的。
首先在场景中加载六个部分,总骨骼树、头发模型、头模型、上身模型、下身模型和脚模型。分别遍历五个部位模型获取需要绑定的根骨骼(RootBone)及所有绑定的骨骼结点(bones),将之替换成总骨骼树上对应名称的骨骼结点。最后将各部位中已经没有用的骨骼树销毁。这样一个人物模型就拼好了,由于以这种形式生成的人物是由五个部位的子物体及骨骼树组成的,所以在换装,也就是替换服饰模型的时候,只需要找到对应的部位模型进行销毁和替换模型的骨骼绑定就行了。
五、模型准备
准备一个包括了所有模型所需骨骼结点的骨骼树(下文称最全骨骼树)和所有替换用服饰模型。
因为我们将最全骨骼树作为整个人物模型的父节点,所以在其上挂animator动画组件。如果你需要将动画模式设置为CullUpdateTransforms,那么还需要挂载一个空的MeshRenderer。具体原因可以参看:,此处不多赘述。
六、实现过程
将所有六个预制体生成(最全骨骼树、头发、头、上身、下身和脚),并将五个需要替换的服饰部件父物体设置成骨骼树。
调用脚本拼接人物模型(传入最全骨骼树):
注意:最全骨骼树的根结点命名需要为“DM_ACTOR_BIND”
public class UCombineSkinnedMgr
{
//骨骼树下所有骨骼节点
Dictionary<string, Transform> transforms = new Dictionary<string, Transform>();
/// <summary>
/// 使用共享骨骼的方法
/// 只合骨骼信息
/// </summary>
/// <param name="skeleton">传入的骨骼</param>
public void CombineObject(GameObject skeleton)
{
//遍历获取骨骼树下所有骨骼节点
Transform tran = skeleton.transform;
Transform rootBone = tran.Find("DM_ACTOR_BIND");
Transform[] allbones = rootBone.GetComponentsInChildren<Transform>(true);
for (int i = 0; i < allbones.Length; i++)
{
transforms[allbones[i].name] = allbones[i];
}
//共享骨骼
SetBonesSharing(tran);
}
/// <summary>
/// 设置Mesh骨骼节点
/// 允许每个部件的骨骼根节点不同
/// </summary>
/// <param name="target">目标对象</param>
private void SetBonesSharing(Transform target)
{
SkinnedMeshRenderer[] targetSkin = target.GetComponentsInChildren<SkinnedMeshRenderer>(true);
for (int i = 0; i < targetSkin.Length; i++)
{
targetSkin[i].receiveShadows = false;
if (null != targetSkin[i].rootBone)
{
//替换骨骼根节点
Transform tf = GetBoneObject(targetSkin[i].rootBone.name);
if (targetSkin[i].rootBone == tf)
{
//增加效率,如果已经替换过骨骼节点,就不需要再次替换
continue;
}
else
{
targetSkin[i].rootBone = tf;
}
}
Transform[] tfs = new Transform[targetSkin[i].bones.Length];
for (int j = 0; j < targetSkin[i].bones.Length; j++)
{
//替换所有骨骼节点
if (null == targetSkin[i].bones[j])
{
//如果骨骼节点为null
Debug.LogWarning(targetSkin[i].name + " " + j);
}
else
{
tfs[j] = GetBoneObject(targetSkin[i].bones[j].name);
}
}
targetSkin[i].bones = tfs;
}
//删除加载的原有的骨骼节点
Transform tran;
for (int i = 0; i < targetSkin.Length; i++)
{
tran = FindChildInTransfromAllChild(targetSkin[i].transform.parent, "DM_ACTOR_BIND");
if (tran != null)
{
Object.Destroy(tran.gameObject);
}
}
}
/// <summary>
/// 获取共享骨骼节点
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
private Transform GetBoneObject(string name)
{
if (transforms.ContainsKey(name))
{
return transforms[name];
}
Debug.LogWarning("bone: " + name + " is missing");
return null;
}
/// <summary>
/// 查找所有父物体下物体
/// </summary>
/// <param name="transform"></param>
/// <param name="name"></param>
/// <returns></returns>
private Transform FindChildInTransfromAllChild(Transform transform, string name)
{
if (transform == null)
{
return null;
}
Transform[] childArray = transform.GetComponentsInChildren<Transform>(true);
List<Transform> childList = childArray.ToList();
return childList.Find(value => value.name == name);
}
}
七、示例工程
后续上传