游戏客户端面经(自整理版)
岗位:游戏客户端开发
C++
1. 深拷贝和浅拷贝
深拷贝和浅拷贝的核心区别在于:拷贝时到底是只复制一层引用,还是把引用指向的内容也一起复制出一份新的。
浅拷贝是什么
定义 浅拷贝是指按字节或按成员逐个复制对象的值。对于指针类型的成员,只复制指针本身(即地址),而不复制指针所指向的内容。 在 C++ 中,如果用户没有显式定义拷贝构造函数和拷贝赋值运算符,编译器会自动生成默认版本,这些默认版本执行的就是浅拷贝。
特点
执行速度快,实现简单
多个对象共享同一份动态资源
容易引发重复释放(double free)或悬垂指针(dangling pointer)问题
深拷贝是什么
定义 深拷贝是指不仅复制对象的成员变量本身,对于指针类型的成员,还会重新分配内存,并将原指针所指向的内容复制到新分配的内存中。这样每个对象都拥有自己独立的资源副本,互不影响。
特点
避免了资源重复释放和意外共享
实现相对复杂,需要显式定义拷贝构造函数和拷贝赋值运算符
性能开销较大(涉及内存分配和数据复制)
2. 编译器默认生成的拷贝构造(拷贝赋值)的语义是什...
TArray
UE源码解析-TArray
这篇文章我们来分析UE里的变长数组的实现,对应模块为 Core 。
在 ContainersFwd.h 中罗列了很多容器模板的声明,里面包含 TArray : 1template<typename T, typename Allocator = FDefaultAllocator> class TArray;
Allocator
默认的分配器是 TSizedHeapAllocator ,在 ContainerAllocationPolicies.h 中声明: 12template <int IndexSize> class TSizedDefaultAllocator : public TSizedHeapAllocator<IndexSize> { public: typedef TSizedHeapAllocator<IndexSize> Typedef; };using FDefaultAllocator = TSizedDefaultAllocator<32>...
cpp3
cpp八股3
这里我们主要手撕一些面试里可能让你手撕的轮子(?
队列
首先看看环形队列,就是固定大小的缓冲区上维护两个指针:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051template<typename T>class CircleQueue{public: CircleQueue(int capacity=5) : cap(capacity), nxt(0), head(0) { buf.resize(cap); } T& Front() { if(Empty()) throw std::runtime_error("Queue is empty"); return buf[head]; } T& Back() ...
cpp2
cpp八股2
实现三个智能指针
首先是 UniquePtr,它是独占式的智能指针,不能被复制,只能被移动。
12345678910111213141516171819202122232425262728293031323334353637383940template<typename T>class UniquePtr{public: UniquePtr(T* p = nullptr) : ptr(p) {} UniquePtr(const UniquePtr&) = delete; UniquePtr& operator=(const UniquePtr&) = delete; UniquePtr(UniquePtr&& other) noexcept : ptr(other.ptr) { other.ptr = nullptr; } UniquePtr& operator=(UniquePtr...
AYS
关于AYShooter
AYShooter是一款使用UE5.7开发的多人在线FPS游戏。
参考游戏:APEX、TTF2、PUBG、CS2、三角洲等。
title
玩法简介
3C
角色采用FPP、TPP双重视角,FPP视角只在客户端渲染,TPP视角在除了自己以外的其他玩家身上渲染。
FPP采用传统状态机模型,TPP采用MotionMatch,考虑使用Lyra的动画作为Database。
扩展CMC,实现冲刺、滑铲、走墙等动作,实现运动状态网络同步以及客户端预测。
使用GAS建模核心玩法操作,例如 GA_Fire , GA_Reload 等。
实现基本的场景交互,例如攀爬、翻越。可以参考GASP的实现。
武器系统
GAS实现武器射击、换弹等功能。
目前先实现一把步枪,后续可以扩展更多武器。
AI
可以考虑使用比较新的StateTree和SmartObject系统。Demo阶段不用考虑。
UI
考虑采用MVC架构,具体用委托实现。这方面无需考虑网络同步。
DevLog1 AYRenderer
开发日志 AYRenderer
渲染Pass,VertexShader
模型矩阵与法线矩阵
在AYRenderer的UI界面中,用户可以自行设置渲染物体的 Transform :
Transform
这个 Transform 会形成所谓的 Model 矩阵,在顶点着色器(VertexShader)中会用到这个矩阵来对顶点进行变换。(即M变换)
当然所谓的顶点不只有位置和旋转的属性,还有法线属性,而法线的变换所对应的 Normal Matrix 则是 Model Matrix 的逆转置矩阵:
123Mat4 modelMatrix = object.transform.GetModelMatrix();// 法线矩阵是模型矩阵的逆转置矩阵Mat4 normalMatrix = modelMatrix.Inverse().Transpose();
我们来证明一下,首先假设模型空间中某顶点对应的切线为 t⃗,该切线可以由模型空间的两个顶点 A, B 表示: t⃗ = B − A.
经过模型变换后,对应的切线 $\vec{t'}$ 为: $$
\vec{t'}...
cpp八股1
cpp八股1
多态
什么是多态?C++中如何实现多态?
多态指的是同一个接口(在不同对象上)有着不同的行为。C++中多态分为编译时多态(静态多态)和运行时多态(动态多态)。
编译时动态顾名思义指的是在编译阶段就能决定哪个接口对应于哪个对象。
典型实现一:函数重载
注意返回值类型不参与重载判断
1234567891011121314151617#include <iostream>using namespace std;// 函数重载:同名函数根据参数不同实现不同行为int add(int a, int b) { return a + b;}double add(double a, double b) { return a + b;}int main() { cout << add(10, 20) << endl; // 调用 int 版本 cout << add(3.5, 2.7) << endl; // 调用 dou...
DesignPatterns
设计模式
资料来源于NJU研究生课程《高级软件设计》
设计模式指的是在软件工程中,针对特定问题的典型解决方案的一种标准化描述。
通常可分为三大类(Design Pattern Catalog): - 创建型模式:与对象创建有关:单例、工厂、抽象工厂等 - 结构型模式:将类或对象按某种布局组成更大的结构:装饰、适配器、外观、组合、代理等 - 行为型模式:类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责:策略、状态、观察者、迭代器、模板、命令等
本文给出以上14个常用设计模式的cpp实现示例。
设计原则
单一职责原则(SRP,Single Responsibility Principle):一个类只负责一项职责
开闭原则(OCP,Open-Closed Principle):软件实体应对扩展开放,对修改关闭
里氏替换原则(LSP,Liskov Substitution Principle):子类对象必须能够替换掉所有父类对象
依赖倒置原则(DIP,Dependency Inversion Principle):高层模块不应该依...
ue-light-flicker
UE5 实现灯光闪烁效果
一种naive的想法是在蓝图用Timeline不断改变灯光的intensity属性。但最近在网上看到了一个更优雅的实现方式,使用灯光材质函数(Light Material Function)来实现。
具体实现如下,定义一个材质(不是材质函数),在Material Domain选择Light Function:
Light Function
你或许已经注意到了关键点,就是“Time”节点和“Sine”节点。具体数学原理如下: 1234567// 参数:Time(运行时间),RandomnessScale, FlickerSpeed, RandomnessShift, LowestBrightness T = (Time + RandomnessScale) * FlickerSpeed; // 时间基准S1 = sin( T * (RandomnessShift + 1.000) ); // 正弦波1S2 = sin( T * (RandomnessShift + 0.735) ); ...
quaternion
在游戏编程中,我们经常需要处理三维旋转。在UE编辑器中,我们常常通过直截了当地设置Pitch、Yaw和Roll来控制旋转。同时在数学运算中我们可以很自然地用一个正交3x3矩阵来表示旋转。 这篇博客将阐述另一个更加高效地表示三维旋转的工具:四元数(Quaternion)。
四元数
1. 复数
我们都知道复数,即 z = a + bi, z ∈ ℂ,其中 a, b ∈ ℝ,以及一个神奇的规定 i2 = − 1。
放到二维平面上来看,如果一个轴表示复数的实部 a,另一个轴表示虚部 b,那么复数 z 就可以表示为平面上从原点 (0, 0) 指向点 (a, b) 的一个向量:
complex1
由于我们要讨论的是四元数和三维旋转的关系。因此在这里,我们可以初步猜想,复数应该与二维空间的旋转有关。
复数加减应该都很熟悉,我们就不提了。来看看复数的乘法,前面我们提到一个神奇的规定 i2 = − 1。
因此,假设有两个复数 z1 = a + bi 和 z2 = c + di,它们的乘积为:
$$
\begin{aligned}
z_1 z_2 &...
