经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » unity » 查看文章
Unity实现简单换装系统
来源:jb51  时间:2021/4/12 9:34:25  对本文有异议

关于Unity的换装,网上有几篇文章,我之前也简单的描述过实现。不过那个时候只是粗略的试验了下。今天好好梳理了下代码。

先上代码(自己的游戏项目,不是公司的,所以放心的贴上项目代码了,部分引用到其他的功能文件,但是核心代码无影响,这里主要看一下细节和思路)

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. public enum AvatarPart
  5. {
  6. helmet,
  7. chest,
  8. shoulders,
  9. gloves,
  10. boots,
  11. }
  12. // 人物换装
  13. public class ActorAvatar : MonoBehaviour
  14. {
  15. // 换装的部件信息
  16. public class AvatarInfo
  17. {
  18. public string partName;
  19. public GameObject defaultPart;
  20. public GameObject avatarPart;
  21. }
  22. protected int _bodyModelId;
  23. protected GameObject _body; // 基础模型动画
  24. protected Dictionary<string, AvatarInfo> _avatarInfo = new Dictionary<string, AvatarInfo>(); // 换装信息
  25. private List<int> _avatarLoadQueue = new List<int>();
  26. void Start()
  27. {
  28. }
  29. void Update()
  30. {
  31. }
  32. // 创建模型
  33. public void LoadModel(int modelId)
  34. {
  35. _bodyModelId = modelId;
  36. ResourceMgr.Instance.LoadModel(modelId, (GameObject obj) =>
  37. {
  38. _body = obj;
  39. // 换装请求
  40. if (_avatarLoadQueue.Count > 0) {
  41. foreach (var avatar in _avatarLoadQueue) {
  42. LoadAvatar(avatar);
  43. }
  44. _avatarLoadQueue.Clear();
  45. }
  46. }, true);
  47. }
  48. // 给人物换装
  49. public void LoadAvatar(int avatarId)
  50. {
  51. // 如果还没有加载完基础模型,则等待
  52. if (_body == null) {
  53. _avatarLoadQueue.Add(avatarId);
  54. return;
  55. }
  56. AvatarData adata = DataMgr.Instance.GetAvatarData(avatarId);
  57. ResourceMgr.Instance.LoadModel(adata.model, (GameObject obj) => {
  58. ChangeAvatar(obj, adata.addpart);
  59. });
  60. }
  61. // 替换部件
  62. public void ChangeAvatar(GameObject avatarModel, string partName)
  63. {
  64. // 先卸载当前部件
  65. AvatarInfo currentInfo;
  66. if (_avatarInfo.TryGetValue(partName, out currentInfo)) {
  67. if (currentInfo.avatarPart != null) {
  68. Destroy(currentInfo.avatarPart);
  69. currentInfo.avatarPart = null;
  70. }
  71. if (currentInfo.defaultPart != null) {
  72. currentInfo.defaultPart.SetActive(true);
  73. }
  74. }
  75. // avatarModel是一个resource,并没有实例化
  76. if (avatarModel == null) {
  77. return;
  78. }
  79. // 需要替换的部件
  80. Transform avatarPart = GetPart(avatarModel.transform, partName);
  81. if (avatarPart == null) {
  82. Debug.LogError(string.Format("Avatar Part Not Found: ", partName));
  83. return;
  84. }
  85. // 将原始部件隐藏
  86. Transform bodyPart = GetPart(_body.transform, partName);
  87. if (bodyPart != null) {
  88. bodyPart.gameObject.SetActive(false);
  89. }
  90. // 设置到body上的新物件
  91. GameObject newPart = new GameObject(partName);
  92. newPart.transform.parent = _body.transform;
  93. SkinnedMeshRenderer newPartRender = newPart.AddComponent<SkinnedMeshRenderer>();
  94. SkinnedMeshRenderer avatarRender = avatarPart.GetComponent<SkinnedMeshRenderer>();
  95. // 刷新骨骼模型数据
  96. SetBones(newPart, avatarPart.gameObject, _body);
  97. newPartRender.sharedMesh = avatarRender.sharedMesh;
  98. newPartRender.sharedMaterials = avatarRender.sharedMaterials;
  99. // 记录换装信息
  100. AvatarInfo info = new AvatarInfo();
  101. info.partName = partName;
  102. if (bodyPart != null) {
  103. info.defaultPart = bodyPart.gameObject;
  104. } else {
  105. info.defaultPart = null;
  106. }
  107. info.avatarPart = newPart;
  108. _avatarInfo[partName] = info;
  109. }
  110. // 递归遍历子物体
  111. public static Transform GetPart(Transform t, string searchName)
  112. {
  113. foreach (Transform c in t) {
  114. string partName = c.name.ToLower();
  115. if (partName.IndexOf(searchName) != -1) {
  116. return c;
  117. } else {
  118. Transform r = GetPart(c, searchName);
  119. if (r != null) {
  120. return r;
  121. }
  122. }
  123. }
  124. return null;
  125. }
  126. public static Transform FindChild(Transform t, string searchName)
  127. {
  128. foreach (Transform c in t) {
  129. string partName = c.name;
  130. if (partName == searchName) {
  131. return c;
  132. } else {
  133. Transform r = FindChild(c, searchName);
  134. if (r != null) {
  135. return r;
  136. }
  137. }
  138. }
  139. return null;
  140. }
  141. // 刷新骨骼数据 将root物体的bodyPart骨骼更新为avatarPart
  142. public static void SetBones(GameObject goBodyPart, GameObject goAvatarPart, GameObject root)
  143. {
  144. var bodyRender = goBodyPart.GetComponent<SkinnedMeshRenderer>();
  145. var avatarRender = goAvatarPart.GetComponent<SkinnedMeshRenderer>();
  146. var myBones = new Transform[avatarRender.bones.Length];
  147. for (var i = 0; i < avatarRender.bones.Length; i++) {
  148. myBones[i] = FindChild(root.transform, avatarRender.bones[i].name);
  149. }
  150. bodyRender.bones = myBones;
  151. }
  152. }

1、Unity换装有三种需求:

添加武器的挂载式换装,这个只要创建对应的模型,并且设置好transform.parent就可以了。

替换纹理,这个取到对应的material,然后设置texture就可以了。

模型部件的替换,这个是此处处理的,也是相对最复杂的换装。

2、最核心的部分是ChangeAvatar,它完成了模型换装的功能。模型部件的替换其实就是替换SkinnedMeshRender中的sharedMesh和sharedMaterials。

(这里稍微插一下sharedMaterials   sharedMaterial  Materials  Material这几个变量的区别。sharedMaterials是共享和引用的关系,只要修改这个,所有使用到这个material的模型都会受到影响。如果是在编辑器模式下,它还会修改实际material文件的属性。Materials是sharedMaterials的一份拷贝,只有当前模型使用。materia是materials数组中的第一个对象,这个仅仅是为了方便书写而存在的。)

仅仅替换了sharedMesh还不够,模型会变成一坨麻花。 还应该修改SkinnedMeshRender中的bones属性,它记录了模型的骨骼信息(其实就是一大堆Transform)。  SetBones函数完成了骨骼替换的操作。它查找avatar部件中的所有骨骼名称,然后查找当前模型中的对应骨骼名字,并存储起来。这个数组就是新部件的骨骼信息。

3、一个逻辑上的处理细节。保留了原始模型的对应部件,并没有销毁这个部件,仅仅是隐藏起来。这样卸载装备的时候,只需要删掉装备部件,然后把默认部件设为可见就可以了。

4、可以考虑使用Unity的CombineInstance把模型合并,这样的好处是可以提高运行性能。但是只有材质共用一个的时候才能真正起到优化效果。有个MeshBaker的插件很酷。如果要进行千人战,就必须考虑这方面的优化。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持w3xue。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号