网易2018校招那套Unity开发实习生笔试题,放到今天依然值得拿出来反复研究。原因倒不是题目本身多难,而是它考得非常“网易”——不追求API死记硬背,而是把C#语言基础、Unity生命周期、渲染管线、性能优化、算法逻辑这些和实际客户端开发强相关的点全部串起来了。面试过Unity客户端的人应该都有印象,牛客网和知乎上关于这套题的讨论帖,到现在还有人在回帖,很多后来者干脆把它当成Unity入门之后的自测清单。这篇文章不打算贴原题复述,而是把考点背后真正需要掌握的原理、底层逻辑和实操经验拆开来讲。不管你是准备校招实习岗位的学生,还是已经工作一两年想系统巩固一遍基础的开发者,都可以把这篇当一份查漏补缺的提纲来用。
网易的笔试向来不靠偏题怪题取胜,他们更希望看到候选人的基础功底和工程思维。Unity客户端岗位尤其如此,因为Unity引擎封装得很深,很多人写过一年多游戏代码,却说不清协程为什么能“暂停”、Shader里的颜色为什么是四维向量、动态批处理时为什么会悄悄失效。这套笔试题恰恰就是围绕这些“你以为你会了,其实没想透”的知识点展开的。我把题目背后考察的核心内容按模块整理了一下,顺便补上一些当年踩坑和后来带新人时的经验,希望对你有实际帮助。
1. 网易这套Unity笔试究竟在考察什么
1.1 题型分布与考察逻辑
从历年流出的版本来看,网易2018校招的Unity笔试整体分三块:基础选择题、逻辑与算法题、开放性简答题。选择题一般覆盖C#语法、数据结构、Unity API使用细节;算法题通常是常见的字符串处理、数组变换、位运算和简单搜索;简答题则会给一个具体的游戏场景,让你分析优化方案或设计某个模块。
考察逻辑很明确:他们不指望实习生上来就能写框架,但要求你在Unity这块领域有扎实的基本功,并且有主动查文档、看源码的工程习惯。举个例子,题目如果问“Update和FixedUpdate的区别”,潜台词其实是问:你有没有在实际开发中遇到过物理帧率不稳定的情况?你知不知道移动平台帧率波动时,物理模拟该怎么处理?这一类问题靠刷题不一定能答好,本质还是靠做项目时积累的理解。
1.2 为什么Unity基础题这么重要
近几年Unity的应用范围越来越广,不止游戏,数字孪生、车载HMI、XR设备(比如PICO 4上的Unity开发)、甚至工业仿真都在用。但不管项目类型怎么变,引擎底层的逻辑是相通的:生命周期不会变,渲染管线不会变,C#的GC机制也不会变。
这就解释了为什么网易笔试几乎不考“XXX插件怎么用”这类问题,因为插件是工具、是会过时的,而Unity的生命周期、坐标系、渲染顺序、批处理原理这些底层知识,五年后还是这些。反过来,一个候选人如果连Awake和Start的执行顺序都说不清,就算简历上写了三四个上线项目,面试官也很难相信项目是他真正负责的。所以这篇博文会花比较大篇幅讲底层的“为什么”,而不是停留在操作层面。
1.3 网上流传版本与本题参考价值
网上能找到的2018版原题有好几个流传版本,有些是同学的回忆帖,有些是培训机构整理的题库,细节上会有出入,但考点高度一致。参考价值最大的不是那几道Java/C++也通用的算法题,而是Unity绑定特别强的那部分:生命周期顺序、协程、Shader、批处理、GC、场景资源管理。
我自己后来带过几个校招新人,实测下来,能把这套题的知识点讲透的人,上手项目速度普遍快很多。因为你把原理理解了以后,看到一个卡顿现象,脑子里会自然浮现出Profiler里面几条可能的原因,而不是盲目地把所有物体的Mesh合并一遍然后祈祷它变流畅。
2. C#与面向对象基础:笔试的第一道分水岭
2.1 值类型与引用类型:笔试题里最阴险的送分题
C#的值类型和引用类型,Unity笔试必考。考法通常不直接问定义,而是给一段代码,让你判断输出结果,或者问一个变量改了之后另一个变量会不会跟着变。这种题看着简单,但错误率特别高,核心原因就是很多人没把“栈上存储”和“堆上存储”这件事在脑子里形成画面。
值类型(int、float、bool、struct、枚举)在栈上直接存储数据本身;引用类型(class、string、数组、List)在栈上存储的是堆内存地址。所以你把一个class对象赋值给另一个变量,其实只是复制了一份地址,两个变量指向同一个堆对象;而把一个struct赋值给新变量,则会把字段整个拷贝一份。Unity的Transform、GameObject都是class,Vector3、Quaternion、Color则都是struct,理解了这一点,你才会明白为什么函数传参时大struct会导致性能问题,而class只需要传引用。
实际开发里有个很典型的坑:你想缓存一个坐标,于是写Vector3 pos = someTransform.position;,然后去改pos.x,结果发现someTransform的位置根本没变。原因就是position返回的是一个拷出来的struct,你改的只是副本,不是Transform内部的状态。这种代码在笔试里改头换面出现时,很多人第一反应是“Unity API是不是有Bug”,其实是值类型和引用类型没彻底想清楚。
2.2 装箱拆箱、泛型与GC隐患
C#的装箱(Boxing)是指把值类型转换成object或接口引用,这个过程会在托管堆上分配一块内存来存储这个值。笔试的典型考法是:用ArrayList存几个int,问会产生什么问题;或者给一段用了Hashtable的旧代码,问为什么帧率会掉。答案是:非泛型容器里存值类型,存一次就装箱一次,而箱体的回收依赖GC,GC一旦频繁触发,游戏就会出现卡顿。
正确做法是用泛型容器,比如List<int>、Dictionary<string, int>,这些容器内部用数组存储值类型元素,不需要装箱。Unity的UI系统、动画系统、事件系统底层大量使用了泛型容器,也是为了避免不必要的分配。
这里多提一句,网上很多“Unity性能优化”的热搜词都绕不开GC,而装箱是新手最容易反复踩的GC来源之一。我自己做项目时有个习惯:写完代码先全局搜一遍object类型的参数、ArrayList、Hashtable,只要发现这两个旧容器,直接换泛型。Python和Java背景转过来的同事尤其容易忽略这一点,因为在他们熟知的运行时里,这种写法可能只是“慢一点”,但在Unity的帧循环里,它是明确的帧率杀手。
2.3 委托、事件与字符串拼接的常见坑
委托和事件在Unity里出现在按钮点击、场景加载回调、动画事件这些地方。笔试会问“委托和事件的区别”,标准答法是:事件是受限制的委托,只能在声明事件的类内部Invoke,外部只能+=或-=。但更值得关注的是内存泄漏问题:如果你给某个事件注册了方法,对象销毁前没有反注册,那么这个对象会被事件源一直引用着,导致无法被GC回收。Unity里的经典场景是:UI面板侦听了GameManager.OnScoreChanged,面板关闭后没有-=,结果每次打开面板,之前的面板对象都还活在内存里,越玩越卡。
字符串拼接也是高频考点。C#的string是不可变类型,每次使用+连接字符串时,都会在堆上创建新的字符串对象。笔试里可能会问“循环1000次str += i.ToString()会产生多少个垃圾”,答案是几乎每次迭代都会产生新的中间字符串和装箱对象。正确做法是用StringBuilder,或者尽量用string.Format。Unity的Debug.Log里拼字符串在循环中尤为危险,因为即使你在Profiler里看不到明显的Draw Call变化,Managed Heap的分配量也在持续上涨。
3. Unity引擎核心机制:从生命周期到协程
3.1 生命周期函数顺序:笔试选择题里的常青树
Unity脚本生命周期是选择题和简答题的高发区。完整顺序是:Awake→OnEnable→Start→FixedUpdate→Update→LateUpdate→OnGUI→OnDisable→OnDestroy。很多人能背出这个顺序,但题目一旦换个问法,比如“脚本组件处于禁用状态时,哪个函数照样会被调用”,就有人懵了。
关键在于理解每个回调的触发条件。Awake在脚本实例被加载时立即调用,哪怕脚本组件是禁用状态也会执行,适合做资源加载、缓存组件引用这类不依赖其他模块的初始化。OnEnable在每次SetActive(true)时执行,适合做事件注册。Start在第一次Update之前、且脚本被启用时执行,适合做跨脚本的初始化,因为此时可以安全地查找其他对象了。很多老手习惯把组件引用获取放Awake,把数据初始化和外部依赖放Start,就是为了避免某些对象还没Ready就拿到空引用。
这里分享一个实际项目里踩过的坑:不要在Awake里FindObjectOfType去拿别的管理器引用。原因是场景加载时,各个脚本的Awake执行顺序并不严格按照Hierarchy面板的显示顺序,如果你的管理器脚本还没执行到Awake,这边就已经把它查出来了——结果不一定为空,但可能是旧场景残留的引用。后来我统一改成:所有管理器类用静态实例或依赖注入,所有组件的引用缓存在Awake、具体寻址在Start,这套规则执行了三年没出过加载顺序问题。
3.2 协程不是线程:这个误区必须第一次就讲清
协程是Unity笔试中的必备考点,常见题目包括:用协程实现一个定时器;问WaitForSeconds是否不受Time.timeScale影响;问协程和线程的区别。第一个坎就是概念:协程不是线程,它依然运行在主线程上,只是利用迭代器(IEnumerator)的暂停/恢复机制,把一个方法的执行过程拆成了多个片段,在多个帧之间分片执行。
底层原理是用到C#的迭代器状态机。你写的yield return null会让Unity在下一帧继续执行后面的代码;yield return new WaitForSeconds(1f)则是让引擎挂起这个协程,直到计时器满足条件后再恢复。正因为协程跑在主线程上,你不可能用它做耗时计算来提升帧率,一帧内的密集运算该卡照样卡,协程只解决“分帧执行”的问题。
笔试里最容易出错的点是“WaitForSeconds受不受Time.timeScale影响”。很多人以为时间缩放只是视觉变慢,但答案是WaitForSeconds受Time.timeScale影响,当Time.timeScale = 0时,WaitForSeconds永远等不到结束。如果游戏里需要做真正的暂停菜单,暂停期间协程里的倒计时还会继续跑,就得改用WaitForSecondsRealtime或者自己记录Time.realtimeSinceStartup。另外,频繁new WaitForSeconds(1f)也会产生少量GC,写循环协程时可以把Wait对象定义为static readonly缓存起来,这在Profiler里能直观看到GC Alloc下降。
3.3 FixedUpdate、Update、LateUpdate的职责划分
这三个更新函数放在一起考的时候,关键是“帧率与时间步长”。Update每帧调用一次,帧率越高调用次数越多,适合处理输入检测、角色移动逻辑、动画状态切换等视觉逻辑。FixedUpdate按固定时间步长调用(默认0.02秒),和真实帧率无关,适合所有物理操作,比如对Rigidbody施力、读取物理射线检测结果。LateUpdate在每帧所有Update调用完之后执行,适合做相机跟随,因为此时角色位置已经全部更新,相机再跟随不会出现角色先动、相机后追上来的抖动感。
实际开发中,把物理相关代码写进Update会导致一个经典bug:低帧率设备上,物体受到的力不够连续,表现为穿透或弹跳异常。因为Unity的物理模拟和Update并不同频,你在Update里持续修改Rigidbody的位置,会打断物理引擎的步进计算。正确姿势是:所有物理交互放FixedUpdate,视觉类位置插值放Update。如果笔试题目给一段代码问你“为什么刚体在低帧率下会穿墙”,答案就是这句话。
3.4 场景查找与组件缓存
GameObject.Find、FindObjectOfType、transform.Find这类API也是笔试常客。它们的性能差距很大:Find按名字遍历整个场景层级,FindObjectOfType要全局扫描所有对象和组件,开销都不低,在循环里调用更是灾难。题目经常会问“在Update里找敌人怎么优化”,标准答案是在Start里缓存引用,或者用一个静态列表维护当前场景所有敌人,新敌人生成时注册进列表,死亡时移除。
组件缓存的思路也一样,GetComponent<T>()在大多数情况下有内部缓存,但如果每帧高频调用,还是建议手动缓存到字段里。笔试答案答到这个深度,再主动提出“可以用Dictionary存储组件索引”,面试官一般都会点头。这也是Unity客户端面试题里最常见的一类:不是考你不会写代码,而是考你有没有被性能问题毒打过。
4. 渲染管线与Shader:Unity笔试的重头戏
4.1 从顶点着色器到片元着色器:把渲染过程讲成人话
渲染相关题目在网易笔试里的比重相当高,范围从“渲染管线包含哪些阶段”到“逐顶点光照和逐片元光照的区别”都有。首先要建立一条主线的认知:物体渲染分三个大阶段——应用阶段(CPU准备顶点、材质、变换矩阵)、几何阶段(顶点着色器做坐标变换,光栅化插值)、片元阶段(片元着色器计算每个像素最终颜色)。
顶点着色器处理的是顶点数据,做MVP变换,把模型局部坐标变成裁剪空间坐标。它还能输出逐顶点的颜色、UV、法线等信息,这些值会在光栅化阶段通过插值变成每个像素的输入。片元着色器才是真正决定像素颜色的地方,在这里你可以读纹理、算光照、做各种后期效果。
笔试里最常见的问法是“逐顶点光照和逐片元光照哪个效果好”。逐顶点光照(Gouraud)在顶点着色器里算完光照,再插值得到中间像素的颜色,优点是快,但高光经常糊成一片甚至出现一个夸张的多边形光斑。逐片元光照(Phong)是每个像素都独立计算光照,效果好,但因为计算量放大到像素级,移动端压力大。记住这个答法之后,再引出Blinn-Phong模型就顺理成章了:环境光加漫反射加高光,高光项用的是半程向量,核心公式是pow(max(dot(N, H), 0), SpecularPower)。
4.2 Alpha Test与Alpha Blend:透明渲染的两条路
Shader题目里关于透明度的问题几乎必考,通常问“Alpha Test和Alpha Blend有什么区别”“透明物体的RenderQueue为什么是3000”。答题框架其实很简单。
Alpha Test是直接裁剪片元,在片元着色器里调用clip(alpha - _Cutoff),如果透明度小于阈值,这个片元直接丢弃,不会写入颜色缓冲。优点是简单、不需要排序,缺点是边缘会出现硬锯齿,因为它是0和1的硬切换。Alpha Blend则是把当前片元的颜色和颜色缓冲中的已有颜色做混合,能实现半透明效果,但必须关闭深度写入(ZWrite Off),同时要按从远到近的顺序渲染,否则透明物体的遮挡关系会出错。
为什么透明物体必须从远到近渲染?因为开启深度测试后,先画的像素写入颜色缓冲,后画的像素测试深度。透明物体不写深度,后画的物体如果比较近,它混合时会覆盖到前面画好的遥远透明物体上,形成错误遮挡。这就是为什么Unity把透明物体的RenderQueue设为3000,排在所有不透明物体之后。笔试再加一档难度就是让你排Background(1000)、Geometry(2000)、AlphaTest(2450)、Transparent(3000)、Overlay(4000)这几个队列的顺序。把这个顺序记清楚,Shader面试题基本能拿住一半分。
4.3 后处理与RenderTexture:几个容易忽略的细节
2018年那套题里,简答题部分出现过类似“如果要实现一个屏幕渐隐效果,你怎么做”的开放题。看起来简单,但多数人会忽略后处理在Unity中的实现方式:通过OnRenderImage拿到当前屏幕的RenderTexture,再用Graphics.Blit配合材质进行一次全屏Pass处理。渐隐如果用纯UI遮罩当然也行,但题目如果限定“屏幕后期效果”,考察的就是你有没有接触过后处理管线。
实际做后处理时,有几个笔试里不会明说但面试官会追问的细节。第一,Graphics.Blit默认使用的全屏三角形,渲染时会把当前屏幕纹理赋给_MainTex,你写的Shader里必须声明sampler2D _MainTex。第二,屏幕后处理的高斯模糊如果要达到比较好的效果,通常需要两个Pass,横竖各一遍,而不是一个Pass里循环采样十几二十次。第三,频繁创建临时RenderTexture会产生GPU压力和大块内存分配,用RenderTexture.GetTemporary可以绕过托管堆,用完后记得ReleaseTemporary。
这些细节在笔试里可能只是一个小问,但在实际项目里决定了你的后处理是打包的时候崩还是跑起来掉帧。如果基础比较薄弱,建议去把Unity官方手册里的“Post-processing Overview”翻一遍,再配合Shader Graph(或者当年流行的ShaderForge)拖一个模糊效果出来,比单纯背题有用得多。
5. 性能优化:笔试里最能拉开差距的板块
5.1 Draw Call与批处理:为什么帧率会莫名其妙地降
性能优化几乎是网易Unity笔试的必答题,而首当其冲的考点就是Draw Call。简单说,Draw Call是CPU向GPU发送一次“请把这份网格和材质画出来”的命令。命令数量越多,CPU和GPU之间的通信开销越大,移动设备上尤其明显。
笔试里的问法通常是“如何减少Draw Call”,标准三步走:一是用图集(Sprite Atlas)合并小图,减少UI内部的材质切换;二是合理使用静态批处理,把不动的物体在构建时合并成一个大网格;三是动态批处理,运行时自动把满足条件的网格合批。但这里有几个关键限制很多人容易答漏:动态批处理要求网格顶点数少于一定数量(不同管线版本有差异,早期大约是900),材质必须相同,且不能包含镜像变换(Scale为负会破坏批处理)。如果你用Spine做的角色,骨骼动画驱动网格时,顶点一变,动态批处理就失效了。
如果你答到这一步,可以再补一句“移动端还可以用GPU Instancing来渲染大量相同网格的物体,比如草、子弹、粒子”,面试官通常会比较满意。GPU Instancing通过把多个实例的Transform数据一次性传给GPU,用UNITY_INSTANCING相关宏在Shader里区分实例,能大幅降低Draw Call和合批压力。注意,它的前提是网格相同、材质相同,不同配色的敌人就得用MaterialPropertyBlock来处理每个实例的参数差异。
5.2 内存与GC优化:这题真的会决定Offer
Unity的内存优化分为托管堆和非托管资源两部分。托管堆就是C#的new对象,持续分配会增加GC压力,GC触发时会暂停主线程,表现就是游戏卡顿。笔试里常见的问法:哪些写法会产生不必要的GC?怎么用Profiler定位?
答案清单我整理过很多次,基本就这几类:字符串频繁拼接、装箱、LINQ查询(尤其带有Lambda的)、协程里频繁new WaitForSeconds、每帧调用Find/GetComponent、UI上的Text.text频繁赋值(底层也会产生字符串和布局重算)。排查手段只有一个,就是打开Unity Profiler的CPU Usage面板,看“GC Alloc”那一列,按分配量排序,一秒就能定位问题。
还有一类问题问“Unity的托管堆会不会返还给操作系统”,答案是Mono运行时一般不会归还,堆被GC清理之后内存通常还留在进程里,后续新分配可以复用这部分空间,但在Profiler里看你依然会看到Managed Heap的峰值。所以不是“内存占用降下来了”就万事大吉,要降低的是分配频率而不是分配峰值。游戏里大量复用对象,推荐用对象池。对象池不只解决GC问题,还能避免频繁实例化和销毁导致的瞬时卡顿,顺便减少了资源加载和层级重建的开销。
5.3 场景加载、AssetBundle与包体管理
2018年的笔试里,简答题出现过类似“如果场景里有200个敌人、50个特效、30种UI界面,你会怎么做加载管理”的题目。这类题目没有标准答案,但一定要体现出“分层加载、按需加载、控制内存”的思路。Unity的经典方案是:通用资源放Resources或常驻场景,关卡资源按AssetBundle或Addressables加载,使用完立即卸载。
AssetBundle考察的核心是“依赖管理”和“卸载时机”。笔试常见的问法是AssetBundle.Unload(false)和Unload(true)区别:false只卸载AssetBundle的骨架数据,不卸载已加载出来的Asset对象,适合需要保留资源实例的场景;true会连同所有已加载资源一起卸载,如果还有对象引用这些资源,就会出现Missing材质、空网格等异常。实际项目中,我习惯维护一个引用计数表:每次从AB加载资源时计数加一,释放时减一,减到0才真正调用卸载,可以最大限度避免重复加载和提前卸载两个问题。
这里顺带说一句,网上搜“Unity解包工具”“Unity混淆”这类关键词的小伙伴,多半是在研究AssetBundle加密和IL2CPP代码保护。笔试不会考太深,但面试如果聊到包体安全和热更新,至少要能说出:IL2CPP把C#转换成C++再编译,让代码逆向难度大幅提升;AssetBundle可以通过加密资源和自定义加载管线来增加解包成本。知道这个层面的概念,比只会说“我们用了XX插件”要加分。
5.4 光照烘焙与运行时反射探针
光照相关题目在笔试里也不少见,尤其是“场景中静态光照太多影响帧率,该怎么优化”。答案是静态光照烘焙。Lightmap烘焙把直接光、间接光的信息预先计算到光照贴图里,运行时静态物体不再需要实时计算灯光贡献,代价是光照贴图占内存和烘焙时间。
理解这个思路后,关于动态物体的性能优化就比较好答了:动态物体会在运行时实时采样探针(Light Probe)来获取周围光照信息,避免使用Lightmap。但探针布太密会增加运行时开销,布太稀又会导致颜色跳跃。笔试答题时能说出“用Light Probe Group插值”就已经超过一半候选人。如果项目里用了实时全局光照,还可以补一句“移动端不要开Realtime GI,用预计算的间接光缓存替代”,这更贴近真实比赛项目的经验。
6. 算法与逻辑题:Unity方向常见的出题风格
6.1 经典A*寻路:不只是背公式
网易的算法题不会让你从零手写一个完整A*,但会出简化版本,比如“在一个网格地图中,怎么实现角色从A点走到B点的最短路径,并避开障碍物”。A的核心公式是f(n) = g(n) + h(n),g(n)是起点到当前节点的实际代价,h(n)是当前节点到终点的估计代价,f(n)决定优先走哪个节点。难点不在公式,而在两个实现细节:一是h(n)的选取,曼哈顿距离适用于网格地图,欧氏距离适用于连续坐标场景,如果h(n)高估了实际代价,A会失去最优性;二是OpenList的数据结构,推荐用二叉堆而不是普通List,因为每次都要取出f(n)最小的节点,用二叉堆能在O(log n)内完成插入和取最小值。
笔试后如果面试官追问“你的A在超大开放地图上性能很差怎么办”,可以答跳点搜索(JPS)或分层寻路(HPA),前者适合均匀网格,后者适合大型场景。这套回答能展示你不仅会背A*,还知道它在Unity项目里的优化方向。很多NavMesh相关的面试题也是从A延伸出来的,提前把A吃透,遇到NavMesh烘焙的问题也能快速理解底层逻辑。
6.2 空间划分:别在Update里遍历1000个敌人
“场景里有1000个敌人,怎么找到离玩家最近的敌人?”这种题几乎每个游戏公司笔试都有。暴力遍历的答案是面试官希望你给出的第一反应,然后他会追问“如果这1000个敌人每帧都要找一次呢”。这时你要答出空间划分:网格划分、四叉树、八叉树,或者直接使用Unity物理引擎的OverlapSphere。
使用OverlapSphere其实是利用物理引擎内部的BroadPhase加速结构(一般是BVH树或空间哈希),查询时只需要检查包围球相交的物体,不用遍历全场景。笔试答到这里,再补一句“如果敌人数量更多且分布动态变化,可以自己实现四叉树来管理单位,每帧只查询玩家周围四个格子内的对象”,这就能展示出工程能力了。注意,四叉树的插入和移除操作要考虑对象跨越格子边界的情况,实战中经常坑在这里,所以答案里能提到“逻辑坐标和空间树的一致性维护”会是加分项。
6.3 位运算、环形缓冲区与对象池
网易笔试中出现过“只出现一次的数字”“统计二进制中1的个数”“判断是否是2的幂”这类位运算题。Unity里位运算的真实应用场景是LayerMask和枚举Flag:1 << LayerMask.NameToLayer("Player")这种写法就是典型的位掩码操作。函数参数用[Flags]枚举时,用|和&来组合、判断状态,也是笔试选择题的常见素材。
环形缓冲区(RingBuffer)则对应音频流、网络消息队列、蓝牙串口数据解析这些场景。Unity开发中处理VLC视频流、蓝牙外设数据时,经常需要把读到的一堆字节先放进环形缓冲区,再按协议解析。笔试题会让你实现一个有限长度队列,核心难点在于写指针追上读指针时怎么判断“满”还是“空”,答案一般是保留一个空位或记录元素计数。如果你还知道GitHub上常见的RingBuffer库是怎么用head和tail两个索引维护状态的,这道数据结构题基本就稳了。
对象池和环形缓冲区的思路其实相通:都是复用资源、避免频繁分配。Unity里频繁生成和销毁特效、子弹、飘字,会导致GC和实例化卡顿,对象池把用过的对象回收后复用,配合Deactivate而不是Destroy,能明显改善帧率。笔试里如果出现“请设计一个通用对象池”,记着把“预创建数量、最大上限、获取/释放API、自动回收机制、组件类型约束”这几个点都答全,基本就是满分答案。
7. 写在最后:刷题之外,真正拉开差距的是动手验证
网上关于网易2018这套Unity笔试题的讨论很多,从牛客网的回帖到各种面试题库,基本把答案都翻烂了。但如果你只是把答案背下来,到了面试官追问“为什么”的环节,还是会露馅。我自己带过好几个校招新人,发现能把这套题真正讲透的人,有个共同习惯:他们不满足于“知道结论”,而是每个考点都自己写Demo验证过。
比如协程和线程的区别,与其背概念,不如在Unity里写一个while(true) { yield return null; },然后打开Profiler看看它是怎么分帧执行的。比如Alpha Blend排序,不如把场景里放几个半透明球,来回拖动相机,观察错误排序发生时物体前后面是怎么穿帮的。这种动手验证带来的记忆深度,比刷十套题都管用。
如果你想把这个过程系统化,我建议按四个方向准备:把C#基础过一遍,尤其是值类型引用类型、委托事件、泛型、GC;把Unity官方手册的Execution Order页面和Scripting API里几个核心类的文档仔细读一遍;用Shader Graph或Unlit Shader把Blinn-Phong从零写一遍;最后再拿Profiler把自己之前做过的项目跑一遍,看看GC Alloc和Draw Call到底卡在哪。做完这几件事再回头看网易这套题,你会发现它不再是考你的知识点,而是帮你把整个Unity客户端知识体系串成了一张网。最后分享一个小技巧:面试前半小时,重新翻一下《Unity User Manual》的Execution Order页面和《Unity Shader入门精要》前六章,这套笔试里至少三成的送分题,其实都藏在这两处地方。