面灵AI→

麦吉太文游戏客户端一面:C++ 八股、UE 网络与扇形攻击设计

轮次
一面
时间
2026-10
来源
牛客网

《面试题目》

  1. 自我介绍。
  2. 把 PC 端操作适配到移动端,具体做了什么?移动端和 PC 端的输入与操作体系有什么异同?
  3. 有的玩家习惯方向键而不是 WASD,或者习惯右手操作摇杆,怎么设计来服务不同玩家?
  4. 登录功能的流程是怎样的?是依靠 HTTP 登录吗?
  5. 客户端和服务端之间怎么通信?如果让你设计消息格式会怎么设计?
  6. 为什么使用 Protobuf?它为什么体积更小、效率更高?用 1 字节第 8 位做标志位的方案作用于哪种数据类型?Protobuf 在字符串类型上有什么优势?JSON 等文本格式有什么缺点?
  7. C++ 的虚函数是怎么实现的?有什么作用?
  8. C++ 的 vector 底层源码是怎样的?怎么实现扩容?
  9. C++ 的 unique_ptr 怎么实现“独占”?
  10. C++ 为什么设计了右值和右值引用相关机制?
  11. dynamic_cast 底层怎么在运行时判断类型?
  12. 声明和定义的区别是什么?在编译和链接层面呢?
  13. 静态库和动态库的区别是什么?底层机制是怎样的?
  14. 已知敌人位置、玩家位置、扇形半径和夹角,设计一个攻击前方扇形区域敌人的函数,说说具体算法。如果让 AI 写,你怎么给提示词?
  15. 了解 Unity 的物理引擎和常用物理算法吗?
  16. 笔试题复盘:说说项目的框架和流程。
  17. 你认为使用 AI 需要注意什么?AI 一般有哪些做得不好的地方?
  18. 能不能接受转 Unity?

《参考解析》

Protobuf 的编码:核心是 TLV(Tag-Length-Value)加变长整数。每个字段先写一个 key,key = (field_number << 3) | wire_type,低 3 位表示线型(varint、64 位、长度前缀、32 位)。varint 用每个字节的最高位做继续标志,低 7 位存数据,所以小整数只占 1 字节;负数如果直接 varint 会占 10 字节,因此 int32/int64 用 ZigZag 把符号位映射到最低位,sint32/sint64 才是适合负数的类型。字符串和 bytes 是长度前缀的线型,只传 UTF-8 字节,不做字段名重复传输;JSON 每个字段都要带 key 文本、数字转十进制字符串、还有括号与引号,体积和解析开销都更大。追问“第 8 位”时要说清楚:它是每个 varint 字节的最高位(continuation bit),不是某个数据类型的专用位。

虚函数与 RTTI:含虚函数的对象头部有 vptr,指向该类的虚表;虚表按声明顺序存虚函数地址,子类覆写时替换对应槽位。多继承时对象会有多个 vptr,调用非主基类的虚函数需要 this 指针调整(thunk)。dynamic_cast 依赖 RTTI:vtable 前面通常放 type_info 指针,运行时沿继承图比较类型信息,引用转换失败抛 bad_cast,指针转换失败返回 nullptr;跨继承(cross cast)和虚继承场景需要运行时遍历,开销比 static_cast 大。构造函数里不要调虚函数,因为此时 vptr 还指向当前正在构造的类。

vector 与 unique_ptr:vector 通常是三个指针(begin、end、capacity_end),size() 是 end-begin,capacity() 是 capacity_end-begin。push_back 超过容量时按增长因子(常见 1.5 或 2)重新分配,把元素移动或拷贝到新内存再释放旧内存,所以扩容后迭代器、指针、引用全部失效;可以先用 reserve 避免反复扩容。unique_ptr 的“独占”靠删除拷贝构造和拷贝赋值、只保留移动语义实现;默认 deleter 为空类时利用空基类优化,让 unique_ptr 和裸指针同大小;自定义 deleter 会作为成员存下来。make_unique 把分配和构造放在一个表达式里,避免异常路径上的泄漏。

声明、定义与库:声明告诉编译器“有这么个名字和类型”,定义真正分配存储或给出函数体。编译阶段各翻译单元独立生成目标文件,未解析的符号留到链接阶段;链接器把符号引用和定义对上,找不到就是 undefined reference,重复的强符号则是 multiple definition。静态库本质是目标文件归档,链接时只把被引用到的成员抽进可执行文件,运行时不再依赖库文件,体积大但部署简单;动态库在链接时只记录依赖,运行时由动态链接器加载并做符号重定位,多个进程可共享同一份代码,升级库不用重编程序,但要处理版本、搜索路径和 ABI 兼容。共享库需要位置无关代码(PIC)。

扇形攻击判定:先做距离裁剪:dx = enemy.x - player.x、dy = enemy.y - player.y,dx*dx + dy*dy <= r*r。再做角度判定:取玩家朝向单位向量 (fx, fy),用点积 dot = dx*fx + dy*fy,除以距离得到夹角余弦,和 cos(halfAngle) 比较即可,避免开方和 atan2。若在 3D 场景,先把敌人和朝向投影到地面平面。工程上还要考虑敌人碰撞体半径(把半径加进距离阈值)、视线遮挡(射线检测)、单帧敌人数量多时用空间分区或 Job 并行。给 AI 的提示词要写清坐标系、输入输出、边界条件(正好在边界上算不算)、性能约束和测试用例,而不是只说“写个扇形攻击”。

移动端输入设计:输入抽象层把“移动向量、视角增量、技能按下/抬起”从具体设备解耦,PC 用 WASD + 鼠标,移动端用虚拟摇杆 + 触摸区域;提供键位映射、摇杆左右手切换、死区与灵敏度、按钮大小与透明度配置。网络同步上,客户端输入要带序号发给服务端做权威校验,服务端下发状态快照,客户端做预测与回滚,避免作弊和手感问题。