秋招笔记——汇总

重新整理了一下秋招的笔记。

题库总览

  • C++ / STL / 语言:455 题
  • 图形学 / 渲染 / OpenGL:52 题
  • UE:42 题
  • Unity:43 题
  • C# / .NET / Mono:40 题
  • 系统 / 网络 / OS:46 题
  • 算法 / 寻路 / 数据结构:38 题
  • 场景题 / 智力题:17 题

C++ / STL / 语言

为什么需要内存对齐 答:CPU硬件读取内存时,是按固定字节块(2/4/8字节)读取,不对齐会触发两次内存读取再拼接数据,性能下降;同时ABI二进制接口规范强制要求对齐,部分硬件架构直接拒绝访问未对齐内存直接崩溃。编译器会在结构体成员之间、末尾填充padding字节补齐对齐数,牺牲少量内存换取访问速度与硬件兼容性。

指针和引用的区别 答:①指针是独立变量,存内存地址,占用存储空间;引用是变量别名,底层编译器大多也实现为指针,但语法层面没有独立内存。②指针可以置空nullptr,可以重新赋值指向别的对象;引用定义时必须绑定实体,绑定后无法改绑其他对象。③指针支持多级指针int**,引用没有多级引用。④取地址&p得到指针变量本身地址;&ref拿到的是原变量的地址。

传递函数参数时传指针和传引用有何区别 答:两者都可以修改外部实参,都避免拷贝对象。指针参数可以传入空指针,函数内部必须做判空校验;引用参数语法上保证一定绑定有效对象,不需要判空。调用时指针需要传&变量,引用直接传变量;函数内部指针要->访问,引用直接.访问。优先用引用,允许空的场景才用指针。

变量的声明和定义的区别 答:声明告诉编译器这个名字、类型存在,不分配内存,可以多次声明;定义是真正创建实体,分配内存/生成变量,变量只能有一次定义。extern int a;是声明;int a = 10;既是声明也是定义。头文件放声明,源文件放定义,防止多重定义编译报错。

什么是顶层const和底层const 答:顶层const:修饰变量本身 不可修改。int* const p,p指针变量本身不能改,但是可以修改*p指向的值。底层const:修饰指针指向的数据 不可修改。const int* p,p指针本身可以改指向,不能修改*p的值。const int *const p同时具备顶层+底层const。

数组名和指针的区别 答:数组名绝大多数表达式场景会隐式退化为首元素指针,但二者本质不同。sizeof(数组名)得到整个数组总字节;sizeof(指针)永远是指针本身大小(64位8字节)。数组是一块连续内存实体;指针只是存储地址的变量。数组不能被赋值;指针可以随意修改指向。数组作为函数形参时会退化成指针,丢失长度信息。

final和override关键字的作用是什么 答:override:编译期校验,标记这个函数是重写父类虚函数,如果父类不存在同名虚函数、签名不匹配直接编译报错,防止手写错函数签名导致没有重写成功。final:修饰虚函数,禁止子类继续重写该虚函数;修饰类,禁止其他类继承这个类。

拷贝初始化和直接初始化有何区别 答:直接初始化A a(10); A a{10};直接调用对应构造函数,直接在a对象内存上构造。拷贝初始化A a = 10; A a = b;,优先尝试调用拷贝构造/移动构造,会先生成临时对象,再把临时对象拷贝/移动给a。explicit修饰的构造函数禁止拷贝初始化,只能直接初始化。

explicit关键字的作用 答:修饰单参数构造函数,禁止编译器做隐式类型转换 。没有explicit,A a = 5;会隐式把5转为A对象;加explicit之后这个写法编译报错,必须显式写A a(5)。避免无意的隐式转换带来诡异bug。

如何理解转换构造函数 答:单参数(或者除第一个参数其余都有默认值)的构造函数,就是转换构造函数,允许编译器把参数类型隐式转换成类对象。不加explicit就开启隐式转换,加explicit关闭隐式转换。

如何理解命名遮蔽 答:内层作用域定义和外层同名标识符,内层名字会把外层同名名字屏蔽,内层看不到外层变量,不是销毁外层变量,只是看不见 。比如函数内部定义局部变量和全局变量同名,函数内优先使用局部。要访问被遮蔽的全局,使用::全局变量。工程尽量不要写同名遮蔽,极易踩调试坑。

什么是移动语义(区域传送是翻译错误) 答:C++11引入,解决大对象拷贝开销大的问题。拷贝会复制一份完整资源;移动语义转移资源所有权 ,把临时对象的堆内存、容器数据直接接管,不拷贝数据,只修改内部指针。依靠右值引用实现。std::move()把左值强制转成右值,触发移动构造/移动赋值。返回vector大临时对象、STL插入临时对象大量使用移动,减少内存分配释放。

如何理解未定义行为UB 答:C++标准没有规定该场景下程序行为,编译器可以做任意处理:正常运行、崩溃、内存篡改、随机输出,不同编译器、不同优化级别结果不一样。常见UB:数组越界、空指针解引用、野指针、重复delete、访问已经销毁对象。UB不会必现崩溃,复现极难,写代码必须规避。

cin/cout和scanf/printf的联系和区别是什么 答:scanf/printf是C语言格式化IO,底层系统调用,速度快,类型不安全,传错参数直接UB。cin/cout是C++流式IO,类型安全,运算符重载易于扩展自定义类型;默认和C stdio同步,速度慢。关闭同步ios::sync_with_stdio(false);cin.tie(nullptr);可以大幅提升cin/cout性能。

如何理解对象切片 答:用派生类对象直接赋值给基类对象,派生类独有的成员全部被切掉,只拷贝基类部分。发生在值拷贝;用基类指针、引用指向派生类就不会切片,实现多态。

迭代器如何删除元素 答:erase删除元素之后,当前迭代器失效 ,返回下一个有效迭代器。正确写法it = vec.erase(it);;不要erase之后继续使用旧it。不同容器迭代器失效规则不同:vector删除之后后面全部迭代器失效;list只被删元素迭代器失效。

仿函数和匿名函数lambda的联系和区别 答:仿函数:重载operator()的结构体/类,可以保存成员状态,不能捕获外部局部变量。lambda是编译器自动生成匿名仿函数的语法糖,可以捕获外部变量(值捕获、引用捕获),就地写逻辑,不用单独定义类。lambda底层本质就是匿名仿函数。

如何理解动态库和静态库 答:静态库:编译链接阶段把库代码完整拷贝进可执行文件;程序体积变大,不需要依赖外部文件,更新库必须重新编译程序。动态库(so/dll):链接只记录符号,运行时才加载库文件;多个程序共享一份库,体积小,支持热更新;发布程序必须附带对应动态库文件。

为什么要内存对齐,哪些东西需要内存对齐 答:见第1题。结构体、类、栈变量、new出来堆上对象都遵循对齐规则。

auto和decltype的区别 答:auto:根据初始化表达式推导变量类型,会剥离顶层const、引用 ,必须有初始化值,用来定义变量。auto a = b;。 decltype:推导表达式本身的类型,保留const、引用属性,不需要初始化 。特殊坑:decltype((x))双层括号,永远推导成引用类型。两者全部编译期计算,无运行开销。

什么是虚表vtable 答:含有虚函数的类,编译器生成一张虚函数表,存储虚函数地址;每个对象头部有虚表指针vptr指向虚表。运行时通过vptr查虚表地址,实现运行时多态。虚表存只读数据段,不属于对象本身,所有同类对象共享同一张虚表。

优先队列priority_queue的底层实现原理 答:是容器适配器,不是真正容器;默认底层容器vector,默认大根堆。堆是完全二叉树,数组存储。push:元素放到vector尾部,向上调整堆;pop:取出堆顶,把末尾元素放到头部,向下调整堆。堆顶永远优先级最高。

如何理解模板的特化 答:模板通用版本处理绝大多数类型;特化给特定类型单独写一套实现。分为全特化(全部模板参数指定)、偏特化(只限定部分模板参数)。常用于类型分发、特定类型性能优化。

如何理解mutable和const 答:const成员函数语法上保证不修改对象逻辑状态;mutable修饰成员变量,即使在const成员函数内部也可以修改该成员。适用场景:缓存、统计计数、日志标记,不改变对象对外表现的逻辑状态。

为什么需要变换Transform 答:物体原始顶点是模型局部坐标,不知道放在世界哪里。通过矩阵变换完成坐标转换:模型变换(局部→世界)、视图变换(世界→相机)、投影变换(相机→裁剪空间)。没有变换所有物体只能待在原点。全部GPU着色器矩阵乘法运算。

为什么在变换的过程中要引入齐次坐标系 答:普通3×3矩阵只能旋转缩放,做不了平移(平移是加法,矩阵乘法只能做线性变换)。齐次坐标增加w分量,变成四维向量,4×4矩阵可以把旋转、缩放、平移全部统一成矩阵乘法。空间点w=1;方向向量w=0,向量不受平移影响。

为什么顶点装配图元一般选择三角形 答:三角形永远是平面,不会出现凹、非平面;任何复杂网格都可以拆成三角形;光栅化硬件专门针对三角形做硬件加速;三角形三个点一定共面,判断点是否在图元内逻辑简单。四边形可能四点不共面,硬件不原生支持。

如何判断一个点是否在三角形内部 答:常用两种:①重心坐标法;②三边叉积法,判断点在三角形三条边的同一侧(全部内侧)。注意浮点数精度误差,不能直接等于0,要给epsilon微小阈值。

跳过(原题号缺失29)

如何理解着色频率 答:着色频率决定光照计算在哪个阶段执行。①顶点着色:每个顶点算一次光照,片元插值颜色,性能快,模型顶点少会有锯齿;②片元着色(像素)每个像素计算光照,效果最好,开销大;③面片着色,整个三角形算一次光照,效果最差。Phong是片元光照,Gouraud是顶点光照。

如何理解网格,网格有哪些相关的操作 答:网格Mesh由顶点、索引、法线、uv等属性组成,描述3D模型几何。操作:网格简化、三角化、法线计算、UV展开、体素化、轮廓提取、网格合并、碰撞网格生成。

如何理解阴影映射,有哪些常见的阴影映射方法 答:阴影映射核心:光源视角渲染一遍,把深度存成深度贴图;正常渲染时,片元对比光源深度贴图,判断是否被遮挡。变种:PCF(软阴影模糊)、VSM方差阴影贴图、ESM指数阴影贴图、PCSS实现柔和接触阴影。缺点:阴影锯齿、分辨率不足、深度冲突。

为什么需要光线追踪 答:传统光栅化只做直接光照,间接光照(反射、折射、全局漫反射)很难模拟。光线追踪从摄像机发射光线,光线和物体求交,递归发射反射折射光线,可以天然实现真实反射、折射、全局光照;缺点计算量大,硬件RT加速单元才可以实时跑。

如何实现光线与球体/三角形求交 答:球体求交:射线参数方程联立球体方程,解一元二次方程,判别式判断有没有交点,取最近有效t。三角形求交:Möller‑Trumbore算法,求解射线与三角形平面交点,再判断交点是否在三角形内部。

AABB盒是什么,有什么用 答:轴对齐包围盒,六个面平行坐标轴,用min、max两个点描述包围物体。构造简单,求交计算极快。用于空间加速结构,做粗筛:AABB不相交,物体一定不相交;AABB相交,再做精细求交,减少大量复杂物体求交计算。

Uniform Spatial Partitions (Grids) 均匀网格划分是什么 答:把3D空间切大小相同方块网格,每个网格存储落在里面物体。查询光线,只遍历光线穿过的网格,跳过大量空网格。适合物体分布均匀场景;物体大小差距巨大时效率很差。

BVH是什么 答:包围体层次树。二叉树,每个节点存储AABB包围盒;叶子节点存物体,父节点包围盒包含所有子节点包围盒。构建递归把物体分组。光线遍历:节点包围盒不相交直接跳过整棵子树;相交递归访问子节点。优点:物体大小差异大场景性能优于均匀网格。

KD树又是什么 答:空间二分树,每次选一个坐标轴,用平面把空间切两半,递归划分空间,划分平面不依附物体包围盒。对光线追踪查询很快;动态物体场景更新代价高,适合静态场景。

什么是辐射度量学在GAMES101中具体起到什么作用 答:辐射度量学定义一套物理光照单位:辐射通量、辐照度、辐射出射度、辐射亮度。把光照从简单颜色运算变成物理单位计算,PBR物理渲染全部基于辐射亮度,保证光照计算符合物理真实。

什么是uniform 答:着色器变量类型,CPU传给GPU,一次drawcall整个批次所有顶点、片元共用同一个值。CPU端设置,着色器内部只读,不能修改。比如光照颜色、矩阵。区别attribute是每个顶点不同。

如何理解混合Blending,具体如何实现 答:实现半透明物体。把当前片元输出颜色(源)和帧缓冲已经存在的颜色(目标),按照混合公式做颜色加权。SrcBlend DstBlend控制权重。半透明物体必须从远到近排序渲染;混合只作用片元输出,不能写深度测试。

面剔除是什么,有何作用 答:三角形有正反面,通过顶点缠绕顺序区分。面剔除直接丢弃背面或者正面图元,不进入光栅化。比如模型背面看不到,剔除背面,减少GPU绘制量,提升性能。

如何理解点阴影 答:点光源是全向光源,阴影贴图需要立方体贴图,光源向六个方向渲染六张深度贴图。开销比方向光阴影大;常用于灯泡类点光源。

Surface Shader和Shader Graph分别是什么 答:Surface Shader是Unity封装的高级着色器,自动处理光照、阴影,只需要写表面属性,底层自动生成顶点片元代码。Shader Graph可视化节点编辑器,不用手写hlsl,拖拽节点拼接shader,输出可以转成Surface或者传统shader。Surface适合手写;Shader Graph适合美术,快速迭代效果。

什么是计算着色器Compute Shader,具体如何使用 答:不绑定图形管线,GPU通用并行计算单元。不处理顶点片元,直接大量线程并行执行代码。用途:后处理、物理模拟、粒子、导航网格预处理、AABB剔除。CPU调度Dispatch,指定线程组数量,读写RWTexture/RWBuffer。

欧几里得距离和曼哈顿距离的区别是什么,在什么情况下选择哪种 答:欧几里得:两点直线距离\\sqrt{(x1-x2)\^2+(y1-y2)\^2},真实物理距离,A*启发式,适合可以斜向移动。曼哈顿:\|x1-x2\|+\|y1-y2\|,只能沿坐标轴移动,网格四向移动;计算没有开根号,速度更快。

如何根据距离动态选择不同的LOD变体 答:LOD根据物体到相机距离切换模型精度。距离远,用面数少低精度模型;距离近用高精度。设置多个LOD层级,每个层级设置距离阈值;引擎每一帧计算物体包围盒到相机距离,匹配对应LOD;同时做LOD淡入淡出,避免突变闪烁。

体素化过程中如何处理不同高度的地形 答:把地形按照高度场采样,每个体素记录高度信息;区分实体体素和空气体素。地形起伏大,不能固定单层体素;采用高度场体素或者多层体素,记录每个XY位置多个Z层占用情况。

洪水算法(Flood Fill)在区域划分中的作用是什么 答:洪水填充从种子点向外扩散,访问所有连通区域。导航网格生成,划分连通可行走区域;封闭区域填充;连通域分割;判断区域是否连通。注意处理障碍物阻断扩散。

Marching Squares算法如何提取轮廓 答:2D轮廓提取算法,输入网格每个格子四角采样值;每个格子根据四个角0/1状态查表生成线段,拼接出等值线轮廓。导航网格、距离场提取轮廓大量使用。3D版本是Marching Cubes提取三角网格。

为什么需要将凹多边形转换为凸多边形 答:光栅化、碰撞检测、SAT分离轴定理只支持凸多边形。凹多边形直接求交逻辑复杂;把凹多边形拆成多个凸多边形,后续计算简单稳定。

距离场(Distance Field)在动态更新中的作用是什么 答:距离场每个像素记录到最近障碍物的距离。用于字体SDF渲染、寻路、避障;动态障碍物移动时,局部更新距离场,快速获取附近障碍物距离。

VO(Velocity Obstacle)速度障碍物算法的核心思想是什么 答:速度空间算法。给定两个圆形Agent,把所有会发生碰撞的相对速度,在速度空间划出一块禁区VO。Agent不能选择禁区内速度,从而避免碰撞。VO只保证不碰撞,不保证运动平滑。

如何计算两个移动圆形的碰撞时间 答:两个圆,半径r1 r2,位置p1 p2,速度v1 v2。相对位置\\Delta p = p2-p1,相对速度\\Delta v = v2-v1。联立方程求解t,判断t>0的最小正解,就是未来碰撞时间;无解代表永远不会碰撞。

空间容差(Spatial Tolerance)的作用是什么 答:几何计算浮点数存在误差,不能直接判断==等于。空间容差就是epsilon极小阈值,两个点距离小于epsilon就认为是同一个点。导航网格生成、几何求交、轮廓提取都要设置容差,避免浮点误差产生碎面片。

性能测试是如何进行的 答:控制变量,固定硬件、固定分辨率画质;多次运行取平均值,避免单次抖动。统计指标:CPU耗时、GPU耗时、帧时间、GC、DrawCall。排除后台负载干扰;做对照组,修改单一变量对比前后数据。

如何与游戏引擎集成 答:C++写底层逻辑,引擎侧封装模块/插件;暴露接口给蓝图;数据资产导入引擎资源管线;Update/Tick驱动逻辑;资源加载走引擎异步加载;注意多线程,计算逻辑放Job,不能直接操作引擎对象。

如何判断点是否在多边形内 答:射线法:从点向右发射一条水平射线,统计和多边形边相交次数;奇数在多边形内部,偶数在外部。注意处理点刚好落在边上的浮点容差。

如何判断两条线段是否相交 答:使用叉积判断线段端点互相在对方线段两侧;快速AABB粗筛,AABB不相交直接返回false,减少计算。

如何计算多边形的面积 答:鞋带公式,遍历多边形顶点,累加叉积,取绝对值除以2。只适用于简单多边形。

增强输入Enhanced Input是什么,有哪些优势或者说优点 答:UE增强输入系统。把输入动作和硬件按键解耦;支持按键重映射、组合键、输入修饰;支持不同设备(手柄键盘鼠标);蓝图C++通用;支持输入优先级阻断。

如何理解行为树 答:AI行为编排工具,树形节点。节点分为:组合节点(序列、选择器、并行)、装饰器、任务叶子节点。每帧从根节点向下执行,返回成功/失败/运行。优点逻辑可视化,易于编辑AI逻辑;缺点复杂逻辑嵌套深性能开销高于状态机。

什么是EQS环境查询系统 答:UE环境查询系统。AI动态查询世界空间,生成采样点,按照条件打分筛选最优位置。比如找掩体、找攻击位置、找落脚点。

每个actor都用一个文件,大规模场景不会文件数量过大吗 答:是的,大量Actor会产生海量uasset文件。解决方案:Level Instance关卡实例、DataLayer数据层;大量重复实体不用Actor,改用MassEntity;静态Actor烘焙进关卡,减少独立文件。

可以介绍一下大世界分区的概念吗,还有底层的基本原理如何使用呢 答:大世界World Partition世界分区。把巨大地图切成很多瓦片,只加载摄像机附近瓦片,远处卸载。每个瓦片保存自己的Actor;根据距离流送关卡瓦片;支持HLOD远处合并静态模型。解决超大地图内存爆炸。

什么是关卡流送Level Streaming底层原理是什么有何意义 答:动态加载/卸载关卡。不用一次性把全部地图加载内存。根据距离、触发条件,异步加载关卡资源,不需要的时候卸载。大世界、开放世界必备。World Partition是自动版本的关卡流送。

什么是属性说明符UPROPERTY说明符 答:UE UPROPERTY宏后面的标记,控制属性行为。EditAnywhere/VisibleAnywhere控制编辑器是否可见;Replicated标记网络复制;Category归类;BlueprintReadWrite蓝图访问。UHT解析元数据实现编辑器、蓝图、网络复制。

虚幻引擎有自己的智能指针吗 答:有。TSharedPtr(共享计数)、TWeakPtr(弱指针)、TUniquePtr独占指针。区别于UE对象系统UObject;UObject对象用垃圾回收,不要用TSharedPtr管理UObject。

黑板Blackboard以怎样的方式存储数据,如何与行为树同步 答:黑板是键值对容器,存储AI变量(向量、布尔、对象)。行为树节点读写黑板键;黑板实例每个AI一份,行为树多个Agent共享同一份黑板模板。行为树运行时直接读写黑板内存。

什么是智能对象Smart Object 答:UE AI系统。标记场景中可以交互的点位,比如椅子坐下、掩体。AI查询智能对象,执行对应交互动作。

什么是数据注册表Data Registry,有何作用怎么使用 答:数据注册表集中管理数据资产,运行时快速查找,不需要遍历资源。可以根据ID查找数据,支持蓝图C++访问,适合大量配置表。

什么是断言Assert,虚幻引擎中提供了哪些断言类型 答:断言校验代码运行时条件,条件不满足直接崩溃打印日志,只在开发版本生效,打包发布版本默认关闭。UE:check、checkSlow、ensure。check失败直接崩溃;ensure只会打印日志继续运行。

GamePlay定位系统是什么,具体来说有何作用底层原理 答:UE Gameplay Location系统。把世界空间位置抽象成定位句柄,支持位置锚点;物体移动自动更新定位;用于AI查询、保存点位。

如何实现在main函数之前和之后执行,底层原理 答:main之前:全局对象构造函数;gcc __attribute__((constructor));MSVC #pragma init_seg。main之后:全局对象析构函数;atexit()注册回调。全局对象构造顺序跨翻译单元不确定,坑很多。

new的底层原理,有几种new 答:①普通new:调用operator new申请堆内存,然后调用构造函数。②operator new:只分配原始内存,不构造。③placement new:在已经分配好的内存上执行构造函数,不分配内存。new\[\]分配数组,会多存对象数量,delete\[\]匹配释放。

sizeof和strlen的区别 答:sizeof编译期计算,得到类型/变量占用字节大小。strlen库函数运行时遍历C字符串,统计\0之前字符数量,不包含结束符。char buf[20]="abc"; sizeof(buf)=20,strlen(buf)=3

C++中结构体和类的区别 答:唯一区别默认访问权限。struct默认public;class默认private。struct也可以继承、虚函数,和class语法能力完全一致。

const和constexpr的区别 答:const表示只读,运行时也可以初始化;constexpr强制编译期常量,编译阶段算出结果,运行时不计算。const int a = getNum();合法;constexpr int a = getNum();要求getNum是constexpr函数。

什么是野指针和悬空指针 答:野指针:指针没有初始化,指向随机垃圾地址。悬空指针:指针曾经指向有效对象,对象已经被delete销毁,指针没有置空。两者解引用都会UB崩溃。

如何理解C++中的重载、重写和隐藏 答:重载:同一作用域,函数名相同,参数列表不同。重写override:子类重写父类虚函数,签名完全一致。隐藏:子类定义同名函数,不管参数,直接屏蔽父类所有同名函数,父类函数不会参与子类调用。

浅拷贝和深拷贝的区别 答:浅拷贝直接复制成员值,如果成员是指针,两个对象指针指向同一块堆内存;析构会重复释放。深拷贝重新分配堆内存,复制一份数据,两个对象完全独立。类管理堆内存必须实现深拷贝,或者移动语义。

delete释放内存的时候怎么知道具体的内存大小 答:malloc/new分配内存时,会在返回地址前面一小块内存记录分配的块大小。delete读取这块元数据,知道释放多大内存。不要free/new返回的指针,不要delete malloc返回指针,元数据格式不一样

分配器Allocator是什么,有什么用 答:STL分配器,把内存分配对象构造拆开。分配器只申请原始裸内存;容器在内存上placement new构造对象,destroy销毁对象。可以自定义分配器实现内存池、共享内存分配。

成员列表初始化和类中初始化的区别是什么 答:类内初始化是给成员默认初始值;成员初始化列表在构造函数体执行之前完成成员初始化。初始化列表优先级高于类内默认值。

哪些情况一定要使用成员初始化列表,为什么 答:必须初始化列表场景:①引用成员;②const成员;③没有默认构造函数的父类;④没有默认构造函数的成员变量。这些类型不能在构造函数体内赋值,只能初始化。

什么是零拷贝 答:减少CPU内存拷贝。数据在内核缓冲区、用户缓冲区之间避免复制。比如mmap映射文件,直接映射内核内存到用户空间,省去拷贝。游戏网络IO、文件IO经常追求零拷贝。

如何判断两个浮点数是否相等 答:浮点数有精度误差,不能直接==。取两者差值绝对值,小于一个极小epsilon阈值,就认为相等。注意不能直接用固定epsilon应对数值差距巨大场景。

类如何实现只能静态分配和动态分配 答:只能堆分配:把析构函数私有,提供Destroy成员函数,外部只能new,不能栈上对象。只能栈分配:把operator new私有,禁止new堆分配。

什么是工厂模式什么是抽象工厂 答:简单工厂:一个函数根据参数创建不同产品。工厂方法:每个产品对应一个工厂子类。抽象工厂:一套接口,创建一整套相关系列产品。解耦对象创建逻辑,不需要知道具体子类。

scanf/printf和cin/cout的区别是什么 答:见14题。

为什么模版类放在.h文件中 答:模板编译期实例化;编译器需要看到模板完整源码,才能根据不同类型生成对应代码。如果模板实现放cpp,其他源文件include头文件看不到实现,链接报错。特例:显式实例化可以把实现放cpp。

什么是条件编译 答:预处理器指令#ifdef #ifndef #endif,根据宏是否定义,选择性编译一部分代码。用于多平台代码、调试代码,发布版本剔除调试逻辑。

Debug和Release的区别 答:Debug开启调试符号,关闭编译器优化,断言生效,方便调试;Release开启O2优化,去掉调试信息,断言失效,代码速度快。Release优化会改变内存布局,部分Debug正常的UB代码Release直接崩溃。

如何理解静态链接和动态链接 答:静态链接:把库目标文件完整合并进程序,编译链接阶段完成。动态链接:运行时加载动态库,程序只记录符号,运行时解析地址。

什么叫静态编译和动态编译 答:静态编译:链接静态库,生成可执行文件不依赖外部库。动态编译:链接动态库,运行依赖so/dll。

NULL和nullptr的区别 答:NULL是宏,等价0整数。nullptrC++11关键字,专门空指针类型。函数重载时,传NULL会匹配int版本;nullptr匹配指针重载,避免歧义。

lambda表达式的底层原理 答:编译器自动生成匿名仿函数类;捕获的变量变成匿名类成员;值捕获拷贝成员;引用捕获存引用。lambda可以转函数指针,前提是没有捕获任何变量

什么是RAII 答:资源获取即初始化。把资源获取写在构造函数,资源释放写在析构函数。对象出作用域自动调用析构,自动释放资源。锁、文件句柄、内存、socket全部适用,异常场景也能释放资源。

STL中的hashtable是如何实现的 答:unordered_map底层hashtable,拉链法。数组每个槽位存链表;哈希函数把key映射下标;哈希冲突同一个槽链表挂多个元素。C++11之后部分实现链表替换成桶链表。

什么是特性萃取traits 答:type_traits,编译期获取类型属性。判断是否是指针、是否是const;根据类型选择不同分支实现,编译期类型分发。

如何释放vector的空间 答:vec.clear()清空元素,不释放capacity内存 。释放内存:vector<T>().swap(vec);临时空vector交换;C++11 vec.shrink_to_fit()建议释放多余内存,不保证一定释放。

如何在共享内存上使用STL的容器 答:STL默认分配器new堆内存;需要自定义分配器,从共享内存申请内存。容器对象本身和全部数据都要放在共享内存,不能用普通堆。注意共享内存没有析构自动调用,手动处理对象生命周期。

构造函数和析构函数可以抛出异常吗 答:语法允许。构造抛异常:对象没有完整构造,析构不会执行。析构函数不建议抛异常;std库要求析构默认noexcept,析构抛异常程序直接终止。

拷贝构造函数和赋值运算符重载的区别 答:拷贝构造:新对象初始化 ,对象还不存在。A b = a; A(a);。赋值运算符operator=:对象已经存在,把别的对象赋值给自己。b = a;

模板类和模板函数的区别 答:模板函数:函数模板,编译器根据实参自动推导模板参数。模板类:类模板,实例化必须显式指定模板参数vector<int>

静态函数可以定义为虚函数吗 答:不可以。static静态成员函数没有this指针;虚函数依赖this拿到虚表指针,语法直接编译报错。

什么是移动构造函数,为什么需要移动构造函数 答:移动构造接收右值引用参数,接管源对象内部堆资源,不拷贝数据。返回大容器临时对象时,避免昂贵拷贝,大幅提升性能。

extern关键字的作用 答:extern声明外部变量/函数,告诉编译器实体定义在别的源文件。extern "C"关闭C++名字修饰,兼容C语言符号。

atomic的底层原理 答:原子变量,CPU硬件原子指令,保证读改写不可分割。不需要锁实现线程安全。底层依赖CPU指令:x86 lock指令。注意atomic只保证变量原子,不解决逻辑业务线程安全。

如何在STL的map和unordered_map的键中使用自定义类型 答:map有序红黑树,自定义key需要重载operator<。unordered_map哈希表,需要提供哈希函数,以及==相等判断。

STL中的优先队列实现原理 答:参考22题。

快表TLB是什么,有何作用 答:翻译后备缓冲。缓存虚拟地址→物理地址页表项。CPU访问虚拟内存,优先查TLB,命中跳过页表多级查询;TLB失效触发页表遍历,速度大幅下降。

什么是动态分区分配算法有几种 答:操作系统堆内存分配。空闲内存块管理。算法:首次适应、循环首次适应、最佳适应、最坏适应。各有碎片问题。

什么是内存地址 答:虚拟地址(用户程序看到);物理地址内存芯片真实地址。现代操作系统全部使用虚拟地址。

什么是局部性原理 答:时间局部性:刚访问过的数据,短时间还会访问。空间局部性:访问某个地址,相邻地址大概率访问。CPU缓存就是利用局部性原理。

什么是抖动现象thrashing 答:内存不足,系统频繁页面置换,大量时间花在磁盘换页,程序几乎不做有效计算,性能暴跌。

用户空间和内核空间分别是什么,是怎样的对应关系 答:每个进程虚拟地址空间分为用户空间(应用代码)、内核空间(操作系统)。用户空间不能直接访问内核;系统调用陷入内核态。内核空间所有进程映射同一份内核。

什么是域名解析DNS和区域传送分别使用什么传输层协议 答:DNS域名转IP。查询使用UDP 53;UDP包太长切换TCP。区域传送(DNS服务器之间同步域名数据)使用TCP53。

什么是粘包/拆包如何解决 答:TCP是字节流,没有数据包边界。应用层多条数据合并收到就是粘包;一条数据分成多次接收就是拆包。解决方案:①长度字段包头;②特殊分隔符;③固定长度包。游戏一般用包头+数据长度。

什么是SSL/TLS 答:传输层加密协议。TCP之上,对数据加密,防窃听篡改;证书验证服务器身份,HTTPS就是HTTP+TLS。

什么是Cookie和Session有何作用 答:Cookie存储在客户端浏览器,key‑value。Session存在服务器,每个用户分配sessionId;Cookie存sessionId发给客户端,实现HTTP无状态协议维持会话。

DDos攻击和SYN泛洪攻击是一回事吗 答:SYN洪水是DDOS其中一种。DDOS统称拒绝服务攻击,耗尽服务器资源。SYN洪水大量发送SYN握手包,不回复ACK,占满服务器半连接队列。

什么是Common UI 答:UE CommonUI,跨平台UI框架,专门针对手柄、主机平台做输入适配,聚焦游戏UI,聚焦导航、焦点管理。

双向A星在什么情况下会失效,你遇到过这种情况吗怎么解决的 答:起点终点之间有巨大障碍物把搜索空间割裂,双向A星两边很难相遇;启发式h很差,也会失效。解决:调整启发式,限制搜索边界,回退单向A星兜底。

项目中遇到的最大技术挑战是什么怎么解决的

面试项目题模板:讲清楚遇到什么性能/逻辑问题,分析根因,做了哪些方案选型,具体做了什么改动,拿到什么量化结果,收获什么。

为什么选择unordered_map而不是map 答:unordered_map哈希表,平均O(1)查找;map红黑树O(logn)。不需要有序、不需要范围遍历,优先unordered_map;需要有序遍历、范围查询用map。注意哈希冲突最坏退化O(n)。

如何设计一个支持O(1)插入、删除、查找的数据结构 答:哈希表;如果需要同时支持按顺序遍历,哈希表+双向链表组合(LRU缓存结构)。

如何实现复杂的技能连击系统,如何处理技能冷却和消耗的同步,如何设计可扩展的伤害计算系统 答:技能状态机管理连击状态;技能冷却用时间戳;服务端权威,客户端只做预测表现;伤害计算做成多阶段Modifier修饰器,属性叠加,支持增伤减伤,配置表驱动。

如何处理属性之间的依赖关系,如生命值百分比保持 答:属性变更回调通知;属性脏标记,变更后重新计算依赖属性;不要在set函数嵌套修改,避免递归死循环。

如何实现属性修改器的优先级系统 答:Modifier分优先级分组,顺序执行;比如先加固定值,再百分比相乘。每个Modifier标记优先级,修改时按优先级排序执行。

如何优化大量属性变更时的性能 答:脏标记批量更新,不要每改一个属性就广播;分帧处理;缓存计算结果,没有变更跳过计算;减少事件广播频率。

如何实现物品的序列化和网络同步 答:protobuf序列化;标记需要同步字段;服务端权威,定期同步变更字段;增量同步,只传变化的部分,减少带宽。

如何设计可扩展的物品效果系统 答:策略模式,每个物品效果实现接口;配置表配置效果ID;运行时创建对应效果对象;支持叠加、持续、触发类效果。

如何设计可靠的状态回滚机制 答:保存关键帧快照;记录输入;回滚到过去快照,重放输入;帧同步游戏大量使用。只保存必要数据,快照做压缩,控制内存。

在RPG游戏中如何管理大量物品和技能数据的内存 答:静态配置表数据只读,常驻内存;运行时实例只存变化部分;对象池复用销毁物品;不用的实例释放;分块加载配置。

如何处理输入映射的动态切换 答:输入动作抽象,绑定关系运行时修改;切换输入配置文件,重新绑定;输入层隔离,上层逻辑不需要关心硬件。

如何实现技能的组合键绑定 答:记录按键按下时间窗口;窗口内检测按键序列;区分长按短按;超时清空状态。

如何设计AI的技能决策系统 答:行为树/状态机;技能评估打分函数;冷却、距离、目标状态打分;选最高分技能执行;服务端校验。

Common UI是什么有何作用 答:见123。

FastArray是什么 答:UE TFastArray,优化容器,适合大量小元素,内存紧凑,减少开销,引擎内部大量用。

MotionMatching是什么 答:动作匹配。从动画库找和当前身体姿态、速度最匹配的片段,实现平滑角色运动,减少动画混合,大量用于角色移动。

微秒级的优化是否有可能受其他因素影响,如何设计实验排除 答:CPU睿频、后台进程、缓存命中率都会干扰微秒统计。实验:关闭睿频;循环多次取平均;隔离CPU;多次运行剔除异常极值;控制变量,只改动待优化代码。

如何理解将高度场压缩并按代理半径进行腐蚀处理,如何从这些区域中提取并简化轮廓线,又如何生成包含高度细节信息的详细网格并转换为Detour格式的导航数据 答:Recast导航网格生成流程:高度场体素化;腐蚀(向内收缩,给Agent半径留安全距离);提取可行走区域轮廓;轮廓简化减少顶点;生成导航多边形网格;最后输出Detour导航网格瓦片。

假如一个点恰好在瓦片与瓦片的边缘位置怎么处理 答:浮点容差epsilon;跨瓦片查询同时检查相邻瓦片;导航查询接口内部做瓦片边界膨胀,防止点落在缝隙导致寻路失败。

为什么选择ProtoBuf,这个协议的优点是什么 答:protobuf二进制序列化;体积小;编译生成代码类型安全;跨语言;向后兼容,字段增减不破坏旧版本。缺点:二进制人不可读。

如何实现的异步消息处理 答:消息队列,生产者把消息压队列;工作线程消费队列;消息结构体;线程安全队列;区分业务消息类型。

Animation Budget Allocator是什么 答:UE动画预算分配器。限制同时播放高开销动画数量;远处角色降级动画,控制CPU动画开销。

如何理解SIMD 答:单指令多数据。一条CPU指令同时操作多组数据。SSE AVX;批量向量运算,游戏物理、寻路大量使用。

什么是计算着色器有何作用 答:见45题。

如何理解PBR材质 答:基于物理渲染,遵循物理光学。分为漫反射、高光BRDF;菲涅尔、粗糙度、金属度;IBL环境光照;不同光照条件材质表现统一。

什么是延迟渲染Deferred Rendering 答:先把法线、位置、粗糙度等信息渲染到Gbuffer多帧缓冲;之后再对每个像素做光照计算。优点大量光源开销稳定;缺点不支持半透明,显存占用高。

如何理解GPU Instancing 答:把大量相同材质网格,一次性传入多组变换矩阵,一次DrawCall绘制成千上万个物体。减少DrawCall,适合草地、树木。

如何实现的雪地交互效果 答:方案1:计算着色器修改高度图,踩踏修改地形高度;方案2:顶点动画;方案3:距离场、纹理写入脚印。

如何实现的龙卷风流体系统 答:计算着色器模拟流体粒子;速度场;粒子受力旋转;粒子生命周期;GPU并行。

如何实现的可撕裂布料在风力作用下的自然飘动与撕裂形变 答:布料质点弹簧模型;每个质点受力风力;弹簧约束;超过阈值断开弹簧实现撕裂;计算着色器并行求解质点。

如何实现的分帧约束求解 答:物理约束太多,一帧算不完,把约束分散到多帧迭代求解,平滑降低单帧CPU峰值。

如何理解ECS架构,为何他支持大规模实体的模拟 答:ECS Entity‑Component‑System。实体只是ID;组件纯数据结构体;System遍历组件做逻辑。数据连续内存排布,CPU缓存友好,适合大量实体。没有虚函数,消除对象开销。

形参和实参的区别 答:形参函数定义的参数;实参调用函数传入的参数。值传递形参拷贝实参;引用传递形参绑定实参本体。

为什么模版类定义一般放在.h文件中 答:见91题。

如何原地实现交换两个数的值 答:std::swap;异或交换只适用于整数,注意两个变量是同一个变量会置零;加减法交换会溢出。工程优先用std::swap。

volatile关键字的作用,具体的应用场景 答:告诉编译器不要优化这个变量,每次读取必须访问内存,不要缓存寄存器。用于硬件寄存器、多线程信号标记。volatile不提供线程安全,不能替代锁、atomic

什么时候必须使用成员初始化列表 答:见85。

delete this指针会怎么样 答:成员函数内部delete this;对象内存释放,析构调用;delete之后不能访问任何this成员。外部栈对象不能调用delete this,直接UB。

lambda表达式和仿函数的联系 答:见17。

RAII是什么 答:见98。

为什么STL中使用红黑树作为map和set的底层 答:红黑树平衡二叉搜索树,插入删除查找O(logn);最坏不会退化。对比AVL树,旋转更少,插入删除性能更好;牺牲一点点查询平衡度。

vector如何释放空间 答:见101。

extern的作用,为什么需要这个关键字 答:见108。

C如何处理函数的返回值 答:简单返回值放寄存器;大对象返回值优化RVO/NRVO,直接在调用方内存上构造对象,避免拷贝。

sort的原理 答:std::sort是Introsort内省排序。快速排序为主;递归深度过大切换堆排序,防止快排最坏O(n²);最后小序列用插入排序。不稳定排序

快表是什么 答:见112 TLB。

SSL/TLS算法是什么 答:密钥交换、对称加密、摘要签名算法。握手协商套件,完成密钥协商、加密、校验完整性。

Cookie和Session是什么 答:见121。

什么是蓝图 答:UE可视化脚本,不用写C++,节点连线实现逻辑;底层编译成引擎内部字节码,运行时执行。分为蓝图类、关卡蓝图。

什么是虚幻引擎的反射系统 答:UHT工具扫描UCLASS/UPROPERTY/UFUNCTION宏,生成元数据。运行时可以动态访问类、属性、函数;支撑蓝图、编辑器、网络复制、序列化。

可以深入说一下软引用按需异步加载的流程吗 答:软引用TSoftObjectPtr,只存资源路径,不触发加载。调用LoadAsync发起异步加载;加载完成回调;没有加载完成访问会阻塞。硬引用TObjectPtr直接持有资源,自动加载。

核心重定向Core Redirects是什么 答:资源路径重定向。资源移动、改名之后,旧路径映射到新资源路径,旧引用不会失效。

如何理解虚幻引擎中的子系统Subsystem,有何作用有哪些应用场景 答:UE子系统,分离模块逻辑,生命周期绑定World/GameInstance/LocalPlayer。不需要挂载Actor,全局访问;替代大量单例,区分不同上下文。

虚幻引擎提供了哪些宏定义分别有何作用 答:UCLASS、UPROPERTY、UFUNCTION、UENUM反射宏;DECLARE_DELEGATE委托;BUILD模块宏;UE_LOG日志;check断言。UHT处理反射宏。

TSubClassOf是什么,有何作用 答:模板,限制蓝图只能选择UObject的指定子类。编辑器下拉选择子类,C++安全类型。

什么是共享引用TSharedPtr和共享指针的区别,有何优点 答:TSharedPtr共享计数智能指针;TWeakPtr弱指针,不增加计数,解决循环引用。注意:不能管理UObject对象

虚幻引擎自定义的数据容器和STL中的数据容器相比有何不同 答:TArray/TMap引擎容器;引擎内存分配器;引擎的容器支持UObject垃圾回收感知;STL使用标准allocator;UE容器优先游戏场景做优化。

行为树是什么,一段话介绍行为树 答:AI行为树,树形节点驱动AI逻辑;组合节点编排子节点,叶子节点执行任务,返回成功/失败/运行;可视化编辑,适合复杂AI逻辑。

虚幻引擎具体如何实现的群体模拟效果 答:MassEntity;Boids算法;大量实体数据驱动,批处理;避免每个实体做成Actor。

你这里提到了Boids算法三原则可以详细说一下如何实现的吗 答:Boids三规则:①对齐:向周围邻居平均速度对齐;②凝聚:向邻居群体中心靠拢;③分离:和邻居保持距离,避免碰撞。每个Agent遍历附近邻居,计算三个速度分量加权叠加得到最终速度。

如何理解虚幻引擎中的体积Volume概念 答:不可渲染的空间盒子。定义一块空间,进入/离开触发逻辑;比如触发体积、雾体积、声音体积。

什么是数据表DataTable和数据资产Data Asset的区别是什么 答:DataTable表格,CSV导入,多行结构化表格数据。DataAsset独立uasset文件,单个配置对象,C++定义结构体,编辑器编辑。DataTable适合大量同类配置;DataAsset适合单个独立配置。

什么是断言,虚幻引擎提供了哪些断言 答:见72。

如何理解虚幻引擎中的群集处理垃圾回收 答:MassEntity不是UObject,不走UObject GC;Mass有自己的内存管理;普通Actor/UObject依靠GC标记清除。

摄像机责任链是什么 答:UE摄像机组件链式处理,多个摄像机修改最终视图,比如震动、镜头畸变,依次链式执行。

如何理解虚幻引擎中的控制器Controller 答:AController,负责控制Pawn。APlayerController玩家控制器;AAIControllerAI控制器。不渲染,处理输入、AI逻辑,操控Pawn移动。

虚幻引擎如何进行资产管理 答:编辑器资产浏览器;资产注册表记录全部资产元数据;软引用硬引用;异步加载;烘焙cooking;版本管理。

资产注册表是什么,数据注册表又是什么 答:资产注册表记录磁盘上所有uasset资产路径、类型。数据注册表专门管理DataAsset,运行时快速查找配置数据。

简言之资产注册表管理"资产是什么在哪",数据注册表管理"数据怎么读怎么用" 答:正确。

GamePlayerTags是什么如何使用 答:GameplayTag标签系统,无枚举的标签标记。字符串层级标签;可以给Actor、组件打标签;判断是否拥有标签,做状态判断,比如buff、状态。不用硬编码枚举。

new分配内存的大小总是等同于sizeof的结果吗 答:new分配内存除sizeof对象,对齐填充;operator new\[\]会额外存对象计数,内存大于sizeof。

什么是WebSocket 答:基于HTTP握手之后长连接;双向全双工通信;浏览器客户端和服务器双向推送消息。游戏网页客户端常用。

红黑树是什么具有哪些性质 答:平衡二叉搜索树。性质:①节点红黑;②根黑色;③叶子NIL黑色;④红色节点子节点必须黑色;⑤从任意节点到后代叶子,黑色节点数量相等。保证最长路径不超过最短路径2倍。

什么是图的最小生成树有哪些相关算法 答:选边连接全部顶点,总权重最小,无环。Kruskal(并查集);Prim。

C++中可以对nullptr调用delete吗 答:语法安全。delete nullptr什么都不做,不会崩溃。

可以访问union中的非活跃成员吗 答:UB未定义行为。union同一时间只有一个成员是活跃;C++20允许std::launder辅助。

  1. 可以分别介绍一下工厂模式、装饰器模式、观察者模式和单例模式吗

答:

  • 工厂模式:隔离对象创建逻辑,上层只调用工厂接口,不需要知道具体子类,也不用手动 new。分为简单工厂、工厂方法、抽象工厂三层。游戏里常用于创建怪物、技能、UI面板,新增子类只需要扩展工厂,不用改上层调用代码。

  • 装饰器模式:不修改原类代码,通过包装对象一层层叠加新功能。比继承更灵活,运行时可以动态增删功能。游戏里常用于 buff 叠加、技能效果嵌套。

  • 观察者模式:一对多依赖关系,主题对象状态变化时,自动通知所有注册的观察者执行更新。彻底解耦事件源和响应逻辑。游戏事件系统、属性变更通知基本都基于这个模式实现。

  • 单例模式:全局只有一个实例,提供统一全局访问点。分饿汉式(程序启动就创建)、懒汉式(第一次使用才创建)。游戏里各种管理器类常用,注意多线程下懒汉的线程安全,以及单例销毁顺序问题。

  1. 可以用 shared_ptr 来管理 this 指针吗?如何解决问题

答:直接用 shared_ptr<T>(this) 会出问题:同一个裸指针 this 构造两个独立的 shared_ptr,会有两套引用计数,析构时重复释放对象。 正确做法是类继承 std::enable_shared_from_this<T>,调用 shared_from_this() 返回一个和外部 shared_ptr 共享计数的智能指针。前提是这个对象必须已经被 shared_ptr 管理,否则调用会抛异常。

  1. 什么是 SAT 分离轴定理

答:判断两个凸多边形是否相交的经典算法。核心思想:如果存在一条轴,两个凸多边形在这条轴上的投影不重叠,那么它们一定不相交;所有轴投影都重叠,才相交。2D 取每条边的法线作为分离轴;3D 取面法线和边叉积。只适用于凸多边形,凹多边形要先拆成多个凸多边形。

  1. GJK 算法又是什么

答:Gilbert-Johnson-Keerthi 算法,判断两个凸体是否相交,不用遍历所有分离轴。通过迭代构造单纯形,不断逼近两个凸体的闵可夫斯基差原点。优点是速度快,只需要支持函数;只能判断是否相交,算不出具体碰撞深度,要深度还得配合 EPA 算法。

  1. 什么是 material 和 shared material,有何区别

答:Unity 里的概念。Shared Material 是共享材质资源,所有使用它的物体共用同一份;修改共享材质会影响所有使用它的物体,打包发布后修改会永久改变资源文件。Material 是运行时实例化出来的副本,每个物体独立一份,修改只影响当前物体,不会污染原资源。运行时直接 renderer.material 会自动创建副本,注意内存泄漏。

  1. 如何在不同分辨率下保持 UI 的一致性

答:核心是锚点 + 适配方案。Canvas Scaler 用缩放模式:屏幕大小缩放模式,按参考分辨率适配;UI 元素用锚点固定相对位置,边角元素锚点钉边角,居中元素锚点居中。宽高比不同时,按宽度适配或者按高度适配,黑边或者裁切补齐。

  1. Graphic Raycaster 是什么,有何作用

答:UGUI 的射线检测组件,挂在 Canvas 上,负责检测鼠标/触摸点是否点中 UI 元素。基于 Graphic 图形组件,不需要物理碰撞体;和物理射线是两套体系。UI 点击、拖拽全部依赖它。

  1. 什么是合批,什么是静态合批和动态合批,有何差异

答:合批就是把多个物体合并成一次 DrawCall 绘制,减少 CPU 提交开销。

  • 静态合批:标记 Static 的物体,烘焙时合并网格,运行时一次绘制。优点运行时零开销;缺点占用更多内存,不能移动。

  • 动态合批:运行时每帧把符合条件的小网格合并成一个缓冲区绘制。优点物体可以移动;缺点每帧都有 CPU 合并开销,网格顶点数有限制。 两者都要求材质相同才能合批。

  1. 如何理解 UI 的动静分离

答:UGUI 优化手段。频繁变化的 UI 和不变的 UI 分到不同 Canvas。Canvas 重建是整脏重算,不变的 Canvas 不会重建,频繁变的单独 Canvas 重建不会影响静态部分,大幅降低重建开销。

  1. 如何拆分过大的 UI

答:按功能模块拆成多个子面板;用 Canvas 拆分动静区域;离屏的 UI 不激活,需要时再加载;列表用滚动复用,不创建全部元素;大面板做成预制体异步加载,进入场景不加载,打开时再实例化。

  1. 为什么要预加载 UI,如何预加载

答:UI 预制体第一次打开要加载资源、实例化、构建网格,会卡顿。预加载就是提前把 UI 预制体加载进内存,实例化缓存起来,打开时直接 SetActive 显示,没有加载开销。常用对象池管理 UI 面板,关闭不销毁,回收到池里。

  1. Mask 是什么,有何作用,对性能有何影响

答:Mask 实现 UI 裁剪,只显示矩形范围内内容。原理是模板缓冲 Stencil。每个 Mask 会打断合批,增加 DrawCall;多层嵌套开销更大。会多一次模板写入绘制,GPU 开销上升。

  1. 为什么 RectMask2D 组件性能更优

答:同样做 UI 裁剪,RectMask2D 不需要模板缓冲,直接在顶点阶段做裁剪计算。不打断同材质合批,DrawCall 更少;只支持矩形裁剪,异形裁剪还是得用 Mask。大部分矩形裁剪优先用 RectMask2D。

  1. ProtoBuf 是什么

答:Google 推出的二进制序列化协议。通过 .proto 文件定义结构,编译生成各语言代码。二进制体积小、序列化快、跨语言、字段前后兼容。游戏网络通信、存档大量使用。

  1. 如何实现高效的数据序列化

答:优先二进制协议(Protobuf、FlatBuffers);只序列化必要字段,增量序列化只传变化数据;内存连续,减少字符串;避免深拷贝,直接序列化内存块;大对象分块序列化,不阻塞主线程。

  1. 什么是自旋锁

答:锁的一种,获取不到锁时不挂起线程,循环空转等待锁释放。优点没有线程切换开销,锁持有时间很短的场景性能好;缺点长时间占锁会空转浪费 CPU。用户态轻量锁,内核互斥锁适合长时间持有。

  1. 原子操作可以避免部分锁的开销吗

答:可以。简单的自增、交换、比较交换,用 atomic 原子指令就能完成,不需要加锁。只有单个变量的并发修改,原子操作比锁轻量得多。但复杂业务逻辑多变量一致性,还是得用锁。

  1. select 和 epoll 是什么

答:IO 多路复用机制,一个线程监听多个 socket。

  • select:数量有限制,每次调用都要全量拷贝文件描述符集合,遍历查询,连接越多越慢。

  • epoll:Linux 高性能方案,事件驱动,只返回就绪的描述符,不用全量遍历,连接数量大性能也稳定。 高并发服务器基本都用 epoll。

  1. 什么是关系型数据库

答:基于关系模型,用表格存数据,行和列组织,SQL 查询。支持事务 ACID,支持复杂关联查询。MySQL、PostgreSQL 都是。适合结构化数据、需要事务保证的业务。

  1. 如何理解数据库范式

答:设计数据库表的规范,减少数据冗余、避免更新异常。第一范式属性不可再分;第二范式消除部分依赖;第三范式消除传递依赖。越高范式冗余越少,但查询 join 越多,性能越差。工程上经常反范式,适当冗余换查询性能。

  1. 以下代码的输出是什么

(通用代码题答题模板) 答:先逐行分析变量生命周期、构造析构顺序、隐式转换、运算符优先级;注意拷贝省略、移动语义、继承虚函数调用;最后给出输出结果,顺带指出易错点。

  1. 算术、移位、关系、位运算、逻辑运算优先级

答:优先级从高到低大致:算术运算 > 移位 > 关系比较 > 位运算 > 逻辑运算。位运算里按位与 > 异或 > 或。实际写代码不要硬记优先级,加括号最稳妥,避免优先级 bug。

  1. 如何用位操作设置、清除和检查特定位

答:设置第 n 位:x |= (1 << n);清除第 n 位:x &= ~(1 << n);翻转第 n 位:x ^= (1 << n);检查第 n 位:(x >> n) & 1。注意位数不要超过类型宽度。

  1. 以下代码的问题是什么,如何修正

(通用代码纠错模板) 答:先指出核心问题(内存泄漏、越界、悬空指针、未定义行为、拷贝遗漏);说明触发条件和后果;给出修正写法,补充最佳实践。

  1. 数组名和指针的区别是什么

答:见第 6 题。

  1. #pragma once 和 #ifndef 的区别

答:都是防止头文件重复包含。

  • #ifndef 是标准预处理器指令,所有编译器都支持,靠宏定义去重,只要宏名不冲突就安全。

  • #pragma once 编译器指令,写法简单,不用写宏名;但不是 C++ 标准,部分老编译器不支持。 工程上一般两者都写,兼顾兼容和简洁。

  1. 如何声明和使用函数指针

答:声明格式 返回值 (*指针名)(参数列表),比如 int (*func)(int, double)。赋值直接把函数名赋给指针;调用和普通函数一样。常用于回调函数、策略模式。typedef 或者 using 起别名,代码更易读。

  1. 原子操作的底层原理

答:底层靠 CPU 硬件原子指令,x86 是 lock 前缀总线锁 + 缓存锁,保证读-改-写整个操作不可分割。单变量原子操作不需要操作系统介入,用户态就能完成,开销远小于锁。复杂操作还是要靠锁。

  1. dynamic_cast 要求对象必须有继承关系,没有继承关系会怎样

答:编译期就会报错。dynamic_cast 运行时靠 RTTI 检查类型,指针转换失败返回 nullptr,引用转换失败抛 bad_cast 异常。只能用于有多态类(有虚函数)的向上/向下转型,没有继承关系编译器直接不通过。

  1. 对于函数来说呢(函数指针的类型转换)

答:函数指针之间不能用 dynamic_cast,dynamic_cast 只针对类对象指针/引用。不同类型函数指针转换,reinterpret_cast 可以强转,但调用必须转回原类型,否则未定义行为。C++ 更推荐用 std::function 做类型安全的函数包装。

  1. stack 和 queue 的底层实现是什么

答:都是容器适配器,默认底层用 deque。stack 只允许尾部 push/pop,栈顶操作;queue 尾部入队、头部出队。也可以指定底层容器,比如 vector、list,只要支持对应接口就行。

  1. 重放攻击是什么

答:攻击者截获合法数据包,原样重复发送给服务器,欺骗服务器执行重复操作。比如重复扣款、重复领取奖励。防范方法:加时间戳、随机 nonce、序列号,服务端校验唯一性,过期丢弃。

  1. 攻击者不需要破译密码或 Token 是什么意思

答:重放攻击不需要知道数据内容,只要原样复制数据包再发一遍就能生效。所以加密只能防窃听,防不了重放,必须加序号、时间戳这类防重放机制。

  1. 首次适配算法会自动合并地址连续的空闲内存块吗

答:首次适配本身只是分配算法,分配时找第一个够大的空闲块。合并连续空闲块叫碎片整理/合并,是释放内存时做的操作。释放内存时检查前后相邻块是不是空闲,是就合并成大块,减少外部碎片。几乎所有内存分配器释放时都会做合并。

  1. noexcept 关键字的作用

答:标记函数承诺不抛出异常。编译器可以做更多优化;标准库容器在移动构造 noexcept 时,扩容会优先用移动而不是拷贝,性能更好。一旦 noexcept 函数真的抛异常,程序直接调用 terminate 终止。

  1. 如何知道递归函数的时间复杂度

答:先写出递推式 T(n) = a*T(n/b) + f(n)。方法:①递归树展开法,算每层工作量加总;②主定理套公式,直接得出复杂度;③数学归纳法验证。注意不能只看调用次数,还要算每次调用里的额外工作量。

  1. move 函数会将对象的生命周期结束吗

答:不会。std::move 只是把左值强转成右值引用,本身不做任何移动操作,也不销毁对象。真正转移资源的是移动构造/移动赋值函数。move 之后源对象处于有效但未定义状态,可以赋值、析构,不能直接读取值。

  1. 什么是 C++ 的大三律

答:如果一个类需要自定义析构函数、拷贝构造、拷贝赋值运算符其中一个,那另外两个大概率也需要自定义。因为需要自定义析构通常意味着管理了堆资源,拷贝就会浅拷贝重复释放,所以三个要一起写。C++11 之后扩展成五大律,加上移动构造和移动赋值。

  1. C++ 内存分区中,常量区存什么,代码区呢

答:

  • 代码区:存放编译后的机器指令,只读,共享。

  • 常量区:字符串字面量、const 全局常量,只读,不能修改。 除此之外还有全局静态区(全局变量、静态变量)、堆(new 分配)、栈(局部变量、函数调用栈)。

  1. C++ 中的拷贝初始化和直接初始化有何区别

答:见第 8 题。

  1. 底层原因:类成员不同初始化方法的差异

答:成员初始化列表是在对象内存分配好之后、构造函数体执行之前,直接调用成员的构造函数初始化。构造函数体内赋值,是先让成员默认构造一遍,再赋值覆盖一遍。引用、const、无默认构造的成员,赋值阶段没法处理,必须初始化列表。

  1. C++ 把临时变量作为返回值时如何处理

答:现代编译器会做 RVO 返回值优化,直接在调用方的内存地址上构造返回对象,省略拷贝构造。C++17 之后 RVO 是标准强制要求。没有优化的情况下,先构造局部临时对象,再移动/拷贝给接收方,局部对象析构。

  1. 如何理解一致性哈希

答:普通哈希节点增减会导致大量数据重新映射。一致性哈希把哈希空间组织成环,数据映射到环上,顺时针找第一个节点。新增/删除节点只影响相邻一小段数据,大部分数据不动。分布式缓存、负载均衡大量使用。

  1. 为什么静态成员函数没有 this 指针,如何理解 this 指针

答:this 指针是成员函数的隐藏参数,指向调用对象本身。静态成员函数属于类,不属于某个对象,没有对象调用它,自然没有 this 参数。静态函数只能访问静态成员,不能访问普通成员变量。

  1. 如何理解 STL 的两级空间分配器

答:SGI STL 分配器设计。大于 128 字节走一级分配器,直接 malloc/free;小于等于 128 字节走二级分配器,内存池管理 16 种规格自由链表,减少小额分配开销和碎片。游戏里大量小对象分配场景很常见。

  1. 原子操作的底层原理,ABA 问题是什么

答:原子底层见 229 题。ABA 问题:变量从 A 变成 B 又变回 A,CAS 检查觉得没变化,但其实中间已经被改过了。解决方法:加版本号,每次修改版本号递增,CAS 同时比较值和版本号。

  1. 如何理解 Canvas 的重建呢,有哪些情况会导致 Canvas 重建

答:Canvas 下任何 UI 元素顶点、材质变化,整个 Canvas 都要重新计算网格、重新生成顶点数据,就是重建。触发原因:颜色改变、大小改变、显隐变化、文字内容变化。优化就是动静分离,频繁变的放单独小 Canvas。

  1. 什么是 Mask 和 RectTransform Mask,有何区别,底层原理

答:Mask 靠模板缓冲,支持异形裁剪,打断合批。RectMask2D 靠顶点裁剪,只支持矩形,不打断合批,性能更好。两者都是 UI 裁剪方案,矩形优先 RectMask2D,异形才用 Mask。

  1. 网络通信代码示例如何实现

答:服务端 socket→bind→listen→accept 循环;客户端 socket→connect;收发数据 send/recv;粘包处理包头+长度;多线程/IO 多路复用管理连接;序列化反序列化用 protobuf。

  1. 如何理解两种同步方式(状态同步/帧同步)

答:

  • 状态同步:服务端权威,同步关键状态,客户端只做表现。逻辑在服务端,客户端预测表现。适合 RPG、MOBA,延迟容忍度高。

  • 帧同步:同步输入帧,所有客户端同样输入跑同样逻辑,结果一致。适合格斗、RTS,同屏单位多;对网络抖动敏感,要求逻辑确定性。

  1. 两种同步方式下,网络状况不好如何改善

答:状态同步:插值、预测、快照缓存,平滑表现;增量同步减少带宽。帧同步:丢包重传、帧缓存抖动缓冲、降频发送;关键帧校验防作弊。

  1. 如何确定一个不透明或者半透明物体的显示状态

答:不透明物体按深度测试,先近后远都可以,深度写入开启,靠深度缓冲遮挡。半透明物体必须从远到近排序渲染,关闭深度写入,靠颜色混合叠加,深度测试只做剔除不写入。

  1. 如何理解前向渲染和延迟渲染

答:

  • 前向渲染:每个物体直接算光照输出颜色。实现简单,支持半透明;光源多了性能线性下降。

  • 延迟渲染:第一遍渲染 G-buffer(位置、法线、粗糙度等),第二遍逐像素算光照。光源数量多性能稳定;不支持半透明,显存占用高。

  1. 为什么延迟渲染不支持透明物体

答:延迟渲染 G-buffer 每个像素只能存一份材质信息,半透明多个物体叠加,G-buffer 存不下多层信息。所以半透明物体一般还是走前向渲染路径,延迟之后单独渲染半透明。

  1. 为什么 ProtoBuf 的效率比普通二进制文件还要高

答:Protobuf 用 varint 整数压缩,小数字占字节少;字段按 tag 编号,不需要存字段名字;二进制紧凑,没有冗余信息;编译生成代码直接序列化,反射开销低。比 JSON、XML 快很多,体积小很多。

  1. 什么是 GPU 蒙皮

答:骨骼动画计算从 CPU 移到 GPU 顶点着色器计算。CPU 只上传骨骼矩阵,GPU 并行计算每个顶点最终位置。大量角色蒙皮 CPU 瓶颈时,GPU 蒙皮能大幅减轻 CPU 负担。

  1. 导航网格简化轮廓导致边缘位置去不了,怎么让 AI 能走到边缘

答:两种思路:①减小腐蚀半径,让可行走区域更贴近边缘,但要保证 Agent 半径不会卡墙;②运行时做边缘偏移,寻路点往外推一点,结合局部避障走到边缘;③增加更细粒度的局部导航,靠近边缘时用射线检测修正位置。

  1. 导航网格两个分区交界处的点如何处理

答:查询点同时查所在瓦片和相邻瓦片;瓦片之间做连通边,寻路可以跨瓦片走;边界处做重叠区域,避免点落在缝隙上查不到。导航系统内部一般都做了瓦片边界膨胀和邻接查询。

  1. 动态障碍物如玩家建造,如何高效更新导航网格

答:分方案:①局部瓦片更新,只重建障碍物所在的少量瓦片,不全图重建;②运行时动态障碍体,寻路时临时避让,不修改导航网格本身;③NavMesh Obstacle 组件,临时避开,不烘焙。

  1. new 和 delete 的底层原理

答:见第 75 题。

  1. STL 迭代器如何删除元素

答:见第 16 题。

  1. sort 底层三种算法

答:见第 170 题 introsort:快排为主,递归过深切堆排,小区间插入排序。

  1. 仿函数和匿名函数的区别

答:见第 17 题。

  1. 仿函数值传递安全,lambda 引用捕获有悬空引用风险

答:仿函数成员变量是值拷贝,生命周期独立,不会悬空。lambda 引用捕获外部局部变量,函数返回后 lambda 还活着,引用的局部变量已经销毁,就悬空了。所以长生命周期的 lambda 尽量值捕获,或者用 shared_ptr 管理生命周期。

  1. 如何实现一个在 main 函数之前执行的函数

答:全局对象构造函数;gcc 的 constructor 属性;MSVC 的 init_seg。注意全局对象跨翻译单元构造顺序不确定,互相依赖会出问题,尽量不要在 main 前写复杂逻辑。

  1. Socket 中客户端和服务器如何建立连接

答:服务端:创建 socket → 绑定地址端口 bind → 监听 listen → 接受连接 accept。客户端:创建 socket → 调用 connect 发起三次握手。连接建立成功后双方就可以收发数据。

  1. 为什么四次挥手最后客户端要等待 2MSL

答:两个原因:①保证服务端收到最后一个 ACK,丢了客户端还能重传;②保证本次连接所有报文都在网络中消失,避免下一个新连接收到旧报文。2MSL 就是两倍最大报文存活时间。

  1. 什么是软链接/硬链接

答:

  • 硬链接:同一个文件多个文件名,inode 相同,删除一个文件名文件还在。不能跨分区,不能链接目录。

  • 软链接(符号链接):独立文件,存目标路径,类似快捷方式。可以跨分区,可以链接目录;目标删了软链接就失效。

  1. 缓存避免重复处理提升加载速度

答:缓存核心是用空间换时间,把计算过/加载过的结果存起来,下次直接用。注意缓存失效策略、容量上限、脏数据更新,不然内存会爆,数据会过期。

  1. 什么是蓝图,它的主要用途是什么

答:UE 可视化脚本系统,节点连线写逻辑。主要用途:快速原型、关卡逻辑、UI 交互、动画状态机。不用编译,美术策划也能上手;复杂逻辑性能不如 C++。

  1. 蓝图有哪些类型,和 C++ 的关系是什么,性能特点是什么

答:类型:蓝图类、关卡蓝图、动画蓝图、材质蓝图。底层都是 UObject,C++ 定义基类,蓝图继承扩展。性能:比原生 C++ 慢,有虚拟机开销;简单逻辑差距不大,复杂循环、大量计算建议 C++ 写。

  1. 蓝图中的事件图表和函数图表有什么区别

答:事件图表可以被外部触发,没有返回值,每一帧事件都在这里挂;函数图表有输入输出参数,调用就执行,执行完返回,适合封装可复用逻辑。事件可以延迟,函数必须同步执行完。

  1. C++ 代码是如何演变到蓝图的

答:不是 C++ 转成蓝图,是蓝图虚拟机执行字节码。C++ 用 UFUNCTION 标记函数暴露给蓝图,蓝图可以调用 C++ 函数。蓝图本身编译成自己的字节码,运行时虚拟机解释执行。

  1. 虚幻引擎的内存管理机制是什么

答:UObject 体系靠垃圾回收标记清除,定期扫描引用,没有引用的对象回收。还有内存分配器、内存池、资源流式加载卸载。原生 C++ 对象自己管生命周期,可以用智能指针。

  1. 如何在动画蓝图中实现 IK 反向运动学

答:先有骨骼起始位置和目标位置;解算 IK 得到每根骨骼旋转;常用 FABRIK、两骨 IK 算法。动画蓝图里有 IK 节点,指定骨骼链和目标,自动计算最终骨骼姿态。

  1. 什么是动画蒙太奇

答:UE 动画资源,把动画片段封装起来,可以控制播放、叠加、混合。用来播一次性动作,比如攻击、受击、技能。可以插事件点,播放到指定位置触发逻辑。

  1. 如何理解动画压缩,UE 中有哪些动画压缩技术

答:原始动画数据量大,压缩减少内存。常见:移除恒定轨道、量化关键帧、曲线精简。UE 自带多种压缩算法,精度和体积权衡。远处角色可以用更低精度压缩。

  1. 如何理解根运动和动画重定向,在 UE 中如何应用

答:

  • 根运动:位移从动画里提取,带动 Actor 移动,移动和动画同步。适合角色移动、技能位移。

  • 动画重定向:一套动画复用到不同骨架,节省美术资源。共用同一套绑定姿势,映射骨骼对应关系。

  1. 什么是网络复制,如何实现

答:UE 网络同步机制。服务器状态自动同步给客户端。标记 Replicated 属性,服务器修改自动同步;变量、RPC、多播三种同步方式。服务端权威,客户端只做表现。

  1. RPC 函数是什么,有哪些类型

答:远程过程调用,一端调用另一端执行。分三类:Server(客户端调用,服务器执行)、Client(服务器调用,指定客户端执行)、NetMulticast(服务器调用,所有客户端执行)。

  1. 如何在 UE 中实现网络游戏

答:权威服务器架构;属性复制同步状态;RPC 同步事件;移动预测补偿延迟;角色、物品、技能全部服务端校验,防作弊。

  1. 如何理解网络带宽优化,有哪些技术

答:只同步变化的属性,增量同步;降低同步频率;量化压缩向量、旋转;重要对象高频,远处低频;对象 Relevance,看不到的不同步;带宽限流,优先同步关键数据。

  1. 什么是网络预测,如何处理延迟,UE 网络调试工具有哪些

答:客户端根据输入预测自己位置,服务器纠正,平滑回滚。延迟补偿:服务器回滚到客户端开枪时刻判定命中。调试工具:NetDebugger、Replication Graph 查看同步状态,统计带宽。

  1. 多个客户端时区不同,服务器怎么做延迟补偿

答:统一用服务器时间,客户端上报自己的 RTT。服务器根据 RTT 回滚对应时间的状态做判定。不用客户端本地时间,全部以服务器时间轴为准。

  1. 服务器如何得知网络延迟

答:客户端定时发心跳,服务器记录往返时间算 RTT;或者同步时间戳,差值算延迟。取一段时间平均值,避免抖动。

  1. UE 的感知系统是什么

答:AI 感知组件。包括视觉、听觉、伤害等感知源。AI 注册感知组件,自动检测范围内敌人、声源。不用自己每帧遍历检测,性能更好。

  1. UE 的 UI 系统有哪些主要组件,如何创建自定义 UI 控件

答:UMG 组件:Canvas、Button、Text、Image 等。自定义控件:继承 UUserWidget,蓝图或 C++ 写逻辑;封装可复用的复杂 UI 单元。CommonUI 是更高层跨平台 UI 框架。

  1. UI 中处理输入和游戏中处理输入有何区别

答:UI 输入走 Slate/UMG 路由,焦点系统,键盘导航。游戏输入走增强输入/PlayerInput,直接映射动作。UI 优先级高于游戏输入,UI 弹出时可以屏蔽游戏输入。

  1. 如何搭建渲染管线,实现了哪些着色

答:自定义渲染管线:先深度预 pass,再渲染不透明物体,阴影贴图,光照计算,透明物体,后处理。着色包括顶点着色、片元着色、计算着色器。PBR 光照、阴影、后处理都在管线里安排顺序。

  1. 如何利用 CUDA 进行渲染加速

答:CUDA 做通用并行计算,适合光线追踪、粒子、后处理这些高度并行任务。把数据拷到显存,启动大量线程核函数并行计算,结果拷回。游戏里一般用 Compute Shader 而不是 CUDA,跨平台更好。

  1. 如何理解 PBR 材质,具体在代码中如何实现

答:PBR 基于物理,分漫反射和高光。漫反射用 Lambert,高光用 Cook-Torrance BRDF,包含法线分布函数 D、几何函数 G、菲涅尔 F。再加上 IBL 环境光。代码里片元着色器按公式逐项计算,最后合成最终颜色。

  1. 有哪些阴影映射方法,底层原理是什么

答:见第 32 题。

  1. Stencil 是什么,典型用途

答:模板缓冲,8 位整数,每个像素存一个模板值。渲染时可以做模板测试,通过才写颜色。典型用途:描边、裁剪、镜面反射、阴影体积、UI 裁剪。

  1. 如何理解屏幕空间后处理效果,有哪些常见的后处理

答:后处理在所有物体渲染完之后,对最终屏幕图像做一次处理。全部在屏幕空间做,不需要场景几何信息。常见:Bloom 泛光、色调映射、抗锯齿、SSAO、景深、运动模糊、颜色校正。

  1. Volume 系统又是什么

答:UE 体积系统,场景里放体积框,影响范围内渲染效果。比如雾体积、后处理体积、光照体积。进入体积就生效,叠加优先级。

  1. Compute Shader 是什么,有哪些应用场景

答:见第 45 题。

  1. 如何优化 A 星算法

答:优化方向:更好的启发函数,减少扩展节点;二叉堆换斐波那契堆;分层寻路,先粗后细;路径缓存复用;跳点搜索 JPS 跳过大量节点;剪枝,方向不变不重复入队。

  1. 如何实现异步消息处理和断线重连

答:消息队列异步消费,不阻塞主线程。断线重连:保存未确认消息,重连后重发;序列号去重;快速重连机制,短时间断线不用重走完整登录流程。

  1. 模块化架构、事件驱动、Manager-Service 分层的意义

答:解耦,每个模块只做一件事;事件驱动降低模块依赖;Manager 管全局生命周期,Service 提供具体能力。好处是易扩展、易维护、多人协作不冲突;过度分层也会增加复杂度。

  1. 如何实现角色、存档、收集和战斗等核心系统

答:角色系统:组件化,属性、技能、移动分开。存档系统:序列化关键数据,本地/云端存储。收集系统:物品表、背包、收集进度。战斗系统:技能、伤害计算、buff、命中判定。全部服务端权威,客户端表现。

  1. const 可以修饰静态成员函数吗

答:不可以。const 成员函数修饰的是 this 指向的对象不可修改;静态成员函数没有 this 指针,加 const 没有意义,语法编译报错。

  1. 如何实现单链表的快排

答:三指针法,partition 分大小两段,递归排序。不用随机访问,选头或尾当基准;交换节点值,或者改指针指向。时间复杂度平均 O(nlogn),最坏 O(n²)。

  1. 析构函数中可以调用虚函数吗,可以实现多态吗

答:语法可以调用,但不会有多态效果。析构时子类部分已经销毁,虚表指针指向当前类,调用的是本类版本,不是最派生类版本。构造函数同理,调用虚函数都不会多态。

  1. 字符串只有 a/b/c,找三种字母数量相等的最长子串

答:前缀和 + 哈希表。记录 a-b、a-c 的差值,相同差值的位置之间三种字母数量差相等。遍历字符串,存第一次出现差值的下标,最长长度就是当前下标减首次下标。

  1. 什么是段页式寻址,有何好处

答:分段 + 分页结合。先分段,段内再分页。分段符合逻辑视图,分页方便内存管理。结合两者优点:段保护共享,页解决碎片。

  1. 什么是平衡二叉树

答:左右子树高度差不超过 1,且左右子树都是平衡二叉树。保证查询 O(logn),不会退化成链表。插入删除要旋转维持平衡。AVL 是严格平衡,红黑树是近似平衡。

  1. 怎么避免内存错误

答:用智能指针管理内存;RAII 包裹资源;边界检查,防止越界;初始化指针,避免野指针;释放后置空;用工具检测:Valgrind、ASan、UE 内存检测工具。

  1. dynamic_cast 必须依赖虚函数吗,成功转换后对象的值会变化吗

答:必须有虚函数,依赖 RTTI。指针转换成功地址可能调整(多继承偏移),但指向的对象内容不变,只是指针类型变了。

  1. 如何实现服务器的排行榜

答:小榜单直接有序数组/红黑树;大榜单用跳表或者 Redis 有序集合;分段排行榜;定时更新,不用实时全量排序;缓存前几页,后面按需查。

  1. 函数模板和类模板的区别

答:函数模板可以自动推导参数类型;类模板必须显式指定模板参数。函数模板偏特化有限,类模板支持全特化偏特化。

  1. 能介绍一下 SAT 定理和 GJK 算法吗

答:见 204、205 题。

  1. 滚动列表 100 个道具怎么优化,剔除原理,UI 对象管理

答:只创建可视范围内的元素,滚动时复用元素,数据和 UI 分离。对象池回收离开屏幕的条目,给新出现的复用。不用创建 100 个 GameObject,只需要屏幕可见的十几个。

  1. 帧同步游戏玩家掉线如何处理

答:短暂掉线缓存输入帧,重连后补发漏掉的帧;掉线时间长就重连后追帧,快速回放赶上;严重不同步就重置到最新快照继续。

  1. 为什么 sort 使用快速排序而不是归并排序

答:快排平均速度更快,原地排序不需要额外内存。归并稳定但需要 O(n) 额外空间,内存开销大。STL sort 追求性能,所以选快排为主,堆排兜底。

  1. 结构体实现了接口,用接口变量初始化结构体实例有什么潜在问题

答:C# 里值类型装箱成接口引用,会发生装箱分配,产生 GC。值类型接口调用会装箱,频繁调用性能差。尽量避免对结构体频繁做接口调用。

  1. unique_ptr 指向一个对象之后如何再指向另一个对象

答:调用 reset() 释放旧对象,接管新指针;或者直接移动赋值 p = std::make_unique<T>()。unique_ptr 独占所有权,不能拷贝,只能移动。

  1. 关闭编译器优化会调用几次构造函数,开启呢

答:关闭优化:局部对象构造 + 拷贝构造返回值,两次。开启优化:RVO/NRVO 返回值优化,直接在接收方构造,只有一次构造。C++17 之后 RVO 是强制的。

  1. 几万条屏蔽词怎么快速检测聊天内容

答:构建 AC 自动机,所有模式串建 trie 树,加失败指针。一次遍历文本就能找出所有匹配的屏蔽词,时间复杂度线性,比逐个匹配快很多。

  1. 如何理解 AC 自动机

答:多模式匹配算法。先把所有关键词建 trie 字典树,再 BFS 构建失败指针,类似 KMP 的 next 数组。匹配文本时,当前字符不匹配就走失败指针,不用回溯,线性时间找出所有匹配词。

  1. 怎么判断线段是否相交

答:见第 59 题。

  1. C++ 中指针与引用的区别

答:见第 2 题。

  1. i++ 与 ++i 的区别,底层实现上的区别

答:++i 前置:先自增,返回自增后的值,不需要临时对象。i++ 后置:先返回旧值,再自增,需要生成临时对象保存旧值。自定义类型后置 ++ 会调用拷贝构造,性能差;基本类型编译器优化后差别不大。

  1. 能说下迭代器的原理吗

答:迭代器是指针的抽象,封装容器遍历方式,让算法不用关心容器具体结构。不同容器迭代器实现不同:vector 就是原生指针封装,list 是节点指针。提供统一的 ++、*、-> 接口。

  1. main 函数执行之前和之后的是什么

答:之前:运行时初始化、全局对象构造、全局变量初始化。之后:全局对象析构、atexit 注册函数、运行时清理。

  1. 结构体内存对齐问题,alignas 和 alignof 如何使用

答:alignof 计算类型对齐字节数。alignas 手动指定对齐大小,强制结构体或变量按指定对齐数对齐。自定义对齐可以满足 SIMD、硬件特殊要求。

  1. strlen 和 sizeof 的区别

答:见第 76 题。

  1. printf/scanf 和 cout/cin 的区别

答:见第 14 题。

  1. allocator 有什么用

答:见第 83 题。

  1. 为什么成员初始化列表比构造函数中赋值快

答:初始化列表直接调用构造函数初始化成员;函数体内是先默认构造,再赋值覆盖。多了一次默认构造和赋值的开销。引用、const 成员根本不能赋值,只能初始化列表。

  1. coredump 是什么

答:程序崩溃时操作系统把进程内存镜像转储成文件。可以用 gdb 打开,查看崩溃时调用栈、变量值,定位崩溃原因。开发调试崩溃必备。

  1. C++ 允许把临时变量作为函数的返回值吗

答:允许。返回局部临时对象,编译器做 RVO 优化,直接在调用方构造。没有优化的话,移动/拷贝构造返回。返回局部变量的指针/引用是 UB,局部对象销毁就悬空了。

  1. 如何理解静态类型和动态类型、静态绑定和动态绑定、静态分配和动态分配

答:

  • 静态类型:编译期确定的类型;动态类型:运行时实际对象类型。

  • 静态绑定:编译期确定调用哪个函数,普通函数、重载都是。动态绑定:运行时查虚表确定,虚函数多态。

  • 静态分配:编译期确定大小,栈、全局变量。动态分配:运行时申请,堆内存。

  1. 怎么判断浮点数是否相等

答:见第 87 题。

  1. 如何理解 C++ 中的组合和继承,相比有何区别

答:继承是 is-a,强耦合,父类改子类受影响。组合是 has-a,弱耦合,灵活,可以运行时替换。优先用组合,少用继承;多态才用继承。

  1. 为什么模版类定义都放在 .h 文件中

答:见第 91 题。

  1. #ifdef 和 #pragma once 的区别

答:见第 227 题。

  1. strcpy 和 memcpy 的区别

答:strcpy 拷贝字符串,遇到 \0 停止,只适用于字符串。memcpy 按字节拷贝,指定长度,任意内存都可以。strcpy 有溢出风险,推荐 strncpy 或者直接用 std::string。

  1. 什么时候使用成员初始化列表,调用过程是什么

答:见第 85 题。调用顺序:先父类构造,再按成员声明顺序初始化成员,最后进入构造函数体。和初始化列表写的顺序无关,只和声明顺序有关。

  1. 为什么友元函数要在类内部声明

答:友元声明在类里,告诉编译器这个函数/类可以访问私有成员。不是定义,只是授予访问权限。定义可以写在类外。友元破坏封装,谨慎使用。

  1. 为什么 C++ 没有垃圾回收机制

答:设计哲学:零开销抽象,不需要的东西不强制加。GC 有停顿、开销、不可控。C++ 用 RAII、智能指针手动管理,性能更可控,代价是开发者要操心生命周期。

  1. 如何理解 this 指针

答:成员函数隐藏参数,指向当前调用对象。编译器自动传入。成员访问变量默认就是 this->。静态函数没有 this。链式调用返回 *this。

  1. nullptr 和 NULL 的区别

答:见第 96 题。

  1. 如何理解 lambda 表达式

答:见第 17、97 题。

  1. 基类的虚函数表存储在内存哪个分区,虚函数呢

答:虚表存在只读数据段,所有同类对象共享一张。虚函数代码存在代码段。对象里只有虚表指针,指向虚表。虚表编译期生成。

  1. 构造函数和析构函数中可以调用虚函数吗

答:见第 304 题。

  1. 模板函数和模板类的区别

答:见第 105、311 题。

  1. 什么叫合成构造函数

答:用户没写构造函数,编译器自动生成的默认构造函数。什么都不做,只是按规则初始化成员。有自定义构造函数,编译器就不生成默认的了。

  1. 如何理解 UE 中的 Living Code(热重载)

答:运行时重新编译 C++ 代码,不用重启编辑器,直接加载新代码。迭代开发快;复杂类、改类结构可能热重载失败,需要重启。

  1. 如何理解 UE 架构

答:分层架构:核心层、引擎层、游戏逻辑层。模块系统,功能拆成模块;UObject 反射、GC;Actor-Component 组件模式;渲染、物理、网络、输入各子系统。

  1. UE 的资产异步加载是如何实现的

答:StreamableManager 管理异步加载;软引用发起 LoadAsync,加载完成回调。后台线程加载资源,主线程回调。加载中可以显示加载界面,不阻塞游戏。

  1. Core Redirects 是什么

答:见第 177 题。

  1. UE 的数据资产是什么

答:DataAsset,继承 UDataAsset 的资源文件。存配置数据,编辑器可编辑,运行时读取。比 DataTable 灵活,适合单个独立配置。

  1. Data Table 和 Data Asset 的区别

答:见第 187 题。

  1. UE 的数据校验是什么

答:UObject 数据合法性校验,编辑器和运行时检查数据是否正确。自定义 Validate 函数,打包前批量检查,减少坏数据。

  1. UE 中的模块是什么

答:功能单元,每个模块 .Build.cs 配置,编译成独立 lib。代码按模块组织,解耦复用。游戏、引擎功能都是模块。

  1. UE 中的引用资产是什么

答:资产之间的引用关系。硬引用加载时自动加载依赖;软引用不自动加载,手动异步加载。引用决定资源加载时机和依赖。

  1. UE 中的资产注册表是什么

答:记录工程所有资产的路径、类型、依赖等元数据。编辑器启动扫描生成,快速查询资产,不用遍历磁盘。

  1. UE 中的编程子系统是什么

答:见第 178 题。

  1. UE 的任务系统 Task Systems 是什么

答:UE 异步任务系统,把任务丢到线程池执行。TaskGraph 任务依赖图,有依赖关系的任务按顺序执行。充分利用多核,主线程不阻塞。

  1. UE 的配置文件有何作用

答:ini 配置文件,分引擎、项目、平台多个层级。运行时读取配置,不用改代码。各种功能参数都可以放配置里,方便调参和平台差异化。

  1. 可以大体介绍一下 UE 中的反射系统吗

答:见第 175 题。

  1. 如何理解 UE 的网络架构

答:权威服务器,客户端表现。属性复制、RPC、多播三层同步。Replication Graph 优化同步范围。预测、插值、延迟补偿改善体验。

  1. 如何理解 UE 中的体积

答:见第 296 题。

  1. UE 中如何保存游戏

答:SaveGame 对象,序列化存磁盘。关键数据存进去,加载时反序列化。支持槽位、自动存档。大存档异步读写,不卡主线程。

  1. 什么是 UBT,什么是 Living Code,三者有何联系和区别

答:UBT 是虚幻编译工具,负责编译 C++ 模块。Living Code 是热重载,运行时编译替换代码。UHT 处理反射宏。三者配合:UHT 生成反射代码,UBT 编译,热重载运行时加载新编译的代码。

  1. 序列化与网络复制是如何依赖于反射的

答:网络复制要知道哪些属性需要同步、什么类型。反射系统提供属性元数据,引擎可以自动序列化属性、对比脏数据、打包发送,不用手写同步代码。

  1. 软/硬引用分别是什么

答:见第 176 题。

  1. 全量编是什么,何时需要

答:全量编译,全部代码重新编译。改了引擎代码、宏、头文件大范围改动时需要。增量编译只编改动文件,日常开发用。

  1. 行为树的内容有何作用

答:行为树结构化组织 AI 逻辑,可视化编辑,复用性强。组合节点控制流程,装饰节点加条件,叶子节点做具体动作。复杂 AI 逻辑也能清晰维护。

  1. 黑板有什么用

答:AI 的数据容器,行为树所有节点共享数据。行为树不直接存状态,全部读写黑板。解耦逻辑和数据,多个行为树可以共用黑板。

  1. 如何理解 UE 中的智能对象,如何使用

答:场景里可交互点位,AI 查询到智能对象就可以执行对应交互。比如开门、坐下、攀爬。把交互逻辑和 AI 解耦,场景摆点 AI 自动能用。

  1. 什么是状态树,如何使用

答:State Tree,UE 新的状态机系统,比行为树轻量,适合简单状态切换。状态 + 过渡条件,性能更好,简单 AI 用状态树更高效。

  1. 场景查询系统是什么

答:EQS,环境查询系统。AI 在场景中采样点位,按条件打分选出最优位置。找掩体、找攻击位、找逃跑路线都用它。

  1. 数据资产注册表是什么

答:Data Registry,集中管理数据资产,运行时快速查找。按 ID 查数据,支持热更新,不用硬引用所有数据。大量配置表用它管理。

  1. Gameplay 的定位系统是什么,有何作用

答:Gameplay Tags 标签系统,用层级字符串标签标记状态、类型。替代枚举,不用改代码就能加标签。判断状态、筛选对象、触发条件都用。

  1. 什么是 RPC 函数呢

答:见第 281 题。

  1. 具体如何构建线性约束和求解线性约束

答:ORCA 避障里,每个障碍物对应一条线性约束,在速度空间构成半平面。所有约束求交集,找最接近期望速度的可行解。用线性规划或者单纯形法求解。

  1. 区别在调度方式、机制与协议一致

答:不同同步方案核心差异在调度策略和数据粒度,底层网络协议都可以用 TCP/UDP。机制不同但底层通信手段一致。

  1. 如何实现小地图

答:相机从上往下渲染场景到 RenderTexture;UI RawImage 显示这张纹理。玩家位置、图标投影到小地图坐标;裁剪、缩放、旋转对齐。大世界小地图可以用预烘焙的地图瓦片。

  1. 怎么实现移动同步

答:状态同步:服务端同步位置速度,客户端插值平滑。帧同步:同步输入,客户端各自算位置。一般还会加预测、平滑、纠正,减少卡顿感。

  1. 如何实现地图传送

答:服务端校验传送点合法性,修改玩家位置;客户端播放传送特效,加载目标关卡;切换场景异步加载,过渡动画遮 loading。

  1. 如何实现 NPC

答:AI Controller + 行为树 + 黑板;感知系统检测玩家;状态机管理巡逻、追击、攻击、死亡;寻路系统移动;技能系统释放技能。

  1. 如何实现任务系统,如何实现有分支的任务系统

答:任务数据配置化;任务状态机:未接、进行中、完成、失败。分支任务:条件节点,满足条件走不同分支线。任务事件驱动,条件达成推进任务。

  1. AOI 是什么

答:兴趣区域。每个玩家只同步自己周围范围内的对象,远处不同步。大幅减少同步量,大世界网游必备。常见九宫格、圆形 AOI。

  1. 存档系统怎么实现的

答:定义存档结构;序列化存二进制/JSON;本地存磁盘,云存档存服务器。版本号兼容,旧存档能升级。异步读写防卡顿。

  1. 如何实现的收集系统呢

答:收集物配置表;玩家收集进度数据;触发检测收集;更新进度、发奖励;UI 展示收集进度。进度存存档。

  1. Gameplay Tags 的作用

答:见第 376 题。

  1. Zen Loader 是什么,有何作用

答:UE 异步加载工具/库,更灵活的资源加载管理。流式加载、优先级调度、批量加载。优化加载时序,控制主线程开销。

  1. UE 中的打包和烘焙是什么

答:烘焙 Cooking:把编辑器资产转换成目标平台运行时格式,压缩纹理、编译 shader。打包 Packaging:把烘焙好的资产、可执行文件、库打包成最终发布版本。烘焙是打包的前置步骤。

  1. UE 的打包管线是什么

答:UBT 编译代码 → UAT 执行烘焙 → 平台特定打包 → 生成可执行程序和资源包。中间有资源压缩、shader 编译、版本处理等步骤。

  1. 像素流送是什么

答:UE 程序跑在服务器,渲染画面编码成视频流推给浏览器/客户端。客户端不用下载安装,低配置设备也能玩高画质游戏。

  1. make_shared 和 shared_ptr 初始化有什么区别

答:shared_ptr<T>(new T) 两次内存分配:对象 + 控制块。make_shared<T>() 一次分配把对象和控制块放一起,内存更少、更快、异常安全。缺点是弱引用存在时,对象内存没法完全释放。

  1. 虚函数能否内联呢

答:普通调用可以内联。通过对象直接调用虚函数,编译期确定类型,就可以内联。通过指针/引用多态调用,运行时才确定函数,没法内联。

  1. 清空 vector 时不断删第 0 个和删最后一个有区别吗

答:区别巨大。删尾部 pop_back O(1),只移动尾指针。删头部 erase(begin) O(n),后面所有元素都要往前搬。频繁头删 vector 性能很差,适合用 deque 或者 list。

  1. 对基于组合的编程思想了解吗

答:组合优于继承。用组件组合出功能,而不是继承层级扩展。Actor-Component、ECS 都是组合思想。优点灵活、低耦合、易复用;缺点关系不如继承直观。

  1. 基于一个裸指针构造两个智能指针会怎么样

答:两个独立 shared_ptr 各自建控制块,两套引用计数,对象会被析构两次,内存错误崩溃。所以永远不要拿同一个裸指针初始化多个 shared_ptr。要用 shared_from_this 或者拷贝 shared_ptr。

  1. 可执行程序的 BSS 段是什么

答:未初始化的全局变量、静态变量。不占磁盘空间,程序加载时内核清零。节省可执行文件体积。

  1. 蓝图在磁盘上是什么格式的

答:uasset 文件,UE 专有资产格式。序列化存蓝图节点、属性、元数据。不是文本,是二进制 + 标签结构。

  1. 蓝图是如何执行的

答:蓝图编译成字节码,虚拟机解释执行。每个节点对应字节码指令,运行时按顺序执行。有自己的栈和上下文。

  1. 如何在一百万个数据中挑选前一千个

答:维护大小为 1000 的小根堆。遍历数据,比堆顶大就替换堆顶,调整堆。一遍 O(n) 遍历,每次堆操作 O(logk),总复杂度 O(n logk)。比全排序 O(n logn) 快很多。

  1. 如何实现排行榜

答:见第 310 题。

  1. 为什么选择 Protocol Buffers 作为通信协议,有什么优势

答:见第 145 题。

  1. 服务器各个 Service 是如何组织的,为什么这样设计

答:按功能拆服务:登录、战斗、社交、匹配。每个服务独立部署,横向扩展。解耦,故障隔离,不同模块可以独立迭代。

  1. 角色系统的数据结构是如何设计的,如何支持多角色

答:角色基础属性、外观、技能、装备分组件。多角色共享配置数据,实例数据各自一份。切换角色切换实例。

  1. 战斗系统的实现逻辑是什么,如何处理技能释放和伤害计算

答:技能状态机:前摇、施法、后摇、冷却。释放校验距离、冷却、消耗。命中检测 → 伤害计算 → 应用 buff → 广播事件。服务端计算,客户端表现。

  1. 装备系统的数据结构设计,如何实现装备槽位管理

答:装备配置表;角色槽位数组,每个槽位存装备实例 ID。换装:卸旧装、穿新装、更新属性。槽位类型限制,不能乱穿。

  1. 如何管理大量玩家数据的内存使用

答:在线玩家数据常驻,离线玩家落地数据库;LRU 缓存热点玩家;分块加载,不用的模块不加载;对象池复用临时对象。

  1. 玩家数据如何持久化存储,数据库设计考虑

答:关系型数据库存核心数据;分表分库,按玩家 ID 分片。重要数据写操作日志,可回滚。缓存 + 数据库两层,定期落库。

  1. 如何保证数据一致性,如何处理并发修改

答:加锁;乐观锁版本号;单线程串行处理玩家数据;原子操作。关键操作事务保证。避免跨玩家死锁。

  1. 任务系统的状态管理是如何实现的

答:任务状态机:未接取、进行中、已完成、已领取、失败。事件驱动,条件满足切换状态。进度更新走事件,不每帧轮询。

  1. 如何防止客户端作弊,服务器验证机制

答:服务端权威,所有关键逻辑服务端算。客户端只发输入,不发结果。校验位置、伤害、冷却、道具数量。异常数据检测封号。

  1. MessageDistributer 是如何工作的

答:消息分发器。收到消息按协议号分发给对应处理函数。注册回调,收到消息查表调用。解耦网络层和业务层。

  1. 技能系统的设计模式,如何支持技能升级

答:策略模式,每个技能一个实现类。配置表驱动,升级改配置数值,不用改代码。效果用装饰器叠加。

  1. URPGAttributeSet 中定义了哪些属性,如何实现属性同步

答:GAS 属性集,定义生命值、法力值、攻击力等属性。标记 Replicated,服务端修改自动同步客户端。属性变化有回调,可以挂事件。

  1. URPGGameplayAbility 是如何扩展基础能力系统的

答:继承 GameplayAbility 实现自定义技能逻辑。激活、结束、取消;输入绑定;消耗冷却。蓝图也可以扩展。

  1. 技能冷却和消耗是如何实现的

答:冷却用 GameplayEffect 加 tag 或者计时。消耗扣属性集里的资源。服务端判定,客户端预测表现。

  1. 如何实现技能的目标选择系统

答:范围检测、射线检测、EQS 查询;筛选目标类型、阵营;最终得到目标列表传给技能伤害计算。

  1. 伤害计算系统是如何实现的

答:基础伤害 × 系数 + 固定增减伤;按优先级叠加修饰器;暴击、护甲减免、伤害类型修正。配置表驱动,支持扩展伤害 modifier。

  1. 如何实现攻击判定和命中检测

答:近战:碰撞体检测、射线检测、扇形检测。远程:弹道飞行、碰撞检测。服务端做判定,客户端播特效。

  1. 技能效果容器 EffectContainer 的作用是什么

答:打包一组 GameplayEffect,技能命中时批量应用。配置化,不用代码写每个效果。

  1. 游戏存档系统是如何设计的

答:分全局存档和角色存档;二进制序列化;版本号向前兼容;自动存档 + 手动存档;云存档同步。

  1. 如何保存和加载玩家进度

答:进度数据结构化,序列化进存档文件。加载时反序列化,恢复到对应状态。异步加载,过渡动画遮加载过程。

  1. 多人游戏中的属性同步是如何实现的

答:UE 复制系统,属性标记 Replicated;服务端变更自动同步客户端;条件复制,相关玩家才同步;增量复制,只传变化值。

  1. 如何确保客户端和服务器状态一致

答:服务端权威,所有关键状态以服务端为准。客户端预测,服务端定期纠正。快照校验,不一致回滚。

  1. 网络延迟如何处理

答:客户端预测、插值平滑、缓冲区抗抖动;服务器延迟补偿;降低同步频率,减少带宽占用。

  1. 项目中如何管理大量物品和技能数据

答:配置表驱动,Excel 导出二进制。运行时只读一份配置,实例引用配置 ID。数据注册表统一管理。

  1. 资源加载和卸载的策略是什么

答:按需加载,进入场景加载,离开卸载;引用计数,没人用就卸载;预加载关键资源;分级加载,先加载必要的,后台加载次要的。

  1. 静态成员函数可以调用虚函数吗

答:不可以。静态函数没有 this 指针,没法查虚表,也没法调用普通虚函数。

  1. 三种智能指针的区别

答:

  • unique_ptr:独占所有权,不能拷贝,只能移动。开销最小。

  • shared_ptr:共享计数,多个指针共用对象。引用计数为零释放。

  • weak_ptr:弱引用,不增加计数,打破循环引用。lock 转 shared_ptr 使用。

  1. 什么是 BVH

答:见第 37 题。

  1. 如何理解菲涅尔效应,在渲染中有哪些具体的应用场景

答:物体边缘视线和法线夹角大,反射更强。比如水面、玻璃边缘更亮,金属边缘反光更强。PBR 高光部分必用菲涅尔。还可以做边缘光、材质过渡效果。

  1. 如何理解游戏循环机制

答:游戏主循环:处理输入 → 更新逻辑 → 渲染画面。不断循环,一帧一次。固定步长物理更新,可变步长逻辑更新。保证不同帧率逻辑速度一致。

  1. 如何处理物理更新频率和渲染帧率不同步可能带来的问题

答:物理固定步长,渲染插值。逻辑状态按物理步长更新,渲染帧在两个物理状态之间插值。画面平滑,物理结果确定,不会因为帧率高逻辑变快。

  1. SSE/AVX 指令集是什么

答:x86 SIMD 指令集。一条指令同时操作 4/8/16 个浮点数。游戏物理、向量运算、动画大量使用,几倍提升计算性能。

  1. 什么是常量指针和指针常量

答:常量指针(底层 const):指向的值不能改,指针本身可以改。const int* p。 指针常量(顶层 const):指针本身不能改,指向的值可以改。int* const p

  1. dynamic_cast 的底层原理

答:运行时检查 RTTI 类型信息,遍历继承链判断转换是否合法。有额外运行时开销。指针失败返回空,引用失败抛异常。

  1. shared_ptr 的底层原理

答:对象指针 + 控制块指针。控制块存引用计数、弱计数、删除器。拷贝 shared_ptr 引用计数原子自增。最后一个 shared_ptr 销毁,引用计数归零,删除对象。

  1. 具体来说怎么使用 weak_ptr 呢

答:weak_ptr 由 shared_ptr 构造,不增加计数。使用前调用 lock() 获取 shared_ptr,成功就可以安全使用,失败说明对象已释放。主要用来打破循环引用、做缓存。

  1. 内存对齐是什么,为什么要内存对齐

答:见第 1 题。

  1. 如何理解对象切片,如何避免

答:见第 15 题。

  1. 类中可以定义引用数据成员吗

答:可以。但必须在成员初始化列表里初始化,绑定之后不能改指向。类赋值、拷贝会有问题,引用不能重新绑定。一般不建议类成员用引用,用指针更灵活。

  1. STL 的分配器 Allocator 是什么,有何作用

答:见第 83 题。

  1. 仿函数和匿名函数有何关系

答:见第 17 题。

  1. auto 和 decltype 有何区别

答:见第 20 题。

  1. 如何实现一个在 main 函数之前和之后执行的函数

答:见第 74、266 题。

  1. 结构体的内存大小怎么看

答:按成员声明顺序,每个成员按自己对齐数对齐,整体对齐到最大对齐数整数倍。中间和末尾会有填充字节。不是成员大小简单相加。

  1. 如何理解单一职责原则

答:一个类只负责一件事,只有一个改变的原因。降低复杂度,易维护易测试。职责太多耦合重,改一处影响全局。

  1. 对指针和引用使用 sizeof 的结果如何

答:sizeof 指针永远是指针本身大小(64 位 8 字节)。sizeof 引用等于引用指向的类型大小,因为引用是别名,sizeof 操作的是原对象。

  1. lambda 表达式中的值捕获的值可以修改吗

答:默认值捕获不可以修改,lambda 是 const 的。加 mutable 关键字就可以修改值捕获的变量,但改的是 lambda 内部副本,不影响外部原变量。

  1. Image 和 RawImage 的区别

答:UGUI 两个组件。Image 显示 Sprite,支持九宫格、填充、精灵图集合批。RawImage 直接显示任意 Texture,不需要 Sprite,适合 RenderTexture、外部贴图。Image 更适合 UI 图标,RawImage 适合视频、渲染纹理。

  1. 如何理解动态合批和静态合批

答:见第 209 题。

  1. 什么是硬链接和软链接

答:见第 269 题。

  1. 25 匹马找最快 5 匹最少几次

答:7 次。先 5 组各比一次(5 次),每组第一再比一次(第 6 次),根据名次淘汰不可能进前 5 的,剩下候选再赛一次(第 7 次)得出前 5。

  1. 9 个砝码 1 个偏轻,最少称几次

答:2 次。三等分,3 个 vs 3 个称一次,找出轻的那一组;再 1 vs 1 称第二次,得出轻的。每次称重三分法,信息效率最高。


图形学 / 渲染 / OpenGL

  1. 如何理解 MVP 变换

答:MVP 是 Model、View、Projection 三个矩阵依次相乘。模型矩阵把顶点从局部空间转到世界空间;视图矩阵转到相机视角空间;投影矩阵转到裁剪空间,做透视/正交投影。顶点着色器里 v = MVP * pos,完成坐标变换流水线。

  1. 具体代码上如何实现 MVP 变换

答:CPU 端计算三个矩阵相乘得到 mvp 矩阵,作为 uniform 传给顶点着色器;顶点着色器里 gl_Position = mvp * vec4(position, 1.0);。矩阵计算可以用 glm 库。

  1. 如何理解光栅化

答:把顶点装配好的图元(三角形),离散化成屏幕上一个个像素片元。判断每个像素中心是否被三角形覆盖,对顶点属性插值得到片元属性,交给片元着色器。是几何到像素的桥梁。

  1. 如何理解采样前低通滤波,有何作用

答:采样高频信号会产生锯齿走样。采样前先做低通滤波,去掉高频分量,再采样就不会出现明显锯齿。对应纹理就是 mipmap,对应屏幕抗锯齿就是 MSAA。本质都是先模糊再采样,减少走样。

  1. 如何理解锯齿,为什么有锯齿,有哪些常见的抗锯齿手段

答:锯齿因为屏幕是离散像素,连续图形采样后边缘出现阶梯状。 常见抗锯齿:

  • SSAA:超采样,高分辨率渲染再缩回来,质量好开销大。

  • MSAA:多重采样,只对边缘多次采样,比 SSAA 快。

  • FXAA/TAA:后处理抗锯齿,屏幕空间做,性能好。

  • TAA temporal 时间上复用历史帧,开销低,现代引擎主流。

  1. 什么是画家算法,深度测试又是什么

答:画家算法:从远到近画物体,近的覆盖远的,不需要深度缓冲。排序开销大,穿插物体没法处理。 深度测试:每个像素存深度值,新片元深度比深度缓冲小才写入。不用排序,硬件加速,现在主流。

  1. 如何理解纹理映射

答:把 2D 纹理图贴到 3D 模型表面。每个顶点带 UV 坐标,光栅化插值得到每个像素 UV,采样纹理颜色。相当于给模型表面贴皮肤。

  1. 纹理在显示器中被放大会怎样,如何解决

答:放大像素不够,会模糊或方块感。过滤方式:邻近过滤有马赛克,线性过滤模糊。高级的用各向异性过滤,斜视角也清晰。

  1. 什么是贝塞尔曲线,如何实现

答:用控制点定义平滑曲线。一阶直线,二阶抛物线,三阶最常用。通过参数 t 插值控制点得到曲线上点。游戏里路径、动画曲线、字体轮廓大量使用。

  1. 为什么要学 LearnOpenGL

答:OpenGL 是最经典的图形 API,学习它能彻底理解渲染管线、着色器、缓冲、纹理、矩阵这些底层概念。懂了 OpenGL 再上手任何引擎渲染都很快。

  1. 如何理解 OpenGL 是一个巨大的状态机

答:OpenGL 维护一套全局状态,当前绑定的缓冲、纹理、着色器、混合模式都是状态。后续所有绘制都基于当前状态。设置一次一直生效,直到再修改。所以封装时要注意状态恢复。

  1. 如何理解 VAO、VBO 以及 IBO/EBO

答:

  • VBO:顶点缓冲对象,显存里存顶点数据。

  • EBO/IBO:索引缓冲,存顶点索引,减少重复顶点数据。

  • VAO:顶点数组对象,保存顶点属性配置(哪个属性对应哪个 VBO、偏移、步长)。绑定 VAO 就不用每次重设顶点格式。 绘制流程:绑 VAO → 绑着色器 → 传 uniform → draw。

  1. 什么是 GLFW

答:跨平台窗口库,负责创建窗口、处理输入、OpenGL 上下文。封装了平台差异,让 OpenGL 程序不用写平台相关窗口代码。

  1. 如何理解 NDC

答:标准化设备坐标。投影变换之后、透视除法之后的坐标空间,xyz 都在 -1,10,1 范围。和屏幕分辨率无关,方便硬件做视口变换转屏幕像素坐标。

  1. 什么是 GLSL,为什么着色器用这种语言来写

答:OpenGL 着色器语言,类 C 语法。专门为 GPU 并行执行设计,有内置向量矩阵类型、纹理采样函数。GPU 不执行 C++,必须用专门着色器语言写管线逻辑。

  1. 如何理解纹理采样中出现的各种问题

答:常见问题:

  • 缩小走样:用 mipmap + 三线性过滤。

  • 放大模糊:各向异性过滤。

  • 接缝:纹理包裹模式、边框像素处理。

  • 浮点精度:用更高精度纹理格式。

  1. LearnOpenGL 中具体如何生成纹理并应用纹理

答:生成纹理对象 → 绑定 → 传图像数据 → 设置过滤和环绕方式 → 着色器里声明 sampler2D → CPU 绑定纹理单元 → 着色器 texture 函数采样。

  1. 在 OpenGL 中如何从零实现一个 Phong 光照模型

答:顶点着色器传位置、法线到片元;片元着色器计算环境光、漫反射、高光三部分。漫反射 = 法线点乘光线方向 × 漫反射颜色。高光 = 反射方向点乘视线方向的 n 次方 × 高光颜色。三者相加就是最终颜色。

  1. 如何理解网格,在 OpenGL 中如何具体实现一个网格

答:网格 = 顶点数组 + 索引数组 + 材质纹理。OpenGL 里创建 VAO/VBO/EBO,上传顶点位置、法线、UV;绘制时 glDrawElements。

  1. 如何理解深度测试,有何作用

答:见第 6 题。

  1. 如何理解深度冲突,怎么解决

答:两个平面几乎重合,深度值精度不够区分,画面闪烁斑马纹。解决:拉开平面距离;提高深度缓冲精度(24 位改 32 位);多边形偏移;尽量把近平面推远,提升深度精度分布。

  1. 为什么调整近平面距离可以增大深度的精度

答:深度值是非线性分布,近平面附近精度高,远平面精度低。近平面太近,大量精度浪费在很近的地方,远处精度很差。适当拉远近平面,整体深度精度更合理,减少深度冲突。

  1. 如何理解模板测试,模板测试在深度测试之前执行吗

答:模板测试在深度测试之前。先过模板测试,再深度测试,最后写颜色。可以控制哪些像素写,哪些不写。描边、裁剪、镜面反射都靠它。

  1. 如何理解帧缓冲 FBO,为什么需要帧缓冲

答:离屏渲染目标,渲染结果不直接到屏幕,渲染到纹理里。可以做后处理、阴影贴图、反射、多次渲染。没有 FBO 很多高级效果做不了。

  1. 如何理解立方体贴图,为什么需要立方体贴图

答:六张正方形纹理组成一个立方体。从中心向六个方向看,对应六张图。用来做环境贴图、天空盒、点光源阴影。

  1. 如何理解几何着色器,有何作用

答:图元阶段着色器,输入完整图元,可以输出 0 或多个新图元。可以做几何体扩增、法线可视化、粒子生成。性能一般,用得不多。

  1. 实例化是什么,为什么可以支持大量数据计算,底层原理是什么

答:一次 DrawCall 绘制大量相同网格,每个实例有自己的变换矩阵。GPU 批量读取实例数据,顶点着色器区分不同实例。减少 DrawCall 和 CPU 提交开销。

  1. 如何理解 gamma 校正

答:显示器输出亮度和输入不是线性的,gamma 2.2 曲线。渲染在线性空间计算光照,最后做 gamma 校正输出到屏幕。不校正画面会偏暗,明暗过渡不对。

  1. 什么是 HDR

答:高动态范围,颜色值可以超过 1,模拟真实亮度范围。最后色调映射压到显示器 0-1 范围。亮处不会直接死白,暗处有细节,更真实。

  1. 法线贴图的概念是什么,底层原理呢

答:纹理存法线方向,不用增加面数就让低模表面有凹凸细节。切线空间法线,随模型方向变化。片元着色器采样法线,替换顶点法线参与光照计算。

  1. 泛光的概念是什么

答:Bloom,亮的区域向周围扩散光晕。模拟相机镜头效果,增强光感。实现:提取亮部 → 高斯模糊 → 和原图叠加。

  1. 如何理解 SSAO

答:屏幕空间环境光遮蔽。只靠深度图,计算每个像素周围有多少被遮挡,遮挡多的地方变暗。模拟全局漫反射阴影,增加立体感,开销低。

  1. 什么是 ShaderLab

答:Unity 特有的着色器描述语言。组织 Shader 结构:Properties、SubShader、Pass。封装了渲染状态、标签、通道。

  1. HLSL 是什么

答:微软的高级着色器语言,Unity、UE 都用。类 C 语法,编译成各平台着色器字节码。写具体的顶点、片元逻辑。

  1. FrameDebugger 是什么,有何作用

答:Unity 调试工具,逐帧抓取所有 DrawCall。可以看每个 DrawCall 渲染结果、材质、状态。定位渲染问题、合批问题非常好用。

  1. 如何理解 Nanite 技术

答:UE5 虚拟几何体技术。超高精度模型也能实时渲染,自动流式加载细节,远处自动简化。不用手工做 LOD,影视级模型直接进引擎。

  1. VSM 又是什么

答:方差阴影贴图。存深度和深度平方,模糊之后仍然可以正确比较深度。支持预过滤,软阴影效果好,不用 PCF 多次采样。缺点容易漏光。

  1. 什么是 HLOD,和传统 LOD 的区别

答:层级 LOD。远处把多个小物体合并成一个大网格,减少 DrawCall。传统 LOD 是单个物体降级,HLOD 是一组物体合并。大世界场景必备。

  1. 如何阻止一个类被实例化

答:构造函数私有;抽象类纯虚函数;删除构造函数。单例、工具类常用。

  1. 什么是软光栅化

答:纯 CPU 实现光栅化,不用 GPU。模拟整个渲染管线,从顶点变换到像素填充。学习渲染原理用,实际项目不用。

  1. 可以大体介绍一下软光栅化管线的内容吗

答:顶点变换 → 图元装配 → 裁剪 → 屏幕映射 → 光栅化 → 深度测试 → 片元着色 → 帧缓冲。和硬件管线逻辑一致,全部软件模拟。

  1. 光栅化的原理、作用和意义是什么

答:见第 3 题。

  1. 深度缓存是什么,如何实现的

答:每个像素存深度值的缓冲。绘制新像素先比较深度,更近才更新颜色和深度。硬件原生支持,解决物体遮挡问题。

  1. 什么是贝塞尔曲线

答:见第 9 题。

  1. 如何基于计算着色器来优化 SSAO/Bloom 等后处理效果

答:后处理天然并行,每个像素独立。计算着色器分工作组并行处理,比传统像素着色器更灵活,还可以做共享内存优化。

  1. 如何在 OpenGL 中实现 GPU 实例化

答:glDrawArraysInstanced;顶点属性设置实例化步进;着色器里 gl_InstanceID 区分实例。实例数据可以存 UBO 或者实例化顶点属性。

  1. ShaderLab、HLSL、Surface Shader、Shader Graph 关系与取舍

答:ShaderLab 是 Unity 外壳组织格式;HLSL 是写着色逻辑的语言。Surface Shader 是高层封装,自动处理光照。Shader Graph 可视化节点。 取舍:快速美术效果用 Shader Graph;复杂自定义光照手写 HLSL;通用 PBR 表面效果 Surface Shader 省代码。

  1. Shader 结构:Properties、SubShader、Pass、Tags

答:

  • Properties:材质面板参数。

  • SubShader:一套渲染方案,按优先级选第一个兼容的。

  • Pass:一次绘制通道,包含着色器代码和渲染状态。

  • Tags:元数据,告诉引擎渲染类型、队列、顺序。控制渲染排序、剔除、阴影。

  1. IBL 环境光照采样、反射/折射、Fresnel 近似 Schlick 如何实现

答:IBL:预计算环境光辐照度图和预过滤环境图,运行时采样。菲涅尔用 Schlick 近似:F = F0 + (1-F0)*(1-cosθ)^5。反射反射向量采样环境贴图;折射用折射向量。

  1. 漏斗算法和贝塞尔曲线的底层原理

答:漏斗算法:路径通道两侧像漏斗口,逐步收紧,得到最短平滑路径。NavMesh 寻路后做路径平滑。 贝塞尔曲线:控制点插值生成平滑曲线。见第 9 题。

  1. GPU 实例化的根据是什么

答:相同网格、相同材质,只有位置/缩放/旋转等参数不同。一次提交网格和一批实例参数,GPU 批量绘制。减少 CPU 提交次数,DrawCall 从 N 变 1。

  1. UE 中打包 Packaging 和烘焙 Cooking 的区别

答:见第 390 题。


UE

  1. 能大致介绍一下 UE 的地形系统吗

答:UE 内置地形系统,高度图 + 图层混合。支持大尺度地形、多层地形材质、植被、碰撞。支持世界分区、距离场、LOD。适合大世界开放世界地形。

  1. 什么是一 Actor 一文件

答:World Partition 模式下,每个 Actor 存单独文件。多人协作可以各自改不同 Actor,合并冲突少。传统关卡所有 Actor 存一个关卡文件,容易冲突。

  1. UE 提供了哪些水体 Actor,如何使用,有何区别

答:

  • 海洋水体:大面积海洋,波浪、泡沫、次表面散射。

  • 河流:样条定义河道,流动效果。

  • 湖泊:静态水面。 区别在于适用场景和流动模拟,都基于水体系统统一材质。

  1. 可以介绍 Data Layers、Level Instance 的概念吗

答:Data Layers 数据层:把 Actor 分到不同层,可以加载卸载指定层。 Level Instance:关卡实例,把关卡作为资源实例化放进别的关卡,复用关卡内容。

  1. 虚幻引擎的 MassEntity 是什么东西

答:Mass 是 UE 的 ECS 框架。面向数据,大量实体批处理。适合群体 AI、粒子、植被这种数量巨大的对象。比传统 Actor 轻量得多。

  1. 如何优化 UE5 的垃圾回收

答:减少 UObject 数量;减少引用复杂度;分帧 GC;增量 GC;对象池复用,不频繁创建销毁;远离蓝图频繁生成对象。

  1. 如何将 Enhanced Input System 与 GAS 结合

答:增强输入触发输入动作,GAS 监听输入事件激活对应的 GameplayAbility。输入动作和技能解耦,通过标签或 ID 映射。

  1. 如何让 AI 角色使用 GAS 系统

答:AI Controller 拥有 ASC 能力系统组件;行为树任务节点激活技能;黑板传目标参数。和玩家共用同一套技能体系。

  1. PSO 预缓存是什么

答:渲染管线状态对象,第一次遇到新的着色器组合会编译,导致卡顿。预缓存就是提前编译好常用 PSO,运行时不编译,消除卡顿。

  1. UHT 是什么

答:Unreal Header Tool,虚幻头文件工具。扫描 C++ 头文件里的 UCLASS/UPROPERTY/UFUNCTION 宏,生成反射代码。支撑整个反射系统。

  1. 如何理解 MassEntity 和 Unity 的 ECS 是不是有些类似

答:思想一致,都是面向数据、组件化、System 驱动逻辑。Mass 是 UE 自己的实现,深度集成 UE 管线;Unity DOTS 是 Unity 的方案。核心理念相同,API 和生态不同。

  1. GAS 内部有哪些组成部分,工作流程是怎么样的

答:核心组件:Ability System Component、AttributeSet、GameplayAbility、GameplayEffect、GameplayTag。 流程:激活能力 → 执行技能逻辑 → 应用 GameplayEffect 修改属性 → 标签状态变化 → 回调通知。

  1. 虚幻引擎中如何销毁一个 Actor

答:调用 Destroy()。不是立刻销毁,标记成待销毁,本帧结束时真正回收。网络环境下服务端销毁自动同步客户端。

  1. 虚幻引擎中如何实现生成 Actor

答:World->SpawnActor()。指定位置、旋转、模板。服务端生成会复制到客户端。

  1. UE 中的 Actor、Pawn、Character 分别是什么,有何联系

答:Actor 是场景实体基类。Pawn 是可以被控制的 Actor。Character 是 Pawn 子类,带角色移动、碰撞、人形特性。继承关系:Actor → Pawn → Character。

  1. PSO 缓存是什么,在虚幻引擎中如何使用

答:见第 9 题。项目设置里开启 PSO 缓存,收集常用 PSO,打包时预编译。

  1. UE 中的类继承关系

答:UObject 是所有对象基类,提供反射、GC、序列化。AActor 继承 UObject,可以放场景里。UActorComponent 挂在 Actor 上的组件。具体业务类再继承它们。

  1. UWorld 是什么,如何理解关卡和世界的概念

答:UWorld 是运行时的游戏世界,包含关卡、Actor、物理、渲染场景。一个 World 可以加载多个关卡。Level 是关卡资源文件,存关卡里的 Actor。

  1. 如何在 C++ 中创建自定义 UE 可使用的组件

答:继承 UActorComponent 或 USceneComponent。加 UCLASS 宏。实现 BeginPlay、Tick 等函数。编译后蓝图里就可以挂到 Actor 上。

  1. UE 中的 RPC 函数类型包括哪些

答:Server、Client、NetMulticast 三种。见第 281 题。

  1. UE 中宏的作用

答:反射宏让 UHT 生成元数据;委托宏声明委托;日志、断言宏提供调试能力。宏是 UE 扩展 C++ 能力的重要手段。

  1. UE 中宏如此重要的根本原因

答:C++ 原生没有反射,UE 靠宏 + UHT 工具生成反射代码,从而支撑蓝图、网络复制、序列化、编辑器集成。没有宏这套系统 UE 的很多特性都没法做。

  1. 如何在 UE 中实现群体行为

答:小规模用行为树 + 感知;大规模用 MassEntity + 群体避障 ORCA + 导航。数量级不同方案不同。

  1. Animation Budget Allocator、Actor Pooling、PSO 预缓存都是什么,有何作用

答:都是性能优化手段。动画预算控制动画 CPU 开销;对象池减少创建销毁开销;PSO 预缓存消除运行时着色器编译卡顿。

  1. 虚幻引擎中的 Actor 和 Actor 之间如何通信

答:直接引用调用函数;委托/事件分发;GameplayTag 广播;消息系统。尽量减少直接强引用,用事件解耦。

  1. 如何理解 Tick,TickGroup 又是什么

答:Tick 每帧执行的更新函数。TickGroup 是一帧内的执行顺序分组,比如 PrePhysics、DuringPhysics、PostPhysics。保证逻辑在物理前后正确执行。

  1. Sequencer 编辑器是什么,有何作用

答:过场动画编辑器。时间轴编辑镜头、动画、物体变换。做剧情过场、UI 动画、序列镜头。

  1. 什么是 UHT

答:见第 10 题。

  1. MassEntity 有什么用

答:见第 5 题。

  1. 为什么 MassEntity 这样的结构可以帮助我们实现大量个体

答:面向数据,内存连续,缓存友好;没有虚函数、对象开销;批量处理,SIMD 友好。传统 Actor 每个对象开销大,数量多了扛不住。

  1. UE 中的群体避障 Mass Avoidance 的底层原理

答:基于 ORCA 速度障碍算法,速度空间构建线性约束,求解最优速度。Mass 批处理大量 Agent,并行计算。

  1. 如何理解 GameState 和 GameMode

答:GameMode 管游戏规则,只存在服务端。GameState 管全局游戏状态,会同步给所有客户端。比如分数、游戏阶段存 GameState。

  1. 为什么 UE 需要网络复制

答:多人游戏要保持客户端状态一致。手动同步每个变量太麻烦,UE 提供自动复制机制,标记属性引擎自动同步,减少手写同步代码。

  1. Animation Budget Allocator、Actor Pooling、PSO 预缓存都分别是什么,如何在虚幻引擎中使用

答:见第 24 题。项目设置开启,配置参数即可。

  1. Oodle 是什么

答:Epic 自研压缩库。压缩率高解压快,用于资源压缩、网络包压缩。减少包体积和带宽。

  1. PSO 缓存是什么,有何作用

答:见第 9 题。

  1. Unreal Insights 是什么,有何作用

答:UE 性能分析工具。抓取 CPU、GPU、内存、渲染各项数据。比 Stat 命令更详细,定位性能瓶颈。

  1. Unreal Automation Tool 是什么

答:UAT 自动化工具。命令行执行打包、烘焙、测试、部署。CI/CD 自动打包用。

  1. ChunkDownloader 是什么

答:UE 热更下载组件。分块下载资源补丁,支持后台下载、断点续传。做热更新用。

  1. UE 中的 C++ 类和蓝图类有何区别

答:C++ 类原生编译,性能好,复杂逻辑写。蓝图类可视化,迭代快,简单逻辑、关卡逻辑写。蓝图可以继承 C++ 类扩展。

  1. UE 中 C++ 和蓝图是如何协同工作的

答:C++ 写底层核心系统,暴露函数、属性给蓝图。蓝图做上层逻辑、关卡编排、UI、动画状态机。性能关键路径 C++,灵活迭代部分蓝图。

  1. 项目中如何实现技能系统,使用了哪些 GAS 组件

答:基于 GAS:ASC 组件挂角色身上;AttributeSet 存属性;GameplayAbility 实现技能逻辑;GameplayEffect 处理伤害、buff、冷却;GameplayTag 标记状态。


Unity

  1. 为何选择客户端 Unity + 服务端 .NET/EF6 的组合,各自的优势与替代方案评估

答:Unity 客户端生态好,跨平台强,开发效率高。.NET 服务端和 C# 技术栈统一,共享代码方便,EF6 数据访问方便。替代方案:服务端 Go/C++ 性能更好,但技术栈不统一。中小团队 C# 全栈开发效率最高。

  1. 客户端资源组织与加载策略,Resources 的使用边界,为什么考虑 Addressables/AssetBundle

答:Resources 简单但所有东西打进一个包,没法热更,内存不好控。AB 包可以拆分、按需加载、热更新。Addressables 在 AB 之上做了更高级的依赖管理、引用计数、异步加载。项目规模大一定要上 Addressables 或 AB。

  1. 进程、线程和协程的区别

答:进程是资源分配单位,有独立地址空间。线程是调度单位,共享进程资源。协程是用户态轻量执行单元,单线程内协作式调度,没有线程切换开销,适合异步 IO、分帧任务。

  1. 进程线程协程的联系和区别

答:见第 3 题。

  1. 虚幻引擎有哪些委托,和 Unity 的委托相比有何区别

答:UE 有单播、多播、动态委托、事件。Unity 是 delegate、Action、UnityEvent。UE 委托更丰富,支持反射、蓝图绑定;Unity 更轻量。本质都是函数回调封装。

  1. 如何理解 Unity 中的 Sprite 和 Texture

答:Texture 是原始纹理资源。Sprite 是打包后的精灵,带 uv、边框、九宫格信息,专给 2D/UI 使用。Sprite 基于 Texture,增加了 2D 渲染所需元数据。

  1. Unity 的材质球是什么

答:材质 = 着色器 + 参数。指定用哪个 Shader,设置贴图、颜色、数值参数。同一个 Shader 可以有无数个材质实例。

  1. Unity 中的层级系统有哪些

答:Transform 父子层级;Layer 层,用于渲染、碰撞过滤;Sorting Layer 排序层,2D/UI 排序。

  1. Unity 提供的 PlayerPrefs 是什么,有何作用

答:本地持久化小数据,键值对,存在注册表/本地文件。适合存设置、简单存档。数据量不能大,不能存敏感数据,明文容易改。

  1. Unity 协程的底层原理是什么

答:基于迭代器 IEnumerator。MoveNext 推进到下一个 yield。主线程每帧检查协程等待条件,满足就继续执行。不是多线程,还是在主线程跑。

  1. Unity 实现跨平台的原理

答:抽象层封装平台差异。不同平台用不同渲染后端、不同输入、不同系统接口。C# 逻辑层跨平台,平台相关用原生插件。打包时编译对应平台 IL2CPP 或 Mono。

  1. 我们来聊聊 UGUI 的底层原理

答:UGUI 基于 Mesh 生成 UI 网格。Canvas 负责合批、重建。Graphic 组件生成顶点数据。事件系统射线检测、分发事件。修改 UI 顶点会触发 Canvas 重建。

  1. UGUI 的运作流程如何优化呢

答:流程:UI 元素变更 → 标记脏 → Canvas 重建网格 → 生成顶点 → 渲染。 优化:动静分离、减少重建;图集合批;减少 Mask;列表复用;避免频繁改顶点色。

  1. 具体如何预加载

答:Resources.LoadAsync 异步加载;Addressables 预加载;对象池提前实例化;场景加载时后台加载常用资源;进入场景前显示 loading 界面预加载关键资源。

  1. Sprite Atlas 是什么,有何作用

答:精灵图集,把多个小 Sprite 打包成一张大贴图。减少 DrawCall,同图集 UI 可以合批。减少纹理切换开销。

  1. 如何理解 UGUI 的合批,和渲染中的动态合批静态合批相比有何区别

答:UGUI 合批是 UI 网格合并,同材质同图集的 UI 合并成一个 Mesh 一次绘制。3D 合批是多个物体合并绘制。原理都是减少 DrawCall,只是应用对象不同。

  1. 如何理解 UGUI 的图集,有何作用

答:见第 15 题。

  1. Unity 的协程的底层原理

答:见第 10 题。

  1. Unity 中的客户端和服务器交互的方式有几种

答:HTTP 短连接;TCP 长连接;WebSocket;Unity WebRequest;第三方网络库。根据游戏类型选:回合制 HTTP,实时 TCP。

  1. Camera 组件的 ClearFlags 是什么

答:相机每帧清屏方式。天空盒、纯色、深度、不清空。多相机叠加渲染时控制清除哪些缓冲。

  1. 如何检测 Unity 中的文件发生变化

答:AssetPostprocessor 监听资源导入;FileSystemWatcher 监听磁盘文件。编辑器扩展常用。

  1. 可以大体上介绍一下 AB 包的概念和原理吗

答:AssetBundle 是 Unity 资源打包格式。资源打成 AB 包,运行时加载,不用打进主包。支持热更新、按需加载。依赖关系打包时记录。

  1. AB 包打包策略、打包加载流程、卸载 Unload true/false 差异

答:策略:按功能模块、按场景、按资源类型分包。 流程:加载 AB → 加载资源 → 实例化使用。 卸载:unload(true) 连加载出来的资源对象一起销毁;unload(false) 只卸 AB 包本身,已加载对象还在。一般用 false,资源不用了再单独销毁。

  1. Unity 中常用的压缩算法

答:纹理压缩:ETC、ASTC、PVRTC。AB 包压缩:LZ4、LZMA。网格压缩。不同平台对应不同压缩格式。

  1. 在 Unity 中具体如何使用 GPU 蒙皮呢

答:开启 GPU Skinning 选项;顶点着色器接收骨骼矩阵,计算顶点位置。大量角色时减轻 CPU 蒙皮负担。

  1. 如何创建一个有限状态机

答:定义状态基类,每个状态实现 Enter/Update/Exit。状态机管理当前状态,切换状态时调用 Exit + Enter。适合 AI、技能、UI 状态。

  1. AssetModificationProcessor 是什么

答:Unity 编辑器类,监听资源创建、删除、移动、保存事件。做自定义资源导入处理、资源校验。

  1. AB 包底层原理

答:序列化资源,记录依赖。运行时加载反序列化,创建资源对象。依赖链加载,引用计数管理。

  1. 和 Unity 相比呢(UE 对比)

答:Unity 上手快、生态好、2D/手游强;UE 画质强、原生 3A 管线、大世界工具链全、蓝图强大。小团队手游选 Unity,3A 画质大世界选 UE。

  1. 状态机和行为树的区别

答:状态机适合少状态、切换明确的逻辑。行为树适合复杂 AI,条件嵌套多,可视化编辑。状态机性能好,行为树表达能力强。

  1. 虚幻引擎是如何实现跨平台的

答:和 Unity 思路类似,抽象层封装平台差异。各平台有独立 RHI 渲染后端、平台接口层。UBT 编译对应平台代码。

  1. Unity 的 Shader 是什么

答:着色器程序,跑在 GPU 上。控制顶点变换、像素颜色计算。决定物体怎么渲染。

  1. GPU Instancing 与 SRP Batcher 是什么,如何在 Unity 的 Shader 中使用

答:

  • GPU Instancing:同网格同材质大量实例,一次 DrawCall。

  • SRP Batcher:SRP 管线批处理,合并不兼容材质但相同 CBuffer 结构的 DrawCall,减少设置开销。 使用:Shader 支持 Instancing 关键字;材质开启对应选项。

  1. 如何理解 Unity Shader 中的变体与关键词管理

答:关键字开关不同功能,编译出多个变体。功能多了变体爆炸,打包慢、内存大。优化:精简关键字,按功能剥离不用的变体。

  1. 状态机和行为树的区别,为什么使用状态机,如何实现

答:见第 30 题。

  1. 在 Unity 中想在协程里退出协程怎么中断

答:yield break; 直接结束。或者 StopCoroutine 停止指定协程。

  1. Unity 中有哪些方法减少 GC 调用

答:对象池复用;少字符串拼接;少装箱拆箱;避免 foreach 分配;用 struct 代替 class;缓存数组;减少匿名函数捕获。

  1. 能大致介绍一下 AB 包吗,它的底层原理

答:见第 22、28 题。

  1. Update 和 FixedUpdate 在游戏循环中有什么区别,各自适用场景

答:Update 每帧一次,帧率不固定,适合逻辑、输入、摄像机跟随。FixedUpdate 固定时间步长,和物理同步,适合物理、刚体操作。

  1. 自己设计一个简单的事件系统,核心结构是什么

答:事件类型枚举/类;委托字典存事件对应的回调列表;注册、注销、触发三个接口。触发时遍历调用回调。注意注销防止内存泄漏。

  1. 烘焙 Baking 是什么

答:离线预计算运行时昂贵的计算。光照烘焙、导航网格烘焙、遮挡剔除烘焙。运行时直接读结果,省性能。

  1. Camera 的 ClearFlags 的含义是什么

答:见第 20 题。

  1. Unity 跨平台的原理

答:见第 11 题。


C# / .NET / Mono

  1. int a=b 和 a++ 是线程安全的吗

答:都不是。赋值在 32 位下 long/double 都不是原子的;a++ 是读改写三步,多线程下会丢数据。要线程安全用 Interlocked 或者 lock。

  1. string 的 Size() 和 Length() 的区别是什么

答:C# string 用 Length 属性。没有 size()。Length 是字符数,不是字节数。字节数要 Encoding.GetByteCount。

  1. 如何理解委托构造函数和转换构造函数

答:C# 没有转换构造函数的说法,类似的是隐式转换运算符。委托构造函数就是构造函数调用同一个类的另一个构造函数,用 :this() 语法,减少重复代码。

  1. trivial destructor 是什么

答:平凡析构函数,编译器自动生成的,什么都不用做的析构。值类型、没有非托管资源的类都是平凡的。GC 回收更快。

  1. 多进程和多线程相比有何区别,如何保障安全

答:进程地址空间独立,崩溃不影响彼此,通信麻烦开销大。线程共享进程内存,通信方便,一个崩了整个进程挂。安全:进程用 IPC;线程用锁、原子操作、线程安全集合。

  1. 什么时候用多线程什么时候用多进程

答:同程序内并行计算用多线程。需要隔离、稳定性要求高、调用第三方不稳定库用多进程。

  1. 你的系统在多线程环境下会有哪些问题,怎么解决

答:竞态条件、死锁、线程安全。解决:锁保护共享数据;用线程安全集合;避免嵌套锁防死锁;尽量减少共享状态,用消息传递。

  1. 如何实现异步线程池

答:CLR 自带线程池 Task.Run。自己实现就是队列 + 工作线程,任务入队,空闲线程取任务执行。控制最大线程数,避免过度调度。

  1. 如何理解 string 中的 \0

答:C# string 不是以 \0 结尾,Length 记录长度。字符串里可以包含 \0 字符,不影响长度。和 C 字符串不一样。

  1. 如何理解 C# 中的 Attribute 特性

答:给类型、方法、字段加元数据。反射可以读取特性。用来做配置、标签、AOP、序列化控制。

  1. C# 的 unsafe 是什么,有何作用

答:不安全代码,可以用指针、直接操作内存。性能敏感、和原生代码交互时用。脱离 GC 管理,容易出内存错误,谨慎使用。

  1. .Net 和 Mono 的关系是什么

答:.NET 是微软官方框架。Mono 是跨平台开源实现,兼容 .NET 接口。Unity 用 Mono 做脚本运行时,支持多平台。

  1. foreach 底层

答:依赖 IEnumerable/IEnumerator。调用 GetEnumerator 拿到枚举器,MoveNext 推进,Current 取当前值。遍历过程中修改集合会抛异常,因为版本号校验。

  1. 为什么 foreach 是只读循环,修改元素会抛异常

答:枚举器有版本机制,集合修改版本号变了,枚举器就失效抛异常。防止遍历过程中集合结构变化导致不可预期行为。

  1. 进程中的文件描述符表是什么,有何作用,线程中有吗

答:Linux 下每个进程有文件描述符表,记录打开的文件、socket、管道。线程共享同一个进程的文件描述符表,不是每个线程一份。

  1. .Net 是什么,Mono 又是什么,MonoBehavior 又是什么

答:.NET 微软 .NET 平台。Mono 跨平台 .NET 实现。MonoBehaviour 是 Unity 脚本基类,继承它的脚本可以挂 GameObject,有生命周期函数。

  1. C# 中的 const 和 C++ 的完全不同

答:C# const 是编译期常量,必须声明时赋值,等价于 C++ constexpr。C# readonly 是运行时只读,构造函数赋值,等价于 C++ 的 const 成员。

  1. C# 中的 string 为什么具有不可修改性

答:字符串不可变,修改字符串其实是新建一个。好处:可以字符串驻留共享;线程安全;可以做哈希键。坏处频繁拼接产生 GC,要用 StringBuilder。

  1. 为什么会有 GC 开销

答:托管堆分配对象,不用手动释放,GC 自动回收。但 GC 扫描、压缩、终结器都有开销。频繁分配小对象会增加 GC 频率,造成卡顿。

  1. 进程和线程的同步方式有何不同

答:进程同步:信号量、共享内存、消息队列、socket。线程同步:lock、Monitor、Mutex、Semaphore、ReaderWriterLock。

  1. 虚幻引擎的委托系统是什么

答:UE 原生 C++ 委托,DECLARE_DELEGATE 宏声明。支持单播多播、绑定 UObject、线程安全。比 C# 委托更底层。

  1. 进程和线程区别

答:见第 3、5 题。

  1. C# 结构体和类的区别,结构体可以继承吗

答:struct 值类型,栈上分配,不能继承,不能有默认构造。class 引用类型,堆上分配,可以继承。小的轻量数据用 struct,复杂对象用 class。

  1. 单例的两种初始化方式(饿汉、懒汉)的线程安全性如何

答:饿汉静态初始化,CLR 保证线程安全。懒汉第一次访问才创建,要加锁或者 Lazy 保证线程安全。

  1. 知道 C# 的静态构造函数吗

答:静态构造函数,类第一次使用前自动调用,只执行一次。初始化静态成员。CLR 保证线程安全。

  1. 标记清除 GC 可以在多线程中运行吗

答:可以。.NET GC 有多线程回收,并发 GC,后台回收不阻塞主线程。标记阶段可以多线程并行标记。

  1. shared_ptr 线程安全吗

答:引用计数增减是原子的,线程安全。但对象本身的读写不是线程安全的。多线程同时修改对象还是要加锁。

  1. 同一个变量怎么保证线程可见性,不同线程之间可以共享什么

答:volatile 保证可见性,禁止寄存器缓存。线程共享进程堆内存、全局变量、静态变量。栈内存线程私有。

  1. string 和 char* 有何区别,为何要特意多实现一个 string 类

答:char* 是原始指针,手动管理内存,容易越界泄漏。string 封装了长度、内存管理、常用方法,安全易用。C# string 还有不可变、驻留等高级特性。

  1. 虚幻引擎中哪些内容可以被 GC 识别并回收,虚幻引擎的 GC 算法是什么

答:UObject 及其子类由 UE GC 管理。标记清除算法,定期扫描引用链,没有引用的对象回收。原生 C++ 对象不归 GC 管。

  1. C# 中的四种委托

答:Action 无返回值;Func 有返回值;Predicate 布尔判断;Comparison 比较。还有自定义 delegate。

  1. C# 中的 property 是什么

答:属性,封装字段。get/set 访问器,可以加逻辑。外部看起来像访问字段,内部是方法。

  1. C# 中怎么创建线程

答:new Thread;Task.Run;线程池;BackgroundWorker。现在一般用 Task 异步模型。

  1. C# 中的类是单继承还是多继承

答:类只能单继承,但可以实现多个接口。接口支持多继承。

  1. C# 的 const 和 readonly 和 C++ 的 constexpr 和 const 的关系

答:C# const ≈ C++ constexpr,编译期常量。C# readonly ≈ C++ const 成员,运行时只读,构造时赋值。不能完全对应,语言设计不同。

  1. C# 值类型和引用类型具体区分方式,为什么不同

答:值类型:栈/内联分配,值拷贝,生命周期随作用域。引用类型:堆分配,引用拷贝,GC 回收。设计目的:小数据值类型高效,复杂对象引用类型灵活。

  1. C# 中的匿名方法的含义

答:内联定义的方法,不用单独起名。配合委托使用。Lambda 是更简洁的匿名方法。

  1. C# 中的 using 有哪些用法

答:引用命名空间;using 语句自动释放 IDisposable 对象,作用域结束自动 Dispose,类似 RAII。

  1. C# 的托管堆是什么,Mono 内存又是什么

答:托管堆是 CLR/Mono 管理的堆内存,分配对象,GC 回收。Mono 是 Unity 用的运行时,自己实现托管堆和 GC。

  1. .Net 和 Mono 的关系

答:见第 12 题。


系统 / 网络 / OS

  1. DNS 域名解析以什么协议为主,为什么

答:查询为主 UDP 53,快、开销小。响应超过 512 字节或者区域传送用 TCP。

  1. DNS 负载均衡是什么

答:DNS 返回多个 IP,轮流分配用户到不同服务器。简单实现流量分摊,缺点是不能感知服务器状态,粒度粗。

  1. 什么是 ARP 和 RARP

答:ARP 地址解析协议,IP 地址转 MAC 地址。RARP 反向,MAC 转 IP。局域网内寻址用。

  1. HTTP 是安全的吗

答:明文传输,不安全。内容可以被窃听、篡改、冒充。HTTPS 加 TLS 才安全。

  1. HTTP 中的缓存方案

答:强缓存:Expires、Cache-Control,有效期内直接用本地缓存。协商缓存:Last-Modified/If-Modified-Since、ETag/If-None-Match,服务器判断是否命中缓存。减少重复传输。

  1. TCP 的封包和拆包是什么

答:见粘包拆包第 119 题。

  1. 如何理解 brk 和 mmap

答:brk 扩展堆顶指针,分配小内存。mmap 映射文件或匿名页,分配大内存。malloc 底层小的 brk,大的 mmap。

  1. 如何生成缓存键,为什么使用量化位置

答:位置是浮点数,直接当键精度太高没法命中缓存。量化到网格,同一格子位置键相同,提高缓存命中率。

  1. 缓存淘汰策略是什么,LRU 和频率如何结合

答:缓存满了按策略删旧数据。LRU 删最久没用的。LFU 删使用频率最低的。结合的如 LFU-Aging、ARC,兼顾时间和频率。

  1. 缓存持久化如何实现,文件格式设计

答:定期快照写入磁盘;增量日志。格式:头部元信息 + 数据块。二进制格式快,JSON 可读。游戏一般二进制。

  1. 为什么选择 TCP + ProtoBuf 而不是其他网络架构

答:TCP 可靠有序,游戏状态同步需要可靠。Protobuf 体积小速度快,跨语言。组合稳定高效,游戏后端主流。

  1. 被 free 回收的内存会立刻返还给操作系统吗

答:不会。free 还给进程内的内存分配器,由分配器管理。可能还在进程地址空间里,下次 malloc 直接复用。大内存 mmap 的 free 可能直接归还。

  1. C++ 中有哪些分配内存的手段,只允许栈上分配怎么做

答:malloc/new/栈分配/allocator。只栈分配:把 operator new 设为 delete 或私有,不能 new。

  1. malloc 的底层

答:glibc ptmalloc。小内存 brk 扩展堆,自由链表管理;大内存 mmap 映射。分配器管理空闲块,合并碎片。

  1. 守护进程、僵尸进程和孤儿进程分别是什么

答:

  • 守护进程:后台运行,脱离终端,长期运行。

  • 孤儿进程:父进程先死,子进程被 init 收养。

  • 僵尸进程:子进程结束,父进程没 wait,PCB 残留,占进程号。

  1. 如何理解操作系统中的中断和异常

答:中断:外部硬件事件,异步。异常:指令执行出错,同步,比如缺页、除零。都是打断当前执行,进内核处理。

  1. 程序从堆中分配内存时虚拟内存有何变化

答:虚拟地址空间分配新页,建立页表映射。物理内存可能延迟分配,真正访问时才分配物理页(缺页中断)。

  1. PCB 进程控制块是什么

答:内核里描述进程的数据结构。存进程状态、寄存器、页表、打开文件、信号等信息。调度的基本单位。

  1. DNS 是什么

答:域名系统,域名转 IP。分布式层级数据库。

  1. 为什么 TCP 需要第三次握手

答:双方都确认收发能力正常;防止历史过期连接请求干扰。两次握手无法确认对方接收能力,也没法区分新连接和旧连接。

  1. 使用 socket 建立完整的 TCP 网络架构是如何实现的

答:服务端 socket-bind-listen-accept 循环;客户端 socket-connect;IO 多路复用管理连接;协议编解码;业务逻辑分发。

  1. 瓦片缓存系统只有 1GB 内存但地图 100 万个瓦片怎么处理

答:LRU 淘汰,只存热点瓦片;分级加载,低精度常驻,高精度按需加载;压缩瓦片数据;超出部分磁盘缓存。

  1. 如何处理物品的堆叠和分解

答:堆叠:同 ID 物品堆一起,记录数量。分解:拆分数量,生成新物品实例。背包格子有限时注意满格处理。

  1. 为什么栈比堆快

答:栈分配就是移动指针,O(1);堆要找空闲块,还有分配器开销。栈连续,缓存友好;堆容易碎片。栈自动释放,不用 GC/手动释放。

  1. 类如何禁止分配在栈上或者堆上

答:禁止栈:析构私有,只能 new 不能栈对象。禁止堆:operator new delete 私有化,只能栈分配。

  1. 进程同步是什么,有哪几种方法

答:多进程协调顺序。方法:信号量、共享内存+互斥、消息队列、套接字。

  1. 守护进程、僵尸进程和孤儿进程分别是什么

答:见第 15 题。

  1. 页面置换又是什么,有哪些常见的算法

答:内存不足时,换出不常用页到磁盘。算法:FIFO、LRU、Clock、OPT。

  1. DNS 是什么,工作原理

答:见第 19 题。递归查询 + 迭代查询,本地缓存 → 根域名 → 顶级域 → 权威域。

  1. 什么是 TCP 粘包/拆包

答:见第 119 题。

  1. 面向数据的技术栈 DOTS 为什么可以实现大规模实体模拟

答:数据连续内存,缓存友好;System 批量处理,SIMD 友好;没有对象头、虚函数开销;并行易。

  1. 大端小端

答:大端:高位字节存低地址。小端:低位字节存低地址。x86 小端,网络字节序大端。跨平台传输要注意字节序转换。

  1. 如何理解僵尸进程和孤儿进程

答:见第 15 题。

  1. malloc 的底层原理

答:见第 14 题。

  1. 如何理解段错误

答:访问非法内存,内核发 SIGSEGV。原因:空指针、越界、野指针、写只读内存。gdb coredump 定位。

  1. TCP 和 UDP 的区别

答:TCP 面向连接、可靠有序、流量控制、字节流。UDP 无连接、不可靠、数据报、快。游戏状态同步 TCP,实时语音、帧同步 UDP。

  1. TCP 如何保证可靠性

答:确认应答、超时重传、序列号、流量控制、拥塞控制、差错校验。多方面保证数据可靠有序到达。

  1. 什么是 IO 多路复用

答:一个线程监听多个文件描述符,就绪了才处理。select/poll/epoll。高并发服务必备,不用每个连接一个线程。

  1. UE 的派生数据缓存 DDC 是什么

答:Derived Data Cache。缓存派生数据,比如压缩纹理、编译后的着色器、烘焙数据。第二次用直接读缓存,不用重新计算,加快导入和打包。

  1. TCP + ProtoBuf 网络通信框架为什么采用,优点是什么,怎么实现

答:TCP 可靠,Protobuf 高效。实现:连接管理、粘包处理、序列化、消息分发、心跳、重连。

  1. new 的对象可以用 free 来释放吗

答:不可以。new 调用构造,free 不会调用析构。而且分配器可能不同,直接 UB。new 配 delete,malloc 配 free。

  1. 被 free 回收的内存立刻返回给操作系统吗

答:见第 12 题。

  1. 如何理解 brk, mmap 和 munmap

答:见第 7 题。brk 改堆边界;mmap 映射内存/文件;munmap 解除映射。

  1. CPU 缓存的工作原理是什么

答:基于局部性原理。CPU 访问数据先查缓存,命中直接用,不命中去内存加载。多级缓存 L1/L2/L3,容量递增速度递减。

  1. 堆排和快排都不稳定,为什么拿堆排作为快排的替代

答:快排最坏 O(n²),堆排最坏也是 O(nlogn)。递归过深切换堆排,保证最坏情况不会太差。内省排序就是这么做的。

  1. 中断和轮询是什么

答:轮询:CPU 不断查状态,CPU 占用高,响应慢。中断:硬件主动通知 CPU,平时 CPU 可以干别的,响应快。


算法 / 寻路 / 数据结构

  1. 请详细解释 A* 算法的核心原理和启发式函数的作用

答:A* = Dijkstra + 贪心启发。f = g + h,g 起点到当前真实代价,h 当前到终点估计代价。每次选 f 最小的节点扩展。 启发式作用:引导搜索方向,减少扩展节点数,加速搜索。h 小于等于真实代价保证最优。

  1. A* 中的开放列表和关闭列表分别存储什么

答:开放列表:待扩展的候选节点,按 f 值排序。关闭列表:已经处理过的节点,不再重复处理。

  1. 为什么选择二叉堆作为开放列表的数据结构

答:取最小值 O(1),插入删除 O(logn)。实现简单,常数小。适合 A* 频繁取最小、插入新节点的操作。

  1. 二叉堆和跳表在 A* 算法中的性能对比如何

答:二叉堆取最小最快,但是查找指定节点慢。跳表查找、更新、删除都 O(logn),但常数比堆大。A* 主要取最小,堆更合适。

  1. 如何实现一个高效的二叉堆,插入和删除操作的时间复杂度

答:数组存完全二叉树。插入放尾部,向上调整 O(logn)。删除堆顶,尾部换上来,向下调整 O(logn)。

  1. 斐波那契堆相比二叉堆有什么优势,为什么没有选择它

答:理论上 decrease-key O(1),A* 更快。但实现复杂,常数大,实际工程不一定比二叉堆快。工程上二叉堆简单够用。

  1. 分层 A* 算法的原理是什么,什么情况下使用

答:先在粗粒度大图上找粗路径,再细化局部精细路径。大地图、全局寻路用,减少搜索节点数。

  1. 双向 A* 如何实现,相比单向 A* 有什么优势

答:起点和终点同时向对方搜索,相遇就得到路径。搜索空间大概是单向的一半,速度更快。大地图明显。

  1. 如何评估 A* 算法的优化效果,你用了什么指标

答:指标:扩展节点数、搜索耗时、路径长度、内存占用。对比不同启发式、不同数据结构的差异。

  1. 什么是 NavMesh,相比传统网格寻路有什么优势

答:导航网格,把可行走区域表示成多边形。比网格更贴近真实地形,节点少,路径平滑,支持不同高度。3D 游戏主流。

  1. NavMesh 的瓦片化 Tiling 原理是什么

答:大地图切成瓦片,每瓦单独生成导航网格。只加载附近瓦片,内存小,支持大世界。寻路可以跨瓦片。

  1. 如何检测 NavMesh 的哪些部分需要更新

答:障碍物包围盒和瓦片求交,受影响的瓦片标记脏。只重建脏瓦片,不全图重建。

  1. NavMesh 生成的具体步骤是什么,从体素化到最终网格

答:体素化 → 高度场 → 腐蚀半径 → 区域划分 → 轮廓提取 → 轮廓简化 → 生成凸多边形 → 生成细节网格。

  1. ORCA 算法相比 VO 算法有什么改进

答:VO 只有不碰撞保证,运动不平滑,容易震荡。ORCA 互惠速度障碍,双方各承担一半避让责任,运动更平滑自然,群体效果更好。

  1. 50% 责任分担在 ORCA 中如何实现

答:相对速度的禁区对半分,各自向自己一侧修正速度。最后合成的相对速度刚好落在边界上,双方各让一步。

  1. ORCA 中的线性约束是如何构建的

答:根据相对位置、相对速度、半径、时间窗,计算出速度空间中一条直线作为边界,一侧安全一侧碰撞。每个障碍物对应一条半平面约束。

  1. 漏斗算法 Funnel Algorithm 的原理是什么

答:路径通道两侧像漏斗边,从起点开始逐步收紧漏斗,遇到拐点更新顶点,最后得到最短的平滑路径。NavMesh 寻路后平滑常用。

  1. 如何优化多个 Agent 同时寻路的性能

答:分层寻路;路径缓存复用;分帧计算,不要一帧全部算;Job 系统并行寻路;远处低频寻路,近处高频。

  1. 群体寻路中的死锁问题如何解决

答:局部避让加随机扰动;加入等待机制;重新规划路径;优先级高的先行。避免互相堵住都不动。

  1. 全局路径缓存的键值对设计是什么

答:起点 + 终点哈希作为键,路径数据作为值。加版本号、过期时间。地形变化失效对应缓存。

  1. 群体寻路的优化效果如何量化

答:平均寻路耗时、帧率、CPU 占用、路径质量、碰撞次数。对比优化前后数据。

  1. 如何管理大量的路径数据

答:LRU 缓存;路径压缩;过期自动清理;复用相同路径。

  1. 如何处理寻路失败的情况

答:回退到就近可达点;走直线 + 局部避障;降低寻路频率;日志记录失败原因。

  1. ORCA 算法中约束是如何构建的,时间复杂度是多少

答:每个障碍物构建一条线性约束,O(n) 个约束。求解线性规划找可行解。Agent 数 n,每帧 O(n²) 两两计算。

  1. VO、RVO 和新算法三者的区别

答:VO 速度障碍,只保证不碰,不平滑。RVO 互惠速度障碍,双方避让,更平滑。ORCA 是 RVO 改进版,线性约束,求解更快更稳定。

  1. 如何基于多边形序列获取到具体的路径点

答:多边形序列是通道,用漏斗算法从通道里提取折线路径点,再平滑得到最终路径。

  1. 为什么需要降噪,路径追踪的原理是什么,为何会产生噪点

答:路径追踪随机采样,样本少的时候方差大,画面有噪点。增加采样数可以降噪,但慢。降噪算法用 AI 或者滤波,少样本也能得到干净画面。

  1. 光线追踪和路径追踪的区别是什么

答:光线追踪是大类,从相机发射光线求交。路径追踪是光线追踪的一种,每条路径随机采样,蒙特卡洛方法求解全局光照。

  1. 如何实现不同体型 AI 的差异化寻路,飞行单位 vs 地面单位

答:不同半径、不同高度生成不同导航网格;或者同一导航网格,查询时用不同半径参数。飞行单位可以无视地形障碍,走直线路径。

  1. 客户端和服务器的导航网格如何保持同步,动态建筑如何影响其他玩家 AI

答:服务端权威,服务端更新导航网格,同步给客户端。动态障碍物事件广播,各端各自更新本地导航。

  1. 如何理解光线追踪和路径追踪

答:见第 28 题。

  1. 针对路径追踪如何降噪

答:空间滤波:高斯、双边滤波。时域复用:TAA 累积历史帧。AI 降噪:深度学习模型预测干净图像。

  1. 如何改进路径平滑算法和避障算法

答:平滑:B 样条、贝塞尔、Catmull-Rom。避障:ORCA + 速度平滑,加阻尼,避免突变。

  1. 为什么 VO 和 ORCA 的速度禁区形状不同

答:VO 是几何碰撞直接推导,圆形禁区。ORCA 是互惠分摊,线性化约束,半平面禁区。建模思路不同,形状不同。

  1. 如何理解 NavMesh 的全局缓存,怎么实现

答:起点终点哈希存路径。LRU 淘汰过期。地形变化失效相关缓存。相同路径直接复用,不用重算。

  1. 如何设计一个高效的启发式函数

答:保证可采纳(不高估);尽量接近真实代价;计算要简单。网格用欧几里得、曼哈顿。NavMesh 用直线距离。

  1. 如何处理寻路算法的边界情况

答:起点终点不可达、在障碍里、刚好在边界、跨瓦片、部分阻挡。容差处理、就近吸附、失败回退。

  1. 如何实现全局路径缓存,大规模场景中如何优化

答:按区域分片缓存;分层缓存,全局粗路径 + 局部细路径;LRU + 过期失效;并行计算 + 分帧。


场景题 / 智力题

  1. A* 在大型地图中的性能瓶颈是什么,如何解决

答:瓶颈:节点扩展太多、开放列表维护慢、重复搜索多。 解决:分层寻路、JPS 跳点、更好的启发式、路径缓存、空间分区剪枝、并行寻路。

  1. 1000 个 NPC 同时寻路性能瓶颈在 CPU 还是 GPU,如何解决

答:主要 CPU 瓶颈,寻路是逻辑计算。 解决:分层寻路、分帧计算、Job 系统并行、路径复用、远处低频寻路、局部避障替代全图寻路。

  1. 三人三鬼过桥问题怎么解

答:约束:船上人数 ≤2,任何一岸鬼不能比人多。 策略:先运两鬼过去,一鬼回来;再运两鬼过去,一鬼回来;两人过去,一人一鬼回来;两人过去,一鬼回来;剩下逐步运鬼。

  1. 25 匹马 5 条赛道找最快 3 匹怎么做

答:7 次。5 次分组赛,1 次每组第一赛,淘汰不可能的组,剩下候选赛 1 次得出前三。

  1. 64 匹马 8 条赛道找最快 4 匹怎么做

答:11 次。思路同上:分组赛 + 第一名赛 + 候选决赛,逐步淘汰不可能进前四的马。

  1. Rand5 怎么构造 Rand7

答:两次 Rand5 构造 25 种等概率结果,0-20 映射到 0-6,大于 20 拒绝重采样。拒绝采样保证等概率。

  1. 1000 瓶饮料 3 空瓶换 1 瓶,最多喝多少瓶

答:1499 瓶。公式:总数 = n + n/(k-1)。3 空换 1,相当于每 2 空瓶喝 1 瓶(不含瓶)。最后可以借一瓶喝完还瓶。

  1. 1000 瓶毒药题,10 只老鼠怎么找

答:二进制编码。10 只老鼠对应 10 位二进制,每瓶编号 0-999,二进制位为 1 就给对应老鼠喝。最后根据死亡老鼠组合反推瓶号。10 只可以覆盖 1024 瓶。

  1. 不均匀绳子怎么计 15、30、45、75 分钟

答:30 分钟:一根绳子两头同时点,烧完就是半小时。 45 分钟:A 两头点,B 一头点,A 烧完点 B 另一头,B 烧完就是 45 分钟。 15 分钟:就是 B 剩下那段两头点的时长。 75 分钟:一根烧完 + 一根两头烧的接起来。

  1. 24 小时内时针、分针、秒针完全重合几次

答:2 次。只有 12 点整和 0 点整三针完全重合。分针时针 22 次重合,但秒针大部分时候不在那个位置。

  1. 100 个奴隶猜帽子,最大存活率是多少

答:99 人生存。第一个人报前面所有人黑帽奇偶性,后面每个人根据奇偶和前面看到的,推出自己帽子颜色。第一个人 50% 概率,后面人都能猜对。

  1. 100 根香蕉、50 米、每次最多搬 50 根,最多搬回多少

答:25 根。中途设中转站,先搬 50 走 x 米放下,回去搬剩下 50,再一起往前走。最优 x=25,最后剩 25 根。

  1. 2 个鸡蛋测 100 层楼,最少几次

答:14 次。第一个鸡蛋递减步长扔,碎了第二个线性试。x + (x-1) + ... +1 ≥ 100,x=14。

  1. 蚂蚁碰头折返的总路程怎么求

答:碰头等价于交换身份继续走。总路程就是所有蚂蚁速度 × 时间,不用管碰撞。

  1. 5 个海盗分金币,1 号怎么分最优

答:逆向推导。1 号给 3 号 1 枚,给 5 号 2 枚,自己拿 97 枚。拉拢关键人物,用最少金币获得半数支持。

  1. 三个火枪手决斗,谁活下来的概率最大

答:枪法最差的活下来概率最大。强者会先互射,弱者坐收渔利。博弈论里的经典结论。

  1. 100 本书轮流拿,每次 1 到 5 本,先手必胜策略是什么

答:先手先拿 4 本,剩下 96 本。之后对方拿 n 本,先手就拿 6-n 本,每轮合计 6 本。最后一本一定先手拿到。核心是控制每轮和为 6。

相关推荐
疯狂打码的少年1 小时前
【数据结构】复习日:树 + 图 + 排序(整理对比表)
数据结构·笔记·算法
苏子寒2 小时前
Nano-VLLM全代码解析笔记(2)-block_manager
人工智能·笔记·python·机器学习·nlp·vllm
心仪久了会沦陷2 小时前
数据结构入门系列——顺序表详解:概念、分类与动态实现
c语言·数据结构·经验分享·笔记·开源
存在morning2 小时前
【PySpark 学习笔记 六】性能优化:分区、Shuffle、缓存与数据倾斜
笔记·学习·性能优化
hongmai6668883 小时前
竖插专用:ESP8684-WROOM-07-H2X模组上手记录
笔记·单片机·嵌入式硬件·物联网·risc-v
是枚小菜鸡儿吖6 小时前
把视频教程变成可复习的笔记:Docker 部署 BiliNote,自动转写与总结
笔记·docker·容器
运维全栈笔记6 小时前
Vue + Spring Boot 前后端分离项目部署笔记(若依 RuoYi-Vue 3.9.2)
运维·服务器·vue.js·spring boot·笔记·开源·开源软件
xian_wwq13 小时前
【学习笔记】-深度认知系列-第1讲-AI不是魔法——三句话讲清楚人工智能到底是什么
笔记·学习·深度认知
M78佐菲13 小时前
c语言学习笔记:排序与查找方法整理
linux·c语言·笔记·学习·算法