转载请注明出处:http://www.cnblogs.com/shamoyuu/p/6505561.html

一、换装原理

  游戏角色换装分为以下几步:

    1.替换蒙皮网格

    2.刷新骨骼

    3.替换材质

  上面这种是比较简单的换装,可以实现,但是一般我们为了降低游戏的Draw Call会合并模型的网格,这就需要我们重新计算UV,还要合并贴图和材质。这种复杂的实现分为以下几步:

    1.替换蒙皮网格(或者直接替换模型换装部位的GameObject,因为合并的时候会合并所有的蒙皮网格,而不会关心它是否属于原来角色身体的一部分,而且如果需要替换的部位有多个配件拥有独立的网格和贴图,那这种方式都可以正常执行。我下面的代码就是直接替换了换装部位的GameObject)

    2.合并所有蒙皮网格

    3.刷新骨骼

    4.附加材质(我下面是获取第一个材质作为默认材质)

    5.合并贴图(贴图的宽高最好是2的N次方的值)

    6.重新计算UV

二、换装实现

using System;
using System.Collections;
using UnityEngine;
using System.Collections.Generic; public class CharacterCombine : MonoBehaviour
{
// 目标物体(必须是骨骼的父物体,不然蒙皮失效)
public GameObject target; // 最终材质(合并所有模型后使用的材质)
public Material material; // 物体所有的部分
private GameObject[] targetParts = new GameObject[]; private string[] defaultEquipPartPaths = new string[]; void Start()
{
// 把FBX的模型按部件分别放入Resources下对应的文件夹里,可以留空,模型需要蒙皮,而且所有模型使用同一骨骼
// 最后的M是Fbx的模型,需要的Unity3D里设置好材质和贴图,部件贴图要勾选Read/Write Enabled
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Head/Head0000/M";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Face/Face0000/M";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Hair/Hair0000/M";
defaultEquipPartPaths[] = "";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Body/Body0000/M";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Leg/Leg0000/M";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Hand/Hand0000/M";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Foot/Foot0000/M";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Wing/Wing0001/M"; Destroy(target.GetComponent<SkinnedMeshRenderer>());
for (int i = ; i < defaultEquipPartPaths.Length; i++)
{
UnityEngine.Object o = Resources.Load(defaultEquipPartPaths[i]);
if (o)
{
GameObject go = Instantiate(o) as GameObject;
go.transform.parent = target.transform;
go.transform.localPosition = new Vector3(, -, );
go.transform.localRotation = new Quaternion();
targetParts[i] = go;
}
} StartCoroutine(DoCombine());
} /// <summary>
/// 使用延时,不然某些GameObject还没有创建
/// </summary>
/// <returns></returns>
IEnumerator DoCombine()
{
yield return null;
Combine(target.transform);
} /// <summary>
/// 合并蒙皮网格,刷新骨骼
/// 注意:合并后的网格会使用同一个Material
/// </summary>
/// <param name="root">角色根物体</param>
private void Combine(Transform root)
{
float startTime = Time.realtimeSinceStartup; List<CombineInstance> combineInstances = new List<CombineInstance>();
List<Transform> boneList = new List<Transform>();
Transform[] transforms = root.GetComponentsInChildren<Transform>();
List<Texture2D> textures = new List<Texture2D>(); int width = ;
int height = ; int uvCount = ; List<Vector2[]> uvList = new List<Vector2[]>(); // 遍历所有蒙皮网格渲染器,以计算出所有需要合并的网格、UV、骨骼的信息
foreach (SkinnedMeshRenderer smr in root.GetComponentsInChildren<SkinnedMeshRenderer>())
{
for (int sub = ; sub < smr.sharedMesh.subMeshCount; sub++)
{
CombineInstance ci = new CombineInstance();
ci.mesh = smr.sharedMesh;
ci.subMeshIndex = sub;
combineInstances.Add(ci);
} uvList.Add(smr.sharedMesh.uv);
uvCount += smr.sharedMesh.uv.Length; if (smr.material.mainTexture != null)
{
textures.Add(smr.GetComponent<Renderer>().material.mainTexture as Texture2D);
width += smr.GetComponent<Renderer>().material.mainTexture.width;
height += smr.GetComponent<Renderer>().material.mainTexture.height;
} foreach (Transform bone in smr.bones)
{
foreach (Transform item in transforms)
{
if (item.name != bone.name) continue;
boneList.Add(item);
break;
}
}
} // 获取并配置角色所有的SkinnedMeshRenderer
SkinnedMeshRenderer tempRenderer = root.gameObject.GetComponent<SkinnedMeshRenderer>();
if (!tempRenderer)
{
tempRenderer = root.gameObject.AddComponent<SkinnedMeshRenderer>();
} tempRenderer.sharedMesh = new Mesh(); // 合并网格,刷新骨骼,附加材质
tempRenderer.sharedMesh.CombineMeshes(combineInstances.ToArray(), true, false);
tempRenderer.bones = boneList.ToArray();
tempRenderer.material = material; Texture2D skinnedMeshAtlas = new Texture2D(get2Pow(width), get2Pow(height));
Rect[] packingResult = skinnedMeshAtlas.PackTextures(textures.ToArray(), );
Vector2[] atlasUVs = new Vector2[uvCount]; // 因为将贴图都整合到了一张图片上,所以需要重新计算UV
int j = ;
for (int i = ; i < uvList.Count; i++)
{
foreach (Vector2 uv in uvList[i])
{
atlasUVs[j].x = Mathf.Lerp(packingResult[i].xMin, packingResult[i].xMax, uv.x);
atlasUVs[j].y = Mathf.Lerp(packingResult[i].yMin, packingResult[i].yMax, uv.y);
j++;
}
} // 设置贴图和UV
tempRenderer.material.mainTexture = skinnedMeshAtlas;
tempRenderer.sharedMesh.uv = atlasUVs; // 销毁所有部件
foreach (GameObject goTemp in targetParts)
{
if (goTemp)
{
Destroy(goTemp);
}
} Debug.Log("合并耗时 : " + (Time.realtimeSinceStartup - startTime) * + " ms");
} /// <summary>
/// 获取最接近输入值的2的N次方的数,最大不会超过1024,例如输入320会得到512
/// </summary>
private int get2Pow(int into)
{
int outo = ;
for (int i = ; i < ; i++)
{
outo *= ;
if (outo > into)
{
break;
}
} return outo;
}
}

三、效果展示

在执行上面的代码前,角色的每个部分都是单独的,并且是激活的状态。

执行后,角色所有的部位都会删除,因为它们的网格都合并到了Player_Girl这个角色根物体上。

模型就不放出来了~

Unity3D换装系统的更多相关文章

  1. Unity 3D换装系统教程/Demo

    Unity3D换装系统教程 本文提供全流程,中文翻译.Chinar坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) 1 Costume Change ...

  2. 【Unity3D】Unity3D SkinnedMeshRenderer换装系统

    转载请注明出处:http://www.cnblogs.com/shamoyuu/p/6505561.html 一.换装原理 游戏角色换装分为以下几步: 1.替换蒙皮网格 2.刷新骨骼 3.替换材质 上 ...

  3. 【Unity3D】Unity3D开发《我的世界》之一、创建一个面

    转载请注明出处:http://www.cnblogs.com/shamoyuu/p/unity_minecraft_01.html 最近总有人问及我的游戏里跟<我的世界>一样的地形是如何实 ...

  4. Unity3d学习 预设体(prefab)的一些理解

    之前一直在想如果要在Unity3d上创建很多个具有相同结构的对象,是如何做的,后来查了相关资料发现预设体可以解决这个问题! 预设体的概念: 组件的集合体 , 预制物体可以实例化成游戏对象. 创建预设体 ...

  5. Unity3d入门 - 关于unity工具的熟悉

    上周由于工作内容较多,花在unity上学习的时间不多,但总归还是学习了一些东西,内容如下: .1 根据相关的教程在mac上安装了unity. .2 学习了unity的主要的工具分布和对应工具的相关的功 ...

  6. TDD在Unity3D游戏项目开发中的实践

    0x00 前言 关于TDD测试驱动开发的文章已经有很多了,但是在游戏开发尤其是使用Unity3D开发游戏时,却听不到特别多关于TDD的声音.那么本文就来简单聊一聊TDD如何在U3D项目中使用以及如何使 ...

  7. warensoft unity3d 更新说明

    warensoft unity3d 组件的Alpha版本已经发布了将近一年,很多网友发送了改进的Email,感谢大家的支持. Warensoft Unity3D组件将继续更新,将改进的功能如下: 1. ...

  8. Unity3D框架插件uFrame实践记录(一)

    1.概览 uFrame是提供给Unity3D开发者使用的一个框架插件,它本身模仿了MVVM这种架构模式(事实上并不包含Model部分,且多出了Controller部分).因为用于Unity3D,所以它 ...

  9. Unity3D 5.3 新版AssetBundle使用方案及策略

    1.概览 Unity3D 5.0版本之后的AssetBundle机制和之前的4.x版本已经发生了很大的变化,一些曾经常用的流程已经不再使用,甚至一些老的API已经被新的API所取代. 因此,本文的主要 ...

随机推荐

  1. HDU-2031-进制转换

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=2031 进制转换 Time Limit: 2000/1000 MS (Java/Others)    M ...

  2. ora-04031

    诊断并解决ORA-04031 错误 当我们在共享池中试图分配大片的连续内存失败的时候,Oracle首先清除池中当前没使用的所有对象,使空闲内存块合并.如果仍然没有足够大单个的大块内存满足请求,就会产生 ...

  3. Windows 7 下 PHP 开发环境搭建(手动)

    Windows 7 下 PHP 开发环境搭建 1.说明 做开发的都知道一句话,就是“站在巨人的肩膀上”.确实现在打开浏览器随便一搜很多一键安装PHP环境的软件,比如wamp.xampp.AppServ ...

  4. Google Analytics之增强型电子商务报告

    虽然Google Analytics很多年以前就提供了电子商务报告的功能,但对于电子商务网站来说,这个报告缺失的东西还太多.而Google Analytics即将推出的增强型电子商务报告有望弥补这一短 ...

  5. java二维码生成-谷歌(Google.zxing)开源二维码生成学习及实例

    java二维码生成-谷歌(Google.zxing)开源二维码生成的实例及介绍   我们使用比特矩阵(位矩阵)的QR码编码在缓冲图片上画出二维码 实例有以下一个传入参数 OutputStream ou ...

  6. 《微信小程序七日谈》- 第六天:小程序devtool隐藏的秘密

    <微信小程序七日谈>系列文章: 第一天:人生若只如初见: 第二天:你可能要抛弃原来的响应式开发思维: 第三天:玩转Page组件的生命周期: 第四天:页面路径最多五层?导航可以这么玩: 第五 ...

  7. 在GEM5模拟器运行时,对Kill命令的使用

    在Linux下开发执行GEM5程序时,需要先启动GEM5,然后使用telnet对GEM5进行连接,才能看到串口信息.因为操作步骤多,所以写了脚本用来运行GEM5和Telnet程序,并且对两个程序进行监 ...

  8. Unity3D中如何计算场景中的三角面和顶点数

    在做游戏开发时,场景中的三角面和顶点数影响着运行效率,尤其是在手机平台上,实时的知道场景中的各项指标,对性能优化来说至关重要,下面我们来实现一个小功能,来实时计算场景中的三角面和顶点数: 如果要知道场 ...

  9. linux - tar命令简单使用

    tar 新建一个tar文档 touch file1 touch file2 mkdir dir1 touch dir1/file3 # 普通tar文档 tar -cf tar-file.tar fil ...

  10. [No0000B7]If else 与 三元表达式? : 效率对比

    先看 if else 一段代码 using System; class Program { private static void Main() { ; ) i = -; ; Console.Writ ...