AssetBundle游戏资源的打包
定义:AssetBundle是unity的一种资源包文件,我们可以将游戏中使用到的游戏资源打包成Assetbundle,在游戏运行时动态加载游戏资源。
动态加载Assetbundle,以及加载过程
- 使用静态方法Assetbundle.LoadFromFile将Assetbundle文件从硬盘加载到内存中
- 之后通过获取到的Assetbundle变量,使用Assetbundle.LoadAsset方法获取Assetbundle中的资源
- 最后通过Instantiate方法实例化出对象
- 优点:可以将资源分块加载,将相关联的资源打包成一个Assetbundle,便于资源的管理。
- 缺点:在每次资源更新之后都需要重新打包。
Resource.Load动态加载,及加载过程
- 可以使用Resources.Load方法加载资源,资源需要放在unity默认的Resources目录中,但在游戏实际开发中并不建议使用。
- Resources.UnloadUnusedAssets() 可以清理内存,可以卸载内存中的GameObject,会把没有使用(或者资源=null)的资源清理掉。
- Resources.UnloadAsset卸载单个资源,只能卸载单个基础资源(如Texture,Sprite),
- 优点:可以不用打包,将资源放入目录就可以在运行时加载了。
- 缺点:Resources目录无法动态更新,所以在游戏发布和版本升级上不适用。
C#中装箱,拆箱
装箱是让一个值类型隐式的转换为引用类型,从任何值类型到object类型; 拆箱是让一个引用类型显式的转换为一个值类型,从object类型到任何值类型
“箱”指的就是托管堆,装箱即指在托管堆中将在栈上的值类型对象封装,生成一份该值类型对象的副本,并返回该副本的地址。而拆箱即是指返回已装箱值类型在托管堆中的地址
- 分配内存: 在托管堆中分配好内存,内存的大小是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员—类型对象指针和同步块索引—所需要的内存量之和。
- 复制对象: 将值类型的字段复制到新分配的内存中。
- 返回地址: 将已装箱的值类型对象的地址返回给引用类型的变量。
- 检查实例:首先检查变量的值是否为null,如果是则抛出NullReferenceException异常;再检查变量的引用指向的对象是不是给定值类型的已装箱对象,如果不是,则抛出InvalidCastException异常。
- 返回地址:返回已装箱实例中属于原值类型字段的地址,而两个额外成员(类型对象指针和同步块索引)则不会返回
C#值类型,引用类型
值类型直接存储其值,而引用类型存储对其值的引用。
值类型: |
派生自System.ValueType:byte,short,int,long,float,double,decimal,char,bool 和 struct |
作为局部变量时,存储在栈上 |
引用类型: |
基类为Objcet:string 和 class |
引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。 |
数组的元素不管是引用类型还是值类型,都存储在托管堆上。
C#的struct和class的区别
- 结构体是值类型,类是引用类型:类在传递的时候,传递的内容是位于托管内存中的位置,结构体在传递的时候,传递的内容是位于程序堆栈区的内容。当类的传递对象修改时,将同时修改源对象,而结构体的传递对象修改时,不会对源对象产生影响。
- 结构体不能被继承,类完全可扩展的,可以继承其他类和接口,自身也能被继承,除非用sealed
- 结构不能包含显式默认构造函数;没有析构函数,没有abstract和sealed(因为不能继承);不能有protected修饰符,可以不使用new初始化。而类有默认的构造函数,有析构函数,可以使用abstract和sealed,有protected修饰符,必须使用new初始化。
unity3d的生命周期
Awake()(当脚本创建的时候调用,仅一次) -> onEnable()(当对象变为可用和激活时调用) -> Start() : (在update方法第一次调用前调用) -> FixedUpdate() (是按照固定时间而非帧率更新的, 多用于物理计算和更新) -> Update() (每一帧时调用) -> LateUpdate() (摄像机跟随的更新) -> OnGUI() (绘制GUI时调用) -> OnDisable() (当对象变为不可用和不激活时调用) -> OnDestory() (当对象销毁时调用)
区别:
- Update() : 大部分游戏行为代码被执行的地方, 是在每次渲染新的一帧的时候才会调用,与当前平台帧数有关, 一般放画面控制逻辑
- FixedUpdate(),是在固定的时间间隔执行,不受游戏帧率的影响, 常用于物理相关的计算, 对Rigidbody的操作
- LateUpdate() 是在所有Update函数调用后被调用,一般放摄像机跟随的更新。所有的update() 操作完才进行摄像机的跟进,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。
unity中的碰撞器和触发器的区别
- 在collider组件上有属性IsTrigger来选择是否为触发器
- 碰撞器彼此之间不会进入对方,而触发器可以
- 当Is Trigger=false时,碰撞器根据物理引擎引发碰撞,碰撞器触发事件时会调用OnCollisionEnter->OnCollisionStay->OnCollisionExit函数。
- 当Is Trigger=true时,碰撞器被物理引擎所忽略,触发器触发事件时会调用OnTriggerEnter->OnTriggerStay->OnTriggerExit函数。
unity的光源有几种类型,分别是什么
- Directional Light:平行光源,如同现实中的太阳光源
- Point Light:点光源,如果现实中的蜡烛
- Spot Light:聚光灯光源,如同现实中的手电筒
- Area Light:区域光源
Coroutines(协同程序)
在主线程运行的同时开启另一段逻辑处理,来协助当前程序的执行,协程很像多线程,但是不是多线程。Unity的协程在每帧结束之后去检测yield的条件是否满足。
lightmap,有什么作用
lightmap就是事先烘焙好的光照贴图,这样可以避免使用实时光照,可以减少Drawcall提高性能。在三维软件里实现打好光,然后渲染把场景各表面的光照输出到贴图上,最后又通过引擎贴到场景上,这样就使物体有了光照的感觉。
ref参数和out参数是什么?有什么区别?
ref和out参数的效果一样,都是通过关键字找到定义在主函数里面的变量的内存地址,并通过方法体内的语法改变它的大小。
不同点:
- 使用ref型参数时,传入的参数必须先被初始化。对out而言,必须在方法中对其完成初始化。
- out适合用在需要retrun多个返回值的地方,而ref则用在需要被调用的方法修改调用者的引用的时候。
- ref参数是引用,out参数为输出参数。
RawImage和Image的区别
- RawImage可以显示任何类型的Texture,而Image只能显示Sprite
- RawImage支持UV Rect,Image支持Simple(简单)/Sliced(切片)/Tiled(平铺)/Filled(填充)
前向渲染 v.s 延后渲染
前向渲染 (Forward Rendering):把空间的点进行各种剪裁后,进行处理。先渲染一遍物体,把法线和高光存在ARGB32的渲染纹理中(法线用rgb通道,高光用a通道),存在了z buffer里;然后通过深度信息,法线和高光信息计算光照(屏幕空间),光照信息缓存在Render Texture中;最后混合。
延后渲染 (Deferred Rendering):将摄像机空间的点光栅化转化成屏幕坐标后再进行处理。先不进行光照运算,对每个像素生成一组数据(G-buffer),包括位置,法线,高光等,然后用这些数据将每个光源以2D后处理的方式施加在最后图像上(屏幕空间),然后只进行了一次光照计算就实现了最终效果。缺点:不适用于半透明。
C#的GC
原理:程序创建出来的对象实例都会被CLR跟踪,CLR都是有记录哪些对象还会被用到(存在引用关系);哪些对象不会再被用到(不存在引用关系)。CLR会整理不会再被用到的对象,在恰当的时机,按一定的规则销毁部分对象,释放出这些对象所占用的内存。
优点:
- 提高了软件开发的抽象度;
- 程序员可以将精力集中在实际的问题上而不用分心来管理内存的问题;
- 可以使模块的接口更加的清晰,减小模块间的偶合;
- 大大减少了内存人为管理不当所带来的Bug;
- 使内存管理更加高效。
避免:
- 减少new产生对象的次数
- 使用公用的对象(静态成员)
- 将String换为StringBuilder. String类型是个不可变的对象,当每次对String进行改变时都需要生成一个新的String对象,然后将指针指向一个新的对象,如果在一个循环里面,不断的改变一个对象,就要不断的生成新的对象,所以效率很低,建议在不断更改String对象的地方不要使用String类型。StringBuilder对象在做字符串连接操作时是在原来的字符串上进行修改,改善了性能.
C#的接口和抽象类的区别
使用抽象类是为了代码的复用,而使用接口的动机是为了实现多态。
相同点:
- 都可以被继承
- 都不能被实例化
- 都可以包含方法声明
- 派生类必须实现未实现的方法
区 别:
- 抽象基类可以定义字段、属性、方法实现。接口只能定义属性、索引器、事件、和方法声明,不能包含字段。
- 抽象类是一个不完整的类,需要进一步细化,而接口是一个行为规范。微软的自定义接口总是后带able字段,证明其是表述一类“我能做。。。”
- 接口可以被多重实现,抽象类只能被单一继承
- 抽象类是从一系列相关对象中抽象出来的概念, 因此反映的是事物的内部共性;接口是为了满足外部调用而定义的一个功能约定, 因此反映的是事物的外部特性
- 接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法
- 抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非虚的,当然您也可以声明为虚的
什么是Drawcall,如何优化Drawcall
定义:引擎每一次对GPU的调用的过程叫做Drawcall,每一个网格(Mesh)使用一个不同的材质(Material)将需要一个单独的Draw Call。
优化:
- 将相关的零散图片资源放到同一个Sprite中,这样多次Drawcall调用机会合并成一次
- 将使用同一图集的对象排序在一起,使用同一图集的对象会被一起绘制以减少Drawcall。
DrawCall多少为好:150以下
Animator里面的状态机
Animator主要包含一个状态机和Avatar, 状态机里管理者各个动画(Animation)的切换条件
- 在场景(Scene)内制造一个GameObject,然后加上Animator组件。
- 在项目文件夹内制造一个AnimatorController并把它链接到上述Animator中。
- 双击 Animator,会出来一个 Animator 面板,拖入三个动作作为状态(第一个拖入的作为默认状态,不过可以右键 Default State 设置其他状态为默认状态)
- 右键 AnyState,Make Transition 连接到三个状态
- 添加状态控制参数 AnimState, 编辑切换状态的条件
- 取消勾选 Can Transition To Self,不然动画会出现抖动
Animator v.s. Animation
- Animation 控制一个动画的播放,而Animator是多个动画之间相互切换,并且Animator 有一个动画控制器,俗称动画状态机。
- Animation只能对UI组件执行动画,但Animator几乎可以对任何对象执行动画
- Animation只有透明度,旋转,平移,伸缩4中属性,而Animator,只要是该控件的属性,且有setter该属性的方法就都可以对该属性执行一种动态变化的效果
static,const,readonly getonly的区别
- const:定义的是静态常量在对象初始化的时候赋值,以后不能改变它的值,属于编译常量
- readonly:只读变量,只能用来修饰类字段,不能修饰局部变量, 属于运行时变量, 可能具有不同的值。
- static:定义的是静态变量,在程序初始化时被分配,直到程序退出前才被释放。
- statc readonly: 在运行时计算出其值的, 能通过静态构造函数来赋值。
什么是反射
反射提供了封装程序集、模块和类型的对象(Type 类型)。可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性。如果代码中使用了属性,可以利用反射对它们进行访问。 Assembly类可以获得正在运行的装配件信息,也可以动态的加载装配件,以及在装配件中查找类型信息,并创建该类型的实例。
Type类可以获得对象的类型信息,此信息包含对象的所有要素:方法、构造器、属性等等,通过Type类可以得到这些要素的信息,并且调用之。
MethodInfo包含方法的信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用之。
如何在不同分辨率下保持UI的一致性
NGUI可以实现屏幕分辨率的自适应性,原理就是计算出屏幕的宽高比跟原来的预设的屏幕分辨率求出一个对比值,然后修改摄像机的size。UGUI通过锚点和中心点和分辨率也解决这个问题
委托是什么?事件是不是一种委托?
委托类似于一种安全的指针引用,在使用它时是当做类来看待而不是一个方法,相当于对一组方法的列表的引用。类似于c或c++中的函数的指针,但函数指针只能引用静态方法,而委托既能引用静态方法,也能引用实例方法。委托可以把一个方法作为参数代入另一个方法。事件是一种特殊的委托 事件的实现依赖于委托
Unity中,照相机的Clipping Planes的作用是什么
剪裁平面 。Near、Fare两个值是从相机到开始渲染和停止渲染之间的距离
简述Unity3D项目中预制组件上出现数据丢失的原因可能有哪些?
一般是组件上绑定的物体对象被删除了
Unity3D中的协程(coroutine)和C#线程之间的区别是什么
多线程程序同时运行多个线程 ,而在任一指定时刻只有一个协程在运行,并且这个正在运行的协同程序只在必要时才被挂起。除主线程之外的线程无法访问Unity3D的对象、组件、方法。
请简述ArrayList和List的主要区别?
ArrayList会把所有插入其中的数据都当做Object来处理,装箱拆箱的操作(费时),List是泛型类,功能跟ArrayList相似,但不存在ArrayList所说的问题。
简述一下对象池,你觉得在FPS里哪些东西适合使用对象池
对象池就存放需要被反复调用资源的一个空间。当一个对象回大量生成的时候如果每次都销毁创建会很费时间,通过对象池把暂时不用的对象放到一个池中(也就是一个集合),当下次要重新生成这个对象的时候先去池中查找一下是否有可用的对象,如果有的话就直接拿出来使用,不需要再创建,如果池中没有可用的对象,才需要重新创建,利用空间换时间来达到游戏的高速运行效果,在FPS游戏中要常被大量复制的对象包括子弹,敌人,粒子等。
简述prefab的用处
在游戏运行时实例化,prefab相当于一个模板,对已经有的素材、脚本、参数做一个默认的配置,以便于以后的修改,同时prefab打包的内容简化了导出的操作,便于团队的交流。
目录作用
Plugins:扩展unity功能的插件
Editor:主要用来扩展unity编辑器的功能
Resources:动态加载游戏资源到场景中
StreamingAssets:存放需要保留原格式的资源
卡顿怎么解决,比如背包系统有3000个物品,用户操作会很卡,怎么解决
C#中四种访问修饰符是哪些?各有什么区别?
1. 属性修饰符:
- Serializable:按值将对象封送到远程服务器。
- STATread:是单线程套间的意思,是一种线程模型。
- MATAThread:是多线程套间的意思,也是一种线程模型。
2. 存取修饰符:
- public:存取不受限制。
- private:只有包含该成员的类可以存取。
- internal:只有当前工程可以存取。
- protected:只有包含该成员的类以及派生类可以存取。
3. 类修饰符:
- abstract:抽象类。指示一个类只能作为其它类的基类。
- sealed:密封类。指示一个类不能被继承。
4. 成员修饰符:
- abstract:指示该方法或属性没有实现。
- sealed:密封方法。可以防止在派生类中对该方法的override(重载)。不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed修饰符总是和override修饰符同时使用。
- delegate:委托。用来定义一个函数指针。C#中的事件驱动是基于delegate + event的。
- const:指定该成员的值只读不允许修改。
- event:声明一个事件。
- extern:指示方法在外部实现。
- override:重写。对由基类继承成员的新实现。
- readonly:指示一个域只能在声明时以及相同类的内部被赋值。
- static:指示一个成员属于类型本身,而不是属于特定的对象。即在定义后可不经实例化,就可使用。
- virtual:指示一个方法或存取器的实现可以在继承类中被覆盖。
- new:在派生类中隐藏指定的基类成员,从而实现重写的功能。 若要隐藏继承类的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。
参考资料:
https://blog.csdn.net/Fenglele_Fans/article/details/80178463