
✨目录✨一、C# 语言核心面试开篇高频基础题1. 请说一下值类型和引用类型的区别2. 什么是装箱和拆箱有什么危害怎么规避3. 讲一下你理解的 GC 垃圾回收为什么会造成卡顿4. 委托和事件有什么区别你项目里怎么用的5. 数组和 List 有什么区别底层是怎么实现的6. Dictionary 的底层原理是什么哈希冲突怎么解决7. foreach 遍历有什么坑闭包常见的坑是什么8. 协程和多线程有什么区别分别适合什么场景二、Unity 核心基础1. 讲一下 MonoBehaviour 的生命周期核心函数分别什么时候用2. GameObject 和 Transform 有什么区别和联系3. Instantiate 和 Destroy 的执行时机是什么频繁用有什么问题4. 对象池的核心思想是什么你项目里怎么实现的5. 单例模式你怎么实现的有什么优缺点6. Prefab 和 Prefab Variant 有什么区别7. Raycast 射线检测是什么用的时候要注意什么三、UGUI 与 UI 开发1. Canvas 的三种渲染模式分别是什么各适合什么场景2. Canvas Scaler 的三种缩放模式项目里用哪种3. UGUI 的屏幕适配是怎么做的4. 什么是 UI 动静分离为什么要做5. UI 图集有什么作用打包的时候要注意什么6. UGUI 性能优化你一般会做哪些7. 如果让你搭一个简易 UI 框架你会怎么设计四、性能优化专项1. 减少 GC 的优化手段有哪些2. 内存优化你会从哪些方面入手3. 渲染优化有哪些常用手段4. 加载速度优化怎么做5. 游戏出现卡顿你的排查思路是什么五、热更新与资源管理1. HybridCLR 热更新的原理是什么怎么理解依赖注入2. AssetBundle 是什么使用的时候要注意什么3. YooAsset 资源框架解决了什么问题4. 完整的热更新业务流程是怎样的六、数据结构与算法1. 说一下几种基础排序算法的原理和时间复杂度2. 常用数据结构的适用场景3. C# 里字符串处理要注意什么七、计算机网络基础1. TCP 和 UDP 的区别分别适合什么游戏场景2. HTTP 和 WebSocket 有什么区别3. 说一下 OSI 七层模型和 TCP/IP 四层模型八、项目高频追问标准回答1. 你项目里做了哪些优化2. 项目里遇到过什么难点你是怎么解决的3. 你在项目中主要负责哪些工作4. 你怎么用 AI 工具辅助开发会不会直接照搬代码一、C# 语言核心面试开篇高频基础题1. 请说一下值类型和引用类型的区别值类型和引用类型最核心的区别在内存分配和赋值逻辑上。值类型分配在线程栈上赋值时是完整拷贝一份数据两个变量互不影响引用类型分配在托管堆上赋值时拷贝的是堆内存的引用地址两个变量指向同一个对象修改一个另一个也会同步变化。 常见的值类型有 int、float、bool、枚举、结构体引用类型有类、字符串、数组、List、Dictionary。2. 什么是装箱和拆箱有什么危害怎么规避装箱就是把值类型转换成引用类型比如 object过程中会在堆上分配内存、拷贝数据拆箱就是反向把堆上的引用类型转回值类型。 它的危害主要有两点一是产生堆内存分配增加 GC 压力二是频繁转换有额外的性能开销。3. 讲一下你理解的 GC 垃圾回收为什么会造成卡顿GC 就是垃圾回收本质是 CLR 自动回收托管堆上无引用对象的内存不需要手动释放。 它造成卡顿的原因是 GC 执行时会暂停所有主线程逻辑也就是 Stop The World暂停时间过长玩家就会感觉到卡顿。 GC 的触发时机主要有堆内存分配达到阈值、场景切换、手动调用 GC.Collect、程序定时触发。4. 委托和事件有什么区别你项目里怎么用的委托可以理解成存储方法引用的类型能把方法当参数传递用来实现回调。事件是对委托的一层封装限制了外部访问权限 —— 外部只能订阅和取消订阅不能直接调用委托、也不能直接赋值安全性更高保证了封装性。5. 数组和 List 有什么区别底层是怎么实现的List 的底层本质是封装了一个数组支持动态扩容。数组长度固定声明时就要指定优点是访问速度快、开销小List 长度动态可变可随时增删元素易用性更强性能比数组多一点点额外开销。 List 默认初始容量是 4元素数量超过容量时会自动扩容容量变为原来的两倍扩容时会创建新数组、拷贝旧数据有一定性能开销所以如果提前知道元素数量最好初始化时就指定容量。6. Dictionary 的底层原理是什么哈希冲突怎么解决Dictionary 底层基于哈希表实现通过哈希函数把 key 计算成数组索引存在对应位置所以查找时间复杂度是 O (1)速度很快。 哈希冲突就是两个不同的 key 计算出了相同的索引C# 的 Dictionary 用拉链法解决数组的每个位置都是一个链表的头节点冲突的元素挂在同一个链表上查找时先算索引再遍历链表比对 key。 当元素数量达到负载因子阈值时Dictionary 也会扩容扩容后重新计算所有元素的哈希位置开销相对较大。7. foreach 遍历有什么坑闭包常见的坑是什么foreach 遍历过程中不能对集合做添加、删除元素的操作否则会抛出异常因为遍历时会校验集合版本号集合修改后版本号不匹配就会报错。 闭包的经典坑是在循环内捕获迭代变量比如 for 循环里给多个按钮注册事件每个事件都引用循环变量 i最终所有按钮触发时拿到的都是循环结束后的最终值而不是对应位置的值。解决方法是在循环内部声明临时变量把迭代变量赋值给临时变量再去捕获这个临时变量。8. 协程和多线程有什么区别分别适合什么场景协程不是线程它在 Unity 主线程内分步执行基于迭代器实现每帧执行一部分逻辑遇到 yield return 就暂停下一帧再继续。它适合轻量异步场景比如延时执行、分帧加载资源、新手引导分步执行不能用来做重度计算否则依然会卡住主线程。 多线程是独立的执行线程和主线程并行运行适合复杂数值计算、大数据处理不会阻塞主线程。但多线程内不能直接访问 Unity API因为 Unity API 大多只允许主线程调用。二、Unity 核心基础1. 讲一下 MonoBehaviour 的生命周期核心函数分别什么时候用核心执行顺序是Awake → OnEnable → Start → Update → FixedUpdate → LateUpdate → OnDisable → OnDestroy。Awake物体创建时立即执行脚本未启用也会触发。我一般用来获取组件引用、单例初始化不依赖其他对象的初始化都放在这里。OnEnable脚本启用时执行。我一般用来注册事件、订阅消息。Start第一次 Update 之前执行适合做依赖其他对象的初始化比如获取其他脚本的数据。Update每帧执行一次处理普通游戏逻辑比如输入检测、状态更新。FixedUpdate固定时间间隔执行默认 0.02 秒不受帧率影响专门处理物理相关逻辑比如 Rigidbody 操作。LateUpdate所有 Update 执行完毕后再执行。我一般用来做相机跟随保证物体移动完成后相机再跟进避免抖动。OnDisable/OnDestroy脚本禁用、物体销毁时执行用来注销事件、清理资源避免内存泄漏。2. GameObject 和 Transform 有什么区别和联系GameObject 就是 Unity 里的游戏对象本身没有任何功能是一个容器通过挂载各类组件实现渲染、物理、逻辑等能力。 Transform 是每个 GameObject 必定自带的组件存储物体的位置、旋转、缩放以及父子层级关系。 简单说GameObject 是载体Transform 是管理物体位置与层级的组件查找子物体、修改位置、设置父物体这些操作都通过 Transform 完成。3. Instantiate 和 Destroy 的执行时机是什么频繁用有什么问题Instantiate 用于克隆预制体或游戏对象调用后会立刻创建新对象并执行新对象的 Awake 和 OnEnable再继续执行后续代码。 Destroy 不是调用后立刻销毁而是在当前帧末尾、所有 Update 逻辑跑完后才销毁对象所以刚调用 Destroy 就判断对象为 null 是错误的。 频繁调用 Instantiate 和 Destroy 有两个核心问题一是每次创建对象都有堆内存分配增加 GC 压力二是创建和销毁本身有性能开销频繁操作会造成卡顿。 所以项目中高频创建销毁的对象比如特效、UI 列表项我都会用对象池优化复用对象而非反复创建销毁。4. 对象池的核心思想是什么你项目里怎么实现的对象池的核心思想是预创建、复用、不销毁。提前创建一批常用对象放到池子里需要用时取出激活用完就回收禁用、放回池子而非直接销毁。5. 单例模式你怎么实现的有什么优缺点我常用两种实现一种是继承 MonoBehaviour 的单例一般用来做管理器挂载在全局对象上场景内唯一提供静态属性访问另一种是普通 C# 静态单例不继承 Mono纯数据或逻辑管理器使用。 优点是提供了全局唯一访问点跨模块调用方便不需要到处传递引用。 缺点也很明显一是耦合度高所有模块都依赖这个单例不利于替换和单元测试二是生命周期难管控容易出现对象已销毁仍被调用的问题三是多线程环境下不安全。6. Prefab 和 Prefab Variant 有什么区别Prefab 即预制体是可复用的资源模板修改预制体模板后场景内所有实例都会同步更新适合批量复用的对象比如怪物、UI 界面。 Prefab Variant 是预制体变体它继承自一个基础预制体拥有基础预制体的全部内容同时可以自行添加组件、修改属性这些修改不会影响基础预制体。 它非常适合做同类型的差异化对象比如先做基础怪物预制体不同怪物做变体修改数值、替换模型复用基础逻辑维护时修改基础逻辑只需要改基预制体效率很高。7. Raycast 射线检测是什么用的时候要注意什么射线检测就是从起点沿指定方向发射一条不可见射线检测路径上带碰撞体的物体并返回碰撞信息常用来实现鼠标点击 3D 物体、地面检测、碰撞判定、视线检测等功能。 使用时有几个注意点第一被检测物体必须挂载 Collider 组件第二合理使用 LayerMask 只检测目标层既提升性能也避免误检测第三UI 的射线检测不是 Physics.Raycast而是 UGUI 的 GraphicRaycaster走独立的 UI 射线检测逻辑。三、UGUI 与 UI 开发1. Canvas 的三种渲染模式分别是什么各适合什么场景三种模式分别是 Screen Space - Overlay、Screen Space - Camera、World Space。Screen Space - OverlayUI 直接渲染在屏幕最上层无需绑定相机是最常用的模式。普通 2D 主界面、弹窗、按钮都用这个我项目里大部分 UI 都是这个模式。Screen Space - Camera绑定指定相机UI 渲染在相机视野内受相机位置、透视参数影响可以做带 3D 效果的 UI比如有景深的界面。World Space世界空间模式UI 放在 3D 场景中和普通 3D 物体一样可旋转缩放适合做场景内的血条、交互面板、VR 场景里的 UI 面板。2. Canvas Scaler 的三种缩放模式项目里用哪种三种模式是 Constant Pixel Size、Scale With Screen Size、Constant Physical Size。 最常用的是Scale With Screen Size即按屏幕尺寸缩放设置参考分辨率后UI 会根据屏幕宽高比等比缩放是当前主流的 UI 适配方案。 Constant Pixel Size 是固定像素大小UI 尺寸不随屏幕变化大屏上 UI 会偏小一般很少使用。 Constant Physical Size 按物理尺寸显示保证不同 DPI 屏幕上物理大小一致使用场景也不多。3. UGUI 的屏幕适配是怎么做的核心依靠三个部分参考分辨率、Canvas Scaler 缩放、锚点 Anchor。 首先确定设计稿参考分辨率比如 1920x1080Canvas Scaler 选择 Scale With Screen Size设置匹配模式一般按宽度或高度匹配。然后给不同 UI 元素设置对应锚点全屏背景把锚点拉到四边固定在角落的按钮锚定对应角居中元素锚定中心。这样不同分辨率下UI 的相对位置和比例都能保持正确不会拉伸错位。 另外异形屏还要做安全区域适配将 UI 向安全区内偏移避开刘海、挖孔区域。4. 什么是 UI 动静分离为什么要做UGUI 中同一个 Canvas 下只要有一个 UI 元素变化就可能触发整个 Canvas 的网格重建Rebuild。如果 Canvas 内元素很多哪怕只有一个小元素变动也要重建整个 Canvas开销非常大。 动静分离就是把静态不变的 UI 和频繁变化的 UI 拆分到不同 Canvas 里比如顶部固定状态栏放一个静态 Canvas中间动态刷新的数值、滚动列表放单独的动态 Canvas。这样动态元素变化时只重建自身的小 Canvas不会带动大的静态 Canvas 一起重建大幅降低网格重建开销提升 UI 性能。5. UI 图集有什么作用打包的时候要注意什么图集就是把多张碎图打包成一张大图核心作用是合并 Draw Call同一张图集内的图片可以一次绘制完成减少渲染开销同时也方便资源统一管理。 打包图集时要注意几点第一按功能模块分包比如主界面一个图集、商店一个图集不要把所有 UI 图打成一个大包避免加载无关资源第二大小相近的图放在一起减少空间浪费第三控制单张图集的最大尺寸避免超过硬件限制第四经常一起显示的图放在同一个图集最大化合批效果。6. UGUI 性能优化你一般会做哪些我一般从这几个方向入手 第一是 Canvas 拆分也就是动静分离频繁变化的 UI 单独放 Canvas减少网格重建开销。 第二是降低渲染开销打图集合并 Draw Call减少透明图层叠加降低 Overdraw。 第三是关闭不必要的组件比如不需要点击的 UI关掉 Graphic Raycaster减少射线检测开销离屏、无用的 UI 及时禁用。 第四是资源优化压缩图片格式清理冗余图片不用超大尺寸 UI 图文本尽量复用字体图集避免频繁动态生成字体。 第五是减少布局组件使用比如 Layout Group频繁变化时重建开销大能用锚点固定就不用自动布局。7. 如果让你搭一个简易 UI 框架你会怎么设计核心思路是模块化解耦、统一管理、数据与表现分离。 首先要有一个 UIManager 单例负责界面的加载、打开、关闭、销毁以及层级管理 —— 比如普通界面、弹窗、顶层提示分不同层级保证弹窗永远在普通界面上方。弹窗一般用栈结构管理打开弹窗压栈、关闭弹栈自动回到上一个界面。 然后做一个界面基类封装通用生命周期方法比如初始化、显示、隐藏、销毁所有界面继承这个基类统一开发规范。 最后模块间用事件系统通信界面不直接引用业务逻辑业务逻辑派发事件界面订阅后自行刷新降低耦合。四、性能优化专项1. 减少 GC 的优化手段有哪些我常用的有这几个 第一字符串频繁拼接不用直接 改用 StringBuilder。因为 string 不可变每次拼接都会生成新字符串产生大量 GC。 第二优先使用泛型集合避免装箱拆箱集合对象尽量复用在循环外声明用完 Clear不要每次循环都 new 新集合。 第三协程里的等待对象比如 WaitForSeconds提前缓存复用不要每次 yield return 都 new 一个。 第四减少匿名函数和闭包的使用它们会产生额外的类分配增加 GC。 第五高频创建销毁的对象用对象池复用比如特效、列表项避免反复实例化产生 GC。2. 内存优化你会从哪些方面入手主要分资源侧和代码侧两部分。 资源侧一是图片优化压缩图片格式移动端用 ETC2、ASTC降低图片分辨率清理冗余无用图片二是模型减面去掉不必要的面数优化骨骼数量三是音频压缩降低码率长音频用流式加载。 代码侧一是及时卸载不用的资源场景切换时清理未使用资源二是避免内存泄漏比如静态变量不要持有场景对象引用事件注册后一定要注销不用的协程要停止否则对象无法销毁三是集合、对象尽量复用不要频繁 new。3. 渲染优化有哪些常用手段核心目标是降低 Draw Call 和减少 Overdraw。 降 Draw Call 方面静态物体开启静态批处理动态物体利用动态批处理UI 打图集合并 Draw Call相同材质的物体尽量共用材质利用 GPU Instancing。 减 Overdraw 方面减少透明图层叠加尤其是 UI避免多层半透明叠加关闭不必要的透明通道远处物体使用 LOD降低模型精度。 另外还可以用遮挡剔除看不见的物体不参与渲染降低渲染压力。4. 加载速度优化怎么做首先是异步加载所有资源加载、场景切换都用异步接口配合协程做加载界面避免阻塞主线程造成假死。 然后是分步加载进入场景时先加载关键资源让玩家先进入玩法后续再分步加载次要资源不要一次性加载全部内容。 还有预加载比如进入战斗前提前加载怪物、特效资源避免战斗过程中加载造成卡顿。 另外资源本身也要优化压缩资源体积、减少加载耗时合理打 AB 包按模块分包只加载当前需要的包。5. 游戏出现卡顿你的排查思路是什么我一般先定位瓶颈再针对性优化主要用 Unity Profiler 工具。 第一步用 Profiler 看 CPU 和 GPU 的耗时占比确定是 CPU 瓶颈还是 GPU 瓶颈。 如果是 CPU 瓶颈先看是不是 GC alloc 过高、GC 频繁触发是的话就做 GC 优化再看哪个函数、哪个模块耗时最高比如是物理计算过重还是逻辑写法有问题然后针对性优化算法、减少计算量。 如果是 GPU 瓶颈用 Frame Debugger 看是 Draw Call 数量太多还是 Overdraw 过高或是像素填充压力大再对应做合批、减少透明层、降低画质。 另外还要排查是不是主线程加载资源造成阻塞是的话就改成异步加载、预加载。五、热更新与资源管理1. HybridCLR 热更新的原理是什么怎么理解依赖注入HybridCLR 是原生 C# 热更新方案原理是给 Unity 的 Mono 运行时补充了元数据处理能力让运行时可以动态加载、解析热更 DLL 程序集直接执行热更代码不需要 Lua 这类中间语言。 它的优势是支持全原生 C# 语法性能接近原生代码不用学习新语言调试也方便和普通 Unity 开发体验基本一致。 关于依赖注入因为热更 DLL 是运行时加载的主项目的 AOT 代码不能直接引用热更里的类否则编译报错。所以一般在主项目里定义接口或抽象基类热更代码里实现这个接口主项目通过反射或工厂模式根据名称创建热更类实例通过接口调用方法。这样就实现了 AOT 代码调用热更代码也就是常说的依赖注入。2. AssetBundle 是什么使用的时候要注意什么AssetBundle 简称 AB 包是 Unity 的资源打包格式可以把模型、贴图、音频、代码等资源打成独立的包运行时动态加载。主要用来做资源热更新、分包加载减小安装包体积。 使用时要注意几点第一是依赖关系一个 AB 包可能依赖其他包加载时要先加载依赖包否则资源会丢失第二是不要重复加载同一个 AB 包会造成内存浪费第三是注意卸载时机资源不用了要及时卸载 AB 包释放内存但不能在资源还在使用时卸载会出现资源丢失。3. YooAsset 资源框架解决了什么问题YooAsset 是基于 AssetBundle 封装的资源管理框架。原生 AB 包使用有很多痛点比如依赖管理繁琐、版本更新逻辑难写、增量更新难实现、编辑器下调试不方便。 YooAsset 把这些底层逻辑都封装好了提供统一的资源加载接口编辑器模拟模式和真机 AB 包模式调用方式一致还自带版本管理、增量热更新、资源加密等功能不用从零手写资源管理系统大幅简化了开发。4. 完整的热更新业务流程是怎样的大概分为这几步游戏启动时客户端向服务器请求最新版本号和版本清单文件。对比本地版本与服务器版本版本一致就直接进游戏不一致就计算差异生成下载列表。下载增量热更资源包边下载边校验文件完整性避免下载损坏。下载完成后新资源覆盖本地更新本地版本号。加载热更代码与资源初始化完成后进入游戏。 一般都是做增量更新只下载变化的文件节省玩家流量。六、数据结构与算法1. 说一下几种基础排序算法的原理和时间复杂度冒泡排序相邻元素两两比较较大的向后交换每轮把最大值沉到末尾。时间复杂度 O (n²)稳定排序。选择排序每轮找到最小元素放到已排序序列的末尾。时间复杂度 O (n²)不稳定。插入排序把未排序元素插入到已排序序列的合适位置类似整理扑克牌。时间复杂度 O (n²)稳定排序数据量小或接近有序时效率很高。快速排序分治思想选一个基准值把数组分成小于基准和大于基准两部分递归排序两边。平均时间复杂度 O (nlogn)不稳定。归并排序分治思想把数组拆成两半分别排序再合并两个有序数组。时间复杂度 O (nlogn)稳定排序。 平时业务开发一般直接用 List 自带的 Sort它底层是混合排序数据量小用插入量大用快排效率很高。2. 常用数据结构的适用场景数组 / List顺序存储随机访问快中间增删慢。适合长度固定、或主要在尾部增删的场景比如配置表、列表数据。链表链式存储头尾增删快随机访问慢。适合频繁头尾操作的场景比如任务队列。Dictionary键值对存储基于哈希表查找 O (1)。适合需要快速查找、键值映射的场景比如配置查询、对象池管理。栈先进后出适合弹窗管理、撤销操作这类后进先出的场景。队列先进先出适合消息队列、任务排队的场景。3. C# 里字符串处理要注意什么最核心的一点是 string 具有不可变性每次修改、拼接字符串都会生成新的字符串对象旧的就变成垃圾产生 GC。 所以如果是频繁的字符串拼接比如动态生成文本、拼接剧情一定要用 StringBuilder它是可变字符串不会频繁生成新对象能大幅减少 GC。 另外尽量避免频繁的字符串拆分、截取操作必要时用其他方式优化。七、计算机网络基础1. TCP 和 UDP 的区别分别适合什么游戏场景核心区别在于 TCP 是面向连接、可靠传输UDP 是无连接、不可靠传输。 TCP 建立连接需要三次握手传输时有确认、重传、拥塞控制机制保证数据完整、按顺序到达但传输速度慢、开销大。适合对数据准确性要求高的场景比如登录、充值、背包数据、技能释放指令不能丢也不能错。 UDP 不需要建立连接发出去就不再管不保证数据到达、也不保证顺序但速度快、开销小、实时性高。适合对实时性要求高、允许少量丢包的场景比如对战游戏的位置同步、语音视频、帧同步。2. HTTP 和 WebSocket 有什么区别HTTP 是短连接采用请求 - 响应模式只能客户端主动发请求服务端被动响应服务端无法主动向客户端推送数据。每次请求都要建立连接结束后断开。适合请求静态资源、非实时数据接口。 WebSocket 是长连接客户端和服务端握手建立连接后双方都可以随时发送数据双向通信服务端可以主动推送数据。适合实时性要求高的场景比如实时对战、聊天、游戏状态同步。3. 说一下 OSI 七层模型和 TCP/IP 四层模型OSI 七层模型从下到上依次是物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。 TCP/IP 四层模型对 OSI 做了合并从下到上是网络接口层、网际层、传输层、应用层。 常用协议中IP 协议在网际层TCP、UDP 在传输层HTTP、WebSocket、FTP 这些在应用层。八、项目高频追问标准回答1. 你项目里做了哪些优化2. 项目里遇到过什么难点你是怎么解决的3. 你在项目中主要负责哪些工作4. 你怎么用 AI 工具辅助开发会不会直接照搬代码注意以上仅我自己总结的一些常问知识点如有错误欢迎在评论区指出噢✨小何同学路漫漫其修远兮吾将上下而求索✨