Unity 面试篇|(二)Unity基础篇 【全面总结 | 持续更新】

目录

1.Unity3d脚本从唤醒到销毁有着一套比较完整的生命周期,列出系统自带的几个重要的方法。
  • Awake —> OnEnable —> Start —> FixedUpdate —>Update —> LateUpdate—> OnGUl —> OnDisable —> OnDestroy
  • 主要执行顺序
    • 编辑器->初始化->物理系统->输入事件->游戏逻辑->场景渲染->GUI渲染->物体激活或禁用->销毁物体->应用结束
    • 主要函数介绍
      Reset 是在用户点击检视面板的Reset按钮或者首次添加该组件时被调用。此函数只在编辑模式下被调用。Reset最常用于在检视面板中给定一个最常用的默认值。
      Awake 用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次,当脚本设置为不可用时,运行时Awake方法仍然会执行一次。Awake在所有对象被初始化之后调用,所以你可以安全的与其他对象对话或用诸如 GameObject.FindWithTag 这样的函数搜索它们。每个游戏物体上的Awke以随机的顺序被调用。因此,你应该用Awake来设置脚本间的引用,并用Start来传递信息 ,Awake总是在Start之前被调用。它不能用来执行协同程序。
      OnEnable当对象变为可用或激活状态时被调用事件监听。
      Start 在behaviour的生命周期中只被调用一次。它和Awake的不同是Start只在脚本实例被启用时调用。你可以按需调整延迟初始化代码。Awake总是在Start之前执行。这允许你协调初始化顺序。
      FixedUpdate 当MonoBehaviour启用时,其在每一帧被调用。处理Rigidbody时,需要用FixedUpdate代替Update。例如:给刚体加一个作用力时,你必须应用作用力在FixedUpdate里的固定帧,而不是Update中的帧。(两者帧长不同)。
      Update 是实现各种游戏行为最常用的函数。
      LateUpdate 每帧调用一次(在 在所有Update函数调用后被调用) 用于更新游戏场景和状态,和摄像机相关的更新。 官网上例子是摄像机的跟随,都是所有的Update操作完才进行摄像机的跟进,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。
      OnGUI 渲染和处理GUI事件时调用。这意味着你的OnGUI程序将会在每一帧被调用。要得到更多的GUI事件的信息查阅Event手册。如果Monobehaviour的enabled属性设为false,OnGUI()将不会被调用。
      OnDisable 不能用于协同程序。当对象变为不可用或非激活状态时此函数被调用。
      OnDestroy 当对象被销毁时调用。
      OnApplicationQuit 当用户停止运行模式时在编辑器中调用。当web被关闭时在网络播放器中被调用。
2.Unity3D中的碰撞器和触发器的区别?
  • 碰撞器是触发器的载体,而触发器只是碰撞器身上的一个属性。
    • 当Is Trigger=false时,碰撞器根据物理引擎引发碰撞,产生碰撞的效果,可以调用OnCollisionEnter/Stay/Exit函数;
    • 当Is Trigger=true时,碰撞器被物理引擎所忽略,没有碰撞效果,可以调用OnTriggerEnter/Stay/Exit函数。
  • 如果既要检测到物体的接触又不想让碰撞检测影响物体移动或要检测一个物件是否经过空间中的某个区域这时就可以用到触发器。
3.物体发生碰撞的必要条件?
  • 两个物体都必须带有碰撞器Collider,其中一个物体还必须带有Rigidbody刚体。
4.简述Unity3D支持的作为脚本的语言的名称?
  • Unity的脚本语言基于Mono的.Net平台上运行,可以使用.NET库,这也为XML、数据库、正则表达式等问题提供了很好的解决方案。
  • Unity里的脚本都会经过编译,他们的运行速度也很快。这三种语言实际上的功能和运行速度是一样的,区别主要体现在语言特性上。
  • Unity支持的语言:C#,JavaScrip(不在使用)
5. .Net与Mono的关系?
  • mono是.net的一个开源跨平台工具,就类似java虚拟机,java本身不是跨平台语言,但运行在虚拟机上就能够实现了跨平台。
  • .net只能在windows下运行,mono可以实现跨平台编译运行,可以运行于Linux,Unix,Mac OS等。
6.OnEnable、Awake、Start运行时的发生顺序?哪些可能在同一个对象周期中反复的发生?
  • Awake–>OnEnable->Start
  • OnEnable在同一周期中可以反复地发生!
7.移动相机动作在哪个函数里,为什么在这个函数里?
  • LateUpdate,是在所有的Update结束后才调用,比较适合用于命令脚本的执行。
    官网上例子是摄像机的跟随,都是所有的Update操作完才进行摄像机的跟进,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。
8.Unity提供了几种光源,分别是什么?
  • 四种。
    • 平行光:Directional Light
    • 点光源:Point Light
    • 聚光灯:Spot Light
    • 区域光源:Area Light
9.简述四元数Quaternion的作用,四元数对欧拉角的优点?
  • 四元数⽤于表示旋转,对旋转⻆度进⾏计算时⽤到四元数
    相对欧拉⻆的优点:
    • 能进⾏增量旋转
    • 避免万向锁
    • 给定⽅位的表达⽅式有两种,互为负(欧拉⻆有⽆数种表达⽅式)
10.CharacterController和Rigidbody的区别?
  • Rigidbody具有完全真实物理的特性,而CharacterController可以说是受限的的Rigidbody,具有一定的物理效果但不是完全真实的。
11.简述prefab的用处
  • 在游戏运行时实例化,prefab相当于一个模板,对你已经有的素材、脚本、参数做一个默认的配置,主要用于经常会用到的物体做成一个集合方便反复使用,以便于以后的修改,同时prefab打包的内容简化了导出的操作,便于团队的交流。
12.简述进程、线程、协程的概念
  • 进程
    保存在硬盘上的程序运行以后,会在内存空间里形成一个独立的内存体,这个内存体有自己独立的地址空间,有自己的堆,不同进程间可以进行进程间通信,上级挂靠单位是操作系统。一个应用程序相当于一个进程,操作系统会以进程为单位,分配系统资源(CPU 时间片、内存等资源),进程是资源分配的最小单位。
  • 线程
    线程从属于进程,也被称为轻量级进程,是程序的实际执行者。线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条- 线程并行执行不同的任务。一个线程只有一个进程。
    每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
    线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。
  • 协程
    协程是伴随着主线程一起运行的一段程序。
    协程与协程之间是并行执行,与主线程也是并行执行,同一时间只能执行一个协程提起协程,自然是要想到线程,因为协程的定义就是伴随主线程来运行的。
    一个线程可以拥有多个协程,协程不是被操作系统内核所管理,而完全是由程序所控制。
    协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
    协成是单线程下由应用程序级别实现的并发。
13.简述协程的作用
  • 在Unity中只有主线程才能访问Unity3D的对象、方法、组件。当主线程在执行一个对资源消耗很大的操作时,在这一帧我们的程序就会出现帧率下降,画面卡顿的现象!
    那这个时候我们就可以利用协程来做这件事,因为协程是伴随着主线程运行的,主线程依旧可以丝滑轻松的工作,把脏活累活交给协程处理就好了!简单来说:协程是辅助主线程的操作,避免游戏卡顿。
14.简述协程的底层原理
  • 协程是通过迭代器来实现功能的,通过关键字IEnumerator来定义一个迭代方法。
    StartCoroutine 接受到的是一个 IEnumerator ,这是个接口,并且是枚举器或迭代器的意思。
    yield 是 C#的一个关键字,也是一个语法糖,背后的原理会生成一个类,并且也是一个枚举器,而且不同于 return,yield 可以出现多次。
    yield 实际上就是返回一次结果,因为我们要一次一次枚举一个值出来,所以多个 yield 其实是个状态模式,第一个 yield 是状态 1,第二个 yield 是状态 2,每次访问时会基于状态知道当前应该执行哪一个 yield,取得哪一个值。
  • 从程序的角度讲,协程的核心就是迭代器。
    想要定义一个协程方法有两个因素,第一:方法的返回值为 IEnumerator 。第二,方法中有 yield关键字。
    当代码满足以上两个条件时,此方法的执行就具有了迭代器的特质,其核心就是 MoveNext方法。
    方法内的内容将会被分成两部分:yield 之前的代码和 yield 之后的代码。yield之前的代码会在第一次执行MoveNext时执行, yield之后的代码会在第二次执行MoveNext方法时执行。
    而在Unity中,MoveNext的执行时机是以帧为单位的,无论你是设置了延迟时间,还是通过按钮调用MoveNext,亦或是根本没有设置执行条件,Unity都会在每一帧的生命周期中判断当前帧是否满足当前协程所定义的条件,一旦满足,当前帧就会抽出CPU时间执行你所定义的协程迭代器的MoveNext。
  • 注意,只要方法中有yield语句,那么方法的返回值就必须是 IEnumerator ,不然无法通过编译。
15.线程与协程的区别
  • 协程:即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程实际上是在一个线程中,只不过每个协程对CPU进行分时,协程可以访问和使用unity的所有方法和component。同一时间只能执行某个协程。开辟多个协程开销不大。协程适合对某任务进行分时处理。
  • 线程:多线程是阻塞式的,每个IO都必须开启一个新的线程,但是对于多CPU的系统应该使用thread,尤其是有大量数据运算的时刻,但是IO密集型就不适合;而且thread中不能操作unity的很多方法和component。同一时间可以同时执行多个线程。开辟多条线程开销很大。线程适合多任务同时处理。
  • 线程和协同程序的主要不同在于:在多处理器情况下,从概念上来讲多线程程序同时运行多个线程;而协同程序是通过协作来完成,在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只在必要时才会被挂起。
16.简述Invoke与InvokeRepeating
  • Invoke
    Invoke() 方法是 Unity3D 的一种委托机制
    如: Invoke(“Test”, 3); 它的意思是:3 秒之后调用 Test() 方法;
  • 使用 Invoke() 方法需要注意 以下3点:
    • 它应该在 脚本的生命周期里的(Start、Update、OnGUI、FixedUpdate、LateUpdate)中被调用;
    • Invoke(); 不能接受含有参数的方法;
    • 在 Time.ScaleTime = 0; 时, Invoke() 无效,因为它不会被调用。
      InvokeRepeating
      InvokeRepeating(“Test”, 3 , 5);
      这个方法的意思是指:3 秒后调用 Test() 方法,并且之后每隔 5 秒调用一次 Test() 方法。
17.简述Invoke与协程的区别
  • Invoke方法:执行没有被挂起,相当于设置完被调用函数的执行时间后即时向下执行。应用到每隔一段时间执行某个函数很方便。
  • Coroutine方法:新开一条执行序列(跟新建线程差不多)并挂起,等待中断指令结束。开销不大。当需要挂起当前执行时使用。
    协程的效率比Invoke高。
18.正在运行的脚本,隐藏物体与禁止脚本导致触发OnDisable时,Invoke与coroutine是否正常运行?
  • 只将脚本禁止:都会正常运行。
    如果把物体直接隐藏:Invoke正常运行,coroutine不会正常运行。
  • 原因:因为游戏物体隐藏了,一切与游戏物体相关的脚本生命周期都会停止,协程自然也会停止 ;
    如果游戏对象没有隐藏,只是将脚本隐藏,游戏对象照样可以通过反射获取协程迭代器对象继续协程的执行。
19.在物体发生碰撞的整个过程中,有几个阶段,分别列出对应的函数 三个阶段
  • OnCollisionEnter、 OnCollisionStay、 OnCollisionExit
20.Unity3d的物理引擎中,有几种施加力的方式,分别描述出来
  • rigidbody.AddForce
  • rigidbody.AddForceAtPosition
21. 物体自身旋转使用的函数?物体绕某点旋转使用函数叫什么?
  • 自身旋转:transform.Rotate()
  • 绕某点旋转:transform.RotateAround
22.Unity3d提供了一个用于保存和读取数据的类(PlayerPrefs),请列出保存和读取整形数据的函数
  • PlayerPrefs类是一个本地持久化保存与读取数据的类
  • PlayerPrefs类支持3中数据类型的保存和读取,浮点型,整形,和字符串型。
  • 分别对应的函数为:
    • SetInt();保存整型数据;GetInt();读取整形数据;
    • SetFloat();保存浮点型数据; GetFlost();读取浮点型数据;
    • SetString();保存字符串型数据; GetString();读取字符串型数据;
23.Image和RawImage的区别
  • Imgae比RawImage更消耗性能
  • Image只能使用Sprite属性的图片,但是RawImage什么样的都可以使用
  • Image适合放一些有操作的图片,裁剪平铺旋转什么的,针对Image Type属性
  • RawImage就放单独展示的图片就可以,性能会比Image好很多
24.在场景中放置多个Camera并同时处于活动状态会发生什么?
  • 受Camera覆盖各场景物件均同时实时绘制,主Camera视场里有多个Camera的渲染合集。可以用depth(深度),Layer(层)+ Culling Mask,enable = false/true来控制,或者调整Viewport Rect可以调整不同摄像机的显示内容。
25.如何销毁一个UnityEngine.Object及其子类?
  • 使用Destroy()方法;
26.请描述游戏动画有哪几种,以及其原理?
  • 主要有关节动画、骨骼动画、单一网格模型动画(关键帧动画)。
    • 关节动画:把角色分成若干独立部分,一个部分对应一个网格模型,部分的动画连接成一个整体的动画,角色比较灵活,Quake2中使用这种动画;
    • 骨骼动画:广泛应用的动画方式,集成了以上两个方式的优点,骨骼按角色特点组成一定的层次结构,有关节相连,可做相对运动,皮肤作为单一网格蒙在骨骼之外,决定角色的外观;
    • 单一网格模型动画:由一个完整的网格模型构成,在动画序列的关键帧里记录各个顶点的原位置及其改变量,然后插值运算实现动画效果,角色动画较真实。
27.请描述为什么Unity3d中会发生在组件上出现数据丢失的情况
  • 一般是组件上绑定的对象被删除了,导致组件找不到该对象了而出现数据丢失现象。或者对象在Editor外部被删除和移动位置。
28.请描述Interface与抽象类之间的不同
  • 语法不同处:
    抽象类中可以有字段,接口没有。
    抽象类中可以有实现成员,接口只能包含抽象成员。
    抽象类中所有成员修饰符都可以使用,接口中所有的成员都是对外的,所以不需要修饰符修饰。
  • 用法不同处:
    抽象类是概念的抽象,接口关注于行为。
    抽象类的子类与父类的关系是泛化关系,耦合度较高,而实现类和接口之间是实现的关系,耦合度比泛化低。
  • 一个类只能继承一个类,但是可以实现多个接口。
29.如何安全的在不同工程间安全地迁移asset数据?三种方法
  • 将Assets和Library一起迁移
  • 导出包package
  • 用unity自带的assets Server功能
30.Unity3D是否支持写成多线程程序?如果支持的话需要注意什么?
  • 支持:如果同时你要处理很多事情或者与Unity的对象互动小可以用thread,否则使用coroutine。
    Unity3d没有多线程的概念,不过unity也给我们提供了StartCoroutine(协同程序)和LoadLevelAsync(异步加载关卡)后台加载场景的方法。
  • 注意:仅能从主线程中访问Unity3D的组件,对象和Unity3D系统调用。C#中有lock这个关键字,以确保只有一个线程可以在特定时间内访问特定的对象
31.如何让已经存在的GameObject在LoadLevel后不被卸载掉?
  • DontDestroyOnLoad(transform.gameObject);
32.U3D中用于记录节点空间几何信息的组件名称,及其父类名称
  • Transform 父类是 Component
33.向量的点乘、叉乘以及归一化的意义?
  • 叉乘 几何意义:得到一个与这两个向量都垂直的向量,这个向量的模是以两个向量为边的平行四边形的面积
  • 点乘 几何意义:可以用来表征或计算两个向量之间的夹角,以及在b向量在a向量方向上的投影
    点乘描述了两个向量的相似程度,结果越大两向量越相似,还可表示投影
    叉乘得到的向量垂直于原来的两个向量
  • 标准化向量:用在只关系方向,不关心大小的时候
34. 为什么dynamic font在unicode环境下优于static font
  • 使用动态字体时,Unity将不会预先生成一个与所有字体的字符纹理, 静态字体体积会很大。
35.请简述如何在不同分辨率下保持UI的一致性
  • 多屏幕分辨率下的UI布局一般考虑两个问题:
    布局元素的位置,即屏幕分辨率变化的情况下,布局元素的位置可能固定不动,导致布局元素可能超出边界;
    布局元素的尺寸,即在屏幕分辨率变化的情况下,布局元素的大小尺寸可能会固定不变,导致布局元素之间出现重叠等功能。
  • 为了解决这两个问题,在Unity UGUI体系中有两个组件可以来解决问题,分别是布局元素的Rect Transform和Canvas的Canvas Scaler组件。
  • CanvasScaler中UI Scale Mode有三种模式,Constant Pixel Size、Scale With Screen Size、Constant Physical Size,其中第二个就是根据屏幕分辨率来进行缩放适配。在这个模式下,有两个参数,一个是我们在开发过程中的标准分辨率,一个是屏幕的匹配模式,通过这里面的设置,就可以完成多分辨率下的适配问题。
36.请简述OnBecameVisible及OnBecameInvisible的发生时机,以及这一对回调函数的意义
  • 当物体是否可见切换之时。可以用于只需要在物体可见时才进行的计算。
37. 什么叫动态合批?跟静态合批有什么区别?
  • 如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。
    动态批处理操作是自动完成的,并不需要你进行额外的操作。
  • 区别:动态批处理一切都是自动的,不需要做任何操作,而且物体是可以移动的,但是限制很多。静态批处理:自由度很高,限制很少,缺点可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了。
38.动态加载资源的方式?
  • instantiate:最简单的一种方式,以实例化的方式动态生成一个物体。
  • Assetsbundle:即将资源打成 asset bundle 放在服务器或本地磁盘,然后使用WWW模块get 下来,然后从这个bundle中load某个object,unity官方推荐也是绝大多数商业化项目使用的一种方式。
  • Resource.Load:可以直接load并返回某个类型的Object,前提是要把这个资源放在Resource命名的文件夹下,Unity不管有没有场景引用,都会将其全部打入到安装包中
  • AssetDatabase.loadasset :这种方式只在editor范围内有效,游戏运行时没有这个函数,它通常是在开发中调试用的。
39.Unity和cocos2d的区别
  • Unity3D支持C#、javascript等,cocos2d-x 支持c++、Html5、Lua等。
  • cocos2d 开源 并且免费
  • Unity3D支持iOS、Android、Flash、Windows、Mac、Wii等平台的游戏开发,cocos2d-x支持iOS、Android、WP等。
40.获取、增加、删除组件的命令分别是什么?
  • 获取:GetComponent
  • 增加:AddComponent
  • 删除:Destroy
41.Unity中,照相机的Clipping Planes的作用是什么?调整 Near、Far两个值时,应该注意什么?
  • 剪裁平面 。从相机到开始渲染和停止渲染之间的 距离。
42.GPU的工作原理?
  • 简而言之,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图形的生成,将图形映射到相应的像素点上,对每个像素进行计算确定最终颜色并完成输出。
43.使用Unity3d实现2d游戏,有几种方式?
  • 使用本身的GUI、UGUI
  • 把摄像机的Projection(投影)值调为Orthographic(正交投影),不考虑z轴;
  • 使用2d插件,如:2DToolKit、NGUI
44.将Camera组件的ClearFlags选项选成Depth only是什么意思?有何用处?
  • 仅深度,该模式用于对象不被裁剪。
45.在编辑场景时将GameObject设置为Static有何作用?
  • 设置游戏对象为Static将会剔除(或禁用)网格对象当这些部分被静态物体挡住而不可见时。因此,在你的场景中的所有不会动的物体都应该标记为Static。
46.将图片的TextureType选项分别选为Texture和Sprite有什么区别
  • Sprite作为UI精灵使用,Texture作用模型贴图使用。
47.实时点光源的优缺点是什么?
  • 可以有cookies – 带有 alpha通道的立方图(Cubemap )纹理。点光源是最耗费资源的。
48. 如何在Unity3D中查看场景的面数,顶点数和Draw Call数?
  • 在Game视图右上角点击Stats。降低Draw Call 的技术是Draw Call Batching
49. Addcomponent后哪个生命周期函数会被调用
  • 对于AddComponent添加的脚本,其Awake,Start,OnEnable是在Add的当前帧被调用的
    其中Awake,OnEnable与AddComponent处于同一调用链上
  • Start会在当前帧稍晚一些的时候被调用,Update则是根据Add调用时机决定何时调用:如果Add是在当前帧的Update前调用,那么新脚本的Update也会在当前帧被调用,否则会被延迟到下一帧调用。
50.层剔除
  • 用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。
51.画布的三种模式.缩放模式
  • 屏幕空间-覆盖模式(Screen Space-Overlay),Canvas创建出来后,默认就是该模式,该模式和摄像机无关,即使场景内没有摄像机,UI游戏物体照样渲染
  • 屏幕空间:电脑或者手机显示屏的2D空间,只有x轴和y轴
  • 覆盖模式:UI元素永远在3D元素的前面
  • 屏幕空间-摄像机模式(Screen Space-Camera),设置成该模式后需要指定一个摄像机游戏物体,指定后UGUI就会自动出现在该摄像机的“投射范围”内,和NGUI的默认UI Root效果一致,如果隐藏掉摄像机,UGUI当然就无法渲染
  • 世界空间模式(WorldSpace),设置成该模式后UGUI就相当于是场景内的一个普通的“Cube 游戏模型”,可以在场景内任意的移动UGUI元素的位置,通常用于怪物血条显示和VR开发
    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 通过物理大小调节来维持缩放。
52.FSM有限状态机
  • 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层
53.行为树与有限状态机
  • 有限状态机系统:是指在不同阶段会呈现出不同的运行状态的系统,这些状态是有限的、不重叠的。这样的系统在某一时刻一定会处于其所有状态中的一个状态,此时它接收一部分允许的输入,产生一部分可能的响应,并且迁移到一部分可能的状态。
  • 基本节点是状态:他包含了一系列运行在该状态的行为以及离开这个状态的条件。
    状态可以任意跳转,实现简单,但是对于大的状态机很难维护,状态逻辑的重用性低。
    每一个状态的逻辑会随着一些新状态的增加而越来越复杂。维持状态的数量和状态逻辑复杂性是一个很大的难点。需要合理的分割以及重用状态。
  • 状态机状态的复用性很差,一旦一些因素变化导致这个环境发生变化。你只能新增一个状态,并且给这个新状态添加连接他以及其他状态的跳转逻辑。
    状态机的跳转条件一旦不满足,就会一直卡在某一个状态。
  • 行为树:一个流行的AI技术,涵盖了层次状态机,事件调度,事件计划,行为等一系列技术。实现AI的过程更加得有技巧,框架设计者较为全面考虑了我们可能会遇到的种种情况,把每种情况都抽象成了一个类型的节点,而我们要做的就是按照规范去写节点,然后把节点连接成一颗行为树。更加得具有面向对象的味道,行为模块间的藕合度相对较低。
    高度模块化状态,去掉状态中的跳转逻辑,使得状态变成一个“行为”。
    "行为"和"行为"之间的跳转是通过父节点的类型来决定的。比如并行处理两个行为,在状态机里面无法同时处理两个状态。
    通过增加控制节点的类型,可以达到复用行为的目的。
    可视化编辑。
54.简述行为树的概念及优缺点
  • 概念:
    对于有限状态机而言,必须明确状态的转换方式;对于行为树,必须明确状态前提:前提条件。
    每一个行为必须有“前提条件” ,这决定了该行为是否被选择。
    行为树的运算也是通过帧循环的update来驱动,不一定是每帧都update,但是要周期性update。
    每一次run从根节点(root)开始,每一运行都会选择一个可行的子节点运行,这种选择可以是随机方式,也可以是预设好优先条件。
    行为树由叶子节点和中间节点组成,叶子节点是最基本的行为(如跑动,攻击),中间节点代表逻辑单元(巡逻,逃跑)。
    当一个叶子节点被选择后,就会激活其对应的基本的行为。
    最基本的行为可能执行成功也可能失败。
    高等级的行为(中间节点)是否执行成功依赖于他们的孩子节点是否执行成功。
    一个子节点失败可能导致父母节点选择另外一个孩子。
    除了选择(selector)一个单独的子节点行为,一个节点还可能顺序(sequence)or并行(concurrent)得运行他的所有子节点。
    一个行为除了有前提条件,可能还有上下文条件(父节点or孩子节点可能存储一定的状态变量)。
    高优先级的行为可能抢占低优先级的行为。
  • 优点:
    行为逻辑和状态数据分离,任何节点写好以后可以反复利用。
    重用性高,可用通过重组不同的节点来实现不同的行为树。
    呈线性的方式扩展,易于扩展。
    可配置,把工作交给designer。
    能够胜任"AI" “掉宝”等等场景。
  • 缺点:
    每一帧都从root开始,有可能会访问到所以的节点,相对State Machine消耗更多的cpu。
    任何一个简单的操作都必须要使用节点。
55.Text 和 TMPText的区别 优缺点
  • Text是像素渲染放大之后就会模糊,使用Text父物体的放大缩小会影响子物体Text的清晰度, TMPText不会,它是网格渲染TMPText会把字体生成一个类似于贴图的东西然后读取贴图的坐标来获取对应的文字,更换文字的消耗会比Text大。
    TMPText更适用于不会变动的文字,特别是在量大的情况下,性能比Text高一些,需要经常变动的问题用Text好点,TMPText在字体库很大的情况下查找更换会比较慢。
56.红点系统的实现
  • 思路:
    红点系统基于MVC的思想,将分为三层:数据层,驱动层,显示层。
    数据层中的数据结构,考虑到需要层级的联系,所以以结点为核心,每个结点会持有其父结点和子结点,有点像双向链表的前驱后继,但是它构成的不是链表而是树。
    当一个结点状态发生变化,它会通知到其父结点,父节点会自行处理变化去通知它自己的父结点,有点递归的意思,如果是数量通知,子节点的消息会以此累计到自己的父节点中,以此类推,具体看需求。
    整个系统数据层驱动层与展示层是剥离的,展示层需要显示什么结点的内容,以该结点的key去注册,数据层与显示层实现了观察者模式,即可收到每次该结点状态变化的通知,并实时更新界面。
57.Animation和Animator的区别
  • Animation和Animator 虽然都是控制动画的播放,但是它们的用法和相关语法都是大有不同的。Animation控制一个动画的播放,而Animator是多个动画之间相互切换,并且Animator有一个动画控制器,俗称动画状态机。
  • Animator利用它做动画的切换是很方便的,但是它有一个缺点就是占用内存比Animation大。
58.简述SkinnedMesh的实现原理
  • 根据骨骼,动态整体实现表层Mesh,相对普通mesh由不同面片堆砌,根据骨骼结构,对顶点的变换计算出不同的蒙皮,最终进行模型的渲染
59.ScriptableObejct
  • ScriptableObject是一个数据容器,它可以用来保存大量数据。
    主要的用处就是在项目中通过将数据存储在ScriptableObject对象,避免值拷贝来减少游戏运行中的内存占用。
  • 当你有一个预制体,上面挂了一个存有不变数据的MonoBehaviour 脚本时,每次我们实例化预制体时都将产生一次数据拷贝,这时我们可以使用ScriptableObject对象来存储数据,然后通过引用来访问预制体中的数据。这样可以避免在内存中产生一份拷贝数据。与MonoBehaviour 一样,ScriptableObject也继承自Unity基类object,但是与MonoBehaviour不同的是,ScriptableObject不能和GameObject对相关联,相反,通常我们会将它保存为Assets资源。
    在编辑器模式下,我们可以在编辑和运行时将数据保存到ScriptableObject,因为保存ScriptableObject需要用到编辑器空间个脚本,但是在开发模式下不能使用ScriptableObject来保存数据,但是你可以使用ScriptableObject资源中已保存的数据。
60.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 //类库,相关文件夹路径目录进行操作,是否存在,创建目录,删除等操作
61.什么是协同程序?
  • 在主线程运行的同时开启另一段逻辑处理,来协助当前程序的执行,协程很像多线程,但是不是多线程,Unity的协程实在每帧结束之后去检测yield的条件是否满足。
62.什么叫做链条关节?
  • Hinge Joint,可以模拟两个物体间用一根链条连接在一起的情况,能保持两个物体在一个固定距离内部相互移动而不产生作用力,但是达到固定距离后就会产生拉力。
63.物理更新一般放在哪个系统函数里?
  • FixedUpdate,每固定帧绘制时执行一次,和Update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率低下的时候FixedUpdate调用次数就会跟着下降。FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关。Update就比较适合做控制。
64.IL是什么?
  • IL的全称是Intermediate Language,很多时候我们看到的是CIL(Common Intermediate Language,特指在.NET平台下的IL标准),其实大部分文章中提到的IL和CIL表示的是同一个东西,即中间语言。IL是一种低阶(lowest-level)的人类可读的编程语言。我们可以将通用语言翻译成IL,然后汇编成字节码,最后运行在虚拟机上。也可以把IL看作一个面向对象的汇编语言,只是它必须运行在虚拟机上,而且是完全基于堆栈的语言。
  • IL有三种转译模式:
    • Just-in-time(JIT)编译:在编译的时候,把C#编译成CIL,在运行时,逐条读入,逐条解析翻译成机器码交给CPU再执行。
    • Ahead-of-Time(AOT)编译:在编译成CIL之后,会把CIL再处理一遍,编译成机器码,在运行的时候交给CPU直接执行,Mono下的AOT只会处理部分的CIL,还有一部分CIL采用了JIT的模式。
    • Full AOT 完全静态编译:在编译成CIL之后,把所有的CIL编译成机器码,在运行的时候直接执行,这个模式适用于iOS操作系统。
65.什么是运行时(Runtime)?
  • 一个程序在运行(执行)的过程中所需要的硬件和软件环境
    运行时的主要作用是提供程序运行所需要的环境和基础设施,通过为程序提供内存分配、线程管理、类型检查、对象实例化和垃圾回收等操作来支持程序的运行
  • 公共语言运行时(Common Language Runtime,CLR)是整个.NET框架的核心,它为.NET应用程序提供了一个托管的代码执行环境。它实际上是驻留在内存里的一段代理代码,负责应用程序在整个执行期间的代码管理工作,比较典型的有:内存管理、线程管理、安全管理、远程管理、即时编译、代码强制安全类检查等
66.Mono和IL2CPP有什么区别?为什么要使用IL2CPP?
  • C#主要运行在.NET平台上,但.NET跨平台支持不好。
  • Mono是.NET的一个开源,跨平台的实现,它包含一个C#编译器,mono运行时(CLR)和一组类库,Mono使得C#有了很好的跨平台能力。C#这种遵循CLI规范的高级语言,会被编译器编译成中间语言IL(CIL),当需要运行它们时就会被实时地加载到运行时库中,由虚拟机动态地编译成汇编代码(JIT)并执行。
  • IL2CPP的编译和运行过程:首先还是由Mono将C#语言翻译成IL,IL2CPP在得到中间语言IL后,将它们重新翻译成C++代码,再由各个平台的C++编译器直接编译成能执行的机器码。
  • 为什么要使用IL2CPP:
    • Mono虚拟机维护成本过大。
    • Mono版本授权受限。
    • 提高运行效率。换成IL2CPP以后,程序的运行效率有了1.5~2.0倍的提升。
67.什么是托管代码,什么是非托管代码?
  • 托管代码:托管代码就是执行过程交由运行时(公共语言运行时,CLR)管理的代码。不管使用的是哪种实现(例如Mono、.NET Framework或.NET Core/.NET 5+)。CLR负责提取托管代码、将其编译成机器代码,然后执行它。除此之外,运行时还提供多个重要服务,例如GC管理、安全边界、类型安全,把托管代码理解成IL中间语言也行
  • 非托管代码:非托管代码会直接编译成目标计算机的机器码,这些代码包含C/C++或C#中以不安全类型写的代码。非托管代码不受CLR管理,需要手动释放内存
  • 一般情况下,我们使用托管代码来编写游戏逻辑,非托管代码通常用于更底层的架构、第三方库或者操作系统相关接口
68.Mono的垃圾回收机制
  • Mono将Simple Generational GC(SGen-GC)设置为默认的垃圾回收器,当我们向垃圾回收器申请内存时,如果发现内存不足,就会自动触发垃圾回收,或者也可以主动触发垃圾回收,垃圾回收器此时会遍历内存中所有对象的引用关系,如果没有被任何对象引用则会释放内存。SGen-GC的主要思想是将对象分为两个内存池,一个较新,一个较老,那些存活时间长的对象都会被转移到较老的内存池中去。这种设计是基于这样的一个事实:程序经常会申请一些小的临时对象,用完了马上就释放。而如果某个对象一段时间没被释放,往往很长时间都不会释放。
  • IL2CPP的虚拟机的内存管理仍然采用类似Mono的方式,因此程序员在使用IL2CPP时无须关心Mono与IL2CPP之间的内存差异。
69.浮点数精度问题导致不同设备计算结果不同,有哪些解决方法?
  • 使用某客户端的计算结果或由服务器决定计算结果,只计算一次,且认定这个值为准确值,把这个值传递给其他设备或模块。
  • 改用int或long类型来替代浮点数,把浮点数乘以10的幂次得到更准确的整数再进行计算,由于整数的计算是确定的,因此就不会存在误差,但要注意计算结果可能超出上限。
  • 用定点数替代浮点数,定点数把整数部分和小数部分拆分开来,都用整数的形式表示,缺点是由于拆分了整数和小数,两个部分都要占用空间,所以受到存储位数的限制。大部分项目都会自己实现定点数,无论是整数部分还是小数部分,都用整数表示,并封装在类中。因此需要重载所有的基本计算和比较符号,也可以使用开源的定点数库。
  • 用字符串代替浮点数,缺点是CPU和内存的消耗特别大,只能做少量高精度的计算。
70.什么时候用结构体,什么时候用类?
  • 用到继承和多态时用类。
  • 结构体复制或者作为参数传递时不会改变原来对象的值,如果需要这种特性可以使用结构体。类对象是引用传递会改变原来对象的值。
  • 因为值类型复制的特性,如果结构体定义了很多字段,复制的成本就会很高,所有结构体适用于数据量小的场景。
71.Destroy和DestroyImmediate的区别?
  • DestroyImmediate是立即销毁,立即释放资源,做这个操作的时候,会消耗很多时间的,影响主线程运行。Destroy是异步销毁,一般在下一帧就销毁了,不会影响主线程的运行。
72.UI上的特效怎么被裁剪?
  • 获取RectMask2D或者Mask的RectTransform,接着去调用GetWorldCorners获得该UI在世界坐标的信息坐标,然后设置参数给Shader,让其根据Rect坐标进行裁剪。而Shader的实现很简单,将超出部分的透明度设置为0。
73.UGUI和NGUI区别
  • UGUI通过Mask,RectMask2D来裁剪,而NGUI通过Panel的Clip。
  • NGUI的渲染前后顺序是通过Widget的depth,depth可以手动设置,而UGUI渲染顺序根据Hierarchy的顺序,越下面渲染在顶层,元素depth是动态算出来的。所以在制作功能界面时,DrawCall控制 NGUI > UGUI。
  • NGUI的UIPanel上有DrawCall Tool可以显示drawcall信息,哪些东西合并成了一个drawcall,UGUI没有这种功能。
  • UGUI不需要绑定Colliders,UI可以自动拦截事件,而NGUI需要绑定,UICamera用射线判断点击的物体并通过SendMessage调用OnClick() OnPress()等函数,而SendMessage利用反射机制。
  • UGUI的Navgation在Scene中能可视化。
  • UGUI界面展示是在Canvas下,而NGUI是在UIRoot下。
  • UGUI使用RectTransform控制元素的位置,缩放等信息,NGUI没有用到这个组件。
  • NGUI全部是用C#开发的,UGUI底层代码可以基于C++进行原生的编程。
  • 元素的更新方式不同,NGUI的UIPanel会在LateUpdate里遍历所有的widget,如果有widget发生变化,则触发更新,即使没有变化的UI元素,也会有正常的轮询操作的开销。UGUI是通过两个队列m_LayoutRebuildQueue和m_GraphicRebuildQueue分别记录Layout和Graphic发生变化的UI元素,在渲染之前,会在这个回调函数Canvas.SendWillRenderCanvas里去处理这两个队列里的元素,即分别进行Rebuild。
  • NGUI是必须先打出图集然后才能开始做界面。这一点很烦,因为始终都要去考虑你的UI图集。比如图集会不会超1024 ,图集该如何来规划等等。而UGUI的原理则是,让开发者彻底模糊图集的概念,让开发者不要去关心自己的图集。做界面的时候只用小图,而在最终打包的时候unity才会把你的小图和并在一张大的图集里面。然而这一切一切都是自动完成的,开发者不需要去care它。
  • 网格更新机制不同,NGUI可以只更新单个DrawCall,UGUI必须重建整个Canvas。 在功能界面的网格更新控制,NGUI >UGUI。在动态HUD界面(如血条,伤害数字,弹出的一些文本等)的网格更新控制,UGUI >> NGUI(因为UGUI网格合并这块的算法是用C++做的,所以会比在C#做的快很多,而且C#难免会触发一些堆内存的问题)
  • NGUI:UIPanel.LateUpdate两种更新方式
    • UIPanel.FillDrawCall 更新单个DrawCall
    • UIPanel.FillAllDrawCall 更新所有DrawCall
  • UGUI:Canvas.BuildBatch 更新所有DrawCall
    • WaitingForJob
    • PutGeometryJobFence
    • BatchRenderer.Flush(开了多线程渲染之后) 所以做优化时建议先关闭多线程渲染
74.NGUI渲染过程
  • Unity在制作一个图元,或者一个按钮,或者一个背景时,都会先构建一个方形网格,网格的绘制单位是图元(点,线,三角面),再将图片放入网格中。可以理解为构建了一个3D模型,用一个网格绑定一个材质球,材质球里存放要显示的图片。
  • 渲染过程:UI元素都继承自UIWidget,UIPanel遍历自己子物体的UIWidget组件,放入到一个List中,按照depth排序。List中相邻元素如果material,texture,shader相同,就传递它们的material,texture,shader,Geometry缓存都传给同一个UIDrawCall,否则就再创建一个新的UIDrawCall。每次有新的UIDrawCall产生,UIPanel就会调用上一个UIDrawCall的UpdateGeometry()函数,来创建渲染所需的对象。这些对象分别是MeshFilter,MeshRender,和最重要的Mesh(Mesh的顶点,UV,Color,法线,切线,还有三角面)。UIDrawcall是渲染UI元素的载体,UIPanel生成UIDrawcall,UIDrawcall是一个组件,挂载在一个GameObject,这个GameObject上再挂载MeshRender、Mesh、MeshFilter、材质等Unity组件,通过这些组件将UI元素渲染出来。我们在Editor中是看不到这个GameObject的,是因为创建的时候设置了HideFlags.HideAndDontSave。
75.UGUI渲染过程
  • UGUI的depth是动态算出来的,按照Hierarchy的节点顺序从上向下进行depth分析,最下层的元素depth = 0,元素相交会先判断是否能合批,材质id一样,图片id一样才能合批,比如元素A和元素B相交且B盖住了A,如果A,B可以合批,那么depthB = depthA,否则 depthB = depthA + 1,如果一个元素盖住了多个元素,则选取下面depth最大的元素进行合批判断。从规则中可以看出,depth值与是否相交有关,与是否为子节点无关。相同depth的元素会根据Material ID和Texture ID(字体的Texture ID就是其字体的ID)进行升序排序。
  • UGUI的渲染过程和NGUI类似,UI组件的基类是Graphic,Graphic保存了当前元素的mesh和material,Graphic实现接口ICanvasElement主要用于重绘,CanvasRenderer用于传递这些数据给Canvas,CanvasRenderer并不是直接渲染,而是交给Canvas,Canvas还要做合批等操作,Canvas会对节点下的Graphic进行合批,所以一个Graphic设置dirty,整个canvas都需要重新计算合批。
76.如何让粒子在界面上正确显示?
  • 修改ParticleSystem的Order in Layer参数,如果特效粒子勾选Render属性,这个特效就会有Order in Layer的概念,就会跟Canvas的order进行混合影响显示层级。
  • 在Prefab根节点上挂Sorting Group,然后根据情况设置Order in Layer。
  • 每个特效挂上脚本,脚本中的类继承MaskableGraphic重写OnPopulateMesh函数,该类是模拟Particle,将其转换成UGUI的Graphic,融入到UGUI体系,所以可以将其当做lmage一样控制。
77. mesh与shareMesh,material与shareMaterial,materials和sharedMaterials的区别?
  • mesh和material都是实例型的变量,对mesh和material执行任何操作,都是额外复制一份后再重新赋值,即使只是get操作,也同样会执行复制操作。也就是说,对mesh和material进行操作后,就会变成另外一个实例,虽然看上去一样,但其实已是不同的实例了。
  • sharedMesh和sharedMaterial与前面两个变量不同,它们是共享型的。多个3D模型可以共用同一个指定的sharedMesh和sharedMaterial,当你修改sharedMesh或sharedMaterial里面的参数时,指向同一个sharedMesh和sharedMaterial的多个模型就会同时改变效果。也就是说,sharedMesh和sharedMaterial发生改变后,所有使用sharedMesh和sharedMaterial资源的3D模型都会表现出相同的效果。
  • materials与sharedMaterials类似,只不过变成了数组形式。materials和sharedMaterials可以针对不同的子网格,material和sharedMaterial只针对主网格。也就是说,material和sharedMaterial等于materials[0]和sharedMaterials[0]。
78.欧拉角,四元数,旋转矩阵优缺点
  • 欧拉角
    优点:直观,容易理解。3个数据可以节省内存空间
    缺点:万向节死锁问题,必须严格按照顺序进行旋转(顺序不同结果就不同)
    应用:只涉及到一个方向的简单旋转可以用欧拉角
  • 四元数
    优点:没有万向节死锁。存储空间小,计算效率高。平滑插值,
    缺点:单个四元数不能表示在任何方向上超过180度的旋转。四元数的数字表示不直观。
    应用:物体旋转的过渡
  • 矩阵旋转:
    优点:旋转轴可以是任意向量,没有万向节死锁
    缺点:元素多,存储空间大
79.在开发过程中哪些地方比较容易造成内存泄漏问题?如何避免?
  • 造成内存泄漏的可能原因:
    • 你的对象仍被引用但实际上却未被使用。 由于它们被引用,因此GC将不会收集它们,这样它们将永久保存并占用内存。
    • 当你以某种方式分配非托管内存(没有垃圾回收)并且不释放它们。
    • 过度使用委托会导致内存泄漏,多播委托会引用多个方法,而当这个方法是实例方法(非静态方法)的话,也就是说这个方法隶属于一个对象。一旦我们使用委托引用这个方法的话,那么这个对象就必须存在于内存当中。即便没有其他地方引用这个对象,因为委托的关系,这个对象也不能释放。因为一旦释放,委托就不再能够间接调用到这个方法了,所以没有正确删除委托的方法会导致内存泄漏。
    • 静态对象没有及时释放。
      如何避免:
      1) 在架构上,多添加析构的abstract接口,提醒团队成员,要注意清理自己产生的“垃圾”。
      2) 严格控制static的使用,非必要的地方禁止使用static。
      3) 强化生命周期的概念,无论是代码对象还是资源,都有它存在的生命周期,在生命周期结束后就要被释放。如果可能,需要在功能设计文档中对生命周期加以描述。
80.如果不想new,但又想获取对象实例,有哪几种方法?
  • 使用反射。
Assembly assembly = Assembly.Load(“xxx”);
Type type = assembly.GetType(“yyy”);
return Activator.CreateInstance(type);
  • 使用原型模式克隆。
  • 反序列化
81.假设一个回合制战斗,战斗过程均由客户端计算,请问使用什么方式使得服务器可以验证此场战斗的数据是合法的?
  • 最简单的方式,根据公式计算当前队伍的伤害上限,只要低于此伤害上限就认为战斗数据合法。
  • 可以将整个战斗过程上传到服务器进行验算。数值计算涉及到随机结果的情况下,客户端、服务器使用同一随机种子及随机算法,保证数值结果的正确合法性。
82.游戏中常用的设计模式
  • 简单工厂模式:把对象的创建封装到类中,根据不同的参数生成不同的对象,如根据建筑的类型生成不同的建筑。
  • 观察者模式:C#的event。
  • 状态模式:使用有限状态机,将行为抽象成一个个状态,通过状态管理器控制状态之间的转换,同一时间只能处于某一个状态。
  • 组合模式:将一些功能抽象成一个个组件,对象创建时根据需求添加不同的组件,增强代码复用性。
  • 单例模式:全局为一,游戏中的管理器。
  • 外观模式:对多个子系统进行封装,通过外观类来获取这些系统,减少系统的互相依赖,减少和其他系统的耦合。
  • 策略模式:定义了一组同类型的算法,在不同的类中封装起来,每种算法可以根据当前场景相互替换,从而使算法的变化独立于使用它们的客户端。
  • 命令模式:将一个命令封装为一个对象,从而实现解耦,改变命令对象,撤销功能
  • 原型模式:在不需要创建新对象的情况下复制现有对象,并根据需要修改一些属性
83.Assetbundle 的生命周期
加载ab ➨ 加载asset ➨ 实例化obj ➨ 销毁obj ➨ 销毁asset ➨ 卸载ab
//先把cube打成ab包
//加载ab
var ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/cube");
//加载asset
var asset = ab.LoadAsset<GameObject>("cube");
//实例化obj
var cube = GameObject.Instantiate(asset);
//销毁obj
Destroy(cube);
cube = null;
//销毁asset
asset = null;
//卸载ab
ab.Unload(false);
ab.Unload(true);
ab = null;
84.如何判断客户端与服务器是否保持连接
  • 使用心跳包,每隔一段时间,客户端向服务器发送一条指定的心跳协议。
85.什么是黏包
  • 收到的数据包不完整,这种现象称之为黏包。
  • 出现黏包的原因:当发送端缓冲区的长度大于网卡的MTU(网络上传送的最大数据包)时,tcp会将这次发送的数据拆成几个数据包发送出去。
    tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
  • 解决办法:
    • 首先将所有接收到的字节流,塞入缓冲池
    • 根据包头信息,判断是否能获取当前完整协议包,若能获取完整,则根据协议头提取数据。此时若有缓冲池还有其他协议数据,若能继续提取就继续,不能则跳过,继续等待接收新字节流,塞入缓冲池。
    • 循环1和2
86.有限状态机和行为树区别
  • 有限状态机将游戏AI行为分为一个一个的状态,状态与状态之间通过状态管理器切换,某一个时刻只能处于其中一种状态
  • 状态机的问题:随着状态的增多,需要考虑任意两个状态之间是否可以切换,逻辑复杂,复用性不好,如果设计一个全新的敌人,又需要重写一套状态节点和切换逻辑
    行为树把行为抽象成一棵树,它是一种“轮询式机制”,即每次更新都会遍历树,判定逻辑是否成立,是否该继续往下执行。行为树从上到下,从左到右遍历节点,行为树的每个节点会有返回一个执行状态,一种设置方式是 {Running,Success,Failure } 三种状态,Running代表正在运行,Success,Failure对应执行成功和失败
  • 综合来看,行为树更适合描述角色在复杂环境下的行为。状态机更适用于处理简单的状态转换,并且适合处理基于输入驱动的场景
87.什么是扩展方法?
  • 扩展方法使你能够向现有类型“添加”方法,无需修改类型
public static void SetText(this TextMeshProUGUI tmpUGUI)
{
    
    
    tmpUGUI.text = "Text";
}
  • 扩展方法必须满足的
    • 必须要静态类中的静态方法
    • 第一个参数的类型是要扩展的类型,并且需要添加this关键字以标识其为扩展方法
88.刚体中的Is Kinematic
  • 勾选Is Kinematic时将会忽略外部对此刚体的作用(重力以及addforce或者其他物体对此物体的冲撞),但是此刚体仍然主动对外部非Kinematic刚体产生物理作用
89. 材质、贴图、纹理的关系
  • 材质 Material 包含贴图 Map,贴图包含纹理 Texture。
    纹理是最基本的数据输入单位,游戏领域基本上都用的是位图。此外还有程序化生成的纹理 Procedural Texture。
  • 贴图的英语 Map 其实包含了另一层含义就是“映射”。其功能就是把纹理通过 UV 坐标映射到3D 物体表面。贴图包含了除了纹理以外其他很多信息,比方说 UV 坐标、贴图输入输出控制等等。
  • 材质是一个数据集,主要功能就是给渲染器提供数据和光照算法。贴图就是其中数据的一部分,根据用途不同,贴图也会被分成不同的类型,比方说 Diffuse Map,Specular Map,Normal Map 和 Gloss Map 等等。另外一个重要部分就是光照模型 Shader ,用以实现不同的渲染效果。
90.垂直同步对游戏帧率有什么影响
  • 垂直同步是一种调整显示器和GPU之间帧率同步的技术。在启用垂直同步时,GPU将帧率锁定为与显示器刷新率相同的数值。例如,如果你的显示器刷新率是60Hz,那么GPU会将帧率锁定为60fps
  • 启用垂直同步有以下影响:
    • 减少画面撕裂
    • 如果游戏的帧率无法达到显示器的刷新率,那么垂直同步将会导致帧率下降。这是因为在垂直同步的情况下,每当显示器完成一次刷新,图形卡必须等待下一次刷新才能开始呈现下一个帧,因此在某些情况下,帧率将被降低到不到60帧以下
91.动画融合是怎么实现的
  • 混合树(Blend Tree)是一种将多个动画片段以位置、速度、角速度为依据经行线性混合的方式,可以将几个动画文件很好的融合在一起
  • 还可以通过动画层(Layer)的方式实现,每一个动画层只对动画主体的部分进行控制,其他部分通过遮罩屏蔽
92.修改Time.timeScale会影响什么?
  • timeScale改变时,会对以下值产生影响:time、deltaTime、fixedTime以及fixedUnscaledDeltaTime
  • timeScale会影响 FixedUpdate 的执行速度,当timeScale为0时,FixedUpdate完全停止。但不会影响Update、LateUpdate的执行速度,如果Update、LateUpdate中使用了deltaTime,则也会影响这部分逻辑的执行
  • timeScale 不会影响 Coroutine本身的执行速度。当timeScale为0时,如果Coroutine中yield了某个WaitForSeconds或者WaitForFixedUpdate,那么该Coroutine会在此处停下。如果想要等待一个不受timeScale影响的时间,请用WaitForSecondsRealtime
93.Unity3D 如何获知场景中需要加载的数据?如何动态资源加载
  • instantiate:最简单的一种方式,以实例化的方式动态生成一个物体。
  • Assetsbundle:即将资源打成 asset bundle 放在服务器或本地磁盘,然后使用WWW模块get 下来,然后从这个bundle中load某个object,unity官方推荐也是绝大多数商业化项目使用的一种方式。
  • Resource.Load:可以直接load并返回某个类型的Object,前提是要把这个资源放在Resource命名的文件夹下,Unity不管有没有场景引用,都会将其全部打入到安装包中
  • AssetDatabase.loadasset :这种方式只在editor范围内有效,游戏运行时没有这个函数,它通常是在开发中调试用的。
94.第一次执行GameObject.Instantiate的时候有明显卡顿,该怎么解决?
  • Instantiate的卡顿与三部分开销相关:相关资源加载、脚本组件的序列化和构造函数的执行,并且绝大部分原因均是相关资源加载导致。所以,我们的建议如下:
    • 通过 Profiler 查看 Instantiate 具体的CPU分配情况;
    • 如果是资源加载导致的性能瓶颈,则一方面通过简化资源来缓解CPU耗时压力,另一方面通过 AssetBundle 依赖关系打包将资源预先加载,即将此处 Instantiate 的总体耗时拆分,平摊到之前帧进行执行(比如切换场景处等),从而让 Instantiate 实例化操作的局部耗时更加平滑;
    • 如果是脚本组件序列化导致的性能瓶颈,则可尝试减少脚本中的序列化信息;
    • 如果是构造函数的执行导致的性能瓶颈,一般只能在策略上进行规避,比如降低 Instantiate 的调用频率等。
95.反向旋转动画的方法是什么?
  • 反转动画,将动画的速度调到-1。
96.写出Animation的五个方法
  • AddClip 添加剪辑、Blend 混合、Play 播放、Stop 停止、Sample 采样 、CrossFade淡入淡出切换动画、IsPlaying是否正在播放某个动画
97.动画层(AnimationState Layers)的作用是什么?
  • 动画层作为一个具有层级动画编辑概念的工具,可以用来制作和处理任何类型的动画
98.Material 和Physic Material 区别?
  • PhysicMaterial 物理材质:物理材质描述,如何处理物体碰撞(摩擦,弹性)。Material 材质(材质类)
  • 为了获得一个对象使用的材质,可以使用Renderer.materia 属性
99.LayerMask.NameToLayer()这个方法有什么作用?
  • LayerMask 的使用是按位操作的,LayerMask.NameToLayer(“Players”) 返回该Layer的编号。
100.Unity3d中static batching和dynamic batching 各有什么用?
  • Dynamic Batching不需要任何操作,只要共享材质(即使是不同的Mesh模型也可以),就会自动被合并。
  • 可以自由移动旋转。但有以下使用要求:
    • 模型文件共计点数不超过900。(重复使用同一个Mesh不计)
    • 单个物体可以不超过300点,Shader可以有法线UV。但如果Shader使用了UV0 UV1两套UV,或
      者Tangent切线的话,单个物体只能不超过180点
    • 游戏对象使用相同模型和材质时,只有相同缩放(即xyz等比缩放,浮点尾数可以有细微差)的会被合并。

      (1,1,1)与(1,1,1)
      (2,2,2)与(2,2,2)
      (0.5,0.5,0.5)与(0.5,0.5,0.5)
      (2,2,2)与(2,2,2.0001)
    • 场景烘焙:烘焙后同材质将不会被烘焙。lightmap 有隐藏的材质参数:offset/scale, 所以使用lightmap的物体不会被合并
    • Shader不能使用多Pass:多Pass的Shader会破坏Dynamic Batching
      Static Batching
  • 原理:运行游戏后将一组游戏对象的多个模型会被动态合并为1个。这组游戏对象所有使用同一材质的
    在一个DrawCall来完成。这些游戏对象运行后无法移动缩放旋转。但是Drawcall一定是最大化合并的,
    并且不受动态合并的诸多限制(见下文详述)。
  • 注意:即使物体都使用了同样的模型,在batch后每一个物体都会创建一份模型对应的geometry,在新的Combined Mesh里。所以过多的batch会增加内存占用。例如场景里的树群就不适合Static Batch,而适合动态合并。
  • 实现方法:
    • MeshRenderer勾选Batching Static:勾了即可
    • 代码中使用UnityEngine.StaticBatchingUtility实现(可以在任何平台调用):
      1)将所有要合并的静态物体(不须勾Batching Static)放入统一一个root
      2)StaticBatchingUtility.Combine(root); 之后就合并好了!
      区别:勾选Batching Static:完全自动合并,在MeshFilter里显示的是Combined Mesh(root:scene)。合并后不能移动
      StaticBatchingUtility:合并到一个游戏对象下。合并后可以移动父节点游戏对象"
101.Unity3d中Awake和Start 谁先执行,update和fixedUpdate 有什么区

别?

  • Awake先执行。Update是在每次渲染新的一帧的时候才会调用,FixedUpdate,是在固定的时间间隔执行,不受游戏帧率(fps)的影响,FixedUpdate的时间间隔可以在项目设置中更改,Edit->Project Setting->time 找到Fixed timestep。就可以修改了、
102.Unity3D中的协程(coroutine)和C#线程之间的区别是什么?
  • 多线程程序同时运行多个线程,而在任一指定时刻只有一个协程在运行,并且这个正在运行的协
    同程序只在必要时才被挂起。
  • 除主线程之外的线程无法访问Unity3D 的对象、组件、方法。
  • Unity3d 没有多线程的概念,不过unity 也给我们提供了StartCoroutine(协同程序)和
    LoadLevelAsync(异步加载关卡)后台加载场景的方法。
  • StartCoroutine 为什么叫协同程序呢,所谓协同,就是当你在StartCoroutine 的函数体里处理一段代码时,利用yield 语句等待执行结果,这期间不影响主程序的继续执行,可以协同工作。而
    LoadLevelAsync 则允许你在后台加载新资源和场景,所以再利用协同,你就可以前台用loading 条或动画提示玩家游戏未卡死,同时后台协同处理加载的事宜。
103.localPosition 与Position 的使用区别?
  • localPosition:自身位置,相对于父级的变换的位置。Position:在世界坐标transform的位置
104.Vector3.forward与Vector3(0,0,1)是一样的意思对吗?
105.去掉敏感字的程序(手写程序)
String s = "你是坏蛋";
s.Replace("坏蛋", "**");
106.Unity3d中resources目录一般用来放些什么,打包的时候会有什么影响?
  • resource一般用来放置一些需要动态加载的资源,打包程序的时候会将Resource目录下的全部文件都加密压缩打进包内,这样再想使用assetbundle方式打小包的话就不行了
107.怎么判断两个平面是否相交?不能用碰撞体,说出计算方法
  • 对于两个平面Ax+By+Cz+D=0与ax+by+cz+d=0,只要(A,B,C)与(a,b,c)不成比例,这两个平面就是相交的。
108.MeshCollider和其他Collider的⼀个主要不同
  • MeshCollider是⽹格碰撞器,对于复杂⽹状模型上的碰撞检测,⽐其他的碰撞检测精确的多,但是相对其他的碰撞检测计算也增多了,所以⼀般使⽤⽹格碰撞也不会在⾯数⽐较⾼的模型上添加,⽽会做出两个模型,⼀个超简模能表⽰物体的形状⽤于做碰撞检测,⼀个⽤于显⽰。
109.当⼀个细⼩的⾼速物体撞向另⼀个较⼤的物体时,会出现什么情况?如何
  • 穿透(碰撞检测失败)
    • 使用合适的碰撞体: 对于复杂形状的物体,使用Mesh Collider或者其他合适的碰撞器(Collider)来更精确地描述物体形状,以避免穿透。
    • 限制最大速度: 如果物体的速度非常高,可能会导致穿透等问题。你可以通过代码来限制物体的最大速度,或者使用Rigidbody的Interpolate属性来减缓高速移动时的问题。
    • 调整物理材质: 使用合适的物理材质,设置摩擦力和弹性系数,以便得到期望的碰撞效果。你可以在Physics Material组件中调整这些参数。
    • 使用连续碰撞检测: Rigidbody组件中有一个属性叫做Continuous Collision Detection(连续碰撞检测),启用它可以减少高速物体的穿透问题。
      把刚体的实时碰撞检测打开Collision Detection修改为Continuous Dynamic
      在unity3d中物体发⽣碰撞的整个过程中,有⼏个阶段,分别列出对应的阶段函数
      主要是三个阶段:
      1.Collider.OnCollisionEnter 进⼊碰撞,当collider/rigidbody开始触动另⼀个rigidbody/collider时OnCollisionEnter被调⽤。
      2.Collider.OnCollisionStay 逗留碰撞, 每个collider/rigidbody触动rigidbody/collider,将在每帧调⽤OnCollisionStay。通俗的说, ⼀个碰撞器或刚体触动另⼀个刚体或碰撞器,在每帧都会调⽤OnCollisionStay,直到它们之间离开不接触。
      3.Collider.OnCollisionExit 退出碰撞, 当 collider/rigidbody停⽌触动另⼀个
      rigidbody/collider时,OnCollisionExit被调⽤。
110.在Unity中如何是西安相机跟随角色?
  • 可以通过将相机的位置设置为角色的位置加上一个偏移量,从而实现相机跟随角色的效果
111.在Unity中如何加载异步资源?
  • 可以使用Unity的Coroutine和AsyncOperation来实现异步资源加载。使用Coroutine可以在单个帧上分解加载工作,而AsyncOperation则可以返回加载进度和资源对象
112.在Unity中如何实现角色动画?
  • 可以使用Animator组件和Animator COntroller来实现角色动画。Animatior组件用于控制角色的动画播放状态,并可以在不同的动画状态之间进行过度.
113.在Unity中如何加载并播放音频?
  • 可以使用Unity的Audio Source组件来加载播放音频文件。通过设置Audio Source的属性,如Clip和Volue。可以控制加载和播放音频。
114.如何在Unity中实现粒子效果?
  • 可以使用Unity的ParticleSystem组件来实现粒子效果。通过调整ParticleSystem的属性,如发色和位置,速度,大小和生命周期,可以创建各种不同的粒子效果
115.在Unity中如何实现游戏的平台差异化处理?
  • 可以使用Unity的宏定义或则预编译指令来实现游戏在不同平台下的差异化处理。
116.在Unity中如何实现游戏对象之间的通信?
  • 在Unity中可以通过消息系统,委托和事件等机制实现对象之间的通信。比如可以使用SendMessage函数向游戏对象发送消息,通过委托和事件可以实现订阅和发布的消息传递。
117.如何在Unity中实现多人联网游戏?
  • 可以使用Unity的UNET,或则第三方网络库(如Photon,Mirror等)来实现多人联网游戏。
118.为何大家都在移动设备上寻求U3D原生GUI的替代方案?
  • 不美观,OnGUI很耗费时间,使用不方便 ,DrawCall
119.举例Unity的四个特殊文件夹,分别是干什么用的?
  • StreamingAssets:文件夹中的文件目录结构和文件会被原封不动地打包进安装包中
  • Resources:需要动态加载的资源存放的位置
  • Editors:编辑器,特殊编辑工具存放的位置
  • Plugins:插件存放的位置
120.Unity调整旋转
  • 矩阵旋转:
    • 优点:旋转轴可以是任意向量
    • 缺点:旋转其实只需要知道一个向量+一个角度(共4个信息值),但矩阵却用了16个元素(矩阵法消耗时间和内存)、
  • 欧拉角旋转
    • 优点:容易理解,形象直观;表示更方便,只需要三个值(分别对应x、y、z轴的旋转角度)
    • 缺点:欧拉角这种方法是要按照一个固定的坐标轴的顺序旋转的,因此不同的顺序会造成不同结果;欧拉角旋转会造成万向锁现象,这种现象的发生就是由于上述固定的坐标轴旋转顺序造成的。理论上,欧拉角旋转可以靠这种顺序让一个物体旋转到任何一个想要的方向,但如果在旋转中不幸让某些坐标轴重合,就会发生万向锁现象,这时就会丢失一个方向上的旋转能力(两个旋转轴(环)重叠),也就是说在这种状态下,我们无论怎么旋转(还是按照原先的旋转顺序),都不可能得到某些想要的结果,除非打破原先的旋转顺序或者同时旋转三个轴。
    • 由于万向锁的存在,欧拉旋转无法实现球面平滑插值。
    • 万向锁的简单解决办法:构造一个不同的旋转层级顺序,但是万向锁总是会在某一个顺序发生,调整旋转顺序不是根本解决办法。(Unity使用的是Z-X-Y顺规,即旋转顺序为z轴、x轴、y轴,虽然某些情况下会出现万向锁,但是这种顺规出现万向锁的概率最小)
    • 万向锁解决办法:将欧拉角转换为四元数,对四元数进行Slerp插值,再将这一系列四元数转换为对应的欧拉角,然后作用于需要进行旋转的对象。这种做法缺点在于消耗内存,但是可以使物体任意旋转,灵活度高。
    • 使用欧拉旋转出现旋转路径偏移的根本原因:在万向锁情况下对欧拉角的插值不是线性的。(突变)
    • 静态欧拉角:其旋转轴使用的是静止不同的参考系。
    • 动态欧拉角:使用object本身的坐标系,因而会随着object旋转而旋转。(局部坐标系会随着对象的旋转而旋转)
  • 四元数旋转
    • 优点:可以避免万向锁;只需要一个4维的四元数就可以执行绕任意过原点的向量的旋转,方便快捷,在某些实现下比旋转矩阵效率更高;而且四元数旋转可以提供平滑插值。
    • 缺点:比欧拉旋转稍微复杂了一点,因为多了一个维度,理解更困难,不直观。
121.本地坐标系 世界坐标系
  • 世界坐标系:世界坐标是指物体在场景中的坐标,当某个物体没有父物体时,它的position即为世界坐标的position,rotation同理;本地坐标是物体相对于它的父物体的坐标而言,这个相对坐标是以父物体本身为坐标轴进行计算的,与世界坐标没有必然联系。而对于没有父物体的物体,可以认为不存在本地坐标这种说法。
  • 本地坐标系:当某个物体有父物体时,它的inspector栏transform中的position实际是localposition,即本地坐标。
  • 使用TransformPoint方法将本地坐标系转为世界坐标系

猜你喜欢

转载自blog.csdn.net/backlighting2015/article/details/135426558