
2017年5月26号那场笔试我到现在还记得。当时我在北京某高校的机房屏幕上打开搜狐畅游的在线笔试系统前面两页个人信息刚填完第三页直接甩过来一套混合题——单选、多选、填空、简答、两道编程题限时两个小时。同场考游戏开发岗的还有另外七八个人监考老师一走满屋子都是敲键盘和点鼠标的声音。我一边做一边心里打鼓这套题的题量不算大但每一道都在往日常实践经验的方向挖跟学校期末考完全两码事。作为国内老牌的MMO厂商搜狐畅游靠《天龙八部》系列积累了大量服务端和客户端实战经验。它的实习生笔试题本质上是给游戏开发岗做的一次认知筛选筛选的不是你会不会背教科书而是你有没有在真实开发的场景里琢磨过事情。这篇文章我按记忆和同行复盘整理出当时的真题回忆版一道题一道题地聊考什么、为什么考、怎么答才能踩中面试官的给分点。内容尽量覆盖算法、C、图形学、网络、移动适配和开放题六个方向对准备暑期实习或秋招游戏开发岗的同学应该比单纯刷LeetCode更有参考价值。1. 天龙八部背后的笔试考场先看清这场考试在筛什么人搜狐畅游的笔试和那些只考力扣风格的互联网公司不太一样它的岗位划分很细客户端、服务端、引擎、工具链、测试开发都有独立的试题卷。你投的是游戏开发实习生那试卷的底层逻辑就会向游戏工程倾斜不会出现太多纯业务向的Web问题。这一点从卷面结构就能看出来我印象里整个卷子大致分成六块。1.1 笔试结构还原两个小时里你要面对什么网上有人回忆过这套题整体结构和市面上大多数游戏公司2017年的校招笔试题高度相似大致是题型题量考察方向不定项选择15道数据结构、C、操作系统、网络、图形学填空题8道计组、内存布局、排序算法复杂度、位运算简答题5道内存管理、多线程、网络协议、移动端性能、游戏架构编程题2道字符串/数组处理、算法设计逻辑推理/图形推理5道抽象思维和空间想象能力开放设计题1道游戏系统设计、数值平衡或玩法分析前四部分是计分主体后面的图形推理类似公务员行测开放设计题则用来判断你有没有游戏感。值得注意的是这套题有一个很鲜明的特点不超纲但每一个知识点都往生产环境里引。比如它考你vector扩容原理不是问你扩容因子是多少而是问你在游戏循环里每帧push_back大量顶点数据会有什么问题怎么优化。答这种题光背《STL源码剖析》是不够的你得真的有在做一个渲染器或者物理模块时被扩容耗时的卡顿折磨过。1.2 出题人的潜台词实习生不是来写业务的是来补齐短板的我后来私下和畅游的一位技术前辈聊过他说实习生笔试的核心目的不是筛出全能王而是找出可培养性。所以题目分布有一个很微妙的权重C占比最高其次是算法再次是图形学和网络。为什么因为游戏客户端尤其是引擎方向C是绝对的生产语言渲染、物理、动画、资源管理几乎全在C的语境下。如果你连结构体内存对齐、虚函数表都说不清楚那就算你Unity脚本写得再溜他们也会犹豫。反过来说算法题在实习生笔试题里占的分量并不像互联网公司那样一题定生死。畅游更关心你有没有工程思维也就是说你写出来的代码是不是能放进一个真实项目里长期维护。所以编程题往往不追求偏难怪而是追求能把一个中等难度的问题写干净、写正确、写高效。2. 算法与数据结构题死在常见题上的基本都是栽在这几个细节里畅游这套题里的编程题难度上限大概在LeetCode Medium偏下但通过率并不高。原因很简单很多人刷题时只背了思路没有抠边界。笔试时间紧编辑器又没有自动补全和编译提示稍微一紧张就把循环边界写错或者忘记处理空指针。下面我把这套题中的典型算法题还原一下每一道都标注了考察意图和易错点。2.1 字符串全排列考察递归思维和剪枝能力真题回忆版输入一个字符串打印该字符串中字符的所有排列。例如输入abc输出abc、acb、bac、bca、cab、cba。要求去重如果有重复字符。这个题在2017年属于烂大街的题但烂大街不等于好写。它的核心是回溯法从头到尾固定第i个字符然后递归排列后面的字符。去重的关键不是在最后用一个set而是在递归过程中剪枝——如果当前要交换的字符已经在之前出现过就直接跳过避免生成同样的排列。我当时写的大致结构是这样的void permute(string s, int start, vectorstring res) { if (start s.size()) { res.push_back(s); return; } bool seen[256] {false}; for (int i start; i s.size(); i) { if (seen[s[i]]) continue; seen[s[i]] true; swap(s[start], s[i]); permute(s, start 1, res); swap(s[start], s[i]); } }考这个题不是为了让你背代码而是看三件事第一你会不会用递归分解问题第二你知道不知道去重剪枝而不是靠set后处理第三你的swap和回溯还原有没有成对出现。第三点特别多人在紧张时漏掉导致结果全是重复的。2.2 数组中最长连续递增子序列区分连续和非连续的两种思路真题回忆版给定一个无序数组找出其中最长的连续递增子序列的长度。例如数组[1, 3, 5, 4, 7, 8, 9]最长连续递增子序列是[4, 7, 8, 9]长度为4。这里有个非常容易踩的坑很多人一看递增子序列立刻想到动态规划里的LIS最长递增子序列然后写一个O(n^2)的解法最后发现题目要求的是连续时间复杂度完全没必要那么高。连续递增子序列的解法就是一次遍历用一个计数器记录当前连续递增长度如果当前元素大于前一个元素就加一否则重置为1同时不断更新最大值。int findLength(vectorint nums) { if (nums.empty()) return 0; int res 1, cur 1; for (int i 1; i nums.size(); i) { if (nums[i] nums[i - 1]) cur; else cur 1; res max(res, cur); } return res; }考察意图很明显你能不能准确理解题面里的连续二字。面试官后来说这题筛掉了将近一半人不是因为他们不会写代码而是因为他们习惯性地把子序列和LIS绑定没认真读题。实习生在项目里最容易犯的错就是拿着旧方案生搬硬套这题的陷阱就是在模拟这个场景。2.3 编程题隐藏的评分标准边界、可读性、鲁棒性编程题在阅卷时不是只有AC或非AC两种状态。我记得畅游的技术笔试判卷会有代码审查环节哪怕你没有完全跑通只要你的思路清晰、数据结构选取得当也可能拿到不少分。反过来一个用暴力方法硬跑通过的代码如果可扩展性太差分也不会高。具体说来评分会看这几个点边界处理空数组、单个元素、全相等数组。你的代码会不会越界或死循环。可读性变量命名是否清晰有没有多余的分支。算法复杂度选了不合适的容器比如在频繁查找的循环里用vector代替unordered_map面试官一眼就能看出来。鲁棒性入参是const引用还是值传递能不能处理大数据量而不爆栈。我当时在字符串全排列那道题上用了递归面试官后来还追问了一句如果输入字符串长度是10000呢你的代码会怎样。这就是在考察你对递归深度的敏感性——标准答案里应该提到改用栈模拟递归或者用STL的next_permutation明显更安全和熟练。3. C与内存管理游戏客户端开发逃不掉的生存题C在畅游的笔试里占了大约三成的分值。题型既有选择题也有简答题但核心就一个主题你是不是真的理解这门语言在底层是怎么跑的。下面几类题目几乎年年出现我按考点分类说明。3.1 析构函数为什么必须是虚函数这是简答题里的常客也是实践中特别重要的一点。答案标准但不死板当通过基类指针delete一个派生类对象时如果析构函数不是虚函数那么只会调用基类的析构函数派生类的资源不会被释放造成内存泄漏和资源泄漏。我记得题目还有追问如果一个类没有虚函数它的大小是多少空类呢带一个int呢这就是在考察内存布局的理解。空类通常大小为1是为了保证不同的对象有不同地址带一个int32位下通常是4字节有虚函数则需要额外4或8字节的虚函数表指针。放到游戏开发里关卡编辑器里的Actor、Component、BehaviorTree节点都是继承体系析构不虚化线上项目分分钟内存泄漏。3.2 结构体内存对齐一个被很多人忽视但笔试题特别爱出的点内存对齐是C高频考点。题目会给你一个struct让你算sizeof。struct Example { char a; // 1字节 int b; // 4字节 char c; // 1字节 };问sizeof(Example)是多少很多人脱口而出6正确答案是12。原因是在默认对齐数通常是4下int b必须放在4字节对齐的地址上所以a后面会有3字节填充c之后再填充3字节让结构体大小对齐到最宽成员大小的整数倍。游戏开发里这个知识点直接关系到网络消息包的构造和资源文件的解析。比如写一个玩家位置同步的结构体如果你的字段排列顺序不对传到服务端的结构体解析出来就是错位的垃圾数据。这也解释了为什么很多引擎里会用#pragma pack(push, 1)来压缩协议结构体。3.3 智能指针和内存泄漏你写过会被“吐槽”的裸指针吗2017年那会儿C11已经普及但很多学生写代码还是new/delete一把梭。畅游的笔试题里给了这样一道场景题在网络游戏大厅模块中一个玩家下线时要释放所在房间和匹配队列里的所有引用你会怎么处理考点是shared_ptr和weak_ptr的配合使用。房间对象可能同时被匹配管理器、玩家对象引用如果直接用裸指针释放时机非常难控制。用shared_ptr管理生命周期用weak_ptr打破循环引用是服务端和客户端通用的解法。还有一个经典追问weak_ptr怎么判断对象是否失效答案是调用lock()如果返回一个空的shared_ptr说明对象已经被释放了。这在异步回调里特别常用因为玩家可能在下线之后网络回调才姗姗来迟这时候你必须判断一下对象还活着没。3.4 虚函数表的实现原理口试和笔试都很喜欢的一个题图形学、消息分发、RTTI都和虚函数有关。选择题里会问下面哪种调用方式不会发生动态绑定——通过对象直接调用、通过指针调用、通过引用调用、内联函数。答案是通过对象直接调用。但简答题往往更深一层让你画出包含单继承、多继承、虚继承时的对象内存布局。这个难度就上来了。多继承下一个对象会有多个虚函数表指针。这意味着继承自两个基类对象时如果子类重写了某个同名虚函数第二个基类持有的vptr会被调整到一个跳转区域。笔试里只要你能说出vptr放在对象头最前面和多继承有多个vptr再辅以对象大小会增加多个指针的描述基本就能拿一半以上的分。4. 图形学与引擎数学从向量点乘到渲染管线的底层逻辑游戏客户端岗位在畅游的笔试里图形学占比不低。即使是实习生岗位他们也希望你至少能区分MVP矩阵的含义知道光照模型的基本结构。更进一步他们会靠填空题考察你对坐标系、齐次坐标和四元数的熟练度。4.1 向量点乘和叉乘一个用于角度判断一个用于方向计算选择题常给出两个向量坐标问你它们的点积是正是负或者在3D空间中两个向量叉乘的结果向量方向是什么。点乘可以判断两个向量的夹角是否大于90度这在AOI兴趣区域管理、敌人视野判断里经常用到。比如玩家的朝向是forward向量敌人的方向是toEnemy向量dot(forward, toEnemy) 0就说明敌人在前方。叉乘则能求出一个同时垂直于两个向量的向量常用于计算法线或判断点在一个多边形的哪一侧。如果你连这些基础都不清楚后续的第三人称摄像机跟随、阴影计算、寻路转向基本就是空中楼阁。4.2 MVP矩阵从模型空间到屏幕空间的坐标变换填空或者简答题可能会问mvp矩阵中的M、V、P分别代表什么它们的先后顺序是怎样的答案是Model矩阵模型变换、View矩阵视图变换、Projection矩阵投影变换。最终变换是position_ndc P * V * M * position_model注意是右乘也就是先进行M变换再进行V变换最后进行P变换。在游戏开发实习的日常里你不太需要手写矩阵库但调试一个物体没有出现在正确位置的bug你一定会用到MVP的概念。当时我们项目里有个新同事把本地坐标当成世界坐标直接丢给Shader结果物体全部挤在原点找了一整天问题最后发现就是忘了乘Model矩阵。4.3 深度缓冲、画家算法与渲染性能一个工程向的压轴题简答题还爱问游戏中半透明物体的渲染顺序应该怎么处理比较旧的回答是按深度从远到近渲染。但工程实践里半透明物体并不完全靠深度排序而是需要关闭深度写入再按从远到近的顺序绘制。为什么因为半透明物体需要和背后的颜色混合如果深度写入开了后面的物体会被半透明物体遮挡掉造成透明你根本看不见窗帘后面的墙。甚至还有进阶问法两个半透明平面相交时怎么处理。标准商业引擎的做法是把物体拆分或改用多层深度排序很难完美。笔试里你只要答到开启混合、关闭深度写入、按距离排序这个级别就能过。5. 操作系统、网络与移动适配笔试中的隐形加分项这部分题量不比C少但知识点更分散覆盖面广。我记得有进程和线程的区别有TCP和UDP如何选择还有游戏卡顿和内存优化的排查思路。5.1 进程线程的区别别只背教科书要结合游戏场景说选择题会问你进程和线程哪个资源开销更小、哪个崩溃会影响整个进程等。标准答案大家都知道线程是CPU调度的基本单位进程是资源分配的基本单位。线程共享进程地址空间进程之间隔离。但畅游更希望你结合游戏场景展开。比如游戏服务端通常用多线程处理网络收包和逻辑更新为什么不用多进程因为玩家数据和场景状态需要共享线程间共享内存的代价低多进程则需要通过IPC通信延迟高且代码复杂。你如果能提到锁竞争、玩家AOI广播和实体状态的一致性面试官会觉得你已经有工程概念了。5.2 TCP黏包和UDP丢包网络游戏开发躲不开的话题有一道简答题我印象很深一个TCP服务端收到客户端发来的多个消息比如玩家移动、攻击、聊天这三条消息一次性到达你怎么区分它们的边界这就是典型的黏包问题。标准解法有四种固定长度、长度前缀、分隔符、自描述消息。游戏项目里最常用的是长度前缀也就是每条消息头里写一个int表示消息体长度服务端先读4字节再根据长度读取剩余的body。UDP部分则会问如果玩家在移动你选择UDP还是TCP为什么答案是对实时性要求高的移动同步UDP更合适因为TCP丢包重传会导致后续消息排队玩家的位置会卡顿。但UDP需要自己在应用层做可靠性和序列号处理比如状态同步里会用快照增量两种消息交替发送或者用一个GSNGame Sequence Number来让客户端判断哪条消息是最新的。5.3 移动端性能优化内存、发热、加载速度的三座大山2017年手游还是市场大热门畅游也有移动端项目所以会很自然地考察移动性能优化。题目问你在手机游戏中遇到卡顿会怎么定位问题标准答题路径会包含先看是CPU瓶颈还是GPU瓶颈。是用Unity Profiler、Instruments还是RenderDoc去判断。检查是否有大量GC Alloc。在Update里频繁new对象、字符串拼接、LINQ表达式都会产生垃圾内存进而触发GC卡顿。检查DrawCall数量和三角形数量是否超标。尤其在低端机上每帧DrawCall超过100就会伴随发热需要检查是否用了动态合批、静态合批、纹理图集。检查资源加载是否有异步加载是否在Switch场景时同步加载了过大的AB包导致IO卡顿。笔试里不需要把每种工具说得特别深但两个关键点一定要说出来一是你在实际工程里怎么测二是你发现瓶颈后怎么解。这两个点背后考察的是你有没有性能意识而不只是会调API。6. 开放设计与策略题拉开差距的不是知识量而是游戏感这套题和其它公司笔试最大的不同是留了一道游戏设计开放题。原题大意类似于如果《天龙八部》要做一个新副本需要三个Boss请设计其中一个Boss的技能、阶段转换和通关策略。我当时愣了几秒因为在座的笔试者大多是程序员思维突然来一道策划题会让人一时半会儿不知道往哪个方向答。6.1 开放题的设计思路程序思维如何配合策划思维你要在纸上画一个Boss关键在于你有没有系统设计的概念。一般我会按三个层面组织回答行为层Boss的AI状态机怎么切。比如分阶段——P1近战物理攻击P2进入狂暴加一个范围AOEP3召唤小怪并施放全屏技。你要能描述从P1到P2的触发条件通常是血量阈值比如70%和30%。表现层Boss的技能表现如何与对应操作绑定。这里不要只写放一个红色圆圈要写前摇0.8秒在地面出现红色预警圈玩家有1.5秒反应时间。这需要你对游戏手感有基本认知。数值层Boss的攻速、移速、伤害区间和玩家血量的比例关系。比如普通攻击造成玩家最大血量的15%伤害那奶妈就有一口抬满的空间而不是两下就团灭。你写出来的方案不一定完美甚至会有逻辑漏洞但只要你展示出能用状态机组织行为、能用数值约束难度、能对玩家体验作出预期笔试的开放题就算稳了。6.2 玩家体验与系统联动开放题也能展示你的架构能力我当时加了一个小细节Boss在P3阶段会随机点名一名玩家3秒后对该玩家当前位置释放一个圆形AOE。这个机制如果独立看只是躲圈但如果与副本中的阵法buff联动会更有趣。比如被点名的玩家如果站在阵法里AOE伤害减半同时阵法会被破坏团队需要在后续战斗中保护阵法。这种设计表达的关键是你在思考机制之间如何互动。考官想看到的是你对游戏系统的理解不是孤立的技能列表而是有生态的。你设计的机制需要和已有系统产生化学反应而不是在真空中设计一个叠加buff。6.3 笔试之后的复盘这些题对实习实际工作有什么影响说实话这套笔试题和我后来在游戏研发团队里做的工作非常贴合。学会了虚函数表就能理解引擎里的委托和事件系统为什么用多态理解了内存对齐才能正确设计跨平台通信的结构体学会深度排序再去做特效显示和UI层级方向感就完全不一样。我建议正在准备游戏研发实习的同学刷题时除了剑指Offer这种经典还要特意关注放在游戏场景里的工程题。比如自己问自己如果要在Unity里做一个技能系统技能CD、Buff、伤害结算这些模块怎么组织如果要用C写一个简单的对象池怎么避免容器迭代器失效多问几个为什么笔试就能少掉几次坑。