游戏开发实习生笔试指南:C++、算法与渲染网络全考点
2026/9/5 7:46:21 网站建设 项目流程

去年5月26号下午,我坐在畅游的笔试教室里,拿到那套游戏开发实习生的题目时,第一反应是:这套题不像网上流传的那些“大厂刷题集”,反而很像一个主程随手给你出的验收卷。整套卷子覆盖C++、算法、基础图形学、物理和一点网络同步,题干不算长,但不少地方埋了细节,稍不留神就会掉坑。如果你是准备投游戏开发实习的同学,这套题值得拿来当一次“摸底考试”做,因为考的不是你背了多少八股,而是你在笔试现场能不能把一个工程问题拆清楚、写干净。

这篇博文我就以18届这次笔试为线索,把游戏开发实习生的常见考点拆开讲。不会去逐字背题,而是说考点、说思路、说现场怎么避坑。你可以把它当成一份“游戏开发实习生笔试能力图谱”,看完之后既能知道该往哪些方向发力,也能避开我自己踩过的那些雷。

1. 笔试整体观感:一场筛选“工程底线”的考试

1.1 题量分布与现场印象

整套试卷的题量不算夸张,大致是选择题、填空题、简答题、手写代码题混排。时间给得比较足,但真正做完的人很少。原因很简单,代码题不是让你写伪代码,而是要求在白纸上写出可以编译的C++代码,包括头文件、边界判断、内存处理都要考虑。这个要求和LeetCode完全不一样,LeetCode只要核心函数,而笔试现场要的是完整思路加严谨细节。

从题目类型来看,C++占的比重最大,大概有三分之一。然后是数据结构和算法,涉及链表、二叉树、排序、动态规划这类经典内容。再往后是游戏开发基础,包括渲染管线、DrawCall、碰撞检测、网络同步的模型。说实话,这些题单个拎出来都不算难,但合在一起就变成了“压力测试”,看你平时积累够不够厚,能不能在有限时间里有条理地输出。

我当时做题的顺序是先扫一遍全卷,把简答题的分拿稳,再回头死磕代码题。这种策略后来证明很有效,因为简答题往往考察的是概念是否清晰,只要平时看过引擎文档或图形学基础,基本都能写出七七八八。代码题卡住了就先跳,不要在一道题上耗二十分钟,否则后面会崩盘。

1.2 出题人想要什么样的人

畅游是端游、手游都做的公司,国内有完整的自研引擎和运维体系,所以它招游戏开发实习生的时候,最看重的不是你会不会用某个引擎,而是有没有“工程底线”。什么是工程底线?就是内存不会随便泄漏,多线程不会写出数据竞争,链表反转不会丢指针,给一个需求能拆成模块而不是一把梭。

从这套题就能看出来,出题人默认你已经掌握了C++的基础语法,并且有实际写代码的经验。他不会问你“什么是虚函数”这种概念题,而是会给一段代码,问你输出结果是什么,或者指出哪里会导致崩溃。所以如果你还停留在“看过《C++ Primer》但没写过几个类”的阶段,这套题会显得很难受。反过来,如果你自己做过小游戏项目,写过一些组件、管理过生命周期,很多题目会觉得很亲切。

另外,题目里还透着一个信号:游戏开发实习生的日常不是天天做玩法,而是先解决一堆底层问题。比如资源加载、对象池、热更新、性能瓶颈排查。笔试就是在看你能不能理解这些底层问题背后的通用原理。

2. C++、内存与多线程:躲不掉的基本功

2.1 指针、引用与内存布局

C++部分几乎绕不开指针和内存布局。常见的考法有两种:一种是给一个结构体,问你sizeof是多少,为什么;另一种是给一段指针操作的代码,问输出或崩溃点。这两类题看起来简单,实际错误率很高,因为牵涉到对齐、虚表指针、引用折叠等一堆细节。

我在备考过程中最常用的方法是“画内存图”。遇到指针相关的问题,不要空想,直接在草稿纸上画出栈、堆、全局区各自的变量,然后把指针的指向用箭头标清楚。一旦把内存图画出来,很多问题就豁然开朗。例如一个类里有虚函数,它的实例大小就多了一个虚表指针;如果是继承且重写了虚函数,内存布局还要看虚基类的情况。这类题在实习笔试里很少考到特别偏的,但基本规则一定要记住。

另一个高频考点是“深拷贝与浅拷贝”。游戏引擎里的资源对象、组件对象经常需要拷贝,如果只是浅拷贝,两个对象会共享同一块堆内存,析构时就会出现double free。笔试通常会拿一个字符串类或者自定义Vector类,让你实现拷贝构造、赋值运算符和析构函数。我当时是直接在纸上写了一个带引用计数的String类,重点在于赋值运算符要先判断自赋值,再释放旧内存,最后分配新内存并拷贝数据。三步缺一不可。

下面是当时我练习时写过的简化版String类核心代码,可以帮你快速复习这个考点:

class String { public: String(const char* str = nullptr) { if (str == nullptr) { m_data = new char[1]; *m_data = '\0'; } else { size_t len = strlen(str); m_data = new char[len + 1]; strcpy(m_data, str); } } String(const String& other) { size_t len = strlen(other.m_data); m_data = new char[len + 1]; strcpy(m_data, other.m_data); } String& operator=(const String& other) { if (this != &other) { delete[] m_data; size_t len = strlen(other.m_data); m_data = new char[len + 1]; strcpy(m_data, other.m_data); } return *this; } ~String() { delete[] m_data; } private: char* m_data; };

这个版本虽然还有异常安全的问题,但笔试阶段已经能说明你理解了拷贝控制。如果你能把“三/五法则”讲清楚,并且在代码里处理自赋值,面试官对你的印象会好很多。

2.2 多线程与同步问题

游戏服务器和客户端引擎都有多线程场景,所以笔试出现线程题我一点也不意外。常见考法是:两个线程并发操作同一个全局变量,问最终值范围是多少,或者让你用锁、原子变量修复它。这类题考点非常集中,无非是数据竞争、临界区、死锁。

当时我遇到的是一个计数自增的简化模型:多个线程并发执行counter++,问你最终结果是否等于线程数乘循环次数。答案是几乎不可能等于。原因就是counter++在编译后不是原子操作,读、加、写三步会被线程调度打断,导致丢更新。修复方式要么加std::mutex,要么用std::atomic<int>。笔试时我直接给出了用std::atomic的方案,因为它在单变量场景下更轻量,语义也更清晰。

还有一类题是“如何避免死锁”。我在准备时总结了一个自己的判断口诀:多个锁的加锁顺序必须全局一致;能用一个锁就不用两个;如果必须持有多个锁,优先用std::lock一次性获取。这个口诀在笔试和面试里救过我很多次。

下面是一个用std::atomic修复并发的简单示例:

#include <atomic> std::atomic<int> counter{0}; void worker(int n) { for (int i = 0; i < n; ++i) { counter.fetch_add(1, std::memory_order_relaxed); } }

这里的memory_order_relaxed可能不是最优选择,但笔试里能写出原子操作已经说明你对并发有意识了。如果面试官追问,再说清楚默认的seq_cst更安全,但性能可能略差,就可以应付过去。

2.3 常见C++笔试大题示例

笔试最后一道C++大题通常是让你写一个“对象池”或“工厂模式”的简化实现。为什么考这个?因为游戏开发里频繁创建和销毁敌人、子弹、特效,如果用newdelete每次分配内存,会产生大量内存碎片,甚至卡顿。对象池就是预先创建一批对象,用状态标记是否存活,取用时找一个空闲对象,归还时重置状态。

写对象池有几个关键点要掌握。第一,池子内部用固定数组或vector,尽量避免在运行期扩容。第二,对象需要有“激活/失活”标志位,而不是真的析构。第三,池子的遍历要考虑性能,最好维护索引或空闲列表。第四,归还对象要重置干净,否则会出现“残留上一帧数据”的bug。

我当时在纸上写了一个子弹对象池,结构大概是这样的:

class Bullet { public: bool active; Vector3 pos; Vector3 velocity; }; class BulletPool { public: BulletPool(int capacity) { m_pool.resize(capacity); } Bullet* Get() { for (auto& bullet : m_pool) { if (!bullet.active) { bullet.active = true; bullet.pos = Vector3::zero; bullet.velocity = Vector3::zero; return &bullet; } } return nullptr; } void Release(Bullet* bullet) { bullet->active = false; } private: std::vector<Bullet> m_pool; };

这个实现足够应付笔试。但你在讲解时一定要提一句“这里存在性能问题,遍历所有子弹找空闲对象,当池子很大时有O(n)开销。可以改成空闲列表,把释放对象的索引放到队列里,Get时直接取队头”。能说到这一步,说明你真的考虑过工程实现,而不只是背模板。

3. 数据结构与算法:熟练度决定上限

3.1 链表类题目的“送分与陷阱”

游戏开发笔试里的算法题不像ACM那么疯狂,很多都是经典题型的变体,比如链表反转、判断链表是否有环、合并两个有序链表。这些题目本身不难,但现场手写容易翻车,因为链表操作对边界极其敏感。我记得当时有一道题是“反转单链表”,看起来是送分题,可一旦在纸上写,很多同学就在头节点的处理上卡住。

写链表反转有四种常见方式:迭代、递归、头插法、栈。笔试时最推荐迭代法,因为它空间复杂度O(1),代码也最容易验证。核心思想就三句话:保存下一个节点,把当前节点的next指向前驱,移动前驱和当前指针。我当时为了不出错,还会先画一个3节点链表,把每一步的指针变化标出来,再往代码里填。

另外要小心“dummy head”技巧。很多链表题,比如删除倒数第K个节点、删除有序链表的重复项,都可以用虚拟头节点规避掉“删除头节点”的特殊处理。这个技巧写起来很快,而且能减少边界Bug。笔试时哪怕题目没有明确要求,我建议也先定义ListNode* dummy = new ListNode(0); dummy->next = head;,最后返回dummy->next,这样头节点就不会额外判断。

链表题的另一个高频陷阱是“是否会造成环”。举个例子,很多同学在合并两个有序链表时,会直接把一个链表的节点插到另一个链表里,但忘记封尾,结果整个链表变成一个环。这种错误在本地调试时很容易发现,但在纸上笔试时很难一眼看出。所以我给自己定了一个习惯:写完链表代码后,从head开始顺序走一遍,检查每个节点的next是否指向了不该指的对象。

3.2 树与图的遍历思路

树和图的题目在游戏开发笔试中也很常见,因为场景管理、寻路、技能效果结算都跟树和图脱不开关系。笔试里一般不会要求写A*寻路的完整实现,但会考二叉树的BFS/DFS、层序遍历、最近公共祖先这类基础题。这些题目主要考察你有没有真正理解递归和队列栈的关系。

我的经验是,遇到树的问题先看递归能不能解,再看迭代能不能解,两个方案都写一下会加分。比如“判断一棵树是否对称”,递归解法是把根节点的左右子树看成两棵树,同时比较外侧和内侧;迭代解法则是用队列成对入队,每次取出两个节点比较,并保证它们的孩子也按对称顺序入队。笔试中我通常给出迭代解法,因为这样能给面试官展示“我不只懂递归”。

图的部分,考得最多的是“判断图中两个节点是否连通”以及“拓扑排序”。前者可以用DFS或BFS,后者在游戏任务依赖、技能树解锁里很实用。拓扑排序有一个模板思路:先统计每个节点的入度,把入度为0的节点入队,每次出队一个节点,把它的邻接节点入度减1,如果入度变成0就继续入队。如果最终入队节点数不等于总节点数,说明图里有环。这个判断环的思路在笔试里经常被用来出陷阱题。

写图算法题的时候,我建议把图明确存储成邻接表,而不是邻接矩阵。原因有两个:游戏场景里的图通常很稀疏,邻接表更省内存;笔试时用vector<vector<int>>做邻接表写起来也最快。如果题目带权,可以把int扩展成pair<int,int>

3.3 动态规划:现场怎么能不慌

动态规划是很多同学最怕的算法题,但游戏开发笔试里的DP通常比较基础,比如最长递增子序列、背包问题、编辑距离。它不会给你一个很偏的状态方程,因为你是在考“游戏开发实习生”,不是在考“算法竞赛选手”。不过一旦考到DP,就一定要把“状态定义、转移方程、初始化、遍历顺序”四件事说清楚。

我拿到DP题的第一件事,不是急着写代码,而是先写状态定义和转移方程,哪怕只是注释。这样就算最终代码有Bug,阅卷人也能看到你的思路。比如最长递增子序列,状态dp[i]表示以第i个元素结尾的最长递增子序列长度,转移方程就是枚举j < i,如果nums[j] < nums[i],就用dp[j] + 1更新dp[i]。时间复杂度O(n^2),笔试完全可接受,不用一上来就写二分优化。

有时候笔试会出“走格子”问题,比如从左上角到右下角有多少种走法,或者最小路径和。这类题的状态转移其实都写在题目里了,只要把边界条件处理好,基本能拿满分。比较坑的是“二维数组越界”和“初始化错误”。我在备考时会把所有DP题都先画一个表格,把表格的0行0列填充好,再按行或按列递推,这样出错的概率会低很多。

如果时间充裕,可以再记一记“背包问题”的滚动数组写法,把二维dp压缩到一维,并注意倒序遍历容量。这个技巧在笔试里很亮眼,而且一旦理解了为什么倒序,你就真正掌握了DP的遍历顺序,不是死记硬背。

4. 游戏渲染、物理与网络:专业方向的试金石

4.1 渲染基础:状态切换与DrawCall

游戏开发实习生的笔试不会让你去写一个光栅化器,但会考一些渲染管线的核心概念。我记得题目里出现了“DrawCall”,并问你为什么大量DrawCall会拖慢帧率,以及如何优化。这个问题现在基本是游戏开发笔试必问,因为它是客户端性能优化的核心。

DrawCall是CPU向GPU发出的一次绘制命令。每切换一次纹理、Shader、状态,GPU可能都需要重新配置渲染管线,这个开销非常大。如果场景里有1000个物体,每个物体都提交一次DrawCall,CPU就容易被拖垮。优化思路常见有几种:合批(Batching)、纹理图集(Atlas)、减少状态切换、使用GPU Instancing。笔试里最好能答出“合批是指把多个小网格合并成一个大网格,一次性提交,从而减少状态切换”。同时要提一句“合批不是万能的,如果物体有动态动画或者不同材质,合批效果会大打折扣”。

有一类题还会给你一个简化场景:100个怪物、50个特效、10个UI面板,问你怎么估算DrawCall。我当时的方法是先算每个对象的材质种类数和网格数量,再进行场景分块,统计每一帧中可见的对象数量。这里要注意“视锥剔除”和“遮挡剔除”,因为不可见对象不需要绘制,DrawCall就会少很多。笔试中写出“先剔除,再合批,最后绘制”这个顺序,比背数字重要得多。

4.2 物理与碰撞检测

物理题是游戏开发笔试的特色之一。常见考点包括AABB碰撞检测、刚体动力学概念、帧率对物理模拟的影响。AABB碰撞检测的考法通常是:两个矩形(或盒子),分别用最小点(xmin, ymin)和最大点(xmax, ymax)表示,问如何判断它们是否相交。检测规则就是判断两个矩形在x轴和y轴的投影区间是否都重叠,只要一个轴不重叠就一定不相交。

这个考点虽然基础,但笔试时最容易漏掉“坐标轴方向”。比如Unity的屏幕坐标系和世界坐标系不一样,World坐标旋转后AABB不一定还能精确表达物体形状,所以大型项目里会再细分OBB或凸包碰撞。如果你能在基础代码之外补一句“AABB适合碰撞粗略检测,精确检测需要更细的碰撞体”,面试官会认为你有实战认知。

物理模拟还有一个经典坑:固定时间步长。游戏如果按帧调用物理更新,帧率波动会导致物体运动速度不一致;帧率很低时甚至会出现物体穿透。面试官喜欢问的解法是“用固定步长的物理流水线,同时做插值渲染”。笔试里只要能把这几句话写出来,就已经超过一大半考生了。

4.3 网络同步的基本模型

网络同步题在很多客户端笔试里会出现,因为MMORPG、竞技游戏都逃不开它。畅游做过不少网游,所以在笔试里考网络同步模型非常合理。常见考点是状态同步和帧同步的区别,以及它们的优缺点。

状态同步的特点是客户端把操作发给服务器,服务器计算最终状态再广播给所有客户端。它的优点是逻辑集中在服务器,好防作弊,但带宽消耗高,战斗打击感容易受延迟影响。帧同步的特点是每个客户端都跑同一份逻辑,只同步操作指令,能极大节省带宽,格斗、RTS游戏常用,但要求客户端逻辑完全确定性,浮点误差会导致不同步。

笔试里如果让你选一种模式实现一个房间内的战斗同步,我会选帧同步方案,并说明关键点:所有随机数种子一致、所有浮点运算必须使用相同精度、逻辑帧率固定、需要定期校验hash。这些关键点才是阅卷人想看的,因为大多数学生只会说“帧同步就是同步指令”,而不知道落地时会踩哪些坑。

网络部分有时候还会考TCP和UDP的区别,游戏里为什么常用UDP。别只答“TCP可靠但慢,UDP不可靠但快”。要补充一句“游戏对延迟更敏感,丢失少量包可以被预测和插值弥补,但延迟高会直接影响操作手感,所以很多实时对战在传输层基于UDP做自定义可靠传输”。能说到这个层面,网络题基本就没问题了。

5. 常见失分点与实用备考建议

5.1 现场最容易翻车的三类失误

第一类是“只会LeetCode式写法,不会完整工程代码”。笔试代码题要求的是严格包含头文件、命名空间、返回值的完整代码,很多同学只写了核心逻辑,结果试卷上缺头文件、缺返回值、缺边界判断,白白丢分。

第二类是“简答题答太短”。游戏笔试的简答题问的是“你认为如何”,其实是想看你的思路链条。比如问“如何降低游戏包体大小”,你不能只回答“压缩贴图”,而是要从资源格式、音频码率、分包加载、动态下载等维度展开。哪怕不完全正确,也要展示你考虑过多种手段。

第三类是“时间分配失衡”。有人在一道C++大题上死磕40分钟,结果后面10道选择题都没时间做。我的策略是先把选择题和简答题全部拿下,再回头写代码题。因为选择题猜对的概率也比空白高,简答题只要写就有分,而代码题一旦思路卡住,两小时都未必能调通。

5.2 给非科班同学的补课路线

如果你不是计算机科班出身,但想投游戏开发实习,我建议按这个顺序补:先花两周把C++的指针、内存、拷贝控制、多线程基础过一遍,不要纠结模板元编程,那是高阶内容。然后刷数据结构,重点是数组、链表、栈、队列、二叉树、哈希表,配合LeetCode简单和中等题目练手。

之后进入游戏开发基础,我推荐用Unity或者Godot做一个小项目,比如一个2D顶视角射击游戏。在做项目的过程中,你会自然地接触到对象池、碰撞检测、动画状态机、UI管理、场景切换。这个过程比听课重要得多。很多笔试简答题,只要做过项目,哪怕规模很小,也能写出答案。

最后再回到算法题,每天保证两到三题,周末做一次限时模拟。笔试前两周,把所有刷过的题用白纸手写一遍,训练自己不用IDE也能写出规范代码。这个训练极其重要,能很大程度减少笔试时的紧张感。

5.3 从笔试题反推项目经验设计

如果你现在还没什么项目可写,不妨根据这套笔试的考点去设计两个小项目。第一个项目是“内存管理组件”,做一个对象池,并封装成Unity里的MonoBehaviour组件,支持预热、取用和归还。第二个项目是“简易战斗同步Demo”,用帧同步做两个角色移动和攻击的联机演示,重点是把逻辑帧和渲染帧分开、固定时间步长、同步随机数种子。这两个项目刚好对应笔试里的C++、数据结构和网络题。

有了项目之后,笔试前还可以把项目里遇到的问题写成一页纸,包括为什么用对象池、为什么固定步长、怎么解决不同步。这些都是面试官非常爱问的内容。因为笔试考的是基础,面试考的是你有没有真的做过东西。基础决定你过不过笔试,项目决定你过不过面试。

如果你能把上面这些点都准备到位,那么面对2017年那套题也好,之后的同类题目也好,心态会稳很多。我当时印象最深的是,不少题目并不是考“会不会”,而是考“现场能不能冷静下来”。所以最后再分享一个小技巧:笔试开始前五分钟,先深呼吸,把全卷扫一遍,用铅笔在每道题旁边标上当次难度和预估用时。这样做完一遍之后,你的大脑会自动进入解题状态,而不是在焦虑里耗尽能量。这个技巧不起眼,但我试过很多次,真的能多拿不少分。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询