游戏开发 面试题库

收录真实面试常见问题,答案长度适合口头表达;拿不准时可返回相关主题复习。

20个问题 初级 高级
全部难度 初级 中级 高级
逐题查看 显示全部答案
报告错误

Unity 脚本

1个问题
01 Unity 协程与普通方法和后台线程分别有什么区别? 初级 常见 查看答案 ▾ 收起 ▴

普通方法会一直执行到返回,协程则能在 yield return 处暂停,并在之后的 PlayerLoop 阶段继续。C# 编译器把这种方法改写为 IEnumerator 状态机,因此执行位置和仍然存活的局部变量可以跨恢复点保存。这不会创建工作线程;每个同步片段仍在 Unity 主线程上执行,而且必须先结束才能交还控制权。所以,把阻塞读取、Thread.Sleep 或昂贵循环放入协程,仍会像放在 Update 中一样卡住当前帧。

延伸阅读 Unity 协程
讲清楚了吗?

计时与 PlayerLoop

2个问题
02 如何在 WaitForSeconds 与 WaitForSecondsRealtime 之间选择? 中级 常见 查看答案 ▾ 收起 ▴

应根据需求指定的时钟来选择。WaitForSeconds 跟随缩放后的游戏时间,因此 Time.timeScale 会改变等待进度,零时间缩放会让它停住;这适合随游戏模拟一起暂停的延迟。WaitForSecondsRealtime 使用未缩放时间,适合暂停菜单提示和现实时间超时。两者都不是精确的墙钟闹钟,因为协程只能在时长过去后的可用帧恢复。实现截止时间或倒计时显示时,应保存目标时刻并重新计算剩余量,不要每次等待后简单减一。

延伸阅读 Unity 协程
讲清楚了吗?
15 Update、FixedUpdate 与 LateUpdate 应如何划分职责? 中级 常见 查看答案 ▾ 收起 ▴

Update 跟随渲染帧,通常用于采样输入、推进非物理状态,并配合 Time.deltaTime。FixedUpdate 跟随物理时间步;一个渲染帧可能没有固定步,也可能包含多个,因此 Rigidbody 施力和物理状态写入应放在这里。持续输入保存最新值,一次性动作则缓冲到某个固定步消费。LateUpdate 位于常规 Update 阶段之后,适合相机这类需要读取本帧已更新目标姿态的使用方。它只提供阶段边界,不会自动实现平滑,也不保证所有 LateUpdate 实例之间的顺序。

讲清楚了吗?

生命周期与所有权

2个问题
03 哪些所有权规则能让 Unity 协程安全地停止和重新启动? 中级 常见 查看答案 ▾ 收起 ▴

应让拥有被修改状态的组件同时拥有正在运行的 Coroutine 句柄,并明确重复请求是忽略、排队、并行还是替换。采用替换策略时,启动新实例前先停止已保存的句柄,并在正常结束和生命周期关闭时清空字段。不要重新创建一个 IEnumerator,再期待它能标识正在运行的实例。生命周期策略也要明确:禁用 MonoBehaviour 不会停止其协程,把所属 GameObject 设为非活动状态则会停止,而且重新激活不会恢复已停止的迭代器。

延伸阅读 Unity 协程
讲清楚了吗?
14 如何为事件订阅选择合适的生命周期回调? 中级 常见 查看答案 ▾ 收起 ▴

先确定监听器只在活动期间接收事件,还是覆盖组件的整个实例生命周期。活动期所有权适合在 OnEnable 订阅、在 OnDisable 取消;两个回调都可重复,因此重新启用后仍保持对称。在 Start 订阅、OnDisable 取消是错误组合,因为 Start 只运行一次。若组件禁用时仍要监听,就应使用更长且明确的所有权边界,并把销毁与从未激活的对象纳入策略。我倾向于使用命名处理器,而不是两段看似相同的 lambda,因为新建 lambda 无法移除最初注册的委托实例。

讲清楚了吗?

异步设计

1个问题
04 在 Unity 中,何时应使用协程、Awaitable 或 C# Job System? 高级 偶尔 查看答案 ▾ 收起 ▴

短小流程若围绕帧、Unity yield 指令和场景对象修改展开,适合使用协程。调用方需要返回值、常规 async 组合、异常传播或跨 I/O 与 Unity 异步 API 的取消时,Awaitable 通常更清楚。目标是对数据执行并行 CPU 计算时,应使用 Job System,并按需要配合 Burst。这些工具都不会自动让任意同步代码变成非阻塞代码。选择前先说明工作在哪个线程执行、谁拥有取消权,以及完成或失败如何到达调用方。

延伸阅读 Unity 协程
讲清楚了吗?

Unity DOTS

1个问题
05 原型与 chunk 如何影响 Unity Entities 的组件设计? 高级 常见 查看答案 ▾ 收起 ▴

原型由实体拥有的完整组件类型集合决定。同一原型的实体存放在 16 KiB chunk 中,每种组件类型对应一个紧密数组,系统查询匹配原型后遍历这些数组。因此,组件边界会影响 chunk 容量与内存流量:把很少读取的大字段放进热组件,会让热路径携带无关数据。添加或移除组件还会把实体搬到另一原型,标签组合过多则可能扩大原型数量。我会先根据实际访问模式设计,再检查 chunk 占用与结构变更,而不是假定组件越小越好。

延伸阅读 Unity DOTS
讲清楚了吗?

并发与 Job

1个问题
06 ISystem 中的 state.Dependency 表示什么契约? 高级 常见 查看答案 ▾ 收起 ▴

state.Dependency 携带当前系统必须遵守的 JobHandle,因为此前工作可能访问了相同组件。调度新 Job 时通常要传入这个句柄,并把返回句柄写回,让后续系统看到新增依赖。只做一半并不完整:省略输入可能与前序工作竞态,丢弃输出则会向后续工作隐藏本 Job。只有同步代码马上消费结果时才应 Complete。每次调度后都完成虽然可能暂时避开报错,却会把原本能够重叠的工作串行化。

延伸阅读 Unity DOTS
讲清楚了吗?

结构变更

1个问题
07 何时应使用 EntityCommandBuffer,而不是可启用组件? 中级 常见 查看答案 ▾ 收起 ▴

当操作确实改变存储形状,例如创建实体、添加或移除组件,而且决策发生在 Job 或查询迭代中时,应使用 EntityCommandBuffer。它先记录命令,再在回放点修改存储。若组件类型与存储应继续存在,只是一个频繁变化的状态需要影响查询匹配,则使用可启用组件;切换启用位不会让实体跨原型搬移。两者并不等价:ECB 有结果何时可见的问题,可启用组件仍占据 chunk 空间,也可能因依赖关系产生同步点。

延伸阅读 Unity DOTS
讲清楚了吗?

性能分析

1个问题
08 如何验证一次 DOTS 重写确实更快? 高级 偶尔 查看答案 ▾ 收起 ▴

先用 Profiler 捕获明确原实现的瓶颈。比较时固定场景输入、目标设备、Player 构建设置、帧率策略和采样区间,并确保两边行为等价。记录主线程时间、工作线程时间、等待、分配、结构变更与实体数量。还要通过 Burst Inspector 和构建日志确认目标 Job 确实经过 Burst 编译,因为播放模式可能在 Burst 编译期间执行托管回退代码。测量稳定阶段前应预热一次性编译与加载成本。若原瓶颈在 GPU、渲染、网络或磁盘,ECS 重写未必改善帧时间。

延伸阅读 Unity DOTS
讲清楚了吗?

Unity 对象模型

1个问题
09 Unity 中的 GameObject 与组件如何划分职责? 初级 常见 查看答案 ▾ 收起 ▴

GameObject 提供标识、活动状态、层级关系、标签、层以及组件所有权;具体能力由组件提供。Transform 保存空间状态,Renderer 负责绘制,Collider 参与物理查询,MonoBehaviour 脚本加入项目行为。我倾向于组合多个职责集中的组件,而不是建立很深的继承树,因为这样每项依赖都能在 Inspector 中看见,也能单独测试。同对象的必需依赖应使用 RequireComponent 并在启动时验证;跨对象引用则应显式连接或注册,不要每帧按名称重新查找。

讲清楚了吗?

序列化与 Inspector

1个问题
10 为什么修改 C# 字段初始值后,现有 Unity 组件的值可能不变? 中级 常见 查看答案 ▾ 收起 ▴

字段初始值只在创建组件时提供起点,之后场景或 Prefab 会保存自己的序列化值。加载时编辑数据优先,所以初始值从 3 改成 5 后,现有组件仍可能保持 3。我会先检查组件值与 Prefab override,而不是怀疑编译失败。若所有现有资源都需要新值,这属于数据迁移,不只是修改默认值。我还会用私有序列化字段接收 Inspector 输入,把公开成员留给有意设计的运行时 API。

讲清楚了吗?

MonoBehaviour 生命周期

2个问题
11 Awake、OnEnable 与 Start 周围有哪些初始化顺序可以安全依赖? 中级 常见 查看答案 ▾ 收起 ▴

对于一个初始活动且已启用的实例,Awake 先于 OnEnable,Start 则在首次帧更新前执行。禁用后重新启用会再次触发 OnEnable,而 Start 在该实例生命周期内只执行一次。场景中初始非活动的对象会把 Awake 推迟到激活时。除非项目明确配置,否则我不会假定不同 GameObject 的 Awake 顺序。同对象引用可在 Awake 建立;跨对象就绪应使用显式引导阶段、事件或有文档的执行顺序规则。OnEnable 中的订阅还必须在 OnDisable 中对应取消。

讲清楚了吗?
13 围绕 Awake、OnEnable 和 Start,哪些执行顺序保证可以用于设计? 中级 常见 查看答案 ▾ 收起 ▴

对于一个初始活动且已启用的实例,Awake 先于 OnEnable,Start 则只运行一次,并位于该实例首次帧更新之前。每次禁用后重新启用都会再次调用 OnEnable,Awake 与 Start 不会重复。场景启动时已经活动的对象会在任何 Start 之前完成 Awake,但 Unity 默认不规定不同 GameObject 的 Awake 相对顺序;运行时实例化还会在以后产生新的 Awake。我把自身初始化放在 Awake,并用 bootstrap、注册事件或范围很小且有文档的 Script Execution Order 规则表达跨对象就绪。

讲清楚了吗?

Prefab 工作流

1个问题
12 Prefab 资源、Prefab 实例与 override 有什么区别? 初级 常见 查看答案 ▾ 收起 ▴

Prefab 资源是保存在项目中的可复用来源层级,场景实例或运行时实例则是从该来源创建的对象。场景实例可以记录相对资源的 override,因此资源后续变更只会进入实例未覆盖的属性。Apply 把选定的实例 override 写回资源,Revert 则从实例丢弃这些差异。运行时编辑通常只改变克隆,并在播放模式结束时消失。排查配置错误时,我会同时检查这三层,因为只看脚本无法知道序列化引用和被遮住的 override。

讲清楚了吗?

Unity 脚本调试

1个问题
16 一个生命周期方法能够编译,却从不运行。你会如何诊断? 中级 偶尔 查看答案 ▾ 收起 ▴

我会先把方法名称、返回类型和参数与当前 Unity 消息签名逐项比较。Awake 与 Update 是引擎消息,不是 MonoBehaviour 的虚方法,因此虚构的 override、拼成 OnEnabled 或增加额外参数,即使看起来合理也不正确。随后检查 GameObject 的活动状态、Behaviour 的启用状态,以及初始非活动对象是否曾经激活。最后用最小探针或 Play Mode 测试复现一次状态转换。Script Execution Order 无法让未识别的方法运行,它只能为配置过的脚本类排列已经识别的回调。

讲清楚了吗?

Unity 数据架构

1个问题
17 数据应在什么情况下使用 ScriptableObject、MonoBehaviour 或普通 C# 对象? 中级 常见 查看答案 ▾ 收起 ▴

我先判断所有权和持久化需求。需要 Unity 资源身份、Inspector 编辑能力,并且要引用其他 Unity 资源的共享编辑数据,适合放进 ScriptableObject。属于某个 GameObject、受场景生命周期驱动的状态或行为,适合放进 MonoBehaviour。不需要资源身份和组件回调的运行时状态,则用普通 C# 对象。我不会仅为了在 Inspector 中显示字段就选择 ScriptableObject,还会明确谁创建对象、变化是否需要跨启动保留,以及谁释放临时 Unity 对象。

讲清楚了吗?

状态所有权

1个问题
18 如何防止共享 ScriptableObject 配置在使用方之间泄漏运行时状态? 中级 常见 查看答案 ▾ 收起 ▴

我会把每个字段归为编辑期定义、每个拥有者独立的状态或缓存。资源只保存最大生命值、基础伤害和视觉引用等定义,并通过只读运行时 API 暴露。每个使用方把当前生命值、冷却和进度放在自身组件或普通状态对象中。若现有 API 必须接收可变 ScriptableObject,我会只克隆一次模板,显式初始化,再让一个拥有者负责销毁。测试时让两个使用方共享同一资源,修改其中一个,并确认另一个使用方和源资源都不变。

讲清楚了吗?

持久化

1个问题
19 存档文件应如何引用 ScriptableObject 资源? 中级 常见 查看答案 ▾ 收起 ▴

我会保存项目定义的稳定内容 ID,不保存 ScriptableObject 本身、GetInstanceID 或编辑器资源路径。存档 DTO 还要携带数量、耐久度等可变值。加载时由目录把 ID 解析为当前资源;遇到未知 ID,应带上足够上下文报告错误。ID 需要唯一性验证和迁移策略,因为它的寿命会超过资源改名,还可能进入遥测或后端数据。往返测试必须先写成文本或字节,再重建 DTO,并在新的对象图中完成解析。

讲清楚了吗?

版本化审查

1个问题
20 审查面向 Unity 6.6 的生成 ScriptableObject 代码时,哪些版本相关假设需要重点检查? 高级 偶尔 查看答案 ▾ 收起 ▴

我会按 Unity 6.6 检查序列化和 Play mode 生命周期结论,而不是照搬旧教程。兼容的 Dictionary 字段加上 SerializeField 后已经可以原生序列化,但多维数组、交错数组和直接嵌套集合仍有限制。Unity 6.6 新项目默认进入 Play mode 时不重载脚本域,因此缓存和事件处理器不能假定会自动重置。我会把 OnEnable 中的重置与真实会话边界比较,执行资源保存后重载测试,并构建 Player,以发现误入运行时程序集的 UnityEditor API。

讲清楚了吗?