12 · 游戏开发专项概念
定位:第 7 周扫盲章。目标不是会写游戏代码,而是能在面试时把每个概念讲 1 分钟、说出 1-2 句亮点。游戏循环、固定/可变步长、ECS、碰撞与空间分区是最高频四项。 学完标准:能对着面试官讲 1 分钟“游戏主循环”;能说清固定/可变步长的取舍;能讲 ECS 比继承体系好在哪;知道四叉树/八叉树/BVH 分别用在哪。
1. 游戏循环(Game Loop)——所有游戏的心脏(必考)
Section titled “1. 游戏循环(Game Loop)——所有游戏的心脏(必考)”1.1 基本结构
Section titled “1.1 基本结构”// 伪代码:所有游戏(引擎)的骨架while (running) { 处理输入(ProcessInput); // 读取键盘/鼠标/手柄/网络 更新(Update, dt); // 游戏逻辑:AI、物理、动画、状态 渲染(Render); // 把场景画到屏幕}| 阶段 | 做什么 | 频率考虑 |
|---|---|---|
| 处理输入 | 收集用户输入事件 | 越快越好(低延迟) |
| 更新 | 推进游戏世界状态(逻辑) | 逻辑频率一般固定(如 30/60/120Hz) |
| 渲染 | 提交绘制命令给 GPU | 与显示器刷新率相关(60/120/144Hz) |
1.2 固定步长 vs 可变步长(必考,重点)
Section titled “1.2 固定步长 vs 可变步长(必考,重点)”// 可变步长(Variable):每帧用真实流逝时间 dtwhile (running) { float dt = 获取真实帧间隔(); // 每帧不同 更新(dt); 渲染();}
// 固定步长(Fixed):逻辑按固定 dt 推进,渲染随意while (running) { while (accumulator >= FIXED_DT) { // FIXED_DT = 1/60s 更新(FIXED_DT); // 物理/逻辑以固定频率推进 accumulator -= FIXED_DT; } 渲染(accumulator 做插值); // 渲染用插值对齐显示}| 可变步长 | 固定步长 | |
|---|---|---|
| 实现 | 简单直接 | 稍复杂(accumulator + 插值) |
| 物理稳定性 | 差:帧率波动 → 物理结果抖动 | 好:物理/逻辑确定性强 |
| 跨机器一致性 | 差 | 好(同输入 → 同结果,联机必须) |
| 表现 | 帧率高时快(跑得快),低时慢 | 表现一致 |
游戏主流做法(混合):渲染可变步长(跟随显示器)+ 逻辑/物理固定步长。物理用固定步长保证稳定与确定性,渲染层把逻辑状态插值后绘制,兼顾流畅。
面试金句:固定步长保证物理稳定 + 确定性(联机公平),可变步长简单但受帧率波动影响;现代引擎用“渲染可变 + 逻辑固定 + 插值”混合方案。
2. 数学基础(概念级)
Section titled “2. 数学基础(概念级)”2.1 向量与矩阵
Section titled “2.1 向量与矩阵”| 概念 | 一句话 | 游戏用途 |
|---|---|---|
| 向量 | 有方向有长度的量 | 位置、速度、力的表示 |
| 点积 | 投影关系,`a·b = | a |
| 叉积 | 垂直于两向量的向量 | 求法线、判断左右/平面 |
| 矩阵 | 线性变换(缩放/旋转/平移) | 模型/视图/投影变换、骨骼蒙皮 |
| 齐次坐标 | 4 维表示 3 维点(w=1),矩阵可含平移 | 变换统一成 4x4 矩阵乘法 |
2.2 四元数(Quaternion)——高频必考
Section titled “2.2 四元数(Quaternion)——高频必考”它解决什么问题?
| 欧拉角(pitch/yaw/roll) | 四元数 | |
|---|---|---|
| 万向节死锁 | 有:两个轴对齐时失去一个自由度,旋转变“卡住/乱转” | 无 |
| 插值 | 插值不平滑(角度路径怪异) | slerp 平滑插值 |
| 存储 | 3 个浮点数 | 4 个浮点数(w, x, y, z) |
| 拼接 | 顺序敏感易错 | 乘法拼接稳定 |
| 人读性 | 直观(角度) | 不直观(但引擎内部通用) |
一句话亮点:四元数用 4 个分量(w 标量 + xyz 向量)表示旋转,避免了欧拉角的万向节死锁,且能做平滑的球面插值(slerp),是现代引擎内部旋转的标准表示;欧拉角只在“给美术/玩家看数值”时才用。
2.3 其他数学概念
Section titled “2.3 其他数学概念”- 变换层级:局部坐标 → 世界坐标(父物体的变换叠加)
- 插值:lerp(线性)、slerp(四元数球面)、平滑阻尼(smoothdamp)
- 浮点陷阱:帧间浮点累计误差(用固定步长缓解)
3. ECS(Entity-Component-System)——游戏架构明星(必考)
Section titled “3. ECS(Entity-Component-System)——游戏架构明星(必考)”3.1 传统继承体系的痛点
Section titled “3.1 传统继承体系的痛点”// 传统 OOP:物体 = 类继承class Entity { ... };class Enemy : public Entity { ... }; // 想加"会飞"?加"可被光照"?class FlyingEnemy : public Enemy { ... }; // 多继承爆炸、脆弱的深继承树// 新增一种组合 → 新建一个类 → 类爆炸3.2 ECS 三要素(背)
Section titled “3.2 ECS 三要素(背)”| 要素 | 是什么 | 例子 |
|---|---|---|
| Entity(实体) | 只是一个 ID(整数),没有数据没有行为 | entity 1024 |
| Component(组件) | 纯数据(struct),描述“有什么” | Position{x,y}、Health{100}、Velocity{dx,dy} |
| System(系统) | 纯逻辑函数,处理一类组件 | 移动系统(Position+Velocity)、伤害系统(Health) |
// 组件是纯数据struct Position { float x, y; };struct Velocity { float vx, vy; };
// 实体 = ID + 组件集合// entity 1024 = { Position, Velocity, Health }
// 系统 = 遍历"拥有这些组件"的所有实体,处理逻辑移动系统() { for each entity 拥有(Position, Velocity): entity.Position += entity.Velocity * dt; // 数据连续 → 缓存友好}3.3 ECS 为什么好?(必背 4 点)
Section titled “3.3 ECS 为什么好?(必背 4 点)”- 缓存友好:同类组件连续存储(SoA,见 11 章)→ 遍历快
- 灵活组合:加能力 = 加组件,不用改类继承树(“飞行的敌人” = 加个 Fly 组件)
- 易并行:不同系统处理不同数据,天然可多线程并行(job system 直接调度)
- 无深继承:告别“菱形继承/脆弱的类树”,逻辑清晰
缺点/代价:组件间通信要小心、调试时“对象”信息分散、上手门槛高、不适合“对象行为差异极大”的场景。
面试金句:ECS 把“对象是啥”(数据/组件)和“对象干啥”(逻辑/系统)彻底分离,用“数据连续 + 批量处理”换缓存友好与并行度,是《守望先锋》等现代引擎(如 Unity DOTS)的核心架构。
4. 碰撞检测与空间分区(必考)
Section titled “4. 碰撞检测与空间分区(必考)”4.1 碰撞检测流程
Section titled “4.1 碰撞检测流程”粗检测(Broad Phase)→ 排除绝大多数不可能碰的 → 用空间分区快速筛选候选对细检测(Narrow Phase)→ 对候选对做精确检测 → AABB/球/OBB 相交测试、多边形分离轴4.2 空间分区结构(重点:各自适用场景)
Section titled “4.2 空间分区结构(重点:各自适用场景)”| 结构 | 原理 | 适用场景 | 亮点一句话 |
|---|---|---|---|
| 网格/空间哈希 | 把世界切成格子,物体登记进格子 | 物体均匀分布(子弹、大量小物体) | 实现最简单 |
| 四叉树(2D) | 递归四等分空间,动态分割 | 2D 游戏、地形、单位查询 | 稀疏分布也能自适应 |
| 八叉树(3D) | 四叉树的 3D 版(八等分) | 3D 场景、体素、视锥剔除 | 3D 空间查询的标准结构 |
| BVH(包围体层次) | 用包围盒树包住物体,动态更新 | 动态物体(角色、刚体) | 实时更新友好,物理引擎核心 |
| BSP 树 | 平面切分空间 | 室内场景(早期 FPS)、寻路 | 静态场景专用 |
一句话选择逻辑:静态场景 → 四叉树/八叉树/BSP;动态物体 → BVH(或网格/空间哈希);均匀分布 → 网格哈希。
4.3 包围体(BV)概念
Section titled “4.3 包围体(BV)概念”- AABB(轴对齐包围盒):与坐标轴对齐的盒子,检测最便宜
- OBB(有向包围盒):可旋转,更贴合物体,检测较贵
- 球体/胶囊体:角色碰撞常用胶囊体(便宜 + 贴合人形)
面试金句:碰撞查询的本质是“空间索引”——用结构先粗筛、再细查,把 O(n²) 的全对全检测降为接近 O(n);物理引擎(如 Bullet、PhysX)都内置了 broad phase 的 BVH/网格。
5. 物理系统(概念级)
Section titled “5. 物理系统(概念级)”| 概念 | 一句话 |
|---|---|
| 刚体动力学 | 用牛顿定律模拟物体的力/速度/碰撞响应 |
| 重力/摩擦/阻尼 | 常见力:重力加速度、地面摩擦、速度阻尼 |
| 碰撞响应 | 弹性/非弹性碰撞、冲量、摩擦、穿透修正 |
| 触发器 Trigger | 不产生物理阻挡,只检测“进入/离开”(做事件:捡拾、区域) |
| 射线检测 Raycast | 从一点发射射线查“撞到谁”(射击、瞄准、AI 视线) |
| 连续碰撞 CCD | 高速物体防止“穿墙”(子弹、快车) |
| 确定性 | 固定步长 + 同输入同结果(联机/回放依赖) |
| 物理引擎 | PhysX(UE/Unity 默认)、Bullet(开源)、Havok |
引擎里物理的层次:Collider(碰撞形状)→ Rigidbody(刚体,受物理影响)→ 碰撞回调(onCollision/onTrigger)→ 约束(关节、铰链)。
亮点一句:物理是“确定性优先”的系统——所以必须固定步长;游戏里 90% 的碰撞需求用 AABB/胶囊体 + 空间分区就能满足,不需要精确凸包。
6. 高频面试题 Q&A(合上书能讲)
Section titled “6. 高频面试题 Q&A(合上书能讲)”Q1:游戏主循环一般是什么结构? 处理输入 → 更新(固定步长逻辑 + 物理)→ 渲染。循环持续到退出。混合方案:逻辑固定步长(accumulator 累积)、渲染可变(跟随刷新率 + 插值)。
Q2:固定步长和可变步长怎么选? 物理/逻辑用固定步长(稳定、确定性、联机公平);渲染用可变步长(流畅)。纯可变实现简单但帧率波动影响表现,还会让物理抖动。
Q3:什么是 ECS?为什么比传统继承好? 实体=ID、组件=纯数据、系统=逻辑。好:缓存友好(数据连续)、灵活组合(加组件即加能力)、易并行、无深继承。代价:调试分散、上手门槛。
Q4:四元数解决什么问题?为什么不用欧拉角? 欧拉角有万向节死锁 + 插值不平滑;四元数无死锁、slerp 平滑、拼接稳定,是现代引擎旋转的标准表示。
Q5:碰撞检测一般怎么做? 两阶段:broad phase 用空间分区(四叉树/八叉树/BVH/网格哈希)粗筛候选对,narrow phase 用包围体(AABB/球/OBB)精确检测。
Q6:四叉树、八叉树、BVH 分别适合什么场景? 四叉树 2D 稀疏场景、八叉树 3D 场景、BVH 动态物体(物理引擎)、网格哈希均匀分布。
Q7:为什么要用固定步长做物理? 确定性:同输入必然同结果(联机、回放、测试依赖);稳定性:可变步长下帧率波动会让物理抖动甚至发散。
Q8:什么是空间分区?解决什么问题? 把空间划分成区域,物体登记到区域,查询只搜邻近区域 → 把碰撞检测从 O(n²) 降到接近 O(n)。
Q9:欧拉角转四元数/四元数插值为什么重要? 角色朝向、相机旋转都要做旋转插值;slerp 保证最短弧线平滑旋转,欧拉角插值会转出奇怪路径。
Q10:引擎里的 Collider 和 Rigidbody 分别干嘛? Collider 定义碰撞形状(不移动);Rigidbody 受物理(力/速度)影响并产生碰撞响应。触发器 = 只检测进入/离开不做阻挡。
7. 本章自测(10 题,限时 15 分钟,先自己答再看答案)
Section titled “7. 本章自测(10 题,限时 15 分钟,先自己答再看答案)”1. 手写游戏主循环骨架(混合步长,含 accumulator 和插值)。
2. 固定步长解决了哪两个问题?
3. ECS 的三个要素分别是什么?各举一例。
4. 为什么说 ECS 缓存友好?
我的原答:和 SoA 什么关系?
5. 四元数的 4 个分量是什么?为什么能避免万向锁?
6. 碰撞检测的两阶段分别干什么?
7. 四叉树和 BVH 的区别(一个场景各举一个)?
8. 什么是触发器(Trigger)?
我的原答:举一个游戏场景。
9. 为什么物理必须确定性?
我的原答:不固定步长会怎样?
10. 点积和叉积在游戏里各有什么用途?
📖 答案(先自己答完再展开)
while(running){ 输入; while(acc>=FIXED_DT){ 更新(FIXED_DT); acc-=FIXED_DT; } 渲染(插值); }(详见 1.2)- 物理稳定性(帧率波动不抖动)和确定性(联机/回放同输入同结果)。
- Entity=ID(如 1024)、Component=纯数据(Position{x,y})、System=逻辑(移动系统处理 Position+Velocity)。
- 同类组件连续存储,批量处理时缓存行全是目标数据;ECS 的组件数组天然是 SoA 布局。
- w + (x,y,z),4 个分量,在 4D 超球面上表示旋转;不会出现欧拉角两个轴对齐锁死的情况。
- Broad phase 用空间分区粗筛候选对;narrow phase 用包围体精确求交。
- 四叉树:静态地形/2D 单位查询;BVH:动态角色/刚体碰撞(包围盒树实时更新)。
- 只检测进入/离开不阻挡,如:玩家走进拾取区域触发加血。
- 联机同步、回放、物理测试都要“同输入同结果”;不固定步长会导致不同机器物理表现不同、联机不同步。
- 点积:判断朝向/光照(cosθ);叉积:求法线、判断左右侧。
- accumulator 插值为什么能让“固定逻辑 + 可变渲染”不卡顿?
- ECS 的 archetype 模型(Unity DOTS)是什么?组件分布怎么按“实体拥有哪些组件”分块?
- 四元数的乘法为什么等于旋转拼接?slerp 和 nlerp 区别?
- 物理引擎的 broad phase 一般用什么结构组合(动态 BVH + 静态网格)?
- 蒙皮动画的数学(骨骼矩阵、权重、GPU 蒙皮)和“每帧每骨骼矩阵”的性能问题?
- 空间哈希为什么适合大量小物体(子弹)?格子大小怎么定?