Unity3D换装系统
转载请注明出处: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换装系统的更多相关文章
- Unity 3D换装系统教程/Demo
Unity3D换装系统教程 本文提供全流程,中文翻译.Chinar坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) 1 Costume Change ...
- 【Unity3D】Unity3D SkinnedMeshRenderer换装系统
转载请注明出处:http://www.cnblogs.com/shamoyuu/p/6505561.html 一.换装原理 游戏角色换装分为以下几步: 1.替换蒙皮网格 2.刷新骨骼 3.替换材质 上 ...
- 【Unity3D】Unity3D开发《我的世界》之一、创建一个面
转载请注明出处:http://www.cnblogs.com/shamoyuu/p/unity_minecraft_01.html 最近总有人问及我的游戏里跟<我的世界>一样的地形是如何实 ...
- Unity3d学习 预设体(prefab)的一些理解
之前一直在想如果要在Unity3d上创建很多个具有相同结构的对象,是如何做的,后来查了相关资料发现预设体可以解决这个问题! 预设体的概念: 组件的集合体 , 预制物体可以实例化成游戏对象. 创建预设体 ...
- Unity3d入门 - 关于unity工具的熟悉
上周由于工作内容较多,花在unity上学习的时间不多,但总归还是学习了一些东西,内容如下: .1 根据相关的教程在mac上安装了unity. .2 学习了unity的主要的工具分布和对应工具的相关的功 ...
- TDD在Unity3D游戏项目开发中的实践
0x00 前言 关于TDD测试驱动开发的文章已经有很多了,但是在游戏开发尤其是使用Unity3D开发游戏时,却听不到特别多关于TDD的声音.那么本文就来简单聊一聊TDD如何在U3D项目中使用以及如何使 ...
- warensoft unity3d 更新说明
warensoft unity3d 组件的Alpha版本已经发布了将近一年,很多网友发送了改进的Email,感谢大家的支持. Warensoft Unity3D组件将继续更新,将改进的功能如下: 1. ...
- Unity3D框架插件uFrame实践记录(一)
1.概览 uFrame是提供给Unity3D开发者使用的一个框架插件,它本身模仿了MVVM这种架构模式(事实上并不包含Model部分,且多出了Controller部分).因为用于Unity3D,所以它 ...
- Unity3D 5.3 新版AssetBundle使用方案及策略
1.概览 Unity3D 5.0版本之后的AssetBundle机制和之前的4.x版本已经发生了很大的变化,一些曾经常用的流程已经不再使用,甚至一些老的API已经被新的API所取代. 因此,本文的主要 ...
随机推荐
- java_web学习(1)理解JavaBean
JavaBean简介 JavaBean是一种特殊的 Java 类,它遵从一定的设计模式,开发工具和其他组件可以根据这种模式来调用JavaBean. JavaBean可以设计得像Swing组 ...
- --@angularJS--指令与控制器之间较复杂的交互demo2
1.index.html: <!DOCTYPE HTML><html ng-app="app"><head> <title>c ...
- B-JUI 实践 之 带搜索与编辑的Datagrid
使用B-JUI 1.31版本 DataGrid 页直接上代码: <div class="bjui-pageHeader" style="background-col ...
- Postgresql standby(备机只读)环境搭建
下载PostgreSQL源码包,放在任意目录 设置/etc/sysctl.conf,增加以下内容 kernel.shmmni= 4096 kernel.sem =501000 6412800000 5 ...
- c#使用DotNetZip封装类操作zip文件(创建/读取/更新)实例
DotnetZip是一个开源类库,支持.NET的任何语言,可很方便的创建,读取,和更新zip文件.而且还可以使用在.NETCompact Framework中. 下载地址在这里:http://dot ...
- plsql developer日期时间格式设置
1 工具->首选项->日期/时间都使用windows格式: 2 在环境变量加入 nls_date_format=YYYY-MM-DD HH24:MI:SS nls_timestamp_fo ...
- request.setAttribute和request.getAttribute还有session.setAttribute和session.getAttribute还有request.getParameter和request.getAttribute区别和联系
1.session.setAttribute()和session.getAttribute()配对使用,作用域是整个会话期间,在所有的页面都使用这些数据的时候使用. 2.request.setAttr ...
- TCP&UDP
TCP(传输控制协议) 建立连接,形成传输数据的通道 在连接中进行大数据传输(数据大小不受限制) 通过三次握手完成连接,是可靠协议,安全送达(三次握手向服务器发送请求,响应请求回复,发送数据) 必须建 ...
- linux的Make使用的重定向
Linux中,脚本语言环境中,即你用make xxx即其他一些普通linux命令,比如ls,find等,不同的数字,代表不同的含义: 数字 含义 标准叫法 0 标准输入 stdin = ...
- Xcode自带iOS测试方法
在说Xcode自带测试方法前先讲下程序在内存中的空间划分, 一般可分为5个部分: #1. BSS段, 存放未初始化的全局变量. BSS是英文Block Started by Symbol的简称.BSS ...