unity 杂记

常用插件

EasyTouch
Dotween
FxMaker
T4M
CineMachine
LitJson
Behaviour Designer
MeshBaker
SimpleLOD
Asset Hunter
MarkLightUI / PowerUI / XmlLayout
Unity Asset Bundle Browser tool
AssetBundle Manager

性能优化

**UI卡顿问题:**
  分Canvas绘制; 动静分离;图集分包;
**DrawCall优化:**
**OverDraw优化:**
**内存优化:**
**GC优化:**
**场景优化:**1)离线合批:只能合批静态不动的物体
	(2)在线合批:可能会导致卡顿,
	使用脚本合并Mesh
	(3)骨骼蒙皮的角色如何进行合批: GPU合批, CPU合批
	插件: Unity-Technologies/ Animation-Instancing 动画合批插件
	AssetBundleBrower 
    (4)LOD: LODGroup 
    (5)遮挡剔除:Occulder Static ---Bake--- Main Camera  ; 游戏物体改变需要重新Bake
    (6)光照贴图:要烘焙的物体 LightMapStatic, 要烘培的光照Bake模式, Bake即可
    (7)合并贴图: 


//UI性能优化
(内存优化)前期不给美术资源制定一个规范,后期做优化会非常的被动
     UI资源的规范:
     图集最大 1024
     同一个界面的UI资源,尽量放到同一个图集,重复利用的公共资源放到Common
     能用九宫格的尽量用九宫格
     尺寸较大的原图,按照比例缩小到500之内
     比较长条的UI原图,可以拆分成2张图
     图集利用率低于1/3, 可以和其他同一个size的图集进行合并
     尽量复用UI资源,减少不必要的原图
     关闭Mipmap
GPU优化:
     关闭雾效
     不渲染场景
     简化shader
     fragement 剔除alpha为0的像素
OverDraw优化:像素重复绘制---半透明物体
    减少UI的重叠
    取消fillCent可以减少中间一些片的绘制
    看不见的元素且没有逻辑功能,要Disable或者挪出裁剪区域,不要设置alpha=0来隐藏
    不要使用alpha=0的image来实现放大响应区域的功能
    打开全屏UI的时候,把下面看不见的UI挪出裁剪区域,关闭主相机渲染
DrawCall优化:合批,了解动态合批机制
    合理分配图集, 不在同一图集的,会打断合批
    相邻节点尽量放在同一图集
    不同字体的图片也是来自不同的图集
    Mask 会打断合批, 使用Rect2Dmask 
    真实的三角面的位置是否有重叠,线框模式
Canvas.SendWillRendCanvas
    顶点重建和布局重建
    simple 模式 4个顶点 
    sliced 模式  36  32 
    Tiled 4N 
    Text 重灾区  4个顶点 ,shadow 8个顶点 , outline 20个
    pixelPerfect, 像素对其操作, 布局重建
Canvas.BuildBatch
    Canvas下的所有顶点会生成一个大的Mesh,根据材质纹理,渲染顺序生成subMesh, 一个subMesh就是一个绘制批次
    当一个顶点发生变化,会重复上面的过程

    顶点数量多
    顶点数量发生变化----动静分离

其他优化
    动态加载页签,减少打开UI时的卡顿
    重复使用的item, 使用对象池
    UI布局不变的时候,不要使用LayOut
    关闭射线检测
    Text的BestFit
    频繁显示和隐藏的UI,不要SetActive, 可以挪出裁剪区


//GPU相关
  1. 填充率,图形单元每秒渲染的像素数量
  2. 像素的复杂度, 比如阴影,光照,复杂的shader
  3. 集合体的复杂度
  4. GPU的现存带宽
  5. shader 避免过多的逐像素计算
   --顶点数量过多,像素计算过于复杂, 减少顶点数量,简化计算的复杂度; 压缩图片,以适应缓存
   保持材质的数目尽可能的少, 这的使得unity更加容易进行批处理
   优化几何体,使用LOD,离得远,看不清的小细节可以忽略
   使用纹理图集代替一系列单独的小贴图
   如果使用了纹理图集和共享材质,使用Render.ShaderdMaterial 来代替 Renderer.material 
   使用lightmap 代替 实时光
   使用移动版的shader
   控制绘制顺序,最大限度的避免overDraw 

1. Unity如何组织场景中的对象

游戏对象的创建,更新和销毁
组件的容器
unity脚本执行顺序
MonoBehaviour组件消息 


class Mono{
	public virtual void Awake(){ }
	public virtual void Start(){ }
	public virtual void OnEnable(){}
	public virtual void Update(){}
}


class GameObject
{
	list<Mono> components=new list<Mono>;

	public void AddComponent(Mono m){
		if(m is Mono==false){
			throw new InvalidOperationException();
		}
		this.components.Add(m);
		m.Awake();
		m.OnEnable();
	}

	public GameObject(params Mono[] coms){
		foreach(var item in coms){
			AddComponent(item);
		}
	}

	public GameObject(params Type[] types){
		foreach(var t in types){
			if(t.IsSubclassof(c:typeof(Mono))==false){
				throw new InvalidOperationException();
			}

			Mono c=(Mono)Activator.CreateInstance(t);
			AddComponent(c);
		}
	}
}


class App
{
	static void Main(string[] args){
		var go=new GameObject(typeof(A),typeof(B));
		Console.Read();
	}
}
1. 面向对象3大特性
2. 值类型和引用类型
3. 装箱和拆箱
4. foreachfor
	foreach 迭代遍历Item是只读的,不能对其修改
5. Foreach遍历原理
6. string, stringBuilder, stringBuffer
   String, 字符串常量
   StringBuffer和StringBuilder, 只开辟一段内存
   StringBUffer 线程安全
   StringBuilder 非线程安全,性能略好

7. ls = new List() , 性能优化
   每增加一个内容就会增加内存, 指定长度

8. obj.CompareTag("Tag") 与 obj.tag=="Tag" 
9. 射线检测:SphereColliderNoAlloc 可以避免产生GC
10.GC产生的原因,如何避免
   GC垃圾回收,定期回收没有有效引用的对象
   (1) 减少New
   (2) 字符串比较和字符串拼接
   (3) List  new 的时候规定内存大小
   (4) 
   射线检测避免使用GC的方式
   (5) Foreach
   (6) 使用枚举取代字符串遍历
   (7) 静态遍历
11. 接口和抽象类
    单继承和多继承
    约束类, 约束行为
12. 反射的实现原理
    运行时,动态获取类型信息,动态创建对象,访问成员
    System.Reflection.Assembly.Load("XXX.dll") --加载程序集
    System.Type.GetType("XXX类名")  --动态获取类型信息
    obj.GetType()  --获取实例的类型
    System.Activator.CreateInstance(Type)  --动态创建实例
    System.Reflection.MethodInfo  method=type.GetMethod("方法名")
    System.Reflection.MethodInfo.Invoke(obj,new obj[]{})  --调用的类实例和实例参数

13. Net 与 Mono 

14. 静态构造函数无需添加修饰符,无参数
15.(a,b)=>{}
16. 常用容器类
    Stack, Queue, Array, ArrayList, List, LinkList, HashTable, Dic, HashSet
    Array 数组: 需要声明长度,不安全
    ArrayList  动态数组
    List  泛型动态数组
    LinkedList  双向链表
    HashTable 
17. 委托和接口的区别
    委托时约束方法的集合
    接口是约束类具备的功能集合
18. unsafe关键字:和Fixed一起使用,指针操作
19. refout : 
    ref 有进有出
    out 带返回值之前必须明确赋值
    引用参数和输出参数 不会创建新的存储位置
20. GameObject a=new GameObject()
    GameObject b=a 
    删除b, 删除的其实是栈中的地址
    a 仍然存在
21. 引用和指针的区别
    引用是某一块内存的别名

22. using(分配资源){
	try{使用资源}
	finally{Resource.Dispose}
}

23. Dic 的内部实现原理

24. 泛型: 是类的模板
    泛型不会对值类型进行装箱和拆箱操作,也不会对引用类型进行向下的强制转换

25. 线程和协程的区别
    协程,同一时间只能执行某阁协程,协程适合对任务进行分时处理    
    线程, 同一时间可以执行多个线程,线程适合多任务同时处理

26. 进程{
		主线程{
			执行任务{
				创建协程---运行协程
				开辟子线程
			}
		}
		子线程
		子线程
	}

27. unity 是否支持多线程程序 ? 

28. unity 如何获知场景中需要加载的数据,动态加载资源
    
29. Collider 和 trigger 的区别

30. 几种施加力的方式:
    Rigibody.AddForce   Rigibody.AddForceAtPosition
    2D Constant Force 
    Force / Relative  Force/Torque
31. 旋转函数
    transfrom.Rotate
    transform.RotateAround

32. 物理更新: FixedUpdate
33. 相机跟随: LateUpdate

34. 反向旋转动画: animation.speed=-1
35. 动画淡入淡出: animation.CrossFade
36. Mathf.Round 四舍五入 ; Mathf.Clamp  左右限值;  Mathf.Lerp 插值
37. 愤怒的小鸟做抛物线轨迹:
    transform.Translate(v-new v3(0,mgt,ft))
38. 去掉敏感字,字符串替换
39. MipMaps  内存增加 1/3
40. UnityAction 和 UnityFunc  都是委托
    Action 不带返回值
    Func 带返回值
41. 性能优化:
    Lod 
    MipMap 

42. 设计模式
    事件--观察者模式
    提取通用接口----外观模式
    管理类-----单例模式
    状态切换----状态模式, 有限状态机
    创建实体 ----实体工厂
    系统之间互相依赖-----中介者模式

43. 对扩展开放,对修改关闭;  子类可以替换基类; 依赖于抽象; 小接口;
    一个实体尽量少与其他类直接相互作用
    多用组合,少用继承

44. Avator 是一种骨架映射关系,方便动画的重定向

45. 使用过那些插件
    shaderGraph  光影效果
    cineMachine+timeLine+postProcessingStack  -- 制作过场动画
    nodeCanvas  制作怪物AI
    EasyTouch 
    unity Record  录制 
46. MeshFilter, MeshRender, SkinnedMeshRender
    MeshFilter, 获取网格模型
    MeshRender, 网格渲染器,渲染材质,光照,探针
    SkinnedMEshRender  渲染人物模型,基本属性, 材质, 光照,探针
    Unity 换装:切换Mesh, RootBone 和 材质贴图

47. SkinnedMesh 实现原理
    分为骨骼和蒙皮
    骨骼是一个层次结构,存储了骨骼的Transform信息
    蒙皮是Mesh顶点附着在骨骼之上,顶点可以被多个骨骼影响,决定了其权重
    将顶点从Mesh空间变换都骨骼空间

48. 安全的迁移asset数据
    导出包, .meta 文件

49. MeshCollider 与 Collider
    MeshCollider 是基于顶点数据的,消耗性能
    BoxCollider 是基于算法的,性能好

50. 细小的高速物体穿过较大的物体,会出现什么情况

51. Material 与 Sharedmaterial 
    共享材质,修改共享材质改变所有使用改材质的物体
    material 只改变该物体的材质

52. 链条关节: 模拟两个物体之间有一个链条连接, 弹簧效果, 门的效果

53. 动画层

54. 物理材质: 摩擦力, 反弹等

55. NavMesh 

56. 动态批处理和静态批处理
    静态批处理将静态的物体合成大的Mesh, 前提是共享材质, 不旋转,不缩放
    动态批处理将 碎片Mesh组合到一起,一次绘制, 共享材质,不超过900顶点

57. 向量的点乘和叉乘

58. 时间复杂度

59. 数字反转: 取模累乘  

60. 洗牌算法: 

61. 二分查找: 根据条件来判断是左查还是 右查

62. Lua 的GC原理是什么,如何避免GC内存

63. 简述Lua面向对象的原理
	1. 表tabel就是一个对象
	2. self 表示该接受者是对象本省, : 可以隐藏该self 参数
	3. 每个对象都有一个原型,原型可以组织多个对象间的行为
	4. setmetatable(a,{__index=B}) 把B设置为A的原型
	5. 继承
	6. 一个函数  funcion 用作——Index方法,实现多继承

64. 同步的细节处理

65. Buff影响,数值回滚

66. 复杂动画转换过度,融合底层逻辑

67. 帧同步,如何侦测不同步

58. 发射子弹的状态同步

69. 状态同步的缺点和优点 

70. 技能系统架构

71. MMO项目的背包系统是如何实现的

72. MMO项目的道具系统的道具是如何实现的

73. MMO项目,资源管理是如何实现的

74. 二叉树

75. 如何使Camera 只观察指定对象

76. 堆栈实现队列

77. 单链表,输出倒数第二个,奇数个节点输出

78. lua 可以做那些优化

79. 状态同步网络卡顿如何解决

80. 背包系统性能优化方案

81. mipmap的作用

82. 判断两个平面是否相交


-----面向对象
83. 将一个table的——index 方法设置为另一个table,后者的方法就被前者继承
    元表就像一个备用的查找表

    父类: class.__index=class

    setmetatable(tempobj,class)

    setmetatable(subclass,class)

    
----捏脸系统的原理



--()=>{} 表达式中的局部变量是按引用来传递的
--[[public class TestLamda : MonoBehaviour
{
    public Button[] buttons;

    void Start()
    {
        for (int i = 0; i < buttons.Length; ++i)
        {
            int index = i;  --重新定义一个局部变量 index 保存住当前的 i
            buttons[i].onClick.AddListener(() => { Debug.Log(index); });
        }
    }
}--]]


84. 文本打字机 效果怎么实现, 使用协程, 每输出一个字符,等待一定的时间

85. 图文混排怎么实现: 使用占位符,修改shader 

86. UI裁剪粒子特效怎么实现: 修改shader, 设置粒子的四个点的世界坐标区域,在区域之外,像素的透明度为0 

87. 文本的外发光效果怎么实现:

88. UI显示模型的方式: 2种, 直接显示模型,使用RT纹理

89. 角色的外发光效果怎么实现: 使用菲尼尔反射,修改shader

90. 不播放某一类音效: 音效设置标签值,停止某一类标签的音效

91. UI的流光效果怎么实现:

92. UI置灰怎么实现: RGB 分量相等, 更滑UI材质

93. 消融效果怎么实现: 噪音贴图 +  透明度裁剪

94. 河流效果怎么实现: 噪音贴图 + UV 动画

95. 用shader 写调色板


资源加载:
Resource.Load()
AssetDataBase.LoadAssetPath()

--打包:
BuildAssetBundles

--资源分组:
共享的UI资源打一个包 
每个界面资源打一个包
材质打一个包
音效资源打一个包
模型资源打一个包
shader 打一个包

--增量更新
--加快资源读取速度

--生成AB包的基本流程:
--把所有需要打包的资源放到某一个目录下面
--合理的划分目录
--给需要打包的资源批量设置包名
--保存 包和资源的映射关系

--profile-- CPU 
{
	WaitForTargetFPS  --当前帧的CPU 等待时间 
	OverHead  --尚不明确的,时间消耗
	PhySics.Simulate    --物理模拟的CPU时间占用
	Monobehaviour.OnMouse  --检测鼠标的输入消息接受和反馈
	GUI.Repaint  --GUI重绘 
	Event.Internal_MakeMasterEventCurrent  --GUI 消息传送
	Cleanup Unused Cached Data  --
	Application.Integrate Asset in Background --遍历预加载线程队列
	Application.LoadLevelAsync  Integrate  --加载场景的Cpu占用
	
}


//lua内存优化
-- string.gsub和string.gmatch 会产生大量的字串
--luaProfile
--字符串拼接 
--对象缓存
--定时器和循坏

//音效管理器的实现
--AudioManager  管理 AudioSource
    --当我们需要播放声音是,需要获取一个空的AudioSource 
    --如果生成大量的audioSource,会降低性能,使用对象池回收audioSource
   

--ClipManager 管理 Clip 
    --根据路径加载音效
    --缓存音效

--SourceManager  总管理器
  --声音的播放,暂停, 停止, 静音
  --停止不同类型的声音,需要区分,封装AudioSource, 赋予tag 属性

//UI性能优化
(内存优化)前期不给美术资源制定一个规范,后期做优化会非常的被动
     UI资源的规范:
     图集最大 1024
     同一个界面的UI资源,尽量放到同一个图集,重复利用的公共资源放到Common
     能用九宫格的尽量用九宫格
     尺寸较大的原图,按照比例缩小到500之内
     比较长条的UI原图,可以拆分成2张图
     图集利用率低于1/3, 可以和其他同一个size的图集进行合并
     尽量复用UI资源,减少不必要的原图
     关闭Mipmap
GPU优化:
     关闭雾效
     不渲染场景
     简化shader
     fragement 剔除alpha为0的像素
OverDraw优化:像素重复绘制---半透明物体
    减少UI的重叠
    取消fillCent可以减少中间一些片的绘制
    看不见的元素且没有逻辑功能,要Disable或者挪出裁剪区域,不要设置alpha=0来隐藏
    不要使用alpha=0的image来实现放大响应区域的功能
    打开全屏UI的时候,把下面看不见的UI挪出裁剪区域,关闭主相机渲染
DrawCall优化:合批,了解动态合批机制
    合理分配图集, 不在同一图集的,会打断合批
    相邻节点尽量放在同一图集
    不同字体的图片也是来自不同的图集
    Mask 会打断合批, 使用Rect2Dmask 
    真实的三角面的位置是否有重叠,线框模式
Canvas.SendWillRendCanvas
    顶点重建和布局重建
    simple 模式 4个顶点 
    sliced 模式  36  32 
    Tiled 4N 
    Text 重灾区  4个顶点 ,shadow 8个顶点 , outline 20个
    pixelPerfect, 像素对其操作, 布局重建
Canvas.BuildBatch
    Canvas下的所有顶点会生成一个大的Mesh,根据材质纹理,渲染顺序生成subMesh, 一个subMesh就是一个绘制批次
    当一个顶点发生变化,会重复上面的过程

    顶点数量多
    顶点数量发生变化----动静分离

其他优化
    动态加载页签,减少打开UI时的卡顿
    重复使用的item, 使用对象池
    UI布局不变的时候,不要使用LayOut
    关闭射线检测
    Text的BestFit
    频繁显示和隐藏的UI,不要SetActive, 可以挪出裁剪区


//GPU相关
  1. 填充率,图形单元每秒渲染的像素数量
  2. 像素的复杂度, 比如阴影,光照,复杂的shader
  3. 集合体的复杂度
  4. GPU的现存带宽
  5. shader 避免过多的逐像素计算
   --顶点数量过多,像素计算过于复杂, 减少顶点数量,简化计算的复杂度; 压缩图片,以适应缓存
   保持材质的数目尽可能的少, 这的使得unity更加容易进行批处理
   优化几何体,使用LOD,离得远,看不清的小细节可以忽略
   使用纹理图集代替一系列单独的小贴图
   如果使用了纹理图集和共享材质,使用Render.ShaderdMaterial 来代替 Renderer.material 
   使用lightmap 代替 实时光
   使用移动版的shader
   控制绘制顺序,最大限度的避免overDraw 

// 修改视距
// 修改画质


   






//UI加载模型




//移动端的shader优化
--颜色和光照一般可以用 fixed 类型, 不用 float 
--贴图共享UV的情况下,可以只使用一套 UV 
--减少光照的数量, 是否支持多光源,延迟渲染
--只开启特定的渲染
--贴图合并,压缩

--合并shader
--CGInclude 着色器模块化
--multi_complie   会产生非常多的变体
--shader_feature   可以减少变体的生成
-- #define  




//openGL知识
-- VBO  顶点缓存对象   存储大量的顶点
--GLuint VBO;
--glGenBuffers(1, &VBO);  

--不同类型的缓冲对象可以绑定
--glBindBuffer(GL_ARRAY_BUFFER, VBO);  

--缓冲数据复制到内存中
--glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);


--OpenGL如何解释顶点数据  VAO

--索引缓冲对象  IBO 


//插件
特效插件: MeshEffect   UIEffect FxMaker
第三人称控制插件: Third Person COntroller 
AI插件  BehavoirDesigner 
摄像机: Pro Camera2D
本地化插件:L2 Localizaton 
Dotween小动画:

//
纹理贴图版的2D流光效果





// unityShader模拟星空效果
//不使用透明模式,只使用一张纹理
_Speed1("_Speed1",Range(0,0.2))=0.05
_Speed2("_Speed2",Range(0,15))=5
_Speed3("_Speed3",Range(0,5))=1



fixed frag(v2f IN):COLOR{
  float2 uv=IN.uv;
  //使用sin函数控制偏移的范围
  float offset_uv=_Speed1*sin(IN.uv*_Speed2+_Time.x*_Speed3);   
  uv+=offset_uv;
  fixed col_1=tex2D(_MainTex,uv);    //这张图片加UV

  uv=IN.uv;
  uv-=offset_uv;
  fixed col_2=tex2D(_MainTex,uv);   //这张图片减UV

  return (col_1+col_2)/2;         //模拟星空
}

音效设置:
在这里插入图片描述

换装系统

--简单的换装系统
--  1. 替换蒙皮网格
--- 2. 刷新骨骼
--- 3. 替换材质

----降低DrawCall 的换装系统

1. 替换蒙皮网格 或者替换 部位的 GameObject 
2. 合并所有蒙皮网格
3. 刷新骨骼
4. 刷新材质
5. 合并贴图
6. 重新计算UV 


----------------------------------------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class TestCustomize : MonoBehaviour
{
    // 切换装备的按钮
    public Button[] equipButtons;
    // 切换武器的按钮
    public Button[] weaponButtons;
    // 装备对应的资源路径
    public string[] equipPaths;
    // 武器对应的资源路径
    public string[] weaponPaths;
    // 普通待机切换的按钮
    public Button idleBtn;
    // 战斗待机切换的按钮
    public Button readyBtn;

    // 实例化的角色对象
    private GameObject _avatarObj;
    // 角色动画组件
    private Animator _avatarAnimator;
    // 角色蒙皮组件
    private SkinnedMeshRenderer _avatarSMR;


    /// <summary>
    /// 添加按钮的回调
    /// </summary>
    void Start()
    {
        for(int i = 0; i < equipButtons.Length; ++i)
        {
            int index = i;
            equipButtons[i].onClick.AddListener(() => { ChangeEquip(index); });
        }
        for (int i = 0; i < weaponButtons.Length; ++i)
        {
            int index = i;
            weaponButtons[i].onClick.AddListener(() => { ChangeWeapon(index); });
        }

        idleBtn.onClick.AddListener(OnIdleBtnClick);
        readyBtn.onClick.AddListener(OnReadyBtnClick);

        LoadAvatar();
    }

    /// <summary>
    /// 加载基本模型
    /// </summary>
    void LoadAvatar()
    {
        GameObject avatarAsset = Resources.Load<GameObject>("Avatar");
        if(avatarAsset != null)
        {
            _avatarObj = GameObject.Instantiate<GameObject>(avatarAsset);
            _avatarObj.transform.position = new Vector3(0, -2, 0);
            _avatarObj.transform.localScale = new Vector3(3, 3, 3);
            _avatarObj.transform.localEulerAngles = new Vector3(0, 180, 0);
            _avatarSMR = _avatarObj.GetComponentInChildren<SkinnedMeshRenderer>();
            _avatarAnimator = _avatarObj.GetComponentInChildren<Animator>();
            ChangeEquip(0);
        }
    }

    /// <summary>
    /// 切换动画普通待机
    /// </summary>
    void OnIdleBtnClick()
    {
        _avatarAnimator.SetTrigger("idle");
    }

    /// <summary>
    /// 切换动画战斗待机
    /// </summary>
    void OnReadyBtnClick()
    {
        _avatarAnimator.SetTrigger("ready");
    }

    /// <summary>
    /// 切换装备
    /// </summary>
    /// <param name="index"></param>
    void ChangeEquip(int index)
    {
        // 1: 加载装备的prefab
        string assetPath = equipPaths[index];
        GameObject equipAsset = Resources.Load<GameObject>(assetPath);
        if (equipAsset == null)
        {
            return;
        }
        // 2: 实例化装备
        GameObject srcObj = GameObject.Instantiate<GameObject>(equipAsset);
        SkinnedMeshRenderer srcSMR = srcObj.GetComponentInChildren<SkinnedMeshRenderer>();
        // 3: 替换骨骼
        Transform[] srcBones = srcSMR.bones;
        List<Transform> destBones = new List<Transform>();
        for(int i = 0; i < srcBones.Length; ++i)
        {
            destBones.Add(FindTransform(_avatarObj.transform, srcBones[i].name));
        }
        _avatarSMR.bones = destBones.ToArray();
        // 4: 替换Mesh
        _avatarSMR.sharedMesh = srcSMR.sharedMesh;
        // 5: 替换材质
        _avatarSMR.sharedMaterial = srcSMR.sharedMaterial;
        // 6: 替换完毕,销毁实例化装备
        GameObject.Destroy(srcObj);
    }

    /// <summary>
    /// 切换武器
    /// </summary>
    /// <param name="index"></param>
    void ChangeWeapon(int index)
    {
        // 1: 加载武器的prefab
        string assetPath = weaponPaths[index];
        GameObject weaponAsset = Resources.Load<GameObject>(assetPath);
        if (weaponAsset == null)
        {
            return;
        }
        // 2: 实例化武器
        GameObject weaponObj = GameObject.Instantiate<GameObject>(weaponAsset);
        // 3: 查找武器挂点,并清空挂点下的子节点
        Transform dummpy = FindTransform(_avatarObj.transform, "bip_weapon_right");
        DestroyChildren(dummpy);
        // 4: 将当前武器的父节点设置为武器挂点
        weaponObj.transform.SetParent(dummpy);
        weaponObj.transform.localPosition = Vector3.zero;
        weaponObj.transform.localScale = Vector3.one;
        weaponObj.transform.localRotation = Quaternion.identity;
    }

    /// <summary>
    /// 递归查找子节点
    /// </summary>
    /// <param name="root"></param>
    /// <param name="name"></param>
    /// <returns></returns>
    Transform FindTransform(Transform root, string name)
    {
        if (root.name == name)
        {
            return root;
        }
        for (int i = 0; i < root.childCount; ++i)
        {
            Transform transform = FindTransform(root.GetChild(i), name);
            if (transform != null)
            {
                return transform;
            }
        }
        return null;
    }

    /// <summary>
    /// 销毁所有的子节点
    /// </summary>
    /// <param name="transform"></param>
    void DestroyChildren(Transform transform)
    {
        while (transform.childCount > 0)
        {
            GameObject.DestroyImmediate(transform.GetChild(0).gameObject);
        }
    }
}


优化点建议:
1:针对每个套装的prefab将SkinnedMeshRenderer的bones数据保存到一个脚本中,并删除掉Prefab中的bone节点,
   过多的节点会影响实例化的速度

2:针对角色的Avatar预制模型,将bone节点与名字的对应关系保存到脚本数据中,避免替换bone过程递归查找节点

3:角色Shader简单的可以通过,合并所有Mesh为一个Mesh,统一用一个shader,传递多张贴图的方式来合并DrawCall

4:修改SkinnedMeshRenderer为MeshRenderer,自定义Shader实现GPU蒙皮计算+开启GPUInstancing的方式,提高性能,减低DrawCall


--------------------------------------------------------------------------
--------------------------优化版的换装系统--------------------------------------------
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[9];

    private string[] defaultEquipPartPaths = new string[9];

    void Start()
    {
        // 把FBX的模型按部件分别放入Resources下对应的文件夹里,可以留空,模型需要蒙皮,而且所有模型使用同一骨骼
        // 最后的M是Fbx的模型,需要的Unity3D里设置好材质和贴图,部件贴图要勾选Read/Write Enabled
        defaultEquipPartPaths[0] = "Model/Player/GirlPlayer/Head/Head0000/M";
        defaultEquipPartPaths[1] = "Model/Player/GirlPlayer/Face/Face0000/M";
        defaultEquipPartPaths[2] = "Model/Player/GirlPlayer/Hair/Hair0000/M";
        defaultEquipPartPaths[3] = "";
        defaultEquipPartPaths[4] = "Model/Player/GirlPlayer/Body/Body0000/M";
        defaultEquipPartPaths[5] = "Model/Player/GirlPlayer/Leg/Leg0000/M";
        defaultEquipPartPaths[6] = "Model/Player/GirlPlayer/Hand/Hand0000/M";
        defaultEquipPartPaths[7] = "Model/Player/GirlPlayer/Foot/Foot0000/M";
        defaultEquipPartPaths[8] = "Model/Player/GirlPlayer/Wing/Wing0001/M";

        Destroy(target.GetComponent<SkinnedMeshRenderer>());
        for (int i = 0; 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(0, -1000, 0);
                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 = 0;
        int height = 0;

        int uvCount = 0;

        List<Vector2[]> uvList = new List<Vector2[]>();

        // 遍历所有蒙皮网格渲染器,以计算出所有需要合并的网格、UV、骨骼的信息
        foreach (SkinnedMeshRenderer smr in root.GetComponentsInChildren<SkinnedMeshRenderer>())
        {
            for (int sub = 0; 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(), 0);
        Vector2[] atlasUVs = new Vector2[uvCount];

        // 因为将贴图都整合到了一张图片上,所以需要重新计算UV
        int j = 0;
        for (int i = 0; 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) * 1000 + " ms");
    }


    /// <summary>
    /// 获取最接近输入值的2的N次方的数,最大不会超过1024,例如输入320会得到512
    /// </summary>
    private int get2Pow(int into)
    {
        int outo = 1;
        for (int i = 0; i < 10; i++)
        {
            outo *= 2;
            if (outo > into)
            {
                break;
            }
        }

        return outo;
    }
}

----------------------------------------------------------------------



unity 合并Mesh

using UnityEngine;


/// <summary>
/// 合并网格
/// </summary>
public class ChinarMergeMesh : MonoBehaviour
{
    void Start()
    {
        MergeMesh();
    }


    /// <summary>
    /// 合并网格
    /// </summary>
    private void MergeMesh()
    {
    	//获取 所有子物体的网格
        MeshFilter[]      meshFilters      = GetComponentsInChildren<MeshFilter>();   

        //新建一个合并组,长度与 meshfilters一致
        CombineInstance[] combineInstances = new CombineInstance[meshFilters.Length]; 
        for (int i = 0; i < meshFilters.Length; i++)                                  //遍历
        {
        	//将共享mesh,赋值
            combineInstances[i].mesh      = meshFilters[i].sharedMesh;                   

            //本地坐标转矩阵,赋值
            combineInstances[i].transform = meshFilters[i].transform.localToWorldMatrix; 
        }

        Mesh newMesh = new Mesh();                                  //声明一个新网格对象
        newMesh.CombineMeshes(combineInstances);                    //将combineInstances数组传入函数
        gameObject.AddComponent<MeshFilter>().sharedMesh = newMesh; //给当前空物体,添加网格组件;将合并后的网格,给到自身网格
        //到这里,新模型的网格就已经生成了。运行模式下,可以点击物体的 MeshFilter 进行查看网格

        #region 以下是对新模型做的一些处理:添加材质,关闭所有子物体,添加自转脚本和控制相机的脚本

        //gameObject.AddComponent<MeshRenderer>().material = Resources.Load<Material>("Materials/Koala"); //给当前空物体添加渲染组件,给新模型网格上色;
        //foreach (Transform t in transform)                                                              //禁用掉所有子物体
        //{
        //    t.gameObject.SetActive(false);
        //}
        //gameObject.AddComponent<BallRotate>();
        //Camera.main.gameObject.AddComponent<ChinarCamera>().pivot = transform;

        #endregion
    }
}

1. 汉诺塔问题: 递归求解 ( 结束条件, 调用自身)

>>>>>LowB 三人组 o(n^2)
2. 冒泡排序: for key for (趟数,该趟是否没有交换, 改趟比较的次数)
3. 选择排序: 每趟找出最小的数, 放到一个地方
4. 插入排序: 每次取出一个数,插入到正确的地方
5. 计算机每秒执行的次数大概为 107次方
使用冒泡排序,排10000个数,大概需要10>>>>>牛逼三人组 o(nlogn)
6. 堆排序,求前几名  
7. 归并排序稳定      
8. 快速排序最快, 特殊环境下,有其他更快的排序算法 

>>>>>其他排序
9. 希尔分组插入排序 
10. 计数排序  限制大,速度快 o(n)
11. 桶排序, 对计数排序的范围进行分割

>>>>>数据结构
1. 用栈处理括号匹配问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值