UE C++ 今日主题C 内存管理 → RAII → UE Actor 生命周期 → 网络复制 → RPC → GAS 实战面试目标不仅知道 API 怎么用还要能够解释为什么这么设计。一、C 内存管理1. 栈和堆有什么区别代码void Test() { int A 10; int* B new int(20); }问题A在哪里B在哪里*B指向的int在哪里Test()执行结束以后发生什么有没有内存泄漏答案通常可以理解为栈 Stack ┌──────────────┐ │ A 10 │ │ B 地址 │ └──────────────┘ 堆 Heap ┌──────────────┐ │ int 20 │ ← B 指向这里 └──────────────┘Aint A 10;局部自动变量通常存放在栈上。当Test();执行结束以后A的生命周期结束。Bint* B;B本身是一个指针变量。通常情况下它作为局部变量也在栈上。但是new int(20)创建出来的int在动态存储区也就是通常所说的堆。所以B → 栈 *B 指向的 int → 堆Test 结束以后B本身销毁。但是new int(20);申请的堆内存没有delete所以它仍然存在。结果B 消失 ↓ 失去指向堆内存的地址 ↓ 堆内存无法释放 ↓ 内存泄漏正确写法void Test() { int* B new int(20); delete B; B nullptr; }不过现代 C 更推荐auto B std::make_uniqueint(20);二、什么是内存泄漏2. 代码void Test() { int* Ptr new int(100); if (SomeCondition) return; delete Ptr; }问题如果SomeCondition true会发生什么答案执行return;以后直接离开函数。所以delete Ptr;不会执行。于是new int(100) ↓ 堆内存创建 ↓ return ↓ Ptr 生命周期结束 ↓ 堆内存没人释放 ↓ 内存泄漏这也是为什么大量使用裸指针new delete容易出现问题。三、什么是 RAII1. 什么是 RAIIRAIIResource Acquisition Is Initialization资源获取即初始化。核心思想让资源的生命周期绑定到对象的生命周期。例如{ std::unique_ptrint Ptr std::make_uniqueint(100); } // 离开作用域离开作用域以后Ptr 析构 ↓ unique_ptr 自动释放 int不需要手动delete2. 为什么 RAII 很重要例如void Test() { auto Ptr std::make_uniqueint(100); if (SomeCondition) return; // ... }即使提前return;Ptr依然会析构。因此资源可以自动释放。这对内存文件锁Socket数据库连接等资源管理都很重要。四、unique_ptr 为什么不能复制代码std::unique_ptrint A std::make_uniqueint(10); std::unique_ptrint B A;这段代码会编译失败。但是std::unique_ptrint B std::move(A);可以。为什么unique_ptr表示唯一所有权假设A ───────→ int如果允许B A;就变成A ───────→ int B ───────→ int那么A 析构 → delete int B 析构 → 再 delete int就可能出现重复释放。所以unique_ptr禁止拷贝。std::move 做了什么std::unique_ptrint B std::move(A);std::move本身并不负责移动资源。它主要是把 A 转换成可以被移动的右值表达形式。最终所有权发生转移之前 A ─────→ int B 之后 A ─────→ nullptr B ─────→ int所以A.get() nullptr通常成立。面试回答unique_ptr表示唯一所有权因此禁止拷贝只允许移动。使用std::move可以转移资源所有权移动后原unique_ptr仍然有效但通常为空。五、shared_ptr代码std::shared_ptrint A std::make_sharedint(10); std::shared_ptrint B A; std::shared_ptrint C B;问题有几个int答案只有1 个 int但是有3 个 shared_ptr结构可以理解成┌──────────────┐ A ─────→│ │ B ─────→│ int 10 │ C ─────→│ │ └──────────────┘ ↑ 引用计数 3所以A.use_count(); // 3B.reset()B.reset();B 不再拥有资源A ─────→ int B C ─────→ int引用计数3 → 2只要还有一个shared_ptr持有资源int就不会被销毁。当A 销毁 C 销毁引用计数2 → 1 → 0当变成0资源才会被释放。六、UE Actor 生命周期1. 常见生命周期函数需要掌握Constructor ↓ PostInitializeComponents ↓ BeginPlay ↓ Tick ↓ EndPlay ↓ Destroyed但注意UE 实际生命周期比这个更复杂尤其是 Actor 的创建、关卡加载、复制和销毁都有不同路径。面试时先掌握核心关系。2. Constructor构造函数主要负责AMyActor::AMyActor() { RootComponent CreateDefaultSubobjectUSceneComponent(TEXT(Root)); Mesh CreateDefaultSubobjectUStaticMeshComponent(TEXT(Mesh)); }适合创建默认组件设置默认属性初始化 CDO 相关配置不适合GetWorld()或者依赖玩家关卡中的其他 ActorGameplay 状态因为此时游戏世界环境可能还没有准备好。七、Constructor 和 BeginPlay 有什么区别这是 UE 面试非常常见的问题。Constructor主要是创建对象结构、默认配置。例如AMyCharacter::AMyCharacter() { Mesh CreateDefaultSubobjectUSkeletalMeshComponent( TEXT(Mesh) ); }BeginPlay主要是游戏真正开始运行以后进行初始化。例如void AMyCharacter::BeginPlay() { Super::BeginPlay(); GetWorldTimerManager().SetTimer(...); }可以访问WorldPlayer其他 ActorGameplay 状态面试总结Constructor 更偏向于对象结构和默认属性初始化而 BeginPlay 是 Actor 进入游戏运行状态后的初始化阶段。比如组件创建通常放 Constructor而依赖 World、Player 或其他 Actor 的逻辑通常放 BeginPlay。八、SpawnActor 后发生什么例如GetWorld()-SpawnActorAMyEnemy( EnemyClass, Location, Rotation );UE 会进行 Actor 的创建和初始化过程。可以粗略理解为SpawnActor ↓ 创建 Actor ↓ 执行构造/对象初始化 ↓ 初始化组件 ↓ PostInitializeComponents ↓ BeginPlay如果 Actor 当前处于 Play 状态通常会进入 BeginPlay。九、为什么不能把所有逻辑放 Tick例如void Tick(float DeltaTime) { FindEnemy(); CalculateDamage(); CheckSomething(); }假设60 FPS那么每秒执行 60 次如果100 个 Actor就可能变成6000 次/秒大量 Tick 会造成性能压力。更好的方式根据需求选择定时器GetWorldTimerManager().SetTimer( TimerHandle, this, AMyActor::CheckEnemy, 1.0f, true );事件驱动例如受到伤害 ↓ 触发事件 ↓ 处理逻辑委托OnHealthChanged OnDeath OnAttackGAS很多 Gameplay 行为可以通过Gameplay AbilityGameplay EffectGameplay EventGameplay CueGameplay Tag驱动。十、什么是网络复制代码UPROPERTY(Replicated) float Health;它表示这个属性可以参与 UE 网络复制。但是仅仅写UPROPERTY(Replicated)还不够。通常还需要在GetLifetimeReplicatedProps()中注册。例如void AMyCharacter::GetLifetimeReplicatedProps( TArrayFLifetimeProperty OutLifetimeProps ) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(AMyCharacter, Health); }十一、Server 修改 HealthClient 会发生什么假设Server Health 100Server 修改Health 50;如果 Health 正确配置为 ReplicatedServer Health 50 ↓ 网络复制 ↓ Client Health 50所以Server 是权威状态的主要来源。十二、什么是 Server Authority多人游戏通常采用Server Authority服务器权威例如敌人Enemy Health 100玩家 Client 不能简单地Enemy-Health 0;然后认为敌人死亡。因为客户端是不可信的。正确流程通常是Client ↓ 发起攻击请求 ↓ Server ↓ 验证攻击 ↓ 计算伤害 ↓ 修改 Enemy Health ↓ 复制结果 ↓ Clients十三、什么是 Server RPC代码UFUNCTION(Server, Reliable) void ServerAttack();意思是客户端调用这个函数时请求在服务器上执行。例如Client ↓ 点击攻击 ↓ ServerAttack() ↓ Server 执行攻击逻辑ServerUFUNCTION(Server)表示这是一个 Server RPC。客户端调用ServerAttack();服务器执行void AMyCharacter::ServerAttack_Implementation() { // Server逻辑 }ReliableReliable表示这是可靠 RPC。适合一些必须可靠传递的重要事件。但是Reliable 不是“可以随便使用”的万能保证。大量高频事件使用 Reliable 可能造成网络压力。十四、什么是 NetMulticast例如UFUNCTION(NetMulticast, Reliable) void MulticastPlayEffect();主要作用Server 调用以后让相关客户端执行这个函数。例如Server ↓ MulticastPlayEffect() ↓ Client A Client B Client C适合播放特效播放音效某些临时表现例如玩家挥剑 ↓ 服务器确认 ↓ 通知所有相关客户端 ↓ 播放攻击特效但是不要把它当成“同步游戏状态”的主要手段。游戏状态通常应该使用Replication十五、Server RPC 和 Multicast 区别类型方向典型用途Server RPCClient → Server请求服务器执行Client RPCServer → 某个 Client通知指定客户端Multicast RPCServer → 多个 Client广播临时表现ReplicationServer → Client同步状态例如玩家点击攻击 Client ↓ Server RPC ↓ Server 验证 ↓ 执行攻击 ↓ 修改 Health ↓ Replication ↓ 所有 Client 更新 Health如果需要播放临时特效Server ↓ Multicast / GameplayCue ↓ Clients 播放特效十六、实战客户端攻击了敌人但是敌人没有掉血这是非常好的面试场景题。假设Client 点击攻击 ↓ 看起来攻击成功 ↓ Enemy 没有掉血应该怎么排查第一层Authority检查HasAuthority()确认真正修改 Health 的代码是不是在 Server。第二层RPC如果攻击从 Client 发起ServerAttack();检查Server RPC 有没有调用Actor 是否属于正确的客户端Server RPC 是否真正执行第三层Damage检查攻击 ↓ 是否检测到 Enemy ↓ 是否计算 Damage ↓ Damage 是否 0第四层GAS如果使用 GASGameplayAbility ↓ 攻击逻辑 ↓ GameplayEffectSpec ↓ Damage ↓ AttributeSet ↓ Health检查Ability 是否激活GameplayEffect 是否应用SetByCaller 是否设置正确Damage Tag 是否正确AttributeSet 是否执行修改ExecutionCalculation 是否执行第五层Replication如果服务器Health 50但是客户端仍然Health 100检查UPROPERTY(Replicated)以及DOREPLIFETIME(...)还要确认 Actor 本身参与复制。十七、ARPG 中一次完整攻击流程假设玩家使用火球术完整流程可以设计成玩家点击输入 ↓ InputTag ↓ AbilitySystemComponent ↓ 激活 GameplayAbility ↓ 播放攻击动画 ↓ 生成火球 ↓ 火球碰撞 Enemy ↓ 确定 Target ↓ 创建 GameplayEffectSpec ↓ 设置 Damage ↓ ApplyGameplayEffect ↓ Enemy AttributeSet ↓ Health - Damage ↓ Health Replication ↓ Client 更新血条 ↓ GameplayCue ↓ 播放受击特效十八、谁负责什么这是面试特别容易问的地方。不要把所有逻辑塞到 Character。推荐拆分Character ↓ 负责角色表现和输入相关部分 GameplayAbility ↓ 负责技能行为 AbilitySystemComponent ↓ 负责 GAS 能力管理 GameplayEffect ↓ 负责属性修改 AttributeSet ↓ 负责 Health / Mana / Strength 等属性 GameplayCue ↓ 负责视觉、音效等表现 Projectile ↓ 负责飞行和碰撞 Enemy ↓ 负责敌人实体和状态十九、为什么要区分 C 对象、UObject、Actor这是今天最重要的综合题之一。可以从三个层次理解。第一层普通 C 对象例如class MyClass { };生命周期通常MyClass* Obj new MyClass(); delete Obj;由new / delete管理。第二层UObject例如UObject由 UE 的 UObject 系统管理。涉及ReflectionGCUPROPERTYUObject 生命周期通常不能简单使用new delete来管理 UObject。第三层ActorActor 是UObject ↓ AActor它进一步和WorldLevelSpawnBeginPlayTickDestroyReplication等系统结合。例如GetWorld()-SpawnActorAMyEnemy();Actor 的生命周期由 UE 世界和 Actor 系统管理。二十、最终面试总结如果面试官问“你对 C 内存和 UE 生命周期有什么理解”可以这样回答C 中需要区分栈对象和动态分配对象裸指针配合 new/delete 容易出现内存泄漏因此现代 C 更推荐 RAII 和智能指针。比如 unique_ptr 使用唯一所有权不能拷贝但可以通过 move 转移所有权。UE 中又有自己的 UObject 生命周期和 GC 机制Actor 在此基础上进一步加入了 World、Spawn、BeginPlay、Tick、EndPlay、Destroy 以及网络复制等生命周期和系统。在多人游戏中我会遵循 Server Authority 的思路客户端主要负责输入和请求服务器负责验证和修改核心游戏状态然后通过 Replication 同步给客户端RPC 更多用于跨端调用而临时的表现效果可以使用 Multicast 或 GAS 的 GameplayCue。今日重点背诵C栈 堆 内存泄漏 RAII unique_ptr shared_ptr std::moveUE 生命周期Constructor ↓ 组件初始化 ↓ BeginPlay ↓ Tick ↓ EndPlay ↓ Destroyed网络Server Authority ↓ Server RPC ↓ Server 修改状态 ↓ Replication ↓ ClientGASInput ↓ ASC ↓ GameplayAbility ↓ GameplayEffect ↓ AttributeSet ↓ Health ↓ Replication ↓ GameplayCue面试最容易被追问的 5 个问题1. std::move 到底做了什么它本身不移动资源主要是将表达式转换成适合移动操作的右值形式让移动构造/移动赋值有机会接管资源。2. unique_ptr 为什么不能拷贝因为它表示唯一所有权拷贝会导致多个对象认为自己拥有同一资源可能造成重复释放。3. 为什么客户端不能直接修改 Enemy Health因为多人游戏通常采用 Server Authority客户端不是权威状态来源否则容易出现作弊和状态不一致。4. Replicated 和 RPC 有什么区别Replicated 主要用于同步状态RPC 主要用于跨网络调用函数。比如 Health 使用 Replication同步攻击请求可以使用 Server RPC。5. GAS 中伤害最终怎么同步给客户端通常由服务器执行伤害 GameplayEffect修改 AttributeSet 中的 Health然后通过属性复制把结果同步给客户端表现层可以通过 GameplayCue 等机制播放受击表现。今日一句话真正的 UE C 开发不是“会不会调用函数”而是要知道对象是谁创建的、谁拥有它、谁负责销毁、谁拥有游戏状态、网络上谁说了算以及系统之间如何协作。

相关新闻

最新新闻

Java Reader 详解:从基础用法到实战技巧

Java Reader 详解:从基础用法到实战技巧

1. 引言在 Java 的 I/O 体系中,Reader 是处理字符输入的核心抽象类。与面向字节的 InputStream 不同,Reader 专门用于读取字符流,能够正确处理 Unicode 编码,是开发中处理文本文件的常用选择。本文将从基础概念出发,逐…

2026/9/6 5:46:10
【精品大数据项目】基于大数据的新生儿败血症临床多指标数据可视化分析,附源码_数据可视化_数据分析_毕设选题推荐_SPark_Hadoop_文档指导ppt

【精品大数据项目】基于大数据的新生儿败血症临床多指标数据可视化分析,附源码_数据可视化_数据分析_毕设选题推荐_SPark_Hadoop_文档指导ppt

💖💖作者:计算机毕业设计杰瑞 💙💙个人简介:曾长期从事计算机专业培训教学,本人也热爱上课教学,语言擅长Java、微信小程序、Python、Golang、安卓Android等,开发项目包括…

2026/9/6 5:46:10
基于Hadoop+Spark的电力数据可视化系统:毕业设计完整方案

基于Hadoop+Spark的电力数据可视化系统:毕业设计完整方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/6 5:46:10
Redis持久化机制

Redis持久化机制

Redis 是一款内存型数据库,绝大多数数据都存储在内存中,读写速度极快。但内存数据是易失性的,一旦机器断电、进程崩溃、重启,内存数据会全部丢失。为了解决数据丢失问题,Redis 提供了持久化机制,把内存数据…

2026/9/6 5:46:10
Minimax H3导演台二次采样:解决AI视频模糊与伪影的实用指南

Minimax H3导演台二次采样:解决AI视频模糊与伪影的实用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/6 5:46:10
健身营养学_营气

健身营养学_营气

问题1:人体四大基础之气(标准教科书版本) 元气、宗气、营气、卫气,这 4 个是最核心的四大气s.dayi.org…。 额外还有:中气(脾胃之气)、脏腑之气、经络之气,属于衍生分类。 1. 元气&a…

2026/9/6 5:41:10