Unity面经总结

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目录无法动态更新,所以在游戏发布和版本升级上不适用。

unity3d的MonoBehaviour生命周期

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的条件是否满足。

官方给的协程定义:

A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.

协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。

详细的协程流程分析:

  • C#层调用StartCoroutine方法,将IEnumerator对象(或者是用于创建IEnumerator对象的方法名字符串)传入C++层。
  • 通过mono的反射功能,找到IEnuerator上的moveNextcurrent两个方法,然后创建出一个对应的Coroutine对象,把两个方法传递给这个Coroutine对象。
  • 创建好之后这个Coroutine对象会保存在MonoBehaviour一个成员变量List中,这样使得MonoBehaviour具备StopCoroutine功能,StopCoroutine能够找到对应Coroutine并停止。
  • 调用这个Coroutine对象的Run方法。
  • Coroutine.Run中,然后调用一次MoveNext如果MoveNext返回false,表示Coroutine执行结束,进入清理流程如果返回true,表示Coroutine执行到了一句yield return处,这时就需要调用invocation(m_Current).Invoke取出yield return返回的对象monoWait,再根据monoWait的具体类型(null、WaitForSeconds、WaitForFixedUpdate等),将Coroutine对象保存到DelayedCallManager的callback列表m_CallObjects中。
  • 至此,Coroutine在当前帧的执行即结束。
  • 之后游戏运行过程中,游戏主循环的PlayerLoop方法会在每帧的不同时间点以不同的modeMask调用DelayedCallManager.Update方法,Update方法中会遍历callback列表中的Coroutine对象,如果某个Coroutine对象的monoWait的执行条件满足,则将其从callback列表中取出,执行这个Coroutine对象的Run方法,回到之前的执行流程中。

总结:

  1. 协程不是线程,不是异步执行的。在主线程中执行,是一个分部执行,遇到条件(yield return)会挂起,直到满足条件才会被唤醒继续执行后面的代码。unity每帧都会处理对象上的协程
  2. StopCoroutine(string methordName); StopAllCoroutines(); 暂停当前脚本下所有协程。或者设置对象active=false,即使再次激活,也不能继续执行。但enable=false 不能停止协程
  3. WaitForSeconds() 等待数秒,受Time.timeScale影响,当time.timeScale=0时,yield return new WaitForSecond(1)不会被满足,WaitForFixedUpdat:等待一个固定帧。 StartCoroutine等待一个协程暂停,WWW等待一个加载完成。
  4. 协程只是看起来像多线程一样,其实还是在主线程上执行。
  5. 协程只是个伪异步,内部的死循环依旧会导致应用卡死。
  6. yield是C#的语法糖,和Unity没有关系。
  7. 避免使用字符串的版本开启一个协程,字符串的版本在运行时要用mono的反射做更多参数检查、函数查询工作,带来性能损失。

来源于: https://sunweizhe.cn/2020/05/08/%E6%B7%B1%E5%85%A5%E5%89%96%E6%9E%90Unity%E5%8D%8F%E7%A8%8B%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86/

lightmap有什么作用

lightmap就是事先烘焙好的光照贴图,这样可以避免使用实时光照,可以减少Drawcall提高性能。在三维软件里实现打好光,然后渲染把场景各表面的光照输出到贴图上,最后又通过引擎贴到场景上,这样就使物体有了光照的感觉。

RawImage和Image的区别

  1. RawImage可以显示任何类型的Texture,而Image只能显示Sprite
  2. RawImage支持UV Rect,Image支持Simple(简单)/Sliced(切片)/Tiled(平铺)/Filled(填充)

前向渲染 v.s 延后渲染

前向渲染 (Forward Rendering):把空间的点进行各种剪裁后,进行处理。先渲染一遍物体,把法线和高光存在ARGB32的渲染纹理中(法线用rgb通道,高光用a通道),存在了z buffer里;然后通过深度信息,法线和高光信息计算光照(屏幕空间),光照信息缓存在Render Texture中;最后混合。

延后渲染 (Deferred Rendering):将摄像机空间的点光栅化转化成屏幕坐标后再进行处理。先不进行光照运算,对每个像素生成一组数据(G-buffer),包括位置,法线,高光等,然后用这些数据将每个光源以2D后处理的方式施加在最后图像上(屏幕空间),然后只进行了一次光照计算就实现了最终效果。缺点:不适用于半透明。

什么是Drawcall,如何优化Drawcall

定义:引擎每一次对GPU的调用的过程叫做Drawcall,每一个网格(Mesh)使用一个不同的材质(Material)将需要一个单独的Draw Call。

优化:

  1. 用sprite atlas将相关的零散图片资源放到同一个Sprite中,这样多次Drawcall调用机会合并成一次
  2. 将使用同一图集的对象排序在一起,使用同一图集的对象会被一起绘制以减少Drawcall。

DrawCall多少为好:150以下

Animator里面的状态机

Animator主要包含一个状态机和Avatar, 状态机里管理者各个动画(Animation)的切换条件

  1. 在场景(Scene)内制造一个GameObject,然后加上Animator组件。
  2. 在项目文件夹内制造一个AnimatorController并把它链接到上述Animator中。
  3. 双击 Animator,会出来一个 Animator 面板,拖入三个动作作为状态(第一个拖入的作为默认状态,不过可以右键 Default State 设置其他状态为默认状态)
  4. 右键 AnyState,Make Transition 连接到三个状态
  5. 添加状态控制参数 AnimState, 编辑切换状态的条件
  6. 取消勾选 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: 在运行时计算出其值的, 能通过静态构造函数来赋值。

如何在不同分辨率下保持UI的一致性

NGUI可以实现屏幕分辨率的自适应性,原理就是计算出屏幕的宽高比跟原来的预设的屏幕分辨率求出一个对比值,然后修改摄像机的size。UGUI通过锚点和中心点和分辨率也解决这个问题

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个物品,用户操作会很卡,怎么解决

摄像机有几种模式,成像原理分别是什么?

  • Projection:Prespective 透视:透视摄像机成像的原理是利用相似三角形来成像

  • Orthographic 正交:正交摄像机直接投影到屏幕成像

Canvas 的渲染流程,一共有几种模式,如何配置?

canvas的RenderMode共有三种模式:

  • Screen Space-OverLay:Canvas 覆盖屏幕,且永远覆盖在其它元素的上层,也就是说 UI 会遮挡场景中的其它元素,显示优先级由 Sort Order 确定
  • Screen Space-Camera:和 Overlay 模式相仿,Canvas覆盖整个屏幕空间画布也是填满整个屏幕空间。不同之处在于,Canvas 被放置于指定摄像机的前方。这种模式下面 UI 并不一定能渲染在 3d 元素之上
  • World Space:Canvas 与场景中其它3D元素没有区别。

同时显示多个摄像机:更改相机的Viewport Path,上面的相机Depth要大。

UGUI如何打包成图集?

  1. 打开Editor->Project Settings 下面有sprite packer的模式。
    • Disabled表示不启用它
    • Enabled For Builds 表示只有打包的时候才会启用它
    • Always Enabled 表示永远启用它,这里的启用它就表示是否将小图自动打成图集
  2. 为每个UI图片制定要打入的图集的tag名字
  3. 打包生成图集Window ->Sprite Packer,点击Pack即可打包生成图集

UGUI 如何实现UI物体淡入淡出?

  • 单个:控制组件alpha透明度
  • 整体:使用CanvasGroup组件,把组件放到界面根节点。CanvasGroup是管理整个界面下的所有UI元素,改变这个组件透明属性,地下所有ui元素该属性都会变
  • 使用DoTween插件

Unity [MenuItem()]是什么意思?

  • 允许自己添加菜单到主菜单和检视面板(仅静态函数能使用menuitem属性)

其中[]是什么意思?

C#里面[]表示的装饰器,和Java里面的注解是类似的,我们可以通过Api函数读取到装饰器。比如,Unity Editor模式下的[MenuItem(“菜单”)]来装饰一个函数为菜单响应函数,那么为什么加了[MenuItem(菜单)] Unity就会生成这个菜单呢?Unity 读取MenuItem这个装饰器,知道这个装饰器的路径,这样根据这个路径在菜单创建一个路径出来,利用C#的反射保存这个装饰器所装饰的方法,这样点击菜单就调用这个方法。

AssetsBundle包打出后.manifest有什么用?

AssetBundle定义和作用:

  • AssetBundle是一个压缩包含模型,贴图,预制体,声音,甚至是整个场景,可以在游戏运行时加载。
  • AssetBundle自身保存依赖关系;(AB包后缀为 manifest的文件可被文本形式打开)
  • 压缩可以使用LZ4和LZMA压缩算法,减少包大小,更快进行网络传输
  • 把一些可以下载的内存放进AssetBundle里,减少安装包的大小

AB加载方式:

  •  从文件中加载 AssetBundle.LoadFromFile 。
  • 内存中加载 AssetBundle.LoadFromMemoryAsync(注意是异步,去掉Asyns既是同步方法)
  • Unity封装的UnityWebRequest对象

打包时的分组:

  • 一个ui界面或者所有ui界面一个包(包含所有贴图和布局信息)
  • 一个角色或者所有角色一个包(包含模型和动画)
  • 所有场景共用一个包
  • 所有声音一个包,所有shader一个包
  • 经常更新的资源和不经常更新的区分开,同时加载的放在一起等等,一个资源两个版本,可通过后缀区分。

.mainfest是一个文本文件里面有ab包的校验码,依赖以及资源内容等相关信息,这个信息是给开发读的,同时这个信息也作为二进制打入了ab包里面,所以使用ab包资源的时候,可以不用带.manifest文件。

Image与RawImage的区别?

  • Image是UGUI里面显示一个图片的常用的组件,他支持图集、单独的图片。一般我们显示图片都用Image组件来显示,因为图集能够把同一个图集里面的图片合并drawcall,这样提升性能。Image支持九宫格等模式。
  • RawImage组件是UGUI显示单个单个的图片而用的,它不支持图集,所以不同图片的RawImage单独占一个drawcall,RawImage可以直接修改贴图的uv参数,这样可以调整图片的显示。类似与打飞机的滚动的背景就可以用RawImage来不断修改uv坐标来实现背景图片的滚动。

Unity如何实现单例模式?

public class GameManager : MonoBehaviour{
    private static GameMangaer instance = null;
    public static GameManager GetInstance {
        get {
               if (instance == null){
                      instance = new GameManager();
               }
               return instance
        }
    }
}

Unity如何实现游戏截图?

  1. 找到main camera, 和屏幕尺寸
  2. 新建一个render texture, 将main camera的渲染的图像输出到render texture里
  3. 新建texture2D, 从屏幕读取像素保存到纹理数据中
  4. 销毁render texture, 保存texture2D

Unity如何调用android与iOS的 API函数?

首先unity支持在C#中调用C++ dll,这样可以在Android和iOS中提供C++接口在unity中调用。由于android和iOS平台加载库的方式不同(android为动态加载,iOS为静态加载),在C#中针对不同平台对dll 接口的引用声明是不一样的。

#if UNITY_EDITOR 
    public static void OpenWebView(string url) { 
        return; 
    } 
#elif UNITY_IPHONE 
    [DllImport ("__Internal")] 
    public static extern void OpenWebView(string url); 
#elif UNITY_ANDROID 
    [DllImport ("libtestunity", CallingConvention = CallingConvention.Cdecl)] 
    public static extern void OpenWebView(string url); 
#endif   

Unity项目框架是如何设计的?有哪些原则?

  • 管理类:这类脚本能控制程序进度、调度场景、管理功能类,基本算是程序的大脑,一般是单例
  • 功能类:负责在程序中完成具体功能的,比如塔防游戏里的塔、怪物、技能、子弹,受管理类控制,自身有不同的功能
  • 工具类:相当于工具箱,比如 Mathf类 ,比如组建协议的脚本,计算时间,数字转换等。
  • 独立类:自己独立功能独立周期的脚本,比如 定时销毁:启动-计时-销毁。
  • 单一原则:明确类的定义,只做一件事。提高类的可读性,更加好维护,降低耦合度。
  • 里氏替换原则:子类可以实现父类的抽象方法,但不能覆盖。子类可增加自己特有方法。
  • 依赖倒置原则:高层模块不能依赖底层模块,二者都依赖抽象,细节依赖抽象,抽象不依赖细节。

资源管理是如何做的,如何更新与打空包?

  1. Unity自动打包资源是指在Unity场景中直接使用到的资源会随着场景被自动打包到游戏中,这些资源会在场景加载的时候由unity自动加载。这些资源只要放置在Unity工程目录的Assets文件夹下即可,程序不需要关心他们的打包和加载,这也意味着这些资源都是静态加载的。但在实际的游戏开发中我们一般都是会动态创建GameObject,资源是动态加载的,因此这种资源其实不多。
  2. Resources资源是指在Unity工程的Assets目录下面可以建一个Resources文件夹,在这个文件夹下面放置的所有资源,不论是否被场景用到,都会被打包到游戏中,并且可以通过Resources.Load方法动态加载。这是平时开发是常用的资源加载方式,但是缺点是资源都直接打包到游戏包中了,没法做增量更新。
  3. AssetBundle资源是指我们可以通过编辑器脚本来将资源打包成多个独立的AssetBundle。这些AssetBundle和游戏包是分离的,可以通过WWW类来加载。AssetBundle的使用很灵活:可以用来做分包发布,例如大多数页游资源是随着游戏的过程增量下载的,或者有些手游资源过大,渠道要求发布的包限制在100M以内,那只能把一开始玩不到的内容做成增量包,等玩家玩到的时候通过网络下载。

为什么不推荐用unity自带的standard shader

  •  Built-In Shader在AssetBundle模式下, 是会粘包的, 就是被打包到每个引用它的材质AssetBundle里, 造成包变大且多次编译的错误, 导致运行时致命的编译时间, 所以使用AssetBundle的情况要把内置Shader下载来放到工程里使用, 避免这种情况. 并且经过测试ShaderVariantsCollection记录的变体对它无效, 仍然被强行编译了.
  • 模型导入时的默认材质, 也是Standard的, 即使设定Import Materials为false仍然在AssetBundle打包的时候会自动包含在包里, 在读取资源完成的时候就自动创建出来了, 跟1的情况一样, 导致包变大多次编译, 所以模型导入后要创建给Prefab用的新材质, 把默认材质删除掉.
  • 推荐看这个

参考资料

  1. https://blog.csdn.net/Fenglele_Fans/article/details/80178463
  2. https://blog.csdn.net/wang_lvril/article/details/108998105
  3. https://blog.csdn.net/ring0hx/article/details/46376709
  4. https://zhuanlan.zhihu.com/p/265442188
posted @ 2020-10-13 22:38  cancantrbl  阅读(2521)  评论(0编辑  收藏  举报