1. 笔试前我最纠结的一件事:突击算法还是补图形学
2020年4月11日晚上7点,我在电脑前坐定,准备参加一家头部游戏公司的游戏研发暑期实习笔试。两个半小时,从19:00到21:30,这个时间安排本身就很像游戏行业的工作节奏——傍晚开始,深夜结束。当时离笔试还有一周时,我最纠结的问题不是“能不能进面试”,而是“我应该把最后这几天花在算法题上,还是赶紧补图形学”。事后回头看,这场考试给我的最大提醒不是某个知识点没掌握,而是游戏研发岗的笔试覆盖面,远比我以为的要宽。
先说结论:如果你现在也在准备类似的游戏研发实习笔试,算法题很重要,但它只是整张试卷的其中一块。游戏研发岗不同于普通后端研发岗,笔试考察的东西往往包含C++底层机制、数据结构、操作系统、网络、图形学基础、游戏引擎常识,甚至还会有一两道需要你站在游戏开发角度去思考的场景题。只刷LeetCode的人可能会在前面做得很快,但到了图形学和引擎相关题目时容易发懵。
我当时的准备顺序大概是这样:前两周集中刷算法题,主要围绕动态规划、DFS/BFS、字典和优先队列;第三周开始看C++语言特性,虚函数、智能指针、内存对齐这些;最后三天仓促翻图形学笔记,结果就是图形学部分准备得最匆忙,考试时也确实在这里丢了一些不该丢的分。如果你问我哪一块性价比最高,我的排序是C++基础大于数据结构与算法、大于图形学常识、大于网络与多线程,但这里说的“大于”只是分值权重的粗略感受,不代表你可以放弃其中任何一块。
笔试前还有一个容易被忽略的点:你需要提前把在线笔试环境跑通。我记得当时笔试平台要求使用指定浏览器,并开启摄像头监控,还需要提前完成设备检测。这些看起来是流程性的事,但确实每年都会有人因为浏览器兼容问题浪费开考后的前十分钟。我建议提前一天做一个模拟测试,把摄像头、麦克风、网络、浏览器权限全部确认一遍,最好再准备一条有线网络作为备用。考试过程中如果突然断网,先别慌,很多平台有断网保护机制,但你需要尽快恢复连接并主动留意倒计时。
2. 两个半小时里,我按什么顺序答题
拿到试卷后,我第一反应是先把整张卷子从头到尾扫了一遍。这种做法在普通笔试里也适用,但在游戏研发笔试里尤其重要,因为它的题型层次很多,不同题型之间的难度曲线并不均匀。有的选择题可能很难,后面反而会跟着一道比较常规的编程题,如果你按顺序死磕,很可能在一道两分的题上耗掉十五分钟,等做到编程题时只剩下很少的时间。
我当时观察到的试卷结构大体可以分成四类:单选或不定项选择、基础理论填空或简答、一两道开放性的场景设计题、以及最后的大题编程。选择题覆盖的科目最杂,C++、数据结构、操作系统、网络、图形学都可能出现,而且有些题的选项本身就是在考察你懂不懂游戏引擎中某个具体概念的实现。简答题里,我印象比较深的是需要结合游戏场景描述一种同步方案或者资源加载方案,这种题没有标准答案,但需要你表现出“我真的做过游戏开发”的细节感。
答题顺序上,我的做法是先花八到十分钟把所有题目快速浏览一遍,把编程题全部读完,在心里对每道题的复杂度做一个粗略判断。然后我做的是编程题,而不是从前往后做选择题。这样安排的原因很简单:编程题分值高,而且读题、思考、写代码本身就需要较长时间,放到最后做容易因为心态浮躁而影响发挥。把编程大题先解决掉,后面的选择题和简答题即使时间紧一些,也不会造成灾难性后果。
当然,这样做也有代价。先把编程题做完后,时间往往已经过去了四五十分钟,留给选择题的时间会被压缩。游戏研发笔试题量通常不小,有些选择涉及复杂的计算,比如求某个空间变换的矩阵结果,或者统计某段代码的输出,这类题如果不留够时间,很容易因为赶进度而看漏条件。我最后的实际节奏是:编程题约七十分钟,选择题和简答题约五十分钟,开放题约十五分钟,最后留五分钟左右检查。整体比较紧张,但好在没有出现大面积空题。
这里有一个我很想强调的经验:如果你发现自己在一道选择题上卡了超过三分钟,先标记并跳过。在线笔试没有“回头率”这个指标,但你宝贵的时间不能陷在一个选项里。尤其是图形学相关的计算题,有时候你觉得自己快算出来了,实际上可能已经陷入一个完全错误的推导思路。跳过之后,等整份卷子剩下的部分都填完了,再回头冷静分析,反而更有可能找到之前忽略的条件。
3. 考点太多,我把它们切成四块来看
游戏研发笔试的考点看着零散,其实可以切分成几块来理解。我当时复习时没有头绪,只能对着知识点清单逐个确认,后来才发现,按照“语言与底层、算法与数据结构、图形与数学、系统与网络”这四个维度去归类,复习起来会清晰很多。下面逐个说,顺便把每个模块里真正容易被问到的细节也列出来。
3.1 C++与内存:分值最密集的一类
C++在游戏研发笔试里基本是默认的主角。这里说的不是“你会不会写for循环”,而是你对语言底层的理解到底有多深。常见的考点包括:虚函数表与动态绑定,构造和析构顺序,拷贝控制与移动语义,智能指针的引用计数机制,内存对齐,栈内存与堆内存的区别,以及const和static在不同场景下的含义。这些知识点很多看起来学过,但如果没在项目里真正踩过坑,选择题一变形就容易出错。
我印象里有一类反复出现的考察方向是“某个STL容器操作会不会导致迭代器失效”。vector在扩容后迭代器是否失效,map删除节点后其他元素的迭代器会怎样,unordered_map重哈希时候的行为是什么。这些问题在游戏开发里很现实,因为你在处理对象列表、技能列表、Buff列表时,经常需要边遍历边删除。如果你写过类似逻辑,应该知道常见的处理方式是先标记再统一清理,或者使用反向迭代器删除。笔试题一般不会问得这么开放,而是给出一段代码,让你判断它运行时会不会崩,这种题对细节把控的要求很高。
移动语义和右值引用也是高频点。游戏里大量对象会被频繁创建和销毁,比如发射子弹、生成特效、播放音效,如果每次都发生深拷贝,内存和CPU的压力会很大。笔试不会直接问你“移动语义能给游戏带来什么优化”,但会通过具体的代码片段考察你是否清楚std::move到底做了什么,以及移动之后的对象处于什么状态。我的建议是,不要只背定义,最好自己写一个简单的字符串类或动态数组类,手动实现拷贝构造、移动构造、赋值运算符,这样应对选择题时心里会踏实很多。
3.2 数据结构和算法:考的不只是套路
算法部分,游戏研发笔试很少出那种纯模板题。更多时候是给一个游戏化的背景,比如“某种怪物在固定路径上巡逻,玩家需要在某个时间内到达某个点,求最短路径”或者“背包里有若干物品,每种物品有重量和冷却时间,求某种条件下的最大收益”。题目内核还是经典的算法模型,但表面披了一层游戏逻辑。
从题型来看,深度优先搜索和广度优先搜索几乎必考,因为它们直接对应游戏里的寻路、连通区域判断、状态搜索等场景。动态规划也是重点,尤其是背包问题和区间DP。优先队列、字典、并查集这些数据结构也经常出现,它们对应的是游戏里的排行榜、怪物分组、阵营关系等需求。二分法不会直接作为一道大题考,但经常作为优化手段出现在解法里,你需要能看出“这个答案具备单调性,可以用二分来逼近”。
我的一个实际感受是:笔试里的编程题,对算法复杂度的要求其实没有竞赛那么高,但对你把问题拆成状态的能力要求很高。很多题目的难点不是想不出解法,而是你读了三遍还没搞清楚题目的输入输出是什么样的。所以我的建议是,平时刷题时养成一个习惯:先用一句话描述“输入是什么、输出是什么、中间要维护哪些状态”。如果这道题的输入都还没理清楚,先不要急着写代码。
3.3 图形学与数学基础:不能只背名词
图形学在游戏研发笔试里占的比例没有C++那么高,但它是区分“只刷过题的人”和“真正接触过游戏渲染的人”的重要分水岭。常见的考点包括:MVP矩阵变换,模型空间、世界空间、观察空间、裁剪空间之间的换算,法线向量的变换与模型顶点变换的区别,光栅化流程,深度缓冲,以及简单的光照模型。
这些内容如果你的课程里没有专门学过,临时应付起来会比较吃力。我的经验是,图形学的选择判断其实考得不算深,但如果连矩阵乘法的顺序都搞错,后面的计算题基本没法做。比如一个顶点要经过模型矩阵、视图矩阵、投影矩阵变换,最终映射到屏幕坐标,正确的乘法顺序是先让顶点左乘模型矩阵,再左乘视图矩阵,再左乘投影矩阵,也就是projection乘view乘model乘position。这个顺序问题看起来基础,却经常被拿出来做文章。
还有一个容易出现的考点是法线变换。很多人的第一反应是“法线跟着模型矩阵变换就行”,但实际上,如果模型矩阵包含非均匀缩放,直接用原矩阵变换后的法线方向可能不再垂直于表面。正确的做法是使用模型矩阵的逆转置矩阵来变换法线。这种细节如果只靠背结论,过两天就会忘。我建议自己推导一遍,你就能理解为什么OpenGL里会有专门的normalMatrix。
3.4 操作系统、网络与多线程:和你的日常开发强相关
操作系统部分考察的内容,主要集中在进程与线程、锁、死锁、内存管理、文件I/O和用户态与内核态相关概念。游戏开发里,渲染线程、游戏逻辑线程、资源加载线程之间的协作是最常见的多线程场景,所以笔试喜欢考察你能否正确使用锁、条件变量和原子操作来同步数据。这里有一个容易被忽略的点:简单地把所有共享数据都加锁,在很多情况下并不会让程序变得安全,反而可能因为锁的粒度太粗导致性能断崖式下跌。选择题里如果出现这类场景,一般会在“锁的粒度、死锁顺序、条件变量唤醒”这些关键词里做文章。
网络方面,TCP与UDP的区别是必考项,但游戏开发更关心的是状态同步和帧同步两种同步模式。状态同步适合RPG类游戏,逻辑在服务器端运行,客户端表现只是状态的投影;帧同步适合MOBA或格斗类游戏,所有客户端执行同一套逻辑,只同步输入指令。笔试里如果给你一个具体的游戏类型,让你选择同步方案并说明理由,你需要能结合游戏特征来答题,而不是只背定义。
我当时在操作系统和网络这部分其实复习得比较仓促,但后来在工作中发现,这块知识对实际开发很有用。比如帧率不稳时排查瓶颈,你需要知道CMutex、CPU缓存、内存带宽这些概念;网络通信出现卡顿或回放问题时,你需要理解UDP的可靠性补偿和延迟隐藏。所以这部分建议不要只为了笔试突击,而是当成从业基础来学。
3.5 开放题和场景题:亮出你的工程判断
除了客观题和编程题,游戏研发笔试里经常会出现一两条开放性的场景题。比如给你一个正在开发的游戏项目,问某个玩法上线后服务器压力过大应该怎么优化;或者问某个技能系统如果做成可扩展结构,你会怎么设计数据结构和代码框架。这种题没有标准答案,面试官想看到的是你有没有工程判断力,而不是背了多少知识点。
我在作答这类题目时,比较推荐的方式是先明确边界,再给方案。举个例子,如果问题是“某个职业技能冷却系统怎么做”,你需要先说明这个技能可能涉及哪些维度:冷却时间、技能等级、道具减少冷却、Buff影响冷却、是否受攻速或急速属性影响、是主角技能还是怪物技能。边界清楚了,再给类设计和数据结构就顺手很多。哪怕最后时间不够,把边界条件列出来,也比你只回答“用两个字典存技能ID和剩余CD”要完整得多。
4. 编程题,我不是写得漂亮,而是先写出可跑的框架
游戏研发笔试的最后一般会有两到三道编程大题,分值占比很高。我当时的策略是先保证框架完整,再追求最优解法。这听起来像正确的废话,但实际操作中很容易跑偏:看到一道有思路的题,总想一步到位写出最精简的版本,结果写着写着自己绕进去了,最后连能通过的代码都没交上去。
4.1 模拟类题目:把流程拆细再动手
第一道编程题通常会更偏向于状态模拟,可能涉及多个角色或物体的行为切换。这种题的特点是读题过程长、边界条件多,但只要把逻辑按时间线梳理清楚,代码本身并不难。我当时犯了一个很典型的错误:题目里有一段“单位时间内的行动顺序”描述,我读完没细想,就直接用一个for循环去模拟,结果到了某些角色死亡后会额外触发技能更新时,逻辑就乱了。
正确的做法是先画一个时间片流程图,把每一帧内事件发生的先后顺序列出来,然后对着这个顺序写代码。如果题目涉及对象数量比较大,还要注意不要在循环里反复查找某些键值,提前把需要用到的数据用字典或数组存好。模拟题的另一个常见坑是大数运算,尤其是涉及生命值百分比、伤害系数时,最好统一换算成不丢精度的整数或分数,避免浮点误差影响最终判断。
4.2 搜索类题目:优先想状态怎么定义
另一道比较常见的编程题会考察搜索,比如“玩家从起点到终点,途中需要收集多个点位,求收集完所有点位并到达终点的最短时间”这种。这类题的难点通常不在搜索算法本身,而在于状态有可能变成“当前所在点位+已收集点位的集合”,如果你只用二维坐标去定义状态,是没办法正确处理重复访问问题的。你需要用状态压缩的方式,把已经收集的点位集合编码成二进制位,再用优先队列去做带权值的扩展。
我在这类题上的经验是:先把状态定义和扩展方向写在草稿纸上,确认不遗漏条件后再开写。扩展时不要贪图代码简洁而省略必要的剪枝,一个可行的剪枝方案是维护一个visited数组,记录某个状态是否已经被更优路径访问过。如果状态空间很大,还可以考虑用双向BFS或者A*,但在笔试环境下,先把普通的优先队列搜索写对,比纠结更高级的优化更实际。
4.3 数值推导类题目:多写几个测试样例
有时也会出现需要推导规律或做数学计算的题目。这类题看上去像是智力题,但通常都有更底层的算法模型。遇到这种题时,我的习惯是先在纸上枚举几个小规模数据,尝试找出模式,再和已知的经典模型对应。比如某些看似奇特的计数问题,本质是排列组合或动态规划;某些贪心问题,本质是先排序再维护堆。
千万不要一上来就想着用数学公式直接求解,除非你能在纸上完整推导。笔试环境下,枚举加递推往往更稳。写完之后,至少手动构造三个测试样例跑一遍:第一个是最小输入,第二个是题目给的示例,第三个是你自己模拟的边界数据,比如最大值、空集合、全部无法到达的情况。编程题的判题通常是隐藏用例制,自己多造数据验证,是在交卷前最有效的止损手段。
4.4 编程语言和环境选择:熟悉比花哨重要
不少在线笔试平台支持多种语言提交,我见过有人在C++和Java之间来回切换,最后两门都写得不太顺手。我的建议是,笔试时只用一门你最熟悉的语言。对你来说,C++在游戏研发岗里是加分项,但如果你平时写C++很少,不要为了迎合岗位描述强行使用。我当时选择C++,是因为我对STL、内存管理、标准输入输出的细节更熟,一旦需要调试运行时错误,能更快定位问题。
还有一点很容易被新手忽略:本地编译环境往往和线上判题环境有差异。比如有些在线平台的编译器版本比较旧,不支持某些新特性;有些平台的输入输出格式对空白字符比较敏感。我的习惯是,尽量使用最基础的语法和标准库函数,不依赖特定平台的扩展能力,这样能减少很多不必要的编译失败。
5. 从这场笔试里,我总结出几个真正的教训
考试结束后,我没有急着对答案,而是花了一晚上把整个过程复盘了一遍。现在回头看,有几个教训虽然常听人说,但只有真正在考场上经历后,才会有切身体会。
5.1 基础概念要形成“本能反应”,而不是靠临时回忆
选择题里有很多考点,看起来不深,却要求你在几十秒内做出判断。比如“某个类的析构函数为什么需要是虚函数”“shared_ptr会不会造成循环引用”“不同容器在中间插入元素的时间复杂度是多少”,这些问题如果还需要从底层原理慢慢推导,速度就会明显跟不上。游戏研发笔试虽然不追求极端快的手速,但两个半小时覆盖这么多科目,慢就是输。
我后续的做法是,把高频概念整理成一张速查表,每天睡前快速过一遍。不是背文字,而是用自己的话解释。如果你能对着一张没有答案的知识清单,把每个点讲清楚,就说明这个知识已经内化了,而不是仅仅“见过”。
5.2 做题习惯比做题数量更重要
刷题刷得多,不代表考试一定考得好。我发现一个很有意思的现象:平时在编辑器里做题,即使答案错了也不太有感觉,因为可以反复调试;但在线上笔试平台,代码提交后只能等判题结果,而且往往看不到失败的细节。你会突然意识到,平时依赖调试器帮你发现边界条件,到了考场上全靠自己。
我后来有意识地调整了刷题方式:尽量在类似笔试的页面里做题,一次性写完再提交,而不是每写几行就本地运行一次。对读题、写代码、造样例、自查答案这一整套流程做限时训练,比单纯追求刷题数量要有用得多。如果平时能养成“写代码前先想清输入输出边界”的习惯,考场上紧张时也不会乱。
5.3 游戏研发的知识面需要长期经营,突击急救容易顾此失彼
这次笔试让我最难受的一点是,很多非算法知识点并不是完全不会,而是记忆已经模糊了。网络同步方案、渲染管线、内存对齐、引擎生命周期,这些内容平时看懂了,但没有真正在项目里用过,几周后就会变得半生不熟。短期突击最多帮你把知识从“完全不会”拉到“有点印象”,但没办法把“有点印象”变成“熟练应用”。
我在之后很长一段时间里,养成了一个新的自学习惯:每学一个概念,都试着问自己“这个知识在游戏开发里对应哪个具体场景”。比如学了哈希表,就想想对象查找和管理;学了渲染管线,就想想一个模型从CPU到屏幕的过程中走了哪几步。这样把知识点挂在具体场景下,记忆的持久度会明显好很多。
6. 如果现在让我重新备考,我会这么安排时间
复盘完已经过去很久了,但如果有人让我针对游戏研发暑期实习笔试重新规划备考,我会分四个阶段来做。这套思路不仅对笔试有用,对后面的面试和个人项目积累同样有帮助。
第一阶段是先搭知识框架。找一份比较全面的岗位知识清单,把C++、数据结构和算法、图形学、网络、操作系统、引擎使用等模块过一遍,对每个模块需要掌握到什么程度做到心里有数。这个阶段不要陷进某一个项目里钻研太深,目标是建立全局地图。
第二阶段是集中刷高频算法题。不用追求偏题怪题,就盯住图搜索、动态规划、数据结构设计、模拟题这些高频类型。刷题之外的晚上,我会把C++语言特性再过一遍,尤其是那些容易被选择题拿来出题的点:移动语义、智能指针、迭代器失效、内存布局、类型转换。
第三阶段是模拟真实笔试环境。找一套在线模拟题,计时两个半小时,全程开启摄像头,不许中途查资料,一次性完成。这个阶段就是训练自己做题节奏和抗压能力,积累“先做编程题还是先做选择题”的经验。模拟过后,把错题对应的知识点单独整理出来,第二天集中攻克。
第四阶段是考前两天的轻量保持。不再做新题了,只看错题笔记和知识速查表,再把笔试平台的环境测试做一遍。同时准备好身份证件、草稿纸、充电器和网络备用方案,确保开考时心情是平和的。
如果时间更充裕,我会提前在游戏引擎里实现一个小Demo,哪怕只是一个简单的2D小游戏,把角色移动、碰撞检测、技能冷却、敌人AI、简单对象池和UI界面都串一遍。这样不仅笔试里的场景题能答得更有底气,简历上的项目经验也算有了可聊的实质内容。笔试只是第一关,真正进入面试环节后,你做过的东西才是最扎实的底气。