来源于唐老狮模拟面试和每日一题1、静态成员属于类而不是实例答案为30 30 30答案为0 20 20 302、简单题3.1、C#中是否可以通过反射获取到类内部的私有成员可以在获取成员的相关方法中可以通过传入参数指定获取非公共的成员3.2、在制作游戏存档功能时C#中反射主要可以发挥出哪些作用至少说出三点1.序列化时动态获取数据结构类信息可以动态获取字段用于存储2.反序列化时可以通过反射实例化对象写入数据3.结构发生变化时我们可以利用反射机制进行判断多的数据抛弃少的数据自定义初始化4、如果在Unity当中制作FPS游戏如何模拟枪械开枪时的后坐力每次开枪时可以给枪械、手臂IK、摄像机等根据实际视角情况决定一个绕x轴往上偏移和y轴左右偏移的旋转角度并且每帧都会归位角度四元数不停向原始位置归位5.1、Unity当中存在多线程时继承MonoBehaviour的脚本是否有必要对其中内容加锁为什么分情况讨论【绝大多数与引擎核心功能相关的 API 和对象都严禁在非主线程即子线程中直接访问或操作】1.如果是想要通过多线程控制或访问场景上对象的相关行为比如移动旋转、资源加载、动态销毁等等那么不用加锁因为Unity的整个执行模型是单线程的所有的游戏逻辑和渲染都在主线中进行。大部分的UnityAPI都只能在主线程上调用如果通过多线程去调用Unity场景上物体的相关API或改变场景中对象的相关属性会直接出现报错。因此加锁也没有意义2.如果是想要通过多线程改变继承MonoBehaviour脚本中的某些公共成员比如一个int一个List。那么可以为这些公共成员加锁防止线程并发带来的问题但是建议尽量不要通过多线程和继承MonoBehaviour的挂载在场景对象中的脚本打交道5.2、我们在Unity中进行一些复杂逻辑处理时比如网路通讯、寻路算法往往会开启多线程进行处理。我们如何保证数据能够和Unity主线程进行正常交互请至少说出1种方式1.对共享数据加锁避免多线程并发带来的数据竞争问题2.是用C#中线程安全的数据结构存储共享数据比如System.Collections.Concurrent命名空间中的ConcurrentQueue和ConcurrentDictionary 3.可以尝试使用Unity当中的Dots系统6.1、Shader当中的Blend渲染命令主要用来干什么?控制当前片元即将渲染的像素颜色与帧缓冲区屏幕上已有颜色如何进行混合计算从而实现各种透明、叠加、发光等视觉效果6.2、Shader当中的Blend渲染命令后面跟的参数是用来干嘛的比如Blend SrcAlpha OneMinusSrcAlpha公式FinalColor (SourceFactor × SourceColor) Operation (DestinationFactor × DestinationColor)Blendrender targetsource RGBdestination RGB,source alphadestination alphaBlend1One Zero,Zero OneBlend SrcAlpha OneMinusSrcAlpha // 传统透明度Blend One OneMinusSrcAlpha // 预乘透明度Blend One One // 加法Blend OneMinusDstColor One // 软加法Blend DstColor Zero // 乘法Blend DstColor SrcColor // 2x 乘法7、请简述Unity中协程的原理我的回答表面上看是和线程类似的概念能做到分出一部分性能处理其他问题但协程并不会多开一个线程而是在主线程上分阶段执行。原理是利用IEnumerator迭代器协程内部每次执行完一个阶段 yield return暂停外部运行时检查是否满足继续的条件满足时通过MoveNext()返回暂停处并执行下一阶段如此往复直到执行完。协程Coroutine是基于迭代器模式实现的、在主线程中分帧执行的异步编程工具。它与线程有本质区别核心在于协作式而非抢占式的任务处理。它的工作原理和关键点如下1.底层机制协程方法返回 IEnumerator接口。利用 yield return语句定义暂停点。每次执行到 yield returnUnity会将当前状态封存并返回主循环等待下一次恢复。2.调度方式由Unity引擎的协程调度器统一管理。在每一帧的游戏循环中例如在 Update方法之后调度器会检查所有活跃协程的暂停条件是否满足如等待时间是否到期若满足则通过调用迭代器的 MoveNext()方法让协程从上次yield的位置继续执行下一段代码。3.生命周期通过 MonoBehaviour.StartCoroutine()启动并通过 StopCoroutine()或 StopAllCoroutines()主动停止。当协程方法执行完毕或所在的GameObject被销毁时协程也会自动终止。“yield return null; 和 yield return WaitForFixedUpdate; 有什么区别”yield return null在下一帧的 Update后恢复。yield return new WaitForFixedUpdate()在下一帧的 FixedUpdate后恢复。“协程可能带来什么性能问题”管理开销大量活跃协程如成千上万个会增加调度器每帧的检查开销。内存问题如果协程中封装了闭包或引用了大对象且协程长期运行或被遗忘可能导致内存泄漏。“如何停止一个协程”使用 StopCoroutine方法。需要注意的是最好在启动协程时将返回的 Coroutine对象存储在变量中然后用这个变量作为参数来停止。直接使用方法名停止的方式在某些情况下可能不可靠。直接使用方法名停止的方式在某些情况下为什么不可靠1。启动与停止方式不匹配如果你使用 StartCoroutine(IEnumerator routine)即传递方法的方式启动协程然后尝试用 StopCoroutine(string methodName)即传递字符串方法名的方式停止停止操作会无效。这两种方式在Unity内部可能被视为不同的调用路径。2.方法重载与参数问题通过字符串方法名启动的协程只能传递一个参数。如果你的协程方法有多个参数或者存在方法重载使用字符串方法名可能无法准确指定要停止的协程。3.拼写错误与反射开销字符串方法名在编译时无法检查拼写错误只有在运行时才会暴露问题可能导致调试困难。同时Unity内部可能需要通过反射来查找方法性能上相对稍差。4.停止的不是“最近启动”的实例同一个协程方法可能被多次启动产生多个不同的实例。使用字符串方法名停止时可能无法精确控制停止的是哪一个实例。8、Unity底层如何处理C#代码https://blog.csdn.net/2303_76354097/article/details/145811160编译阶段从 C# 到中间语言 (IL)Unity 使用 C# 编译器通常是 Roslyn 将开发者编写的 C# 源代码 (.cs) 编译为与平台无关的中间语言 (IL, Intermediate Language)并存储在 .NET 程序集通常是 .dll 文件中。运行时处理IL 到机器码的转换 (Mono vs IL2CPP)IL 代码不能直接被 CPU 执行需要通过脚本后端转换为原生机器码。IL2CPP先将 C# 代码编译成 IL然后再将 IL 转换为 C 代码最后使用平台特定的 C 编译器将 C 代码编译成本地机器码。我的回答Mono是JIT编译阶段把C#转为平台无关的IL语言运行时再转为机器码ILCPP是编译阶段把C#转为IL语言再转为cpp代码再转为对应平台的机器码是AOT的运行时效率更高9、Application.streamingAssetsPath 和 Application.persistentDataPath两个路径有何区别有什么作用10、Unity中多线程不能访问哪些内容1.子线程负责纯计算让子线程处理耗时计算如路径搜索、复杂数学运算、数据预处理、网络请求等得到结果。2.将结果传回主线程通过线程安全的机制将子线程的计算结果或需要在主线程执行的操作“打包”并传递。3.主线程应用结果在主线程的更新循环如Update中取出结果并安全地调用Unity API来应用这些结果如设置物体位置、更新UI等。MainThreadDispatcher 模式创建一个单例MonoBehaviour使用一个线程安全的队列如ConcurrentQueue来存储需要在主线程执行的回调Action然后在Update()中逐一执行它们。11、请说明C中的VectorC#中的List是如何扩容的11.1、那么哈希表字典的扩容是怎么扩的参考https://mp.weixin.qq.com/s?__bizMzg5NzA0NjYyNAmid2247488677idx1snc67a0ff65e7ead4c0b76eabdeae305d0chksmc1a59d7fad40418bd0f623003a3687306e10b035faa100312d3d23f252483f93af55d0b29514#rd以cunordered_map为例12、事件和委托在使用上的区别委托是函数的容器可以理解为存储函数的变量类型。事件只能作为成员存在于类和接口以及结构体中事件在外部只能-不能赋值调用防止外部随意置空委托防止外部随意调用委托事件相当于对委托进行了一次封装 让其更加安全13、值和引用类型在变量赋值时的区别是什么此处为C#Cclass传值会复制一个而C#是引用改另一个原本的也会改14、Unity中点乘和叉乘对于我们来说的作用是什么点乘点乘的结果是一个标量它核心揭示了两个向量之间的方向关系。0, 夹角90°;0, 90°;090°。叉乘叉乘的结果是一个新的向量这个向量垂直于原来两个向量所构成的平面其应用更侧重于“方向”和“构造”。1.判断左右方位结果向量的y 0目标位于参考方向的右侧顺时针方向。结果向量的y 0目标位于参考方向的左侧逆时针方向。等于0时平行或共线2.计算法线向量在3D图形学中叉乘是计算平面法线的标准方法。给定一个平面或三角形上两个不共线的向量它们的叉积结果就是这个平面的法线向量广泛应用于光照、碰撞检测和背面剔除15、Unity或C#中的装箱和拆箱是什么装箱是指将值类型转换为 object类型或该值类型实现的任何接口类型的过程。这个过程是隐式的intvalue100;// 值类型存储在栈上objectobjvalue;// 装箱将值类型转换为引用类型// value 的值被复制到堆上obj 引用该堆对象objectobj100;// 装箱隐式intnum(int)obj;// 拆箱将引用类型显式转换回值类型// 堆上的值被复制到栈上的 num// 避免这样写导致装箱ArrayListarrayListnewArrayList();arrayList.Add(42);// 装箱发生// 推荐这样写无装箱泛型ListintintListnewListint();intList.Add(42);// 无装箱拆箱是指将 object类型或接口类型转换回值类型的过程。这个过程是显式的需要进行强制类型转换16、请说说你认为C#中 和 Equals 的区别是什么17、浅拷贝和深拷贝的区别浅拷贝只复制值新对象中的引用和指针地址和源对象一样修改一其中一个会影响另一个对象深拷贝会递归的复制嵌套引用确保所有层次的引用对象都被复制会创建一个完全独立的复制品两个对象互不影响。使用浅拷贝场景性能要求较高对象结构简单需要多个对象共享相同的数据以减少内存使用eg.缓存数据的共享浅拷贝可以使得多个对象共享相同的引用。使用深拷贝场景数据独立性要求高复杂数据结构并发编程在多线程环境中深拷贝可以防止线程之间的相互干扰提高程序的安全性和稳定性。18、什么是DrawCallDrawCall为什么会影响游戏运行效率如何减少DrawCall什么是DrawCallCPU调用图形API命令GPU渲染的操作DrawCall为什么会影响游戏运行效率CPU和GPU通过一个命令缓冲区进行协作CPU负责准备渲染数据包括顶点、材质、纹理、着色器等设置渲染状态并将drawcall命令写入缓冲区GPU取出指令并执行。若有大量的drawcall命令CPU会忙于准备数据提交命令而GPU空闲。每次DrawCall都可能涉及渲染状态的改变例如切换纹理、着色器、混合模式、深度测试设置等。这些状态切换对GPU而言是额外的开销。如果连续两个DrawCall使用相同的渲染状态GPU可以保持状态不变但状态频繁切换会带来性能损耗。如何减少DrawCall静态批处理将不动的静态物体建筑、地形等设为staticunity会将这些合并为一个大网格从而大幅减少drawcall次数但是静态批处理会增加内存开销因为Unity会复制网格数据避免使用过多的材质。尽量在不同的网格之间共用同一个材质。避免使用大量很小的网格。当不可避免地需要使用很小的网格结构时考虑是否可以合并它们。遮挡剔除减少实时光的使用以及阴影效果UI中需要同一panel渲染状态图集材质都相同才会合批。19、Unity中当一个细小高速物体撞击另一个较大物体时会出现什么情况如何避免最常见的问题是穿透Penetration即小物体直接“穿”过了大物体碰撞检测仿佛失效了。Unity的物理引擎按固定的时间步长Fixed Timestep 更新默认每秒50次约0.02秒一次。在“Discrete”模式下引擎只检查物体在每个时间步长开始和结束时刻的位置状态。如果一个物体速度极快比如每秒移动100米它在一帧内0.02秒就能移动2米。如果目标碰撞体的厚度小于2米这个快物体就很可能从一侧直接“跳”到了另一侧引擎在起点和终点都没有检测到碰撞因此就穿透了解决方法1、使用连续碰撞检测选中高速物体的Rigidbody组件将其 Collision Detection 属性从默认的 Discrete 改为 Continuous 或 Continuous Dynamic。区别Continuous Dynamic用于检测与其他连续或静态碰撞体的碰撞Continuous则用于静态几何体。2、减小 Fixed Timestep增加物理更新的频率提高精度。20、内存泄漏指什么常见的内存泄漏有哪些内存泄漏指的是申请一块内存后等到这块内存不再使用时依然不释放。c中可能出现的情况1、忘记释放或错误释放比如数组只释放第一个2、析构函数非虚基类指针删除派生类对象派生类的析构不会调用3、shared_ptr循环引用引用技术无法清零。unity游戏开发1、静态变量引用对象即使对象销毁也不会释放2、委托事件、协程没有注销那么内存也会泄漏。21、什么是闭包举例说明本质是一个函数或匿名方法能够捕获并记住其外部作用域中的变量即使这个外部函数已经执行完毕。这使得闭包可以访问和操作定义时的上下文环境。形成闭包的三个典型条件1、函数嵌套一个函数内部定义了另一个函数。2、内部函数引用外部变量内部函数使用了外部函数的局部变量。3、内部函数被外部调用内部函数被返回或在外部作用域被使用。22、游戏中的成就系统用什么设计模式来做观察者模式可以注册订阅各种事件监听玩家的行动一旦达成条件即可触发成就。策略模式将判断条件抽象成一个类可随意添加新的策略不同的成就可以灵活选择不同的判断条件。23、泛型有哪些约束值类型structT必须是值类型引用类型classT必须是引用类型无参构造new()T必须有无参构造基类:基类T必须是指定基类或其派生类接口接口T必须实现指定接口裸类型UT必须是另一个参数U或其派生类非托管类型unmanagedT必须是不可为null的非托管类型指 CLR 无法自动管理的资源不可为nullnotnullT必须不为null枚举System.EnumT必须是枚举类24、.NET跨语言跨平台的原理.NET通过公共语言运行时CLR会编译成统一的中间语言IL这是平台无关的可以根据不同平台转化成对应的机器码。JIT和AOTJIT是运行时编译在目标机器编译编译快运行慢需要内存存储编译后的机器码能够根据运行时信息优化AOT是运行前编译运行快编译慢需要为每个目标平台提前编译完全依赖于构建时所能进行的静态代码分析无法根据运行时信息进行优化。Unity使用IL2CPP打包时应该注意什么如何避免AOT的缺陷在这里体现Unity用IL2CPP将C#转为C属于AOT代码剥离它的静态裁剪会把没用到的代码去除但是可能会用反射来调用这时候就可能找不到比如序列化和反序列化可以在link.xml 文件显示要求保留。通过接口调用且涉及值类型如枚举的泛型虚方法也会被忽略可以创建一个方法调用确保生成代码。不支持任何动态生成代码的方法比如system.reflection.emit25、C# 中的Action和Func和Unity 中的UnityAction都是委托不同是Action无返回值Func有返回值Func的泛型参数列表最后一个总是返回值类型。UnityAction支持unity编辑器可视化地添加和移除事件监听。26、生命周期函数Awake脚本实例被加载时就调用无论脚本是否激活都会调用OnEnable脚本激活时调用因此可执行多次Start脚本首次激活后在update之前调用Update每帧调用受帧率影响FixedUpdate每个物理帧更新不受帧率影响多用于计算LateUpdate在update后调用适用于相机跟随等在角色移动后的逻辑OnDisable对象失活或销毁时调用OnDestroy脚本实例被销毁时调用27、接口的作用1、固定函数的输入输出2、不定义函数具体实现由继承的类实现。接口定义一个实现的规则继承接口的类必须实现这个接口规定的所有函数以实现接口所需的功能。接口可以规范实现的输入输出但把灵活的实现方式保留让继承它的类来实现。这样能够实现多态的效果比如计算不同形状的边长面积可以有不同的实现方式。28、C#中如何让自定义容器类能够使用for循环遍历通过 类对象[索引] 的形式遍历1、实现索引器2、实现IEnumerable接口支持 foreachusingSystem.Collections;usingSystem.Collections.Generic;publicclassMyContainerT:IEnumerableT{privatereadonlyListT_itemsnewListT();// 索引器publicTthis[intindex]{get_items[index];set_items[index]value;}// 添加元素publicvoidAdd(Titem)_items.Add(item);// 实现 IEnumerableTpublicIEnumeratorTGetEnumerator()_items.GetEnumerator();// 显式实现非泛型 IEnumerableIEnumeratorIEnumerable.GetEnumerator()GetEnumerator();// 容器大小publicintCount_items.Count;}29、Unity中的协同程序中yield return不同的内容代表的含义不同请说明下面这些yield return的含义1.yield return 数字;2.yield return null;3.yield return new WaitForSeconds(数字);4.yield return new WaitForFixedUpdate();5.yield return new WaitForEndOfFrame();6.yield break;12都是暂停下一帧继续执行3是暂停后等几秒后执行4是暂停后等下一物理帧执行5是暂停后等到帧渲染结束执行可用于截图6是这次执行完后不再返回执行。30、请简述GC垃圾回收产生的原因并说出避免GC发生的方式GC原理在内存不足以分配时触发暂停所有线程并遍历所有可达对象将未访问的对象视为垃圾并回收。GC是C#为了方便程序员使用设计的自动管理内存的机制。好处是避免程序员疏忽导致的内存泄漏悬空指针双重释放等问题对象不再使用时就自动回收它们的内存。1、在update中减少不必要的new最好移至start中2、用对象缓存池避免频繁创建回收生命周期很短的对象导致的大量GC3、减少不必要的装箱拆箱比如用泛型代替object4、简单的数据用struct代替class因为struct在栈上分配空间不需要GC5、StringBuilder代替普通的字符串拼接因为string不可变拼接会创建临时字符串。31、UGUI底层UGUI是用3D网格建立的每个UI元素都会有个材质球在UGUI中材质球主要是纹理图片不同一次DrawCall命令GPU如何渲染一个网格其中会用到材质球信息大量不同的材质球会引起过多的DrawCall而相同的材质球是可以共用一个DrawCall的因此可以把图片合并为图集这样就可以算作相同的材质球共享一个网格。在屏幕空间中两个相同材质的UI元素中间若有不同材质的UI元素会打断合批。最好还要动静分离因为一旦UI元素移动则会重新计算网格一个动态的UI元素会导致整个Canvas的网格重建因此要动静分离32、请说明Thread、ThreadPool、Task分别是什么并简单说明彼此的区别thread是线程可用于处理复杂计算thread pool是线程池线程创建销毁有开销频繁开线程会很卡因此线程池可以优化性能task是对thread pool的封装用起来比较方便33、C#中元组对于我们的作用是什么捆绑多个值时不想搞个数据结构就用元组1、字典的键可以用元组实现多个键 var lootTable new Dictionary(string, string), Item();2、函数返回多个值 (bool success, Vector3 position) TryGetSpawnPoint();3、交换两个值 (a, b) (b, a);。34、LOD多细节层次和 MipMap纹理图的作用是什么如果没有会出现远处很小的物体却有大量顶点和高分辨率的纹理占用大量性能开销的情况这两个都是根据摄像机距离调整精细程度LOD是会减少顶点数量mipmap是降低分辨率。35、
Unity面试题——唐老师模拟面试、每日一题记录
发布时间:2026/5/31 12:41:00
来源于唐老狮模拟面试和每日一题1、静态成员属于类而不是实例答案为30 30 30答案为0 20 20 302、简单题3.1、C#中是否可以通过反射获取到类内部的私有成员可以在获取成员的相关方法中可以通过传入参数指定获取非公共的成员3.2、在制作游戏存档功能时C#中反射主要可以发挥出哪些作用至少说出三点1.序列化时动态获取数据结构类信息可以动态获取字段用于存储2.反序列化时可以通过反射实例化对象写入数据3.结构发生变化时我们可以利用反射机制进行判断多的数据抛弃少的数据自定义初始化4、如果在Unity当中制作FPS游戏如何模拟枪械开枪时的后坐力每次开枪时可以给枪械、手臂IK、摄像机等根据实际视角情况决定一个绕x轴往上偏移和y轴左右偏移的旋转角度并且每帧都会归位角度四元数不停向原始位置归位5.1、Unity当中存在多线程时继承MonoBehaviour的脚本是否有必要对其中内容加锁为什么分情况讨论【绝大多数与引擎核心功能相关的 API 和对象都严禁在非主线程即子线程中直接访问或操作】1.如果是想要通过多线程控制或访问场景上对象的相关行为比如移动旋转、资源加载、动态销毁等等那么不用加锁因为Unity的整个执行模型是单线程的所有的游戏逻辑和渲染都在主线中进行。大部分的UnityAPI都只能在主线程上调用如果通过多线程去调用Unity场景上物体的相关API或改变场景中对象的相关属性会直接出现报错。因此加锁也没有意义2.如果是想要通过多线程改变继承MonoBehaviour脚本中的某些公共成员比如一个int一个List。那么可以为这些公共成员加锁防止线程并发带来的问题但是建议尽量不要通过多线程和继承MonoBehaviour的挂载在场景对象中的脚本打交道5.2、我们在Unity中进行一些复杂逻辑处理时比如网路通讯、寻路算法往往会开启多线程进行处理。我们如何保证数据能够和Unity主线程进行正常交互请至少说出1种方式1.对共享数据加锁避免多线程并发带来的数据竞争问题2.是用C#中线程安全的数据结构存储共享数据比如System.Collections.Concurrent命名空间中的ConcurrentQueue和ConcurrentDictionary 3.可以尝试使用Unity当中的Dots系统6.1、Shader当中的Blend渲染命令主要用来干什么?控制当前片元即将渲染的像素颜色与帧缓冲区屏幕上已有颜色如何进行混合计算从而实现各种透明、叠加、发光等视觉效果6.2、Shader当中的Blend渲染命令后面跟的参数是用来干嘛的比如Blend SrcAlpha OneMinusSrcAlpha公式FinalColor (SourceFactor × SourceColor) Operation (DestinationFactor × DestinationColor)Blendrender targetsource RGBdestination RGB,source alphadestination alphaBlend1One Zero,Zero OneBlend SrcAlpha OneMinusSrcAlpha // 传统透明度Blend One OneMinusSrcAlpha // 预乘透明度Blend One One // 加法Blend OneMinusDstColor One // 软加法Blend DstColor Zero // 乘法Blend DstColor SrcColor // 2x 乘法7、请简述Unity中协程的原理我的回答表面上看是和线程类似的概念能做到分出一部分性能处理其他问题但协程并不会多开一个线程而是在主线程上分阶段执行。原理是利用IEnumerator迭代器协程内部每次执行完一个阶段 yield return暂停外部运行时检查是否满足继续的条件满足时通过MoveNext()返回暂停处并执行下一阶段如此往复直到执行完。协程Coroutine是基于迭代器模式实现的、在主线程中分帧执行的异步编程工具。它与线程有本质区别核心在于协作式而非抢占式的任务处理。它的工作原理和关键点如下1.底层机制协程方法返回 IEnumerator接口。利用 yield return语句定义暂停点。每次执行到 yield returnUnity会将当前状态封存并返回主循环等待下一次恢复。2.调度方式由Unity引擎的协程调度器统一管理。在每一帧的游戏循环中例如在 Update方法之后调度器会检查所有活跃协程的暂停条件是否满足如等待时间是否到期若满足则通过调用迭代器的 MoveNext()方法让协程从上次yield的位置继续执行下一段代码。3.生命周期通过 MonoBehaviour.StartCoroutine()启动并通过 StopCoroutine()或 StopAllCoroutines()主动停止。当协程方法执行完毕或所在的GameObject被销毁时协程也会自动终止。“yield return null; 和 yield return WaitForFixedUpdate; 有什么区别”yield return null在下一帧的 Update后恢复。yield return new WaitForFixedUpdate()在下一帧的 FixedUpdate后恢复。“协程可能带来什么性能问题”管理开销大量活跃协程如成千上万个会增加调度器每帧的检查开销。内存问题如果协程中封装了闭包或引用了大对象且协程长期运行或被遗忘可能导致内存泄漏。“如何停止一个协程”使用 StopCoroutine方法。需要注意的是最好在启动协程时将返回的 Coroutine对象存储在变量中然后用这个变量作为参数来停止。直接使用方法名停止的方式在某些情况下可能不可靠。直接使用方法名停止的方式在某些情况下为什么不可靠1。启动与停止方式不匹配如果你使用 StartCoroutine(IEnumerator routine)即传递方法的方式启动协程然后尝试用 StopCoroutine(string methodName)即传递字符串方法名的方式停止停止操作会无效。这两种方式在Unity内部可能被视为不同的调用路径。2.方法重载与参数问题通过字符串方法名启动的协程只能传递一个参数。如果你的协程方法有多个参数或者存在方法重载使用字符串方法名可能无法准确指定要停止的协程。3.拼写错误与反射开销字符串方法名在编译时无法检查拼写错误只有在运行时才会暴露问题可能导致调试困难。同时Unity内部可能需要通过反射来查找方法性能上相对稍差。4.停止的不是“最近启动”的实例同一个协程方法可能被多次启动产生多个不同的实例。使用字符串方法名停止时可能无法精确控制停止的是哪一个实例。8、Unity底层如何处理C#代码https://blog.csdn.net/2303_76354097/article/details/145811160编译阶段从 C# 到中间语言 (IL)Unity 使用 C# 编译器通常是 Roslyn 将开发者编写的 C# 源代码 (.cs) 编译为与平台无关的中间语言 (IL, Intermediate Language)并存储在 .NET 程序集通常是 .dll 文件中。运行时处理IL 到机器码的转换 (Mono vs IL2CPP)IL 代码不能直接被 CPU 执行需要通过脚本后端转换为原生机器码。IL2CPP先将 C# 代码编译成 IL然后再将 IL 转换为 C 代码最后使用平台特定的 C 编译器将 C 代码编译成本地机器码。我的回答Mono是JIT编译阶段把C#转为平台无关的IL语言运行时再转为机器码ILCPP是编译阶段把C#转为IL语言再转为cpp代码再转为对应平台的机器码是AOT的运行时效率更高9、Application.streamingAssetsPath 和 Application.persistentDataPath两个路径有何区别有什么作用10、Unity中多线程不能访问哪些内容1.子线程负责纯计算让子线程处理耗时计算如路径搜索、复杂数学运算、数据预处理、网络请求等得到结果。2.将结果传回主线程通过线程安全的机制将子线程的计算结果或需要在主线程执行的操作“打包”并传递。3.主线程应用结果在主线程的更新循环如Update中取出结果并安全地调用Unity API来应用这些结果如设置物体位置、更新UI等。MainThreadDispatcher 模式创建一个单例MonoBehaviour使用一个线程安全的队列如ConcurrentQueue来存储需要在主线程执行的回调Action然后在Update()中逐一执行它们。11、请说明C中的VectorC#中的List是如何扩容的11.1、那么哈希表字典的扩容是怎么扩的参考https://mp.weixin.qq.com/s?__bizMzg5NzA0NjYyNAmid2247488677idx1snc67a0ff65e7ead4c0b76eabdeae305d0chksmc1a59d7fad40418bd0f623003a3687306e10b035faa100312d3d23f252483f93af55d0b29514#rd以cunordered_map为例12、事件和委托在使用上的区别委托是函数的容器可以理解为存储函数的变量类型。事件只能作为成员存在于类和接口以及结构体中事件在外部只能-不能赋值调用防止外部随意置空委托防止外部随意调用委托事件相当于对委托进行了一次封装 让其更加安全13、值和引用类型在变量赋值时的区别是什么此处为C#Cclass传值会复制一个而C#是引用改另一个原本的也会改14、Unity中点乘和叉乘对于我们来说的作用是什么点乘点乘的结果是一个标量它核心揭示了两个向量之间的方向关系。0, 夹角90°;0, 90°;090°。叉乘叉乘的结果是一个新的向量这个向量垂直于原来两个向量所构成的平面其应用更侧重于“方向”和“构造”。1.判断左右方位结果向量的y 0目标位于参考方向的右侧顺时针方向。结果向量的y 0目标位于参考方向的左侧逆时针方向。等于0时平行或共线2.计算法线向量在3D图形学中叉乘是计算平面法线的标准方法。给定一个平面或三角形上两个不共线的向量它们的叉积结果就是这个平面的法线向量广泛应用于光照、碰撞检测和背面剔除15、Unity或C#中的装箱和拆箱是什么装箱是指将值类型转换为 object类型或该值类型实现的任何接口类型的过程。这个过程是隐式的intvalue100;// 值类型存储在栈上objectobjvalue;// 装箱将值类型转换为引用类型// value 的值被复制到堆上obj 引用该堆对象objectobj100;// 装箱隐式intnum(int)obj;// 拆箱将引用类型显式转换回值类型// 堆上的值被复制到栈上的 num// 避免这样写导致装箱ArrayListarrayListnewArrayList();arrayList.Add(42);// 装箱发生// 推荐这样写无装箱泛型ListintintListnewListint();intList.Add(42);// 无装箱拆箱是指将 object类型或接口类型转换回值类型的过程。这个过程是显式的需要进行强制类型转换16、请说说你认为C#中 和 Equals 的区别是什么17、浅拷贝和深拷贝的区别浅拷贝只复制值新对象中的引用和指针地址和源对象一样修改一其中一个会影响另一个对象深拷贝会递归的复制嵌套引用确保所有层次的引用对象都被复制会创建一个完全独立的复制品两个对象互不影响。使用浅拷贝场景性能要求较高对象结构简单需要多个对象共享相同的数据以减少内存使用eg.缓存数据的共享浅拷贝可以使得多个对象共享相同的引用。使用深拷贝场景数据独立性要求高复杂数据结构并发编程在多线程环境中深拷贝可以防止线程之间的相互干扰提高程序的安全性和稳定性。18、什么是DrawCallDrawCall为什么会影响游戏运行效率如何减少DrawCall什么是DrawCallCPU调用图形API命令GPU渲染的操作DrawCall为什么会影响游戏运行效率CPU和GPU通过一个命令缓冲区进行协作CPU负责准备渲染数据包括顶点、材质、纹理、着色器等设置渲染状态并将drawcall命令写入缓冲区GPU取出指令并执行。若有大量的drawcall命令CPU会忙于准备数据提交命令而GPU空闲。每次DrawCall都可能涉及渲染状态的改变例如切换纹理、着色器、混合模式、深度测试设置等。这些状态切换对GPU而言是额外的开销。如果连续两个DrawCall使用相同的渲染状态GPU可以保持状态不变但状态频繁切换会带来性能损耗。如何减少DrawCall静态批处理将不动的静态物体建筑、地形等设为staticunity会将这些合并为一个大网格从而大幅减少drawcall次数但是静态批处理会增加内存开销因为Unity会复制网格数据避免使用过多的材质。尽量在不同的网格之间共用同一个材质。避免使用大量很小的网格。当不可避免地需要使用很小的网格结构时考虑是否可以合并它们。遮挡剔除减少实时光的使用以及阴影效果UI中需要同一panel渲染状态图集材质都相同才会合批。19、Unity中当一个细小高速物体撞击另一个较大物体时会出现什么情况如何避免最常见的问题是穿透Penetration即小物体直接“穿”过了大物体碰撞检测仿佛失效了。Unity的物理引擎按固定的时间步长Fixed Timestep 更新默认每秒50次约0.02秒一次。在“Discrete”模式下引擎只检查物体在每个时间步长开始和结束时刻的位置状态。如果一个物体速度极快比如每秒移动100米它在一帧内0.02秒就能移动2米。如果目标碰撞体的厚度小于2米这个快物体就很可能从一侧直接“跳”到了另一侧引擎在起点和终点都没有检测到碰撞因此就穿透了解决方法1、使用连续碰撞检测选中高速物体的Rigidbody组件将其 Collision Detection 属性从默认的 Discrete 改为 Continuous 或 Continuous Dynamic。区别Continuous Dynamic用于检测与其他连续或静态碰撞体的碰撞Continuous则用于静态几何体。2、减小 Fixed Timestep增加物理更新的频率提高精度。20、内存泄漏指什么常见的内存泄漏有哪些内存泄漏指的是申请一块内存后等到这块内存不再使用时依然不释放。c中可能出现的情况1、忘记释放或错误释放比如数组只释放第一个2、析构函数非虚基类指针删除派生类对象派生类的析构不会调用3、shared_ptr循环引用引用技术无法清零。unity游戏开发1、静态变量引用对象即使对象销毁也不会释放2、委托事件、协程没有注销那么内存也会泄漏。21、什么是闭包举例说明本质是一个函数或匿名方法能够捕获并记住其外部作用域中的变量即使这个外部函数已经执行完毕。这使得闭包可以访问和操作定义时的上下文环境。形成闭包的三个典型条件1、函数嵌套一个函数内部定义了另一个函数。2、内部函数引用外部变量内部函数使用了外部函数的局部变量。3、内部函数被外部调用内部函数被返回或在外部作用域被使用。22、游戏中的成就系统用什么设计模式来做观察者模式可以注册订阅各种事件监听玩家的行动一旦达成条件即可触发成就。策略模式将判断条件抽象成一个类可随意添加新的策略不同的成就可以灵活选择不同的判断条件。23、泛型有哪些约束值类型structT必须是值类型引用类型classT必须是引用类型无参构造new()T必须有无参构造基类:基类T必须是指定基类或其派生类接口接口T必须实现指定接口裸类型UT必须是另一个参数U或其派生类非托管类型unmanagedT必须是不可为null的非托管类型指 CLR 无法自动管理的资源不可为nullnotnullT必须不为null枚举System.EnumT必须是枚举类24、.NET跨语言跨平台的原理.NET通过公共语言运行时CLR会编译成统一的中间语言IL这是平台无关的可以根据不同平台转化成对应的机器码。JIT和AOTJIT是运行时编译在目标机器编译编译快运行慢需要内存存储编译后的机器码能够根据运行时信息优化AOT是运行前编译运行快编译慢需要为每个目标平台提前编译完全依赖于构建时所能进行的静态代码分析无法根据运行时信息进行优化。Unity使用IL2CPP打包时应该注意什么如何避免AOT的缺陷在这里体现Unity用IL2CPP将C#转为C属于AOT代码剥离它的静态裁剪会把没用到的代码去除但是可能会用反射来调用这时候就可能找不到比如序列化和反序列化可以在link.xml 文件显示要求保留。通过接口调用且涉及值类型如枚举的泛型虚方法也会被忽略可以创建一个方法调用确保生成代码。不支持任何动态生成代码的方法比如system.reflection.emit25、C# 中的Action和Func和Unity 中的UnityAction都是委托不同是Action无返回值Func有返回值Func的泛型参数列表最后一个总是返回值类型。UnityAction支持unity编辑器可视化地添加和移除事件监听。26、生命周期函数Awake脚本实例被加载时就调用无论脚本是否激活都会调用OnEnable脚本激活时调用因此可执行多次Start脚本首次激活后在update之前调用Update每帧调用受帧率影响FixedUpdate每个物理帧更新不受帧率影响多用于计算LateUpdate在update后调用适用于相机跟随等在角色移动后的逻辑OnDisable对象失活或销毁时调用OnDestroy脚本实例被销毁时调用27、接口的作用1、固定函数的输入输出2、不定义函数具体实现由继承的类实现。接口定义一个实现的规则继承接口的类必须实现这个接口规定的所有函数以实现接口所需的功能。接口可以规范实现的输入输出但把灵活的实现方式保留让继承它的类来实现。这样能够实现多态的效果比如计算不同形状的边长面积可以有不同的实现方式。28、C#中如何让自定义容器类能够使用for循环遍历通过 类对象[索引] 的形式遍历1、实现索引器2、实现IEnumerable接口支持 foreachusingSystem.Collections;usingSystem.Collections.Generic;publicclassMyContainerT:IEnumerableT{privatereadonlyListT_itemsnewListT();// 索引器publicTthis[intindex]{get_items[index];set_items[index]value;}// 添加元素publicvoidAdd(Titem)_items.Add(item);// 实现 IEnumerableTpublicIEnumeratorTGetEnumerator()_items.GetEnumerator();// 显式实现非泛型 IEnumerableIEnumeratorIEnumerable.GetEnumerator()GetEnumerator();// 容器大小publicintCount_items.Count;}29、Unity中的协同程序中yield return不同的内容代表的含义不同请说明下面这些yield return的含义1.yield return 数字;2.yield return null;3.yield return new WaitForSeconds(数字);4.yield return new WaitForFixedUpdate();5.yield return new WaitForEndOfFrame();6.yield break;12都是暂停下一帧继续执行3是暂停后等几秒后执行4是暂停后等下一物理帧执行5是暂停后等到帧渲染结束执行可用于截图6是这次执行完后不再返回执行。30、请简述GC垃圾回收产生的原因并说出避免GC发生的方式GC原理在内存不足以分配时触发暂停所有线程并遍历所有可达对象将未访问的对象视为垃圾并回收。GC是C#为了方便程序员使用设计的自动管理内存的机制。好处是避免程序员疏忽导致的内存泄漏悬空指针双重释放等问题对象不再使用时就自动回收它们的内存。1、在update中减少不必要的new最好移至start中2、用对象缓存池避免频繁创建回收生命周期很短的对象导致的大量GC3、减少不必要的装箱拆箱比如用泛型代替object4、简单的数据用struct代替class因为struct在栈上分配空间不需要GC5、StringBuilder代替普通的字符串拼接因为string不可变拼接会创建临时字符串。31、UGUI底层UGUI是用3D网格建立的每个UI元素都会有个材质球在UGUI中材质球主要是纹理图片不同一次DrawCall命令GPU如何渲染一个网格其中会用到材质球信息大量不同的材质球会引起过多的DrawCall而相同的材质球是可以共用一个DrawCall的因此可以把图片合并为图集这样就可以算作相同的材质球共享一个网格。在屏幕空间中两个相同材质的UI元素中间若有不同材质的UI元素会打断合批。最好还要动静分离因为一旦UI元素移动则会重新计算网格一个动态的UI元素会导致整个Canvas的网格重建因此要动静分离32、请说明Thread、ThreadPool、Task分别是什么并简单说明彼此的区别thread是线程可用于处理复杂计算thread pool是线程池线程创建销毁有开销频繁开线程会很卡因此线程池可以优化性能task是对thread pool的封装用起来比较方便33、C#中元组对于我们的作用是什么捆绑多个值时不想搞个数据结构就用元组1、字典的键可以用元组实现多个键 var lootTable new Dictionary(string, string), Item();2、函数返回多个值 (bool success, Vector3 position) TryGetSpawnPoint();3、交换两个值 (a, b) (b, a);。34、LOD多细节层次和 MipMap纹理图的作用是什么如果没有会出现远处很小的物体却有大量顶点和高分辨率的纹理占用大量性能开销的情况这两个都是根据摄像机距离调整精细程度LOD是会减少顶点数量mipmap是降低分辨率。35、