游戏开发面试题库
收录真实面试常见问题,答案长度适合口头表达;拿不准时可返回相关主题复习。
Unity脚本
1个问题 · 0 已看01 Unity协程与普通方法和后台线程分别有什么区别? 查看答案 ▾ 收起 ▴
普通方法会一直执行到返回,协程则能在yield return处暂停,并在之后的PlayerLoop阶段继续。C#编译器把这种方法改写为IEnumerator状态机,因此执行位置和仍然存活的局部变量可以跨恢复点保存。这不会创建工作线程;每个同步片段仍在Unity主线程上执行,而且必须先结束才能交还控制权。所以,把阻塞读取、Thread.Sleep或昂贵循环放入协程,仍会像放在Update中一样卡住当前帧。
计时与PlayerLoop
2个问题 · 0 已看02 如何在WaitForSeconds与WaitForSecondsRealtime之间选择? 查看答案 ▾ 收起 ▴
应根据需求指定的时钟来选择。WaitForSeconds跟随缩放后的游戏时间,因此Time.timeScale会改变等待进度,零时间缩放会让它停住;这适合随游戏模拟一起暂停的延迟。WaitForSecondsRealtime使用未缩放时间,适合暂停菜单提示和现实时间超时。两者都不是精确的墙钟闹钟,因为协程只能在时长过去后的可用帧恢复。实现截止时间或倒计时显示时,应保存目标时刻并重新计算剩余量,不要每次等待后简单减一。
15 Update、FixedUpdate与LateUpdate应如何划分职责? 查看答案 ▾ 收起 ▴
Update跟随渲染帧,通常用于采样输入、推进非物理状态,并配合Time.deltaTime。FixedUpdate跟随物理时间步;一个渲染帧可能没有固定步,也可能包含多个,因此Rigidbody施力和物理状态写入应放在这里。持续输入保存最新值,一次性动作则缓冲到某个固定步消费。LateUpdate位于常规Update阶段之后,适合相机这类需要读取本帧已更新目标姿态的使用方。它只提供阶段边界,不会自动实现平滑,也不保证所有LateUpdate实例之间的顺序。
生命周期与所有权
2个问题 · 0 已看03 哪些所有权规则能让Unity协程安全地停止和重新启动? 查看答案 ▾ 收起 ▴
应让拥有被修改状态的组件同时拥有正在运行的Coroutine句柄,并明确重复请求是忽略、排队、并行还是替换。采用替换策略时,启动新实例前先停止已保存的句柄,并在正常结束和生命周期关闭时清空字段。不要重新创建一个IEnumerator,再期待它能标识正在运行的实例。生命周期策略也要明确:禁用MonoBehaviour不会停止其协程,把所属GameObject设为非活动状态则会停止,而且重新激活不会恢复已停止的迭代器。
14 如何为事件订阅选择合适的生命周期回调? 查看答案 ▾ 收起 ▴
先确定监听器只在活动期间接收事件,还是覆盖组件的整个实例生命周期。活动期所有权适合在OnEnable订阅、在OnDisable取消;两个回调都可重复,因此重新启用后仍保持对称。在Start订阅、OnDisable取消是错误组合,因为Start只运行一次。若组件禁用时仍要监听,就应使用更长且明确的所有权边界,并把销毁与从未激活的对象纳入策略。我倾向于使用命名处理器,而不是两段看似相同的lambda,因为新建lambda无法移除最初注册的委托实例。
异步设计
1个问题 · 0 已看04 在Unity中,何时应使用协程、Awaitable或C# Job System? 查看答案 ▾ 收起 ▴
短小流程若围绕帧、Unity yield指令和场景对象修改展开,适合使用协程。调用方需要返回值、常规async组合、异常传播或跨I/O与Unity异步API的取消时,Awaitable通常更清楚。目标是对数据执行并行CPU计算时,应使用Job System,并按需要配合Burst。这些工具都不会自动让任意同步代码变成非阻塞代码。选择前先说明工作在哪个线程执行、谁拥有取消权,以及完成或失败如何到达调用方。
Unity DOTS
1个问题 · 0 已看05 原型与chunk如何影响Unity Entities的组件设计? 查看答案 ▾ 收起 ▴
原型由实体拥有的完整组件类型集合决定。同一原型的实体存放在16 KiB chunk中,每种组件类型对应一个紧密数组,系统查询匹配原型后遍历这些数组。因此,组件边界会影响chunk容量与内存流量:把很少读取的大字段放进热组件,会让热路径携带无关数据。添加或移除组件还会把实体搬到另一原型,标签组合过多则可能扩大原型数量。我会先根据实际访问模式设计,再检查chunk占用与结构变更,而不是假定组件越小越好。
并发与Job
1个问题 · 0 已看06 ISystem中的state.Dependency表示什么契约? 查看答案 ▾ 收起 ▴
state.Dependency携带当前系统必须遵守的JobHandle,因为此前工作可能访问了相同组件。调度新Job时通常要传入这个句柄,并把返回句柄写回,让后续系统看到新增依赖。只做一半并不完整:省略输入可能与前序工作竞态,丢弃输出则会向后续工作隐藏本Job。只有同步代码马上消费结果时才应Complete。每次调度后都完成虽然可能暂时避开报错,却会把原本能够重叠的工作串行化。
结构变更
1个问题 · 0 已看07 何时应使用EntityCommandBuffer,而不是可启用组件? 查看答案 ▾ 收起 ▴
当操作确实改变存储形状,例如创建实体、添加或移除组件,而且决策发生在Job或查询迭代中时,应使用EntityCommandBuffer。它先记录命令,再在回放点修改存储。若组件类型与存储应继续存在,只是一个频繁变化的状态需要影响查询匹配,则使用可启用组件;切换启用位不会让实体跨原型搬移。两者并不等价:ECB有结果何时可见的问题,可启用组件仍占据chunk空间,也可能因依赖关系产生同步点。
性能分析
1个问题 · 0 已看08 如何验证一次DOTS重写确实更快? 查看答案 ▾ 收起 ▴
先用Profiler捕获明确原实现的瓶颈。比较时固定场景输入、目标设备、Player构建设置、帧率策略和采样区间,并确保两边行为等价。记录主线程时间、工作线程时间、等待、分配、结构变更与实体数量。还要通过Burst Inspector和构建日志确认目标Job确实经过Burst编译,因为播放模式可能在Burst编译期间执行托管回退代码。测量稳定阶段前应预热一次性编译与加载成本。若原瓶颈在GPU、渲染、网络或磁盘,ECS重写未必改善帧时间。
Unity对象模型
1个问题 · 0 已看09 Unity中的GameObject与组件如何划分职责? 查看答案 ▾ 收起 ▴
GameObject提供标识、活动状态、层级关系、标签、层以及组件所有权;具体能力由组件提供。Transform保存空间状态,Renderer负责绘制,Collider参与物理查询,MonoBehaviour脚本加入项目行为。我倾向于组合多个职责集中的组件,而不是建立很深的继承树,因为这样每项依赖都能在Inspector中看见,也能单独测试。同对象的必需依赖应使用RequireComponent并在启动时验证;跨对象引用则应显式连接或注册,不要每帧按名称重新查找。
序列化与Inspector
1个问题 · 0 已看10 为什么修改C#字段初始值后,现有Unity组件的值可能不变? 查看答案 ▾ 收起 ▴
字段初始值只在创建组件时提供起点,之后场景或Prefab会保存自己的序列化值。加载时编辑数据优先,所以初始值从3改成5后,现有组件仍可能保持3。我会先检查组件值与Prefab override,而不是怀疑编译失败。若所有现有资源都需要新值,这属于数据迁移,不只是修改默认值。我还会用私有序列化字段接收Inspector输入,把公开成员留给有意设计的运行时API。
MonoBehaviour生命周期
2个问题 · 0 已看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个问题 · 0 已看12 Prefab资源、Prefab实例与override有什么区别? 查看答案 ▾ 收起 ▴
Prefab资源是保存在项目中的可复用来源层级,场景实例或运行时实例则是从该来源创建的对象。场景实例可以记录相对资源的override,因此资源后续变更只会进入实例未覆盖的属性。Apply把选定的实例override写回资源,Revert则从实例丢弃这些差异。运行时编辑通常只改变克隆,并在播放模式结束时消失。排查配置错误时,我会同时检查这三层,因为只看脚本无法知道序列化引用和被遮住的override。
Unity脚本调试
1个问题 · 0 已看16 一个生命周期方法能够编译,却从不运行。你会如何诊断? 查看答案 ▾ 收起 ▴
我会先把方法名称、返回类型和参数与当前Unity消息签名逐项比较。Awake与Update是引擎消息,不是MonoBehaviour的虚方法,因此虚构的override、拼成OnEnabled或增加额外参数,即使看起来合理也不正确。随后检查GameObject的活动状态、Behaviour的启用状态,以及初始非活动对象是否曾经激活。最后用最小探针或Play Mode测试复现一次状态转换。Script Execution Order无法让未识别的方法运行,它只能为配置过的脚本类排列已经识别的回调。
Unity数据架构
1个问题 · 0 已看17 数据应在什么情况下使用ScriptableObject、MonoBehaviour或普通C#对象? 查看答案 ▾ 收起 ▴
我先判断所有权和持久化需求。需要Unity资源身份、Inspector编辑能力,并且要引用其他Unity资源的共享编辑数据,适合放进ScriptableObject。属于某个GameObject、受场景生命周期驱动的状态或行为,适合放进MonoBehaviour。不需要资源身份和组件回调的运行时状态,则用普通C#对象。我不会仅为了在Inspector中显示字段就选择ScriptableObject,还会明确谁创建对象、变化是否需要跨启动保留,以及谁释放临时Unity对象。
状态所有权
1个问题 · 0 已看持久化
1个问题 · 0 已看19 存档文件应如何引用ScriptableObject资源? 查看答案 ▾ 收起 ▴
我会保存项目定义的稳定内容ID,不保存ScriptableObject本身、GetInstanceID或编辑器资源路径。存档DTO还要携带数量、耐久度等可变值。加载时由目录把ID解析为当前资源;遇到未知ID,应带上足够上下文报告错误。ID需要唯一性验证和迁移策略,因为它的寿命会超过资源改名,还可能进入遥测或后端数据。往返测试必须先写成文本或字节,再重建DTO,并在新的对象图中完成解析。
版本化审查
1个问题 · 0 已看20 审查面向Unity 6.6的生成ScriptableObject代码时,哪些版本相关假设需要重点检查? 查看答案 ▾ 收起 ▴
我会按Unity 6.6检查序列化和Play mode生命周期结论,而不是照搬旧教程。兼容的Dictionary字段加上SerializeField后已经可以原生序列化,但多维数组、交错数组和直接嵌套集合仍有限制。Unity 6.6新项目默认进入Play mode时不重载脚本域,因此缓存和事件处理器不能假定会自动重置。我会把OnEnable中的重置与真实会话边界比较,执行资源保存后重载测试,并构建Player,以发现误入运行时程序集的UnityEditor API。
没有符合筛选条件的问题。