转载请注明出处: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. osgearth earth文件规范-符号参考

    osgearth earth文件规范-符号参考 osgEarth用样式表渲染要素和注记. 本文档列出了可在样式表中使用的所有符号属性.不是每个符号是适用于每种情况:这只是一个主列表. 跳转到符号: • ...

  2. 关于异常“The 'Microsoft.ACE.OLEDB.12.0' provider is not registered on the local machine”的处理

    我们在利用SqlBlukcopy技术倒2010 或者2007格式的文件到SqlServer 数据库的时候,会发生如下错误: 原因如下: 1.在用SQL SERVER 2005访问.xlsx文件(off ...

  3. iOS开发中@property的属性weak nonatomic strong readonly等介绍

    @property与@synthesize是成对出现的,可以自动生成某个类成员变量的存取方法.在Xcode4.5以及以后的版本,@synthesize可以省略. 1.atomic与nonatomica ...

  4. 如何在sublime中安装使用eslint

    1:首先你需要全局安装eslint npm install -g eslint 安装完成后在控制台 输入 eslint -v 有版本号说明就可以在npm中使用了,可以检查语法的错误处,但还不能在sub ...

  5. java精度计算代码,指定精确小数位

    java代码: public class App2 { public static void main(String[] args) { String val = checkNumber(" ...

  6. Java IO流学习总结三:缓冲流-BufferedInputStream、BufferedOutputStream

    Java IO流学习总结三:缓冲流-BufferedInputStream.BufferedOutputStream 转载请标明出处:http://blog.csdn.net/zhaoyanjun6/ ...

  7. react native 运行项目下载gradle慢的解决办法

    react-native run-android 慢 React-native run-Android中需要下载https://services.gradle.org/distributions/gr ...

  8. bppm与AD域集成

    1. 使用admin登录BMC ProactiveNet Operations Console,点击选项-> 集成编辑 2. 勾选LDAP集成方式,配置相关信息,点击应用 3. 查看配置文件%B ...

  9. matlab 子函数的使用

    本文参考了该篇博客:http://www.cnblogs.com/MarshallL/p/4048846.html 对其进行学习,为我所用吧. 一. 在matlab的函数定义中,如果函数如果函数较长或 ...

  10. matlab for循环应用(阶乘及the day of year)

    一.N的阶乘 %脚本文件:test.m %N的阶乘 使用举例 % 定义变量 % ii ---循环变量,也就是循环次数 % N ---N的阶乘 % N_factorial --计算N的阶乘 clc;cl ...