【Unity3D】Unity3D SkinnedMeshRenderer换装系统
转载请注明出处: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】Unity3D SkinnedMeshRenderer换装系统的更多相关文章
- [Unity3D]Unity3D持久性数据的游戏开发PlayerPrefs采用
大家好,我是秦培,欢迎关注我的博客,我的博客地址">blog.csdn.net/qinyuanpei. 博主今天研究了在Unity3D中的数据持久化问题.数据持久化在不论什么一个开发领 ...
- [Unity3D]Unity3D游戏开发之飞机大战项目解说
大家好,我是秦元培,欢迎大家继续关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei. 首先感谢大家对我博客的关注,今天我想和大家分享的是一个飞机大战的项目.这是一个比較综合的 ...
- [Unity3D]Unity3D圣骑士模仿游戏开发传仙灵达到当局岛
大家好,我是秦培.欢迎关注我的博客.我的博客地址blog.csdn.net/qinyuanpei. 在前面的文章中.我们分别实现了一个自己定义的角色控制器<[Unity3D]Unity3D游戏开 ...
- [Unity3D]Unity3D游戏开发3D选择场景中的对象,并显示轮廓效果强化版
大家好,我是秦培,欢迎关注我的博客,我的博客地址blog.csdn.net/qinyuanpei. 在上一篇文章中,我们通过自己定义着色器实现了一个简单的在3D游戏中选取.显示物体轮廓的实例. 在文章 ...
- [Unity3D]Unity3D游戏开发之在3D场景中选择物体并显示轮廓效果
大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei. 在<仙剑奇侠传>.<古剑奇谭>等游戏中,常常须要玩家在一个3D场景中 ...
- [Unity3D]Unity3D游戏开发之使用EasyTouch虚拟摇杆控制人物移动
大家好,欢迎大家关注我的博客,我是秦元培,我的博客地址是blog.csdn.net/qinyuanpei.今天呢,我们来一起学习在Unity3D中使用EasyTouch虚拟摇杆来控制人物移动.虽然Un ...
- [Unity3D]Unity3D游戏开发之自己主动寻路与Mecanim动画系统的结合
大家好,欢迎大家关注我的博客,我是秦元培,我的博客地址是blog.csdn.net/qinyuanpei. 这段时间博主将大部分的精力都放在了研究官方演示样例项目上,主要是希望能够从中挖掘出有价值的东 ...
- [Unity3D]Unity3D游戏开发之《愤慨的小鸟》弹弓实现
各位朋友,大家晚上好, 我是秦元培.欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei.今天我们来做一个高端大气上档次的东西. 我相信大家都玩过一款叫做<愤慨的 ...
- [Unity3D]Unity3D发展偷看游戏初期阶段NGUI
朋友,大家晚上好. 我是秦培.欢迎关注我的博客,我的博客地址blog.csdn.net/qinyuanpei.近期博主開始研究NGUI了,由于NGUI是Unity3D中最为流行的界面插件,所以不管从学 ...
随机推荐
- PHP:验证手机号码合法性
文章来源:http://www.cnblogs.com/hello-tl/p/7592333.html /** * [verifyPhone description] 效验手机号码合法性 * @par ...
- 集训第四周(高效算法设计)N题 (二分查找优化题)
原题:poj3061 题意:给你一个数s,再给出一个数组,要求你从中选出m个连续的数,m越小越好,且这m个数之和不小于s 这是一个二分查找优化题,那么区间是什么呢?当然是从1到数组长度了.比如数组长度 ...
- 设计方案--移动端延迟300ms的原因以及解决方案
一.前言 移动端浏览器提供一个特殊的功能:双击(double tap)缩放. 二.移动端延迟300ms的原因 为什么要用触摸事件?触摸事件是移动端浏览器特有的html5事件. 因为移动端的clic ...
- Mac os安装MySQL数据库,系统提示mysql: command not found该怎么办
当我们安装好MySQL后,在终端输入mysql命令,发现并不能看到自己安装的数据库,这是因为你没有配置环境变量. 在os系统中安装MySQL数据库默认保存在/usr/local/mysql 那么我们应 ...
- Spring之HelloWorld
[Spring是什么?] 1.Spring是一个开源框架. 2.Spring为简化企业级应用开发而生,使用Spring可以使简单的JavaBean实现以前只有EJB(EJB是sun的JavaEE服务器 ...
- 如何抓取崩溃的log日志
4.手机录屏工具的推荐 Andriod:录屏大师,易录屏等等. iOS:AirPlayer,iTools. 5.如何抓取崩溃的log日志? android闪退获取日志方法: 1.下载adb工具包 2. ...
- Leetcode 207.课程表
课程表 现在你总共有 n 门课需要选,记为 0 到 n-1. 在选修某些课程之前需要一些先修课程. 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1] 给定课程总 ...
- POJ1308/HDU1325/NYOJ129-Is It A Tree?,并查集!
Is It A Tree? Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 28838 Accepted: 9843 -& ...
- Mongo的Replica Sets (复制集)的配置全过程和心得体会
http://blog.csdn.net/bloggongchang/article/details/7272403 一.MongoDB Replica Sets(副本集)简单的说就是有自动故障恢复功 ...
- msp430入门学习00
在TI官网上找到MSP430的程序例程.数据手册.使用指南等文件.以MSP430F169为例,步骤如下: 1)进入ti官网:http://www.ti.com.cn/ 或者http://www.ti. ...