[Unity实战]详解换装系统(三)

在阅读本文章之前,本人强烈建议你先看看本系列的前两篇文章,对换装系统有一些了解后再继续!

在上一篇文章中,运行之后是这样的:

我们的target上挂上各种类型的mesh,而每一个mesh上都有一个Skinned Mesh Renderer组件,这无疑会增加运算量,根据官方demo的指引,我们应该合并mesh,这样target上就只有一个Skinned Mesh Renderer组件,从而达到优化的目的!

本人对上一篇文章的代码进行了一些修改,主要是添加了6处新的代码,并对不需要的代码进行了注释(不删除),方便了理解。

其实跟上一篇文章的代码差别不太大。上一篇文章是针对target下的单个部位更换mesh,绑定骨架。而这片文章是先把target需要的所有部件的mesh、材质、骨架全部存放好,再一次性的赋给target下唯一的Skinned Mesh Renderer。

代码如下:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class AvatarSys2 : MonoBehaviour{

    public static AvatarSys2 instance;

    //来源模型
    private Transform source;
    //目标骨架
    private Transform target;

    //存放来源模型的信息,分别为部位名字,部位编号,部位的SkinnedMeshRenderer组件
    //根据部位名字,部位编号取得想要的部位
    private Dictionary<string, Dictionary<string, SkinnedMeshRenderer>> sourceData =
        new Dictionary<string, Dictionary<string, SkinnedMeshRenderer>>();

    //new 01
    SkinnedMeshRenderer targetSmr;//target上只有一个SkinnedMeshRenderer组件
    List<CombineInstance> combineInstances = new List<CombineInstance>();//存放target上的所有mesh
    List<Material> materials = new List<Material>();//存放target上的所有material
    List<Transform> bones = new List<Transform>();//存放target上的所有bone

    ////存放目标骨架的信息,分别为部位名字,部位上的SkinnedMeshRenderer组件
    ////没有编号是因为target上只有一套
    //private Dictionary<string, SkinnedMeshRenderer> targetSmr =
    //    new Dictionary<string, SkinnedMeshRenderer>();

    //存放目标骨架的骨架信息,用于mesh绑定骨架
    private Transform[] hips;

    //部位信息,目标骨架初始化为模型时需要的部位,注意命名是依据来源模型上的部位命名的
    public string[,] avatarStr = new string[,]{{"face","1"},{"hair","1"},
    {"pants","1"},{"shoes","1"},{"top","1"}};

    // Use this for initialization
    void Start()
    {
        instance = this;
        InstantiateSource();//实例化来源模型
        InstantiateTarget();//实例化目标骨架
        SaveData();//存储来源模型和目标骨架的信息
        InitAvatar();//将目标骨架初始化为模型

        target.animation.PlayQueued("walk");
    }

    void InstantiateSource()
    {
        GameObject sourceObj = Instantiate(Resources.Load("Source")) as GameObject;
        source = sourceObj.transform;
        sourceObj.SetActive(false);
    }

    void InstantiateTarget()
    {
        GameObject targetObj = Instantiate(Resources.Load("Target")) as GameObject;
        target = targetObj.transform;
        hips = target.GetComponentsInChildren<Transform>();
        //new 02
        targetSmr = target.gameObject.AddComponent<SkinnedMeshRenderer>();
    }

    void SaveData()
    {
        if ((source == null) || (target == null))
            return;

        SkinnedMeshRenderer[] parts = source.GetComponentsInChildren<SkinnedMeshRenderer>(true);//true表示把隐藏部位的组件也获得
        foreach (SkinnedMeshRenderer part in parts)
        {
            string[] partName = part.name.Split('-');
            if (!sourceData.ContainsKey(partName[0]))//每有一种新类型的部位
            {
                ////每有一种新类型的部位就在骨架下生成一个空GameObject
                //GameObject partobj = new GameObject();
                //partobj.name = partName[0];
                //partobj.transform.parent = target;

                //部位类型只记录一次
                sourceData.Add(partName[0], new Dictionary<string, SkinnedMeshRenderer>());

                ////targetSmr只记录一套部位的信息,例如裤子有两套模型,只记录一套的
                ////因为target是用来展示的,各种部位只要一套即可
                //targetSmr.Add(partName[0], partobj.AddComponent<SkinnedMeshRenderer>());
            }
            //sourceData记录所有部位信息
            sourceData[partName[0]].Add(partName[1], part);
        }
    }

    public void ChangeMesh(string part, string num)//传入部位名字,编号
    {
        //因为sourceData记录所有部位信息,所以可以查找到所要的新的部位
        SkinnedMeshRenderer smr = sourceData[part][num];

        //List<Transform> bones = new List<Transform>();
        //根据新的部位绑定的骨架信息,在target的骨架上找到相应的位置
        //例如头发mesh,在source中它绑定在脑袋上,那么我们在target上也要找到脑袋这个位置
        foreach (Transform bone in smr.bones)
        {
            foreach (Transform hip in hips)
            {
                if (hip.name == bone.name)
                {
                    bones.Add(hip);
                    break;
                }
            }
        }

        //targetSmr[part].materials = smr.materials;//更换材质
        //targetSmr[part].sharedMesh = smr.sharedMesh;//更换mesh
        //targetSmr[part].bones = bones.ToArray();//将mesh绑定到合适的骨架中

        //new 03
        CombineInstance ci = new CombineInstance();
        ci.mesh = smr.sharedMesh;
        combineInstances.Add(ci);
        materials.AddRange(smr.materials);
    }

    //new 04
    public void InitAvatar()
    {
        Empty();
        int length = avatarStr.GetLength(0);
        for (int i = 0; i < length; i++)
        {
            ChangeMesh(avatarStr[i, 0], avatarStr[i, 1]);
        }
        Combine();
    }

    //new 05
    void Empty()
    {
        combineInstances.Clear();
        materials.Clear();
        bones.Clear();
    }

    //new 06
    void Combine()
    {
        targetSmr.sharedMesh = new Mesh();
        targetSmr.sharedMesh.CombineMeshes(combineInstances.ToArray(),false,false);
        targetSmr.materials = materials.ToArray();
        targetSmr.bones = bones.ToArray();
    }
}

运行后:

怎么样?很酷吧!

时间: 2024-08-03 15:38:28

[Unity实战]详解换装系统(三)的相关文章

[Unity实战]详解换装系统(四)

关于换装系统基本上就是前三篇文章所说的那样了,这里说一下一些琐碎的.. 1.在网游中,推荐将各个部位打包成assetbundle,比如一个男性的角色,将他的基础骨骼打进一个包,再将身体各个部分的模型分别打包.如果一个模型由头.脸.身体.手臂.脚五个部分组成,那么打包后将会有六个资源包,分别是基础骨骼.头.脸.身体.手臂.脚. 2.合并mesh要注意的地方: 来源模型与材质数量必须相对应,否则模型的贴图将会变得不正常,也就是说如果裤子的 material 有两个,其他部位的 materail只有一

《iOS 7 应用开发实战详解》

<iOS 7 应用开发实战详解> 基本信息 作者: 朱元波    管蕾 出版社:人民邮电出版社 ISBN:9787115343697 上架时间:2014-4-25 出版日期:2014 年5月 开本:16开 页码:382 版次:1-1 所属分类:计算机 > 软件与程序设计 > 移动开发 > iPhone 更多关于>>><iOS 7 应用开发实战详解> 编辑推荐 新版本 全面讲解了iOS 7开发的各种技术 热门技术 基本控件.数据存储.多场景处理.界

机器学习Spark Mllib算法源码及实战详解进阶与提高视频教程

38套大数据,云计算,架构,数据分析师,Hadoop,Spark,Storm,Kafka,人工智能,机器学习,深度学习,项目实战视频教程 视频课程包含: 38套大数据和人工智能精品高级课包含:大数据,云计算,架构,数据挖掘实战,实时推荐系统实战,电视收视率项目实战,实时流统计项目实战,离线电商分析项目实战,Spark大型项目实战用户分析,智能客户系统项目实战,Linux基础,Hadoop,Spark,Storm,Docker,Mapreduce,Kafka,Flume,OpenStack,Hiv

Android高效率编码-第三方SDK详解系列(三)——JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送

Android高效率编码-第三方SDK详解系列(三)--JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送 很久没有更新第三方SDK这个系列了,所以更新一下这几天工作中使用到的推送,写这个系列真的很要命,你要去把他们的API文档大致的翻阅一遍,而且各种功能都实现一遍,解决各种bug各种坑,不得不说,极光推送真坑,大家使用还是要慎重,我们看一下极光推送的官网 https://www.jpush.cn/common/ 推送比较使用,很多软件有需要,所以在这个点拿出来多讲讲,我们本节

Scala 深入浅出实战经典 第78讲:Type与Class实战详解

王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载: 百度云盘:http://pan.baidu.com/s/1c0noOt6 腾讯微云:http://url.cn/TnGbdC 360云盘:http://yunpan.cn/cQ4c2UALDjSKy 访问密码 45e2土豆:http://www.tudou.com/programs/view/2vZ06RMcD6I/优酷:http://v.youku.com/v_show/id

Scala 深入浅出实战经典 第53讲:Scala中结构类型实战详解

王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 腾讯微云:http://url.cn/TnGbdC 360云盘:http://yunpan.cn/cQ4c2UALDjSKy 访问密码 45e2土豆:http://www.tudou.com/programs/view/pR_4sY0cJLs/优酷:http://v.youku.com/v_show/id_

logback logback.xml常用配置详解(三) &lt;filter&gt;

转自:logback logback.xml常用配置详解(三) <filter> logback 常用配置详解(三) <filter> <filter>: 过滤器,执行一个过滤器会有返回个枚举值,即DENY,NEUTRAL,ACCEPT其中之一.返回DENY,日志将立即被抛弃不再经过其他过滤器:返回NEUTRAL,有序列表里的下个过滤器过接着处理日志:返回ACCEPT,日志会被立即处理,不再经过剩余过滤器. 过滤器被添加到<Appender> 中,为<

Dream------scala--类的属性和对象私有字段实战详解

Scala类的属性和对象私有字段实战详解 一.类的属性 scala类的属性跟java有比较大的不同,需要注意的是对象的私有(private)字段 1.私有字段:字段必须初始化(当然即使不是私有字段也要赋值) 2.属性默认是public级别的,而且无法用public修饰. 3.可以有很多类,并且默认是public级别(如果声明的时候加上会报错,不知为何) 4.如果属性是public的,会默认生成类属性的getter和setter方法,无需显示的提供getter,setter方法 5.私有字段(用p

第131讲:Hadoop集群管理工具均衡器Balancer 实战详解学习笔记

第131讲:Hadoop集群管理工具均衡器Balancer 实战详解学习笔记 为什么需要均衡器呢? 随着集群运行,具体hdfs各个数据存储节点上的block可能分布得越来越不均衡,会导致运行作业时降低mapreduce的本地性. 分布式计算中精髓性的一名话:数据不动代码动.降低本地性对性能的影响是致使的,而且不能充分利用集群的资源,因为导致任务计算会集中在部分datanode上,更易导致故障. balancer是hadoop的一个守护进程.会将block从忙的datanode移动到闲的datan