本文将整理的面试题大致分为以下几个模块,方便针对性学习和背题!
由于大部分常用的面试题在网上基本上已经有比较标准的答案了,所以说面试题类的文章基本上大同小异。
所以本篇文章中的部分内容也是直接从网上摘选来的
如果有不对的地方也欢迎指正(尽力不会出现这种情况),某个模块的内容不够也欢迎在评论区指出,我去重新添加上。
本文部分内容参考文章如下:
https://blog.csdn.net/u014361280/article/details/100135129
https://blog.csdn.net/u014361280/article/details/121499143
https://blog.csdn.net/u014361280/article/details/121534764
https://blog.csdn.net/qq_21407523/article/details/108814300
值类型:包含了所有简单类型(整数、浮点、bool、char)、struct、enum。
继承自System.ValueTyoe
引用类型包含了string,object,class,interface,delegate,array
继承自System.Object
引用类型的基类是System.Object值类型的基类是 System.ValueType
同时,值类型也隐式继承自System.Object
ArrayList存在不安全类型(ArrayList会把所有插 ⼊其中的数据都当做Object来处理)装箱拆箱的 操作(费时)IList是接⼝,ArrayList是⼀个实现了 该接⼝的类,可以被实例化
List类是ArrayList类的泛型等效类。它的大部分用法都与ArrayList相似,因为List类也继承了IList接口。最关键的区别在于,在声明List集合时,我们同时需要为其声明List集合内数据的对象类型。
GC为了避免内存溢出而产生的回收机制
避免:
1)减少 new 产生对象的次数
2)使用公用的对象(静态成员)
3)将 String 换为 StringBuilder
类声明时可防止其他类继承此类,在方法中声明则可防止派生类重写此方法。
可以在加载程序运行时,动态获取和加载程序集,并且可以获取到程序集的信息反射即在运行期动态获取类、对象、方法、对象数据等的一种重要手段
主要使用的类库:System.Reflection
核心类:
通过以上核心类可在运行时动态获取程序集中的类,并执行类构造产生类对象,动态获取对象的字段或属性值,更可以动态执行类方法和实例方法等。
.Net是一个语言平台,Mono为.Net提供集成开发环境,集成并实现了.NET的编译器、CLR 和基础类库,使得.Net既可以运行在windows也可以运行于 linux,Unix,Mac OS 等。
构造函数格式为public+类名如果加上 static 会报错(静态构造函数不能有访问、型的对象,静态构造函数只执行一次;
运行库创建类实例或者首次访问静态成员之前,运行库调用静态构造函数;
静态构造函数执行先于任何实例级别的构造函数;
显然也就无法使用this和 base 来调用构造函数。
一个类只能有一个静态函数,如果有静态变量,系统也会自动生成静态函数
如果是处理字符串的话,用string中的方法每次都需要创建一个新的字符串对象并且分配新的内存地址,而 stringBuilder 是在原来的内存里对字符串进行修改,所以在字符串处理
方面还是建议用stringBuilder这样比较节约内存。但是 string 类的方法和功能仍然还是比 stringBuilder 类要强。
string类由于具有不可变性(即对一个 string 对象进行任何更改时,其实都是创建另外一个 string 类的对象),所以当需要频繁的对一个 string 类对象进行更改的时候,建议使用StringBuilder 类,StringBuilder 类的原理是首先在内存中开辟一定大小的内存空间,当对此 StringBuilder 类对象进行更改时, 如果内存空间大小不够, 会对此内存空间进行扩充,而不是重新创建一个对象,这样如果对一个字符串对象进行频繁操作的时候,不会造成过多的内存浪费,其实本质上并没有很大区别,都是用来存储和操作字符串的,唯一的区别就在于性能上。
String主要用于公共 API,通用性好、用途广泛、读取性能高、占用内存小。
StringBuilder主要用于拼接 String,修改性能好。
不过现在的编译器已经把String的 + 操作优化成 StringBuilder 了, 所以一般用String 就可以了
String是不可变的,所以天然线程同步。
StringBuilder可变,非线程同步。
(a,b) => {};
public int CountNumber(int num) {
if (num == 1 || num == 2) {
return 1;
} else {
return CountNumber(num -1) + CountNumber(num-2);
}
}
public static void BubblingSort(int[]array) {
for (int i = 0; i < array.Length; i++){
for (int j = array.Length - 1; j > 0; j--){
if (array[j] < array[i]) {
int temp = array[j];
array[j] = array[j-1];
array[j - 1] = temp;
}
}
}
}
List,HashTable,Dictionary,Stack,Queue
//哈希表结构体
private struct bucket {
public Object key;//键
public Object val;//值
public int hash_col;//哈希码
}
//字典结构体
private struct Entry {
public int hashCode; // 除符号位以外的31位hashCode值, 如果该Entry没有被使用,那么为-1
public int next; // 下一个元素的下标索引,如果没有下一个就为-1
public TKey key; // 存放元素的键
public TValue value; // 存放元素的值
}
private int[] buckets; // Hash桶
private Entry[] entries; // Entry数组,存放元素
private int count; // 当前entries的index位置
private int version; // 当前版本,防止迭代过程中集合被更改
private int freeList; // 被删除Entry在entries中的下标index,这个位置是空闲的
private int freeCount; // 有多少个被删除的Entry,有多少个空闲的位置
private IEqualityComparer<TKey> comparer; // 比较器
private KeyCollection keys; // 存放Key的集合
private ValueCollection values; // 存放Value的集合
性能排序:
不带泛型的容器需要装箱和拆箱操作速度慢所以泛型容器效率更高数据类型更安全
简单值类型:包括 整数类型、实数类型、字符类型、布尔类型
复合值类型:包括 结构类型、枚举类型
**接口(interface)**是约束类应该具备的功能集合,约束了类应该具备的功能,使类从千变万化的具体逻辑中解脱出来,便于类的管理和扩展,同时又合理解决了类的单继承问题。
C#中的委托 是约束方法集合的一个类,可以便捷的使用委托对这个方法集合进行操作。
在以下情况中使用接口:
1.无法使用继承的场合
2.完全抽象的场合
3.多人协作的场合
以上等等
在以下情况中使用委托:多用于事件处理中
非托管代码才需要这个关键字一般用在带指针操作的场合。
项目背包系统的任务装备栏使用到
ref修饰引用参数。参数必须赋值,带回返回值,又进又出
out修饰输出参数。参数可以不赋值,带回返回值之前必须明确赋值,
引用参数和输出参数不会创建新的存储位置
如果ref参数是值类型,原先的值类型数据,会随着方法里的数据改变而改变,
如果ref参数值引用类型,方法里重新赋值后,原对象堆中数据会改变,如果对引用类型再次创建新对象并赋值给ref参数,引用地址会重新指向新对象堆数据。方法结束后形参和新对象都会消失。实参还是指向原始对象,值不够数据改变了
for循环可以通过索引依次进行遍历,foreach和Enumerator.MoveNext通过迭代的方式进行遍历。
内存消耗上本质上并没有太大的区别。
但是在Unity中的Update中,一般不推荐使用foreach 因为会遗留内存垃圾。
通过StringBuilder那进行append,这样可以减少内存垃圾
设计单例模式进行创建对象或者使用对象池
Just-In-Time -实时编译
执行慢安装快占空间小一点
Ahead-Of-Time -预先编译
执行快安装慢占内存占外存大
void SortArray(Array arr){Array.Sort(arr);}
foreach不能进行元素的删除,因为迭代器会锁定迭代的集合,解决方法:记录找到索引或者key值,迭代结束后再进行删除。
存在,b删除只是将它在栈中的内存删除,而A对象本身是在堆中,所以A还存在
大致来说,委托是一个类,该类内部维护着一个字段,指向一个方法。事件可以被看作一个委托类型的变量,通过事件注册、取消多个委托或方法。
○ 委托就是一个类,也可以实例化,通过委托的构造函数来把方法赋值给委托实例
○ 触发委托有2种方式: 委托实例.Invoke(参数列表),委托实例(参数列表)
○ 事件可以看作是一个委托类型的变量
○ 通过+=为事件注册多个委托实例或多个方法
○ 通过-=为事件注销多个委托实例或多个方法
○ EventHandler就是一个委托
结构体是一种值类型,而类是引用类型。(值类型、引用类型是根据数据存储的⻆度来分的)就是值类型用于存储数据的值,引用类型用于存储对实际数据的引用。
那么结构体就是当成值来使用的,类则通过引用来对实际数据操作
委托类似于一种安全的指针引用,在使用它时是 当做类来看待而不是一个方法,相当于对一组方 法的列表的引用。
用处:使用委托使程序员可以将方法引用封装在 委托对象内。然后可以将该委托对象传递给可调 用所引用方法的代码,而不必在编译时知道将调 用哪个方法。与C或C++中的函数指针不同,委托 是面向对象,而且是类型安全的。
如果集合需要foreach遍历,是否可行,存在一定问题
foreach中的迭代变量item是的只读,不能对其进行修改,比如list.Remove(item)操作
foreach只读的时候记录下来,在对记录做操作,或者直接用for循环遍历
foreach对int[]数组循环已经不产生GC,避免对ArrayList进行遍历
for语句中初始化变量i的作用域,循环体内部可见。
通过索引进行遍历,可以根据索引对所遍历集合进行修改
unity中for循环使用lambda表达式注意闭包问题
foreach遍历原理
任何集合类(Array)对象都有一个GetEnumerator()方法,该方法可以返回一个实现了 IEnumerator接口的对象。
这个返回的IEnumerator对象既不是集合类对象,也不是集合的元素类对象,它是一个独立的类对象。
通过这个实现了 IEnumerator接口对象A,可以遍历访问集合类对象中的每一个元素对象
对象A访问MoveNext方法,方法为真,就可以访问Current方法,读取到集合的元素。
List<string> list = new List<string>() { "25", "哈3", "26", "花朵" };
IEnumerator listEnumerator = list.GetEnumerator();
while (listEnumerator.MoveNext())
{
Console.WriteLine(listEnumerator.Current);
}
简单的说:C# 与C++ 比较的话,最重要的特性 就是C# 是一种完全面向对象的语言,而C++ 不 是,另外C# 是基于IL 中间语言
和.NET Framework CLR 的,在可移植性,可维 护性和强壮性都比C++ 有很大的改进。C# 的设 计目标是用来开发快速稳定可扩展的应用程序, 当然也可以通过Interop和Pinvoke 完成一些底层操作
具体对比:
C#不支持指针,但可以使用Unsafe,不安全模式,CLR不检测
C#可以定义指针的类型、整数型、实数型、struct结构体
C#指针操作符、C#指针定义
使用fixed,可以操作类中的值类型
相同点:都是地址
指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。
不同点:
指针是个实体,引用是个别名。
sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;
引用是类型安全的,而指针在不安全模式下
通常保存着我们代码执行的步骤,如在代码段1中 AddFive()方法,int pValue变量,int result变量等等。 而堆上存放的则多是对象,数据等。(译者注:忽略编 译器优化)我们可以把栈想象成一个接着一个叠放在 一起的盒子。当我们使用的时候,每次从最顶部取走 一个盒子。栈也是如此,当一个方法(或类型)被调 用完成的时候,就从栈顶取走(called a Frame,译 注:调用帧),接着下一个。堆则不然,像是一个仓 库,储存着我们使用的各种对象等信息,跟栈不同的 是他们被调用完毕不会立即被清理掉。
Mock与Stub的区别:Mock:关注行为验证。细粒度的 测试,即代码的逻辑,多数情况下用于单元测试。 Stub:关注状态验证。粗粒度的测试,在某个依赖系 统不存在或者还没实现或者难以测试的情况下使用, 例如访问文件系统,数据库连接,远程协议等。
Unicode是国际组织制定的可以容纳世界上所有⽂字和符号的字符编码⽅案。
使⽤动态字体时,Unity将不会预先⽣成⼀个与所有字体的字符纹理。
当需要⽀持亚洲语⾔或者较⼤的字体的时候,若使⽤正常纹理,则字体的纹理将⾮常⼤。
String是字符串常量。StringBuffer是字符串变量 ,线程安全。StringBuilder是字符串变量,线程不安全。
String类型是个不可变的对象,当每次对String进⾏改变时都需要⽣成⼀个新的String对象,然后将指针指向⼀个新的对象,如果在⼀个循环⾥⾯,不断的改变⼀个对象,就要不断的⽣成新的对象,所以效率很低,建议在不断更改String对象的地⽅不要使⽤String类型。
StringBuilder对象在做字符串连接操作时是在原来的字符串上进⾏修改,改善了性能。这⼀点我们平时使⽤中也许都知道,连接操作频繁的时候,使⽤StringBuilder对象。
三者性能比较 StringBuilder>StringBuffer>String
泛型集合命名空间using System.Collections.Generic;
任何键都必须是唯一
该类最大的优点就是它查找元素的时间复杂度接近O(1),实际项目中常被用来做一些数据的本地缓存,提升整体效率。
实现原理
多个代码对 【不同数据类型】 执行 【相同指令】的情况
泛型:多个类型共享一组代码
泛型允许类型参数化,泛型类型是类型的模板
5种泛型:类、结构、接口、委托、方法
类型占位符 T 来表示泛型
泛型类不是实际的类,而是类的模板
从泛型类型创建实例
声明泛型类型》通过提供【真实类型】创建构造函数类型》从构造类型创建实例
类 泛型类型参数
性能:泛型不会强行对值类型进行装箱和拆箱,或对引用类型进行向下强制类型转换,所以性能得到提高
安全:通过知道使用泛型定义的变量的类型限制,编译器可以在一定程度上验证类型假设,所以泛型提高了程序的类型安全。
IEnumerable;GetEnumerator
List和Dictionary类型可以用foreach遍历,他们都实现了IEnumerable接口,申明了GetEnumerator方法。
里氏替换原则(Liskov Substitution Principle LSP)⾯向对象设计的基本原则之⼀。
可以在加载程序运行时,动态获取和加载程序集,并且可以获取到程序集的信息反射即在运行期动态获取类、对象、方法、对象数据等的一种重要手段
主要使用的类库:System.Reflection
核心类:
通过以上核心类可在运行时动态获取程序集中的类,并执行类构造产生类对象,动态获取对象的字段或属性值,更可以动态执行类方法和实例方法等。
审查元数据并收集关于它的类型信息的能⼒。
实现步骤:
1. 导⼊using System.Reflection;
2. Assembly.Load("程序集")加载程序集,返回类型是
⼀个Assembly
3. foreach (Type type in assembly.GetTypes())
{
string t = type.Name;
}
得到程序集中所有类的名称
4. Type type = assembly.GetType("程序集.类名");获取
当前类的类型
5. Activator.CreateInstance(type); 创建此类型实例
6. MethodInfo mInfo = type.GetMethod("⽅法名");获取
当前⽅法
7. mInfo.Invoke(null,⽅法参数);
代理就是⽤来定义指向⽅法的引⽤。
C#事件本质就是对消息的封装,⽤作对象之间的通信;发送⽅叫事件发送器,接收⽅叫事件接收器;
字典:内部用了Hashtable作为存储结构
哈希表:
属性修饰符:
Serializable:按值将对象封送到远程服务器。
STATread:是单线程套间的意思,是⼀种线程模型。
MATAThread:是多线程套间的意思,也是⼀种线程模
型。
存取修饰符:
public:存取不受限制。
private:只有包含该成员的类可以存取。
internal:只有当前⼯程可以存取。
protected:只有包含该成员的类以及派⽣类可以存
取。
类修饰符:
abstract:抽象类。指示⼀个类只能作为其它类的基
类。
sealed:密封类。指示⼀个类不能被继承。理所当
然,密封类不能同时⼜是抽象类,因为抽象总是希望
被继承的。
成员修饰符:
abstract:指示该⽅法或属性没有实现。
sealed:密封⽅法。可以防⽌在派⽣类中对该⽅法的
override(᯿载)。不是类的每个成员⽅法都可以作为
密封⽅法密封⽅法,必须对基类的虚⽅法进⾏᯿载,
提供具体的实现⽅法。所以,在⽅法的声明中,
sealed修饰符总是和override修饰符同时使⽤。
delegate:委托。⽤来定义⼀个函数指针。C#中的事
件驱动是基于delegate + event的。
const:指定该成员的值只读不允许修改。
event:声明⼀个事件。
extern:指示⽅法在外部实现。
override:᯿写。对由基类继承成员的新实现。
readonly:指示⼀个域只能在声明时以及相同类的内
部被赋值。
static:指示⼀个成员属于类型本身,⽽不是属于特定
的对象。即在定义后可不经实例化,就可使⽤。
virtual:指示⼀个⽅法或存取器的实现可以在继承类中
被覆盖。
new:在派⽣类中隐藏指定的基类成员,从⽽实现᯿
写的功能。 若要隐藏继承类的成员,请使⽤相同名称
在派⽣类中声明该成员,并⽤ new 修饰符修饰它。
List<int> ls = new List<int>(new int[]{ 1, 2, 3, 4, 5 });
foreach (int item in ls)
{
Console.WriteLine(item * item);
ls.Remove(item);
}
会产⽣运⾏时错误,因为foreach是只读的。不能⼀边遍历⼀边修改。
使用For循环遍历可以解决。
C#装箱是将值类型转换为引用类型;
拆箱是将引用类型转换为值类型。
牵扯到装箱和拆箱操作比较多的就是在集合中,例如:ArrayList或者HashTable之类。
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范。
用一种业务逻辑、数据、界面显示分离的方法,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。
答:碰撞器是触发器的载体,而触发器只是碰撞器身上的一个属性。
当Is Trigger=false时,碰撞器根据物理引擎引发碰撞,产生碰撞的效果,可以调用OnCollisionEnter/Stay/Exit函数;
当Is Trigger=true时,碰撞器被物理引擎所忽略,没有碰撞效果,可以调用OnTriggerEnter/Stay/Exit函数。
如果既要检测到物体的接触又不想让碰撞检测影响物体移动或要检测一个物件是否经过空间中的某个区域这时就可以用到触发器。
答:两个物体都必须带有碰撞器Collider,其中一个物体还必须带有Rigidbody刚体。
答:四元数用于表示旋转
相对欧拉角的优点:能进行增量旋转、避免万向锁、给定方位的表达方式有两种,互为负(欧拉角有无数种表达方式)
答:Awake–>OnEnable->Start
OnEnable在同一周期中可以反复地发生!
答:修改sharedMaterial将改变所有物体使用这个材质的外观,并且也改变储存在工程里的材质设置。 不推荐修改由sharedMaterial返回的材质。如果你想修改渲染器的材质,使用material替代。
网络接口层:这是协议栈的最低层,对应OSI的物理层和数据链路层,主要完成数据帧的实际发送和接收。
网络层:处理分组在网络中的活动,例如路由选择和转发等,这一层主要包括IP协议、ARP、ICMP协议等。
传输层:主要功能是提供应用程序之间的通信,这一层主要是TCP/UDP协议。
应用层:用来处理特定的应用,针对不同的应用提供了不同的协议,例如进行文件传输时用到的FTP协议,发送email用到的SMTP等。
四种。
对象池就存放需要被反复调用资源的一个空间
比如游戏中要常被大量复制的对象,子弹,敌人,以及任何重复出现的对象。
特点:用内存换取cpu的优化
Rigidbody具有完全真实物理的特性,而CharacterController可以说是受限的的Rigidbody,具有一定的物理效果但不是完全真实的。
LateUpdate,是在所有的Update结束后才调用,比较适合用于命令脚本的执行。
官网上例子是摄像机的跟随,都是所有的Update操作完才进行摄像机的跟进,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。
在游戏运行时实例化,prefab相当于一个模板,对你已经有的素材、脚本、参数做一个默认的配置,以便于以后的修改,同事prefab打包的内容简化了导出的操作,便于团队的交流。
简而言之,GPU的图形(处理)流水线完成如下的工作:(并不一定是按照如下顺序)。
顶点处理:这阶段GPU读取描述3D图形外观的顶点数据并根据顶点数据确定3D图形的形状及位置关系,建立起3D图形的骨架。在支持DX8和DX9规格的GPU中,这些工作由硬件实现的Vertex Shader(定点着色器)完成。
光栅化计算:显示器实际显示的图像是由像素组成的,我们需要将上面生成的图形上的点和线通过一定的算法转换到相应的像素点。把一个矢量图形转换为一系列像素点的过程就称为光栅化。例如,一条数学表示的斜线段,最终被转化成阶梯状的连续像素点。
纹理帖图:顶点单元生成的多边形只构成了3D物体的轮廓,而纹理映射(texture mapping)工作完成对多变形表面的帖图,通俗的说,就是将多边形的表面贴上相应的图片,从而生成“真实”的图形。TMU(Texture mapping unit)即是用来完成此项工作。
像素处理:这阶段(在对每个像素进行光栅化处理期间)GPU完成对像素的计算和处理,从而确定每个像素的最终属性。在支持DX8和DX9规格的GPU中,这些工作由硬件实现的Pixel Shader(像素着色器)完成。
最终输出:由ROP(光栅化引擎)最终完成像素的输出,1帧渲染完毕后,被送到显存帧缓冲区。
总结:GPU的工作通俗的来说就是完成3D图形的生成,将图形映射到相应的像素点上,对每个像素进行计算确定最终颜色并完成输出。
是指在显示器上为了显示出图像而经过的一系列必要操作。 渲染管道中的很多步骤,都要将几何物体从一个坐标系中变换到另一个坐标系中去。
主要步骤有: 本地坐标->视图坐标->背面裁剪->光照->裁剪->投影->视图变换->光栅化。
答:OnCollisionEnter、 OnCollisionStay、 OnCollisionExit
Hinge Joint,可以模拟两个物体间用一根链条连接在一起的情况,能保持两个物体在一个固定距离内部相互移动而不产生作用力,但是达到固定距离后就会产生拉力。
Transform.Rotate()
PlayerPrefs.SetInt()、 PlayerPrefs.GetInt()
答:Awake——>Start——>Update——>FixedUpdate——>LateUpdate——>OnGUI——>OnDisable——>OnDestroy
主要执行顺序
编辑器->初始化->物理系统->输入事件->游戏逻辑->场景渲染->GUI渲染->物体激活或禁用->销毁物体->应用结束
主要函数介绍
FixedUpdate,每固定帧绘制时执行一次,和Update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率低下的时候FixedUpdate调用次数就会跟着下降。
FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关。 Update就比较适合做控制。
游戏界面可以看到很多摄像机的混合。
使用Destroy()方法;
主要有关节动画、骨骼动画、单一网格模型动画(关键帧动画)。
一般是组件上绑定的物体对象被删除了
Alpha Blend 实现透明效果,不过只能针对某块区域进行alpha操作,透明度可设。
diffuse = Kd x colorLight x max(N*L,0);Kd 漫反射系数、colorLight 光的颜色、N 单位法线向量、L 由点指向光源的单位向量、其中N与L点乘,如果结果小于等于0,则漫反射为0。
LOD(Level of detail)多层次细节,是最常用的游戏优化技术。
它按照模型的位置和重要程度决定物体渲染的资源分配,降低非重要物体的面数和细节度,从而获得高效率的渲染运算。
缺点:增加了内存
LOD简单示例:【100个 Unity踩坑小知识点】| Unity 的 LOD技术(多细节层次)
本影和半影:
顶点着色器 是一段执行在GPU上的程序,用来取代fixed pipeline中的transformation和lighting,Vertex Shader主要操作顶点。
Vertex Shader对输入顶点完成了从local space到homogeneous space(齐次空间)的变换过程,homogeneous space即projection space的下一个space。在这其间共有world transformation, view transformation和projection transformation及lighting几个过程。
MipMapping:在三维计算机图形的贴图渲染中有常用的技术,为加快渲染进度和减少图像锯齿,贴图被处理成由一系列被预先计算和优化过的图片组成的文件,这样的贴图被称为MipMap。
语法不同处:
用法不同处:
mono是.net的一个开源跨平台工具,就类似java虚拟机,java本身不是跨平台语言,但运行在虚拟机上就能够实现了跨平台。
.net只能在windows下运行,mono可以实现跨平台编译运行,可以运行于Linux,Unix,Mac OS等。
Unity的脚本语言基于Mono的.Net平台上运行,可以使用.NET库,这也为XML、数据库、正则表达式等问题提供了很好的解决方案。
Unity里的脚本都会经过编译,他们的运行速度也很快。这三种语言实际上的功能和运行速度是一样的,区别主要体现在语言特性上。
Unity支持的语言:C#,JavaScrip(不在使用)
支持:如果同时你要处理很多事情或者与Unity的对象互动小可以用thread,否则使用coroutine。
Unity3d没有多线程的概念,不过unity也给我们提供了StartCoroutine(协同程序)和LoadLevelAsync(异步加载关卡)后台加载场景的方法。
注意:仅能从主线程中访问Unity3D的组件,对象和Unity3D系统调用。C#中有lock这个关键字,以确保只有一个线程可以在特定时间内访问特定的对象
DontDestroyOnLoad(transform.gameObject);
Transform 父类是 Component
用于表示线性变换:旋转、缩放、投影、平移、仿射
注意矩阵的蠕变:误差的积累
穿透(碰撞检测失败)(例如CS射击游戏,可以使用开枪时发射射线,射线碰撞到则掉血击中)
不美观,OnGUI很耗费时间,使用不方便
多屏幕分辨率下的UI布局一般考虑两个问题:
为了解决这两个问题,在Unity GUI体系中有两个组件可以来解决问题,分别是布局元素的Rect Transform和Canvas的Canvas Scaler组件。
当物体是否可见切换之时。可以用于只需要在物体可见时才进行的计算。
如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。
动态批处理操作是自动完成的,并不需要你进行额外的操作。
区别:动态批处理一切都是自动的,不需要做任何操作,而且物体是可以移动的,但是限制很多。静态批处理:自由度很高,限制很少,缺点可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了。
四种。
平⾏光:Directional Light
点光源:Point Light
聚光灯:Spot Light
区域光源:Area Light
LightMap:就是指在三维软件里实现打好光,然后渲染把场景各表面的光照输出到贴图上,最后又通过引擎贴到场景上,这样就使物体有了光照的感觉。
Unity3D支持C#、javascript等,cocos2d-x 支持c++、Html5、Lua等。
cocos2d 开源 并且免费
Unity3D支持iOS、Android、Flash、Windows、Mac、Wii等平台的游戏开发,cocos2d-x支持iOS、Android、WP等。
剪裁平面 。从相机到开始渲染和停止渲染之间的 距离。
在游戏运行时实例化,prefab相当于一个模板, 对你已经有的素材、脚本、参数做一个默认的配 置,以便于以后的修改,同时prefab打包的内容 简化了导出的操作,便于团队的交流。
剪裁平面 。从相机到开始渲染和停止渲染之间的距离。
在Game视图右上角点击Stats。降低Draw Call 的技术是Draw Call Batching
Alpha Test,中文就是透明度测试。
简而言之就是V&F shader中最后fragment函数输出的该点颜色值(即上一讲frag的输出half4)的alpha值与固定值进行比较。Alpha Test语句通常于Pass{}中的起始位置。Alpha Test产生的效果也很极端,要么完全透明,即看不到,要么完全不透明。
对旋转角度进行计算时用到四元数
仅深度,该模式用于对象不被裁剪。
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
设置游戏对象为Static将会剔除(或禁用)网格对象当这些部分被静态物体挡住而不可见时。因此,在你的场景中的所有不会动的物体都应该标记为Static。
把A组物体的渲染对列大于B物体的渲染队列
Sprite作为UI精灵使用,Texture作用模型贴图使用。
答:没有区别,因为不管几张贴图只渲染一次。
Unity中,每次引擎准备数据并通知GPU的过程称为一次Draw Call。DrawCall越高对显卡的消耗就越大。降低DrawCall的方法:
可以有cookies – 带有 alpha通道的立方图(Cubemap )纹理。点光源是最耗费资源的。
四元数⽤于表示旋转,对旋转⻆度进⾏计算时⽤到四元数
相对欧拉⻆的优点:
1)能进⾏增量旋转
2)避免万向锁
3)给定⽅位的表达⽅式有两种,互为负(欧拉⻆有⽆
数种表达⽅式)
对于AddComponent添加的脚本,其Awake,Start,OnEnable是在Add的当前帧被调用的
其中Awake,OnEnable与AddComponent处于同一调用链上
Start会在当前帧稍晚一些的时候被调用,Update则是根据Add调用时机决定何时调用:如果Add是在当前帧的Update前调用,那么新脚本的Update也会在当前帧被调用,否则会被延迟到下一帧调用。
用layermask ,通过位运算的方式去设置
在代码中使用时如何开启某个Layers?
LayerMask mask = 1 << 你需要开启的Layers层。
LayerMask mask = 0 << 你需要关闭的Layers层。
举几个例子:
LayerMask mask = 1 << 2; 表示开启Layer2。
LayerMask mask = 0 << 5;表示关闭Layer5。
LayerMask mask = 1<<2|1<<8;表示开启Layer2和Layer8。
LayerMask mask = 0<<3|0<<7;表示关闭Layer3和Layer7。
顶点着⾊器是⼀段执⾏在GPU上的程序,⽤来取代 fixed pipeline中的transformation和lighting,Vertex Shader主要操作顶点。‘’
像素着色器实际上就是对每一个像素进行光栅化的处理期间,在GPU上运算的一段程序。
不同与顶点着色器,像素着色器不会以软件的形式来模拟像素着色器。
像素着色器实质上是取代了固定功能流水线中多重纹理的环节,而且赋予了我们访问单个像素以及访问每一个像素纹理坐标的能力
缩放模式:
Property: | Function: |
---|---|
UI Scale Mode | Canvas中UI元素的缩放模式 |
Constant Pixel Size | 使UI保持自己的尺寸,与屏幕尺寸无关。 |
Scale With Screen Size | 屏幕尺寸越大,UI越大 |
Constant Physical Size | 使UI元素保持相同的物理大小,与屏幕尺寸无关。 |
Constant Pixel Size、Constant Physical Size实际上他们本质是一样的,只不过 Constant Pixel Size 通过逻辑像素大小调节来维持缩放,而 Constant Physical Size 通过物理大小调节来维持缩放。
FSM是一种数据结构,它由以下几个部分组成:
为什么要用FSM?
因为它编程快速简单,易于调试,性能高,与人类思维相似从而便于梳理,灵活且容易修改
FSM的描述性定义:
一个有限状态机是一个设备,或是一个模型,具有有限数量的状态。它可以在任何给定时间根据输入进行操作,使得系统从一个状态转换到另一个状态,或者是使一个输出或者一种行为的发生,一个有限状态机在任何瞬间只能处于一种状态。
State 状态基类,定义了基本的Enter,Update,Exit三种状态行为,通常在这三种状态行为的方法里会写一些逻辑。每个State都会有StateID(状态id,可以是枚举等),FSMControl(控制该状态的状态控制器的引用),Check方法(用来进行状态判断,并返回StateID,通过FSMControl驱动)
FSMControl,包含了一下FSMMachine,封装层。
FSMMachine,驱动它的State列表,Update方法调用当前State的Check方法来获得StateID,当currentState的Check方法返回的StateID和当前StateID不同,则切换状态。
这是一个简单的FSM状态机系统,根据需要自己写个Control继承FSMControl来驱动状态。因为Check是State的职责,所以每一个不同对象的行为如Human的Idle和Dog的Idel区分肯定也不同。因此需要分别去写HumanIdleState和DogIdleState。如果还有Cat,Fish,可想而知代码量会有多么庞大。
因此我将FSMControl抽象为一个公共基类,把State的Check具体实现作为FSMControl的Virtual方法。这样在IdleState里的Check方法就不用写具体的状态切换判断逻辑,而是调用它FSMControl子类(自己写的继承自FSMControl的Control类)的重写方法
这样每次添加的新对象只要有Idle这个状态,就可以用一个公用的StateIdle,状态切换的逻辑差异放在Control层
因人而异,可以去简单了解一下要说的插件,没用过也可以,至少你知道这个插件了!
插件名 | 作用 |
---|---|
shader graph | 制作shader光影效果 |
cinemachine+timeline+postprocessingstack | 制作过场动画 |
nodecanvas | 制作怪物ai |
easytouch | 手游触摸控制 |
DoTween | 动画插件 |
Fungus | 对话插件 |
3D WebView | 浏览器插件 |
Vectrosity | 划线插件 |
AVPro Video | 视频播放插件 |
Rigidbody具有完全真实物理的特性,⽽CharacterController可以说是受限的
Rigidbody,具有⼀定的物理效果但不是完全真实的。
答:射线是3D世界中一个点向一个方向发射的一条无终点的线,在发射轨迹中与其他物体发生碰撞时,它将停止发射 。
Hinge Joint,可以模拟两个物体间用一根链条连 接在一起的情况,能保持两个物体在一个固定距 离内部相互移动而不产生作用力,但是达到固定 距离后就会产生拉力。
两个物体都必须带有碰撞器Collider,其中一个物体还必须带有Rigidbody刚体
碰撞器是触发器的载体,而触发器只是碰撞器身 上的一个属性。
当Is Trigger=false时,碰撞器根据物理引擎引发 碰撞,产生碰撞的效果,可以调用 OnCollisionEnter/Stay/Exit函数;
当Is Trigger=true时,碰撞器被物理引擎所忽略, 没有碰撞效果,可以调用OnTriggerEnter/Stay/ Exit函数。
如果既要检测到物体的接触又不想让碰撞检测影 响物体移动或要检测一个物件是否经过空间中的 某个区域这时就可以用到触发器
射线是3D世界中一个点向一个方向发射的一条无 终点的线,在发射轨迹中与其他物体发生碰撞 时,它将停止发射 。
rigidbody.AddForce/AddForceAtPosition,都在 rigidbody系列函数中。
穿透(碰撞检测失败)
从一个起点向一个方向发射一条物理射线,返回碰撞到的物体的碰撞信息
简单来说在一个Canvas下,需要相同的material,相同的纹理以及相同的Z值。例如UI上的字体Texture使用的是字体的图集,往往和我们自己的UI图集不一样,因此无法合批。还有UI的动态更新会影响网格的重绘,因此需要动静分离。
Sprite作为UI精灵使用,Texture作用模型贴图使用。
屏幕分辨率的自适应 性,原理就是计算出屏幕的宽高比跟原来的预设 的屏幕分辨率求出一个对比值,然后修改摄像机 的size。
主要有关节动画、⻣骼动画、单一网格模型动画(关键 帧动画)。
用户提供的模型骨架和Unity的骨架结构进行适配,是一种骨架映射关系。
方便动画的重定向
AnimationType有三种类型
Humanoid人型:可以动画重定向,游戏对象挂载animator,子类原始模型+重定向模型,设置原始模型和使用模型的AnimationType为Humanoid类型
Generic非人型
Legacy旧版
Avator Mask身体遮罩,身体某一部分是否受到动画影响
反向动力学 IK,通过手或脚来控制身体其他部分
动画淡入淡出
SkinnedMesh蒙皮网格动画
分为骨骼和蒙皮两部分
骨骼是一个层次结构,存储了骨骼的Transform数据
蒙皮是mesh顶点附着在骨骼之上,顶点可以被多个骨骼影响,决定了其权重等,
还有将顶点从Mesh空间变换到骨骼空间~
动画分层
身体部位动画分层,比如我只想动动头,身体其他部分不发生动画,可以方便处理动画区分
协程Coroutine在Unity中一直扮演者重要的角色。
可以实现简单的计时器、将耗时的操作拆分成几个步骤分散在每一帧去运行等等,而尽量不阻塞主线程运行。
在主线程运行时同时开启另一段逻辑处理,来协助当前程序的执行。
换句话说,开启协程就是开启一个线程。可以用来控制运动、序列以及对象的行为。
多线程程序同时运行多个线程 ,而在任一指定时刻只 有一个协程在运行,并且这个正在运行的协同程序只 在必要时才被挂起。
除主线程之外的线程无法访问Unity3D的对象、组件、 方法。
Unity3d没有多线程的概念,不过unity也给我们提供了 StartCoroutine(协同程序)和LoadLevelAsync(异步 加载关卡)后台加载场景的方法。
StartCoroutine为什 么叫协同程序呢,所谓协同,就是当你在 StartCoroutine的函数体里处理一段代码时,利用yield 语句等待执行结果,这期间不影响主程序的继续执 行,可以协同工作。
官方案例)
function Start() {
// - After 0 seconds, prints "Starting 0.0"
// - After 0 seconds, prints "Before WaitAndPrint
Finishes 0.0"
// - After 2 seconds, prints "WaitAndPrint 2.0" // 先打印"Starting 0.0"和"Before WaitAndPrint
Finishes 0.0"两句,2秒后打印"WaitAndPrint 2.0" print ("Starting " + Time.time );
// Start function WaitAndPrint as a coroutine. And continue execution while it is running
// this is the same as WaintAndPrint(2.0) as the compiler does it for you automatically
// 协同程序WaitAndPrint在Start函数内执行,可以视 同于它与Start函数同步执行.
StartCoroutine(WaitAndPrint(2.0));
print ("Before WaitAndPrint Finishes " + Time.time );
}
function WaitAndPrint (waitTime : float) {
// suspend execution for waitTime seconds // 暂停执行waitTime秒
yield WaitForSeconds (waitTime);
print ("WaitAndPrint "+ Time.time );
}
作用:一个协同程序在执行过程中,可以在任意位置使
用yield语句。yield的返回值控制何时恢复协同程序向 下执行。协同程序在对象自有帧执行过程中堪称优 秀。协同程序在性能上没有更多的开销。 缺点:协同程序并非真线程,可能会发生堵塞。
更多协程内容:Unity零基础到入门 ☀️| 小万字教程 对 Unity 中的 协程 ❤️全面解析+实战演练❤️
//获取的目录路径最后不包含 /
//获得的文件路径开头包含 /
Application.dataPath; //Asset文件夹的绝对路径
//只读
Application.streamingAssetsPath; //StreamingAssets文件夹的绝对路径(要先判断是否存在这个文件夹路径)
Application.persistentData ; //可读写
//资源数据库 (AssetDatabase) 是允许您访问工程中的资源的 API
AssetDatabase.GetAllAssetPaths; //获取所有的资源文件(不包含meta文件)
AssetDatabase.GetAssetPath(object) //获取object对象的相对路径
AssetDatabase.Refresh(); //刷新
AssetDatabase.GetDependencies(string); //获取依赖项文件
Directory.Delete(p, true); //删除P路径目录
Directory.Exists(p); //是否存在P路径目录
Directory.CreateDirectory(p); //创建P路径目录
AssetDatabase //类库,对Asset文件夹下的文件进行操作,获取相对路径,获取所有文件,获取相对依赖项
Directory //类库,相关文件夹路径目录进行操作,是否存在,创建目录,删除等操作
PlayerPrefs类是一个本地持久化保存与读取数据的类
PlayerPrefs类支持3中数据类型的保存和读取,浮点型,整形,和字符串型。
分别对应的函数为:
SetInt();保存整型数据;GetInt();读取整形数据;
SetFloat();保存浮点型数据; GetFlost();读取浮点型数据;
SetString();保存字符串型数据; GetString();读取字符串型数据;
using UnityEditor;
using System.IO;
public class CreateAssetBundles //进行AssetBundle打包
{
[MenuItem("Assets/Build AssetBundles")]
static void BuildAllAssetBundles()
{
string dir = "AssetBundles";
if (Directory.Exists(dir) == false)
{
Directory.CreateDirectory(dir);
}
BuildPipeline.BuildAssetBundles(dir, //路径必须创建
BuildAssetBundleOptions.ChunkBasedCompression, //压缩类型***
BuildTarget.StandaloneWindows64);//平台***
}
}
None | Build assetBundle without any special option.(LAMA压缩,压缩率高,解压久) |
---|---|
UncompressedAssetBundle | Don’t compress the data when creating the asset bundle.(不压缩,解压快) |
ChunkBasedCompression | Use chunk-based LZ4 compression when creating the AssetBundle. |
(压缩率比LZMA低,解压速度接近无压缩)|
第一种
IEnumerator Start()
{
string path = "AssetBundles/wall.unity3d";
AssetBundleCreateRequest request =AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
yield return request;
AssetBundle ab = request.assetBundle;
GameObject wallPrefab = ab.LoadAsset<GameObject>("Cube");
Instantiate(wallPrefab);
}
第二种
IEnumerator Start()
{
string path = "AssetBundles/wall.unity3d";
AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(path);
yield return request;
AssetBundle ab = request.assetBundle;
GameObject wallPrefab = ab.LoadAsset<GameObject>("Cube");
Instantiate(wallPrefab);
}
第三种
IEnumerator Start()
{
string uri = @"http://localhost/AssetBundles/cubewall.unity3d";
UnityWebRequest request = UnityWebRequest.GetAssetBundle(uri);
yield return request.Send();
AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request);
GameObject wallPrefab = ab.LoadAsset<GameObject>("Cube");
Instantiate(wallPrefab);
}
第四种WWW(无依赖)
private IEnumerator LoadNoDepandenceAsset()
{
string path = "";
if (loadLocal)
{
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
path += "File:///";
#endif
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
path += "File://";
#endif
path += assetBundlePath + "/" + assetBundleName;
//www对象
WWW www = new WWW(path);
//等待下载【到内存】
yield return www;
//获取到AssetBundle
AssetBundle bundle = www.assetBundle;
//加载资源
GameObject prefab = bundle.LoadAsset<GameObject>(assetRealName);
//Test:实例化
Instantiate(prefab);
}
第四种WWW(有依赖)
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
public class LoadAssetsDemo : MonoBehaviour
{
[Header("版本号")]
public int version = 1;
[Header("加载本地资源")]
public bool loadLocal = true;
[Header("资源的bundle名称")]
public string assetBundleName;
[Header("资源的真正的文件名称")]
public string assetRealName;
//bundle所在的路径
private string assetBundlePath;
//bundle所在的文件夹名称
private string assetBundleRootName;
private void Awake()
{
assetBundlePath = Application.dataPath + "/OutputAssetBundle";
assetBundleRootName = assetBundlePath.Substring(assetBundlePath.LastIndexOf("/") + 1);
Debug.Log(assetBundleRootName);
}
IEnumerator LoadAssetsByWWW()
{
string path="";
//判断是不是本地加载
if(loadLocal)// loadLocal=true为本地资源
{
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
path+="File:///";
#endif
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
path+="File://";
#edif
}
//获取要加载的资源路径【bundle的总说明文件】
path+=assetBundle+"/"+assetBundleRootName;
//加载
WWW www=WWW.LoadFromCacheOrDownload(path,version);
yield return www;
//拿到其中的bundle
AssetBundle manifestBundle=www.assetsBundle;
//获取到说明文件
AssetBundleManifest manifest=manifest.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
//获取资源的所有依赖
string[] dependencies=manifest.GetAllDependencies(assetBundleName);
//卸载Bubdle和解压出来的manifest对象
manifestBundle.Unload(true);
//获取到相对路径
path =path.Remove(path.LastIndexOf("/")+1);
//声明依赖的Bundle数组
AssetBundle[] depAssetBundle=new AssetBundle[dependencies.Length];
//遍历加载所有的依赖
for(int i=0;i<dependencies.Length;i++)
{ //获取到依赖Bundle的路径
string depPath=path+ dependencies[i];
//获取新的路径进行加载
www=WWW.LoadFromCacheOrDownload(depPath,version);
yield return www;
//将依赖临时保存
depAssetBundles[i]=www.assetsBundle;
}
//获取路径
path+=assBundleName;
//加载最终资源
www=WWW.LoadFromCacheOrDownload(path,version);
//等待下载
yield return www;
//获取到真正的AssetBundle
AssetBundle realAssetBundle=www.assBunle;
//加载真正的资源
GameObject prefab=realAssetBundle.LoadAsset<GameObject>(assetBundle);
//生成
Instantiate(prefab);
//卸载依赖
for(int i-0;i<depAssetBundle.Length;i++)
{
depAssetBundle[i].Unload(true);
}
realAssetBundle.Unload(true);
}
}
AssetBundle.Unload(bool),T
true卸载所有资源
false只卸载没使用的资源,而正在使用的资源与AssetBundle依赖关系会丢失,调用Resources.UnloadUnusedAssets可以卸载。
或者等场景切换的时候自动调用Resources.UnloadUnusedAssets。
三种方式
第一种:官方不推荐
第二种:如果Resource文件下的Lua文件,使用Lua的Require函数即可
第三种:如果Lua文件是下载的,使用自定义Loader可满足
- BuildPipeLine.BuildAssetBundles(输出AB包路径)
- File.WriteAllLines(将依赖项写入文件里)
2. 加载资源
- 异步加载资源AB包,AssetBundleRequest请求,AssetBundle.LoadFromFileAsync
- 先检查依赖项,再异步加载AB包依赖项
- 加载成功后都有对应的回调方法,将资源作为参数传入
序列化 简单理解成把对象转换为容易传输的格式的过程。
⽐如,可以序列化⼀个对象,然后使⽤HTTP通过Internet在客户端和服务器端之间传输该对象
UDP协议全称是用户数据报协议
TCP协议全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。三次握手、四次挥手
TCP:
网络接口层:这是协议栈的最低层,对应OSI的物理层和数据链路层,主要完成数据帧的实际发送和接收。
网络层:处理分组在网络中的活动,例如路由选择和转发等,这一层主要包括IP协议、ARP、ICMP协议等。
传输层:主要功能是提供应用程序之间的通信,这一层主要是TCP/UDP协议。
应用层:用来处理特定的应用,针对不同的应用提供了不同的协议,例如进行文件传输时用到的FTP协议,发送email用到的SMTP等。
什么是粘包?
答:顾名思义,其实就是多个独立的数据包连到一块儿。
什么情况下需要考虑粘包?
答:实际情况如下:
所以说:Tcp连续发送消息的时候,会出现消息一起发送过来的问题,这时候需要考虑粘包的问题。
粘包出现的原因 (在流传输中,UDP不会出现粘包,因为它有消息边界。)
解决粘包
注:要记住这一点:TCP对上层来说是一个流协议,所谓流,就是没有界限的一串数据.大家可以想想河里的流水,是连成一片的,其间是没有分界线的,也就是没有包的概念。所以我们必须自己定义包长或者分隔符来区分每一条消息。
项目中采用了socket通信,通过TCP发送数据给服务器端,因为项目需要,要同时开启大量的线程去发送不同的数据给服务器端,然后服务器端返回不同的数据。由于操作频繁,经常会阻塞,或没有接收到服务器端返回的数据;
因此考虑到使用一个队列:将同一ip下的数据存入一个队列中,通过队列协调发送;当第一条数据发送出去没有收到服务器端返回的数据时,让第二条数据插入队列中排队,当第三条数据也发送出来后,继续排队,以此类推;
如果当第四条数据发出来的时候,存入队列中,第一条数据收服务器端返回数据后,队列中的第二条第三条数据就扔掉,直接发送第四条数据
LightMap:就是指在三维软件⾥实现打好光,然后渲染把场景各表⾯的光照输出到贴图上,最后⼜通过引擎贴到场景上,这样就使物体有了光照的感觉。
MipMapping:在三维计算机图形的贴图渲染中有常⽤的技术,为加快渲染进度和减少图像锯⻮,贴图被处理成由⼀系列被预先计算和优化过的图⽚组成的⽂件,这样的贴图被称为MipMap。
Alpha Test,中文就是透明度测试。
简而言之就是V&F shader中最后fragment函数输出的该点颜色值(即上一讲frag的输出half4)的alpha值与固定值进行比较。Alpha Test语句通常于Pass{}中的起始位置。Alpha Test产生的效果也很极端,要么完全透明,即看不到,要么完全不透明。
没有区别,因为不管几张贴图只渲染一次。
可以有cookies – 带有 alpha通道的立方图(Cubemap )纹理。点光源是最耗费资源的。
原理就是对水面的贴图纹理进行扰动,以产生波光玲玲的效果。用shader可以通过GPU在像素级别作扰动,效果细腻,需要的顶点少,速度快
修改sharedMaterial将改变所有物体使用这个材质 的外观,并且也改变储存在工程里的材质设置。
不推荐修改由sharedMaterial返回的材质。如果你 想修改渲染器的材质,使用material替代。
是指在显示器上为了显示出图像⽽经过的⼀系列必要 操作。 渲染管道中的很多步骤,都要将⼏何物体从⼀个坐标系中变换到另⼀个坐标系中去。
主要步骤有: 本地坐标->视图坐标->背⾯裁剪->光照->裁剪->投影-> 视图变换->光栅化。
GPU工作流程:顶点处理、光栅化、纹理贴图、像素处理
总结:GPU的⼯作通俗的来说就是完成3D图形的⽣成,将图形映射到相应的像素点上,对每个像素进⾏ 计算确定最终颜⾊并完成输出。
在Game视图右上⻆点击Stats。降低Draw Call 的技术是Draw Call Batching
diffuse = Kd x colorLight x max(N*L,0);
Kd 漫反射系数、colorLight 光的颜⾊、N 单位法线向量、L 由点指向光源的单位向量、其中N与L点乘,如果结果⼩于等于0,则漫反射为0。
本影和半影:
本影:景物表⾯上那些没有被光源直接照射的区域
(全⿊的轮廓分明的区域)。
半影:景物表⾯上那些被某些特定光源直接照射但并⾮被所有特定光源直接照射的区域(半明半暗区域)
⼯作原理:从光源处向物体的所有可⻅⾯投射光线,将这些⾯投影到场景中得到投影⾯,再将这些投影⾯与场景中的其他平⾯求交得出阴影多边形,保存这些阴影多边形信息,然后再按视点位置对场景进⾏相应处理得到所要求的视图(利⽤空间换时间,每次只需依据视点位置进⾏⼀次阴影计算即可,省去了⼀次消隐过程)
把A组物体的渲染对列⼤于B物体的渲染队列。
作用就是Alpha混合。公式:最终颜色 = 源颜色源透明值 + 目标颜色(1 - 源透明值)
顶点着⾊器是⼀段执⾏在GPU上的程序,⽤来取代fixed pipeline中的transformation和lighting,Vertex Shader主要操作顶点。
Vertex Shader对输⼊顶点完成了从local space到homogeneous space(⻬次空间)的变换过程,homogeneous space即projection space的下⼀个space。
在这其间共有world transformation, view transformation和projection transformation及lighting⼏个过程
表⾯着⾊器 的抽象层次⽐较⾼,它可以轻松地以简洁⽅式实现复杂着⾊。表⾯着⾊器可同时在前向渲染及延迟渲染模式下正常⼯作。
顶点⽚段着⾊器可以⾮常灵活地实现需要的效果,但是需要编写更多的代码,并且很难与Unity的渲染管线完美集成。
固定功能管线着⾊器可以作为前两种着⾊器的备⽤选择,当硬件⽆法运⾏那些酷炫Shader的时,还可以通过固定功能管线着⾊器来绘制出⼀些基本的内容。
更多优化知识学习文章:【Unity 优化篇】 | 优化专栏《导航帖》,全面学习Unity优化技巧,让我们的Unity技术上升一个档次
对象池就存放需要被反复调⽤资源的⼀个空间,⽐如游戏中要常被大量复制的对象,⼦弹,敌⼈,以及任何重复出现的对象。
Unity中,每次引擎准备数据并通知GPU的过程称为一次Draw Call。DrawCall越高对显卡的消耗就越大。降低DrawCall的方法:
LOD(Level of detail)多层次细节,是最常用的游 戏优化技术。
它按照模型的位置和重要程度决定 物体渲染的资源分配,降低非重要物体的面数和 细节度,从而获得高效率的渲染运算。
如果动态物体共用着相同的材质,那么Unity会自动对 这些物体进行批处理。动态批处理操作是自动完成 的,并不需要你进行额外的操作。
区别:动态批处理一切都是自动的,不需要做任何操 作,而且物体是可以移动的,但是限制很多。
静态批 处理:自由度很高,限制很少,缺点可能会占用更多 的内存,而且经过静态批处理后的所有物体都不可以 再移动了。
有很多种方式,例如
GC垃圾回收机制,避免堆内存溢出,定期回收那些没有有效引用的对象内存
GC优化,就是优化堆内存,减少堆内存,即时回收堆内存
GC归属于CLR
避免:
更多GC内容可查看本篇文章:
Unity零基础到进阶 ☀️| Unity中的 GC及优化 超级全面解析 ☆(ゝω・)v 建议收藏!
最初我们使用了DXT5作为贴图压缩格式,希望能减小贴图的内存占用,但很快发现移动平台的显卡是不支持的。因此对于一张1024x1024大小的RGBA32贴图,虽然DXT5可将它从4MB压缩到1MB,但系统将它送进显卡之前,会先用CPU在内存里将它解压成4MB的RGBA32格式(软件解压),然后再将这4MB送进显存。于是在这段时间里,这张贴图就占用了5MB内存和4MB显存;而移动平台往往没有独立显存,需要从内存里抠一块作为显存,于是原以为只占1MB内存的贴图实际却占了9MB!
所有不支持硬件解压的压缩格式都有这个问题。经过一番调研,我们发现安卓上硬件支持最广泛的格式是ETC,苹果上则是PVRTC。但这两种格式都是不带透明(Alpha)通道的。因此我们将每张原始贴图的透明通道都分离了出来,写进另一张贴图的红色通道里。这两张贴图都采用ETC/PVRTC压缩。渲染的时候,将两张贴图都送进显存。同时我们修改了NGUI的shader,在渲染时将第二张贴图的红色通道写到第一张贴图的透明通道里,恢复原来的颜色:
fixed4 frag (v2f i) : COLOR
fixed4 col;
col.rgb = tex2D(_MainTex, i.texcoord).rgb;
col.a = tex2D(_AlphaTex, i.texcoord).r;
return col * i.color;
fixed4 frag (v2f i) : COLOR
{
fixed4 col;
col.rgb = tex2D(_MainTex, i.texcoord).rgb;
col.a = tex2D(_AlphaTex, i.texcoord).r;
return col * i.color;
}
Unity中导入的每张贴图都有一个启用可读可写(Read/Write Enabled)的开关,对应的程序参数是TextureImporter.isReadable。
选中贴图后可在Import Setting选项卡中看到这个开关。只有打开这个开关,才可以对贴图使用Texture2D.GetPixel,读取或改写贴图资源的像素,但这就需要系统在内存里保留一份贴图的拷贝,以供CPU访问。
一般游戏运行时不会有这样的需求,因此我们对所有贴图都关闭了这个开关,只在编辑中做贴图导入后处理(比如对原始贴图分离透明通道)时打开它。
这样,上文提到的1024x1024大小的贴图,其运行时的2MB内存占用又可以少一半,减小到1MB。
static int Fn(int n) {
if (n <= 0) {
throw new ArgumentOutOfRangeException();
}
if (n == 1||n==2)
{
return 1;
}
return checked(Fn(n - 1) + Fn(n - 2)); // when n>46 memory will overflow
}
string a = new string("abc");
a = (a.ToUpper() +"123").Substring(0,2);
实在C#中第⼀⾏是会出错的(Java中倒是可⾏)。
应该这样初始化:
string b = new string(new char[] {'a','b','c'});
三个临时对象:abc、ABC、AB
public static void BubblingSort(int[]array) {
for (int i = 0; i < array.Length; i++){
for (int j = array.Length - 1; j > 0; j--){
if (array[j] < array[i]) {
int temp = array[j];
array[j] = array[j-1];
array[j - 1] = temp;
}
}
}
}