UE 面试题库 · 整理版

第11章 高频真题补充

本章共 10 道题,覆盖:真实面试中高频出现但主章节覆盖不足的架构设计、动画系统、技能系统、算法与系统设计题。

一、架构与组件设计

1.1 为什么 Actor 不自带 Transform,而是放在 SceneComponent 里?

★★★ 困难

考点: Actor 与 Component 的职责分离、组合优于继承设计哲学、内存与性能考量、GamePlay 框架理解

参考答案:

  1. 核心原因:并非所有 Actor 都需要空间变换。AGameModeBase、APlayerState、AGameStateBase、APlayerController、AHUD 等纯逻辑/数据类 Actor 没有物理位置,强制携带 Transform(FVector+FQuat≈28 字节)会白白浪费内存,大型关卡中成千上万个此类 Actor 累积开销不可忽视。
  2. UE 采用组合优于继承(Composition over Inheritance)的设计哲学——"不为不需要的功能付出代价":
    • Actor 的 Transform 实际来自 RootComponent(USceneComponent*),可以为 nullptr;
    • Components[] 中非 Scene 组件(UActorComponent,如 UInputComponent、UMovementComponent)没有 Transform。
  3. SetActorLocation() 的本质是转发调用 RootComponent,而非直接修改 Actor 成员:
bool AActor::SetActorLocation(const FVector& NewLocation, bool bSweep,
                               FHitResult* OutSweepHitResult, ETeleportType TeleportType)
{
    if (RootComponent)
    {
        return RootComponent->SetWorldLocation(NewLocation, bSweep, OutSweepHitResult, TeleportType);
    }
    return false;
}

FVector AActor::GetActorLocation() const
{
    return RootComponent ? RootComponent->GetComponentLocation() : FVector::ZeroVector;
}
  1. 边界情况:没有 RootComponent 的 Actor 调用 SetActorLocation 不会崩溃,但也不会生效(位置始终为 (0,0,0))。
  2. 理由总结:内存效率(纯逻辑 Actor 不携带无用 Transform)、职责分离(Actor 负责"存在与逻辑",SceneComponent 负责"空间表现")、组合灵活性(自由组合 SceneComponent 构建层级)、一致性(所有空间变换统一走 Component 体系)。
  3. 加分点:一个 Actor 只能有一个 RootComponent,但可通过 AttachToComponent 构建组件树;网络同步时 Actor 的 Transform 通过 FRepMovement 从 RootComponent 读取并复制。

1.2 ChildActorComponent 是什么?有什么用?

★★ 中等

考点: Actor 组合模式、ChildActorComponent 生命周期管理、UE4.14 ChildActorTemplate 改进、与直接 SpawnActor 的对比

参考答案:

  1. UChildActorComponent 是一种特殊的 USceneComponent,允许在一个 Actor 内部创建并持有另一个 Actor,形成父子关系。
  2. 核心用途:
    • Actor 组合:通过 ChildActorComponent 组合武器、护甲等独立 Actor;
    • 编辑器可视化配置:在 Details 面板直接配置子 Actor 属性,无需运行时动态 Spawn;
    • 关卡设计师友好:可直接在场景中放置"组合型"Actor。
UCLASS()
class UWeaponSlotComponent : public UChildActorComponent
{
    GENERATED_BODY()
public:
    UWeaponSlotComponent()
    {
        SetChildActorClass(AWeaponActor::StaticClass());
    }
};
  1. UE4.14 之前的问题:子 Actor 属性修改不会保存到蓝图/关卡;4.14 引入 ChildActorTemplate,子 Actor 属性序列化为 Template 保存在父 Actor 的 CDO 中。
  2. 生命周期管理:组件注册时自动 CreateChildActor(),销毁时自动 DestroyChildActor(),子 Actor 自动 Attach 到组件 Transform。
void UChildActorComponent::CreateChildActor()
{
    if (ChildActorClass)
    {
        FActorSpawnParameters SpawnParams;
        SpawnParams.Owner = GetOwner();
        SpawnParams.Instigator = GetOwner()->GetInstigator();
        SpawnParams.OverrideLevel = GetOwner()->GetLevel();

        ChildActor = GetWorld()->SpawnActor<AActor>(ChildActorClass, SpawnParams);
        if (ChildActor)
        {
            ChildActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform);
        }
    }
}
  1. 对比直接 SpawnActor:
对比维度 ChildActorComponent 直接 SpawnActor
生命周期 自动随父组件创建/销毁 手动管理
编辑器支持 可在 Details 面板配置 只能运行时创建
Transform 层级 自动附加为子节点 需手动 Attach
序列化 属性可保存(4.14+) 不可序列化
适用场景 编辑器组合、固定结构 运行时动态生成
  1. 易错点:不要滥用 ChildActorComponent,每多一个 Actor 就多一份管理开销;大量使用会导致 Actor 数量膨胀,影响遍历与序列化性能。通过 GetOwner() 可从子 Actor 反向找父 Actor,用 GetComponents<UChildActorComponent>() 遍历。

二、技能与战斗系统设计

2.1 如何设计一个技能系统框架?

★★★ 困难

考点: 架构设计能力、数据驱动 vs 硬编码、技能流程建模、效果组合与扩展性、网络同步意识

参考答案:

  1. 设计思路先从需求分析开始:技能数据在哪里(配置表/DataTable/DataAsset)、怎么触发(输入→检查→执行)、效果有哪些(伤害/Buff/位移/召唤/变身)、效果怎么组合、怎么同步(客户端预测+服务端权威)。
  2. 核心结构分层:FSkillData(数据定义,配置表驱动)与 FSkillInstance(运行时实例),由挂在角色上的 USkillSystemComponent 统一管理。
USTRUCT(BlueprintType)
struct FSkillData : public FTableRowBase
{
    GENERATED_BODY()
    UPROPERTY(EditAnywhere) FName SkillID;
    UPROPERTY(EditAnywhere) float Cooldown = 1.0f;
    UPROPERTY(EditAnywhere) float CostValue = 0.0f;
    UPROPERTY(EditAnywhere) UAnimMontage* AnimMontage = nullptr;
    UPROPERTY(EditAnywhere) TArray<FSkillEffectData> Effects;   // 一个技能可有多个效果
    UPROPERTY(EditAnywhere) FGameplayTagContainer RequiredTags;  // 施放所需 Tag
    UPROPERTY(EditAnywhere) FGameplayTagContainer BlockedTags;   // 拥有即禁施放
};
  1. 技能执行流程:检查(冷却/消耗/Tag/状态)→ 扣除消耗、设置冷却、播放前摇 → 执行(播 Montage、AnimNotify 触发弹道/判定、应用效果)→ 清理状态、播放后摇、处理连锁效果。
  2. 效果组合:Effect 列表 + 执行优先级 + 条件 Tag(如仅暴击触发额外效果)+ 瞬时/持续时长类型。
  3. 网络同步:客户端本地预测播放动画并扣消耗,服务端验证后执行真正效果,冲突时回滚客户端预测状态。
UFUNCTION(Server, Reliable, WithValidation)
void Server_RequestActivateSkill(FName SkillID);

bool USkillSystemComponent::Server_RequestActivateSkill_Validate(FName SkillID)
{
    USkillInstance* Skill = FindSkillInstance(SkillID);
    return Skill && Skill->CanActivate();
}

void USkillSystemComponent::Server_RequestActivateSkill_Implementation(FName SkillID)
{
    TryActivateSkill(SkillID);
    Multicast_OnSkillActivated(SkillID);
}

UFUNCTION(NetMulticast, Unreliable)
void Multicast_OnSkillActivated(FName SkillID);
  1. 考察重点是系统设计能力而非背诵:需求分析、数据驱动(新技能不改代码只改配置)、明确生命周期状态机、可扩展性、网络同步意识。能提到 GAS 设计思想是加分项。

2.2 扇形/圆形/矩形技能命中判定如何实现?

★★ 中等

考点: 3D 数学基础(向量点积、投影)、碰撞检测实现、判定技巧、性能优化(避免逐帧遍历)

参考答案:

  1. 通用思路:先用球形 Overlap 预筛候选目标,再对每个目标做精确形状判定。
  2. 圆形 AOE:距离平方比较,避免开方。
bool IsInCircle(const FVector& Center, const FVector& TargetPos, float Radius)
{
    float DistanceSq = FVector::DistSquared2D(Center, TargetPos);
    return DistanceSq <= Radius * Radius;
}
  1. 扇形 AOE(最常考):需同时满足在半径内 + 在角度内。角度用点积判断 dot(Direction, ToTarget) >= cos(半角),避免反三角函数开销。
bool IsInSector(const FVector& Origin, const FVector& Direction,
                const FVector& TargetPos, float Radius, float HalfAngleDeg)
{
    FVector ToTarget = (TargetPos - Origin);
    float DistSq = FVector::DistSquared2D(Origin, TargetPos);
    if (DistSq > Radius * Radius) return false;

    FVector ToTarget2D = FVector(ToTarget.X, ToTarget.Y, 0.0f).GetSafeNormal();
    FVector Dir2D = FVector(Direction.X, Direction.Y, 0.0f).GetSafeNormal();
    float DotResult = FVector::DotProduct(Dir2D, ToTarget2D);
    float CosHalfAngle = FMath::Cos(FMath::DegreesToRadians(HalfAngleDeg));
    return DotResult >= CosHalfAngle;
}
  1. 矩形 AOE:局部坐标系投影,将 ToTarget 投影到 Forward(纵向)与 Right(横向)两轴。
bool IsInRectangle(const FVector& Origin, const FVector& Forward,
                   const FVector& TargetPos, float HalfLength, float HalfWidth)
{
    FVector ToTarget = TargetPos - Origin;
    float ProjForward = FVector::DotProduct(ToTarget, Forward.GetSafeNormal());
    if (FMath::Abs(ProjForward) > HalfLength) return false;

    FVector Right = FVector::CrossProduct(FVector::UpVector, Forward).GetSafeNormal();
    float ProjRight = FVector::DotProduct(ToTarget, Right);
    if (FMath::Abs(ProjRight) > HalfWidth) return false;

    return true;
}
  1. 性能优化:先 OverlapMultiByChannel(FCollisionShape::MakeSphere)球形粗筛,再精确形状判定;大量目标时可用 Grid/QuadTree 空间索引,并用 Collision Channel 过滤。
  2. 加分点:扇形判定用点积+cos 而非 acos 比较角度;距离用平方比较;讨论高度差处理(是否忽略 Z 轴)、用预计算扇形查找表替代实时三角函数。

2.3 GAS(Gameplay Ability System)核心架构

★★★ 困难

考点: GAS 四大核心组件、技能执行流程、与自定义技能系统对比、网络同步设计

参考答案:

  1. 为什么需要 GAS:从零实现技能系统需处理数据管理、执行流程、属性修改(Buff/Debuff)、效果堆叠互斥、网络同步、UI 绑定、动画交互,GAS 将这些通用能力标准化。
  2. 四大核心组件:
    • GameplayAbility(技能):定义技能的完整生命周期(CanActivate → Activate → Commit → End);
    • GameplayEffect(效果):定义对属性的修改规则(Duration Policy / Modifiers / Tags),通常编辑器配置;
    • GameplayAttribute(属性):AttributeSet 定义角色数值(Health/Mana/...),支持 PreAttributeChange 限幅与 PostGameplayEffectExecute 回调;
    • GameplayTag(标签):条件判断语言,用于技能前置条件、效果分类、状态管理。
UCLASS()
class UGA_MeleeAttack : public UGameplayAbility
{
    GENERATED_BODY()
public:
    UGA_MeleeAttack()
    {
        NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
        InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
    }

    virtual bool CanActivateAbility(...) const override
    {
        if (!Super::CanActivateAbility(...)) return false;
        UAbilitySystemComponent* ASC = ActorInfo->AbilitySystemComponent.Get();
        float Stamina = ASC->GetNumericAttribute(UMyAttributeSet::GetStaminaAttribute());
        return Stamina >= StaminaCost;
    }

    virtual void ActivateAbility(...) override
    {
        CommitAbility(Handle, ActorInfo, ActivationInfo);   // 扣除消耗

        UAbilityTask_PlayMontageAndWait* MontageTask =
            UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, NAME_None, AttackMontage);
        MontageTask->OnCompleted.AddDynamic(this, &UGA_MeleeAttack::OnMontageCompleted);
        MontageTask->ReadyForActivation();

        UAbilityTask_WaitGameplayEvent* EventTask =
            UAbilityTask_WaitGameplayEvent::WaitForGameplayEvent(this,
                FGameplayTag::RequestGameplayTag(FName("Event.Montage.Hit")));
        EventTask->EventReceived.AddDynamic(this, &UGA_MeleeAttack::OnHitEvent);
        EventTask->ReadyForActivation();
    }
};
  1. AttributeSet 关键点:FGameplayAttributeData + ATTRIBUTE_ACCESSORS 宏 + DOREPLIFETIME 复制;PreAttributeChange 限制范围,PostGameplayEffectExecute 做死亡检测等逻辑。
  2. Tag 用法:Ability Tags 标识技能;Activation Required Tags 需要拥有、Activation Blocked Tags 拥有即禁;AddLooseGameplayTag / HasMatchingGameplayTag / RemoveLooseGameplayTag 运行时操作。
  3. 对比自己写 vs GAS:网络同步(内置客户端预测+服务端权威 vs 手动实现预测回滚)、属性修改(自动 Modifier 堆叠 vs 手动计算)、效果互斥(Tag 自动处理 vs 手动)、冷却(内置 Cooldown Effect vs 手动计时器)、UI 绑定(Attribute Delegate vs 手动);代价是学习成本高、受框架约束,适合大项目复杂技能。
  4. 网络同步实现:ASC 的 Prediction Key 与 ScopedPredictionWindow 机制实现客户端预测;GameplayCue 负责视觉反馈(粒子/音效/UI),TargetData 抽象目标选择。

2.4 UE 中如何实现一个简单的背包系统?

★★ 中等

考点: 数据结构选择(TArray/TMap)、物品堆叠逻辑、序列化与持久化、网络同步(FFastArraySerializer)、UI 绑定

参考答案:

  1. 需求分析:添加/移除物品、堆叠、按类型或 ID 查询、容量限制、网络同步、UI 显示。
  2. 数据结构:FItemData(配置表驱动,含 MaxStackSize 等)与 FInventoryItem(运行时实例:ItemID + Quantity + SlotIndex)。
  3. 方案选择:
    • TArray(固定格子,与 UI 一一对应)——推荐大多数游戏,查找需遍历;
    • TMap(无限容量,O(1) 查找)——UI 显示需额外处理;
    • TArray + TMap 混合(TArray 显示,IndexMap 快速查找)。
  4. 添加物品核心逻辑:先尝试堆叠到已有格子(不超过 MaxStackSize),再放入空格子,返回实际添加数量。
UFUNCTION(BlueprintCallable)
int32 AddItem(FName ItemID, int32 Quantity)
{
    if (Quantity <= 0) return 0;
    const FItemData* ItemData = GetItemData(ItemID);
    if (!ItemData) return 0;

    int32 Remaining = Quantity;

    for (FInventoryItem& Slot : Items)          // 1. 堆叠到已有格子
    {
        if (Remaining <= 0) break;
        if (Slot.ItemID == ItemID && Slot.Quantity < ItemData->MaxStackSize)
        {
            int32 CanAdd = FMath::Min(Remaining, ItemData->MaxStackSize - Slot.Quantity);
            Slot.Quantity += CanAdd;
            Remaining -= CanAdd;
        }
    }

    for (FInventoryItem& Slot : Items)          // 2. 放入空格子
    {
        if (Remaining <= 0) break;
        if (Slot.IsEmpty())
        {
            int32 CanAdd = FMath::Min(Remaining, ItemData->MaxStackSize);
            Slot.ItemID = ItemID;
            Slot.Quantity = CanAdd;
            Remaining -= CanAdd;
        }
    }

    int32 Added = Quantity - Remaining;
    if (Added > 0) OnInventoryChanged.Broadcast();
    return Added;
}
  1. 移除物品从后往前遍历避免空洞,数量归零则清空格子;交换格子时若物品相同先尝试合并(不超过 MaxStack),否则 Swap。
  2. 网络同步优化:默认 TArray 复制会复制整个数组,用 FFastArraySerializer 只同步变化的元素(NetDeltaSerialize 调用 FastArrayDeltaSerialize,配合 PreReplicatedRemove/PostReplicatedAdd/PostReplicatedChange 回调)。
  3. UI 绑定:用 Delegate 驱动刷新而非每帧轮询——OnInventoryChanged.AddDynamic(this, &RefreshUI),刷新时重建格子 Widget。
  4. 易错点/加分项:容量初始化 Items.SetNum(Capacity);服务端权威修改后靠复制同步;扩展方向含装备/合成/交易系统、SaveGame 存档、ListView 虚拟化、UMG Drag & Drop 拖拽排序。

三、动画系统

3.1 Root Motion 是什么?什么场景下使用?

★★ 中等

考点: 动画驱动 vs 代码驱动移动、Root Motion 工作原理、适用场景与限制、网络同步处理

参考答案:

  1. Root Motion 指角色的位移和旋转由动画数据驱动(动画师烘焙根骨骼位移 → 引擎提取 → 应用到 Actor),而非由 CharacterMovementComponent 计算。
  2. 工作原理:UE 在动画更新时提取根骨骼每帧位移作为移动输入,CMC 将 Root Motion 作为额外移动输入且优先级高于普通移动输入。
  3. 适用场景:
场景 是否使用 原因
普通行走/跑步 否 代码控制更灵活,支持变速急停
重攻击/蓄力攻击 是 前冲/后撤需与动画匹配
翻滚/闪避 是 位移距离和节奏需匹配
处决/终结技 是 精确位移和对齐
跳跃 否 物理驱动更合理
  1. 启用方式:Montage 编辑器勾选 Enable Root Motion;或 AnimInstance 设置 Root Motion Mode(RootMotionFromEverything / RootMotionFromMontagesOnly(推荐) / NoRootMotionExtraction)。
  2. 网络同步是最大坑点:延迟导致 Montage 开始时间不一致、帧率差异采样精度不同。解决:CMC 内置 Root Motion 网络同步;Montage 用 Reliable Multicast 保证同帧播放;推荐用 GAS 的 UAbilityTask_PlayMontageAndWait 自动同步开始时间。
  3. 常见陷阱:
    • Root Motion 移动不经过 CMC 碰撞检测 → 用 CMC 的 Root Motion 处理而非直接改 Transform;
    • 动画结束后 Actor 弹回代码控制位置 → 用 Root Motion Lock 或平滑过渡;
    • 循环动画 Root Motion 累积导致越走越远 → 用 Wrap 动画或循环点重置位移。
  4. 常被追问:Root Motion 与 CMC Velocity 的关系——Root Motion 会覆盖 Velocity,结束后恢复代码控制。

3.2 动画蒙太奇(Montage)的 Slot 和槽位系统

★★ 中等

考点: Montage 与 AnimGraph 关系、Slot 节点作用、多 Slot 分层播放、AnimNotify 使用

参考答案:

  1. Slot 是 AnimGraph 中的特殊节点,允许在状态机之上叠加播放 Montage:状态机负责基础动画层,Slot 负责叠加层;无 Montage 播放时 Slot 直接透传状态机输出。
  2. 常见 Slot:DefaultSlot(全身覆盖)、UpperBody(腰部以上,射击/换弹)、LowerBody(腰部以下)、Face(面部表情)。
  3. 使用流程:AnimGraph 添加 Slot 节点 → Montage 编辑器指定 Slot → 代码 Montage_Play,Montage 自动在对应 Slot 叠加播放。
void AMyCharacter::PlayShootMontage()
{
    UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
    AnimInstance->Montage_Play(ShootMontage);  // Slot 设为 UpperBody,不影响下半身
}
  1. 多 Slot 分层播放:边跑步边射击 = Locomotion 状态机接 LowerBody Slot,射击状态机接 UpperBody Slot,两个 Slot 混合输出。
  2. AnimNotify 在关键帧触发游戏逻辑:伤害判定(挥砍中间帧触发碰撞检测)、音效、特效、技能释放。
UCLASS()
class UAnimNotify_MeleeHit : public UAnimNotify
{
    GENERATED_BODY()
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite) float DamageRadius = 100.0f;

    virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
                        const FAnimNotifyEventReference& EventReference) override
    {
        AActor* Owner = MeshComp->GetOwner();
        if (!Owner) return;

        FVector HitOrigin = MeshComp->GetSocketLocation(TEXT("WeaponTip"));
        TArray<FHitResult> HitResults;
        FCollisionShape Sphere = FCollisionShape::MakeSphere(DamageRadius);
        Owner->GetWorld()->SweepMultiByChannel(HitResults, HitOrigin, HitOrigin,
            FQuat::Identity, ECC_Pawn, Sphere);

        for (const FHitResult& Hit : HitResults)
        {
            AActor* HitActor = Hit.GetActor();
            if (HitActor && HitActor != Owner)
            {
                UGameplayStatics::ApplyDamage(HitActor, 50.0f,
                    Owner->GetInstigatorController(), Owner, nullptr);
            }
        }
    }
};
  1. AnimNotifyState 是持续性 Notify:NotifyBegin/NotifyEnd 用于武器拖尾、连招输入窗口(ComboWindow)等。
  2. Montage Section 实现连招:典型三段攻击 Montage 分段,Montage_JumpToSection 跳转下一段,配合 ComboWindow Notify 开连招输入窗口。
  3. 常被追问:没有 Slot 节点 Montage 能否播放?——不能,Slot 是 Montage 的播放通道。

四、算法与逻辑推理

4.1 25 匹马 5 个赛道,最少赛几场找出最快的 3 匹?

★ 简单

考点: 逻辑推理能力、淘汰法思维、分组策略优化

参考答案:

  1. 答案:最少 7 场。
  2. 第一阶段(5 场分组赛):25 匹马分 A/B/C/D/E 五组各赛一场,得到组内排名。每组第 4、5 名不可能是总前 3,直接淘汰 10 匹。
  3. 第二阶段(第 6 场冠军赛):5 组冠军 A1/B1/C1/D1/E1 比赛,假设结果 A1 > B1 > C1 > D1 > E1。推理淘汰:
    • A1 必是总第 1;
    • D、E 组全淘汰(D1 才第 4);
    • C 组仅 C1 保留(C1 最多第 3);
    • B 组仅 B1、B2 保留。
  4. 剩余候选:总第 1 已定为 A1,争夺第 2、3 名的候选为 A2、A3、B1、B2、C1 共 5 匹。
  5. 第三阶段(第 7 场):A2 A3 B1 B2 C1 比赛,前 2 名即总第 2、3。总场次 5+1+1=7。
  6. 为什么不能少于 7 场:5 场只能确定组内排名,无法跨组比较;6 场确定了组间关系但仍有 5 匹马争 2 个名额无法区分。
  7. 考察重点是推理过程:分治 + 淘汰法 + 利用传递性(A>B>C),面试官看的是清晰的淘汰逻辑而非答案本身。

4.2 洗牌算法(Fisher-Yates)实现

★ 简单

考点: 概率论基础、算法正确性证明、时间/空间复杂度、常见错误实现辨析

参考答案:

  1. 问题:将数组随机打乱,要求每个元素出现在每个位置的概率相等。
  2. 正确实现:Fisher-Yates,从后往前,每次从 [0, i] 随机选一个索引交换。时间复杂度 O(n),空间 O(1)。
template<typename T>
void FisherYatesShuffle(TArray<T>& Array)
{
    int32 Num = Array.Num();
    for (int32 i = Num - 1; i > 0; i--)
    {
        int32 j = FMath::RandRange(0, i);   // 关键:范围是 [0, i]
        Array.Swap(i, j);
    }
}
  1. 正确性证明:元素 X 出现在位置 k 的概率为逐轮"未被前面位置选中"的连乘,最后约简为 1/n,均匀分布。
  2. 常见错误:
    • 错误实现 1:每次全范围随机 RandRange(0, Num-1)——结果不均匀,n^n 条交换路径不能被 n! 整除;
    • 错误实现 2:每次随机取一个放入新数组——RemoveAt 导致 O(n²) 且需额外 O(n) 空间。
  3. 实际应用:随机抽卡(先洗牌取前 N)、随机播放列表、随机生成关卡出生点。
  4. 延伸:std::shuffle(C++11)内部即 Fisher-Yates;加权随机(元素概率不同)需权重采样。

五、附录:知识点速查表

编号 题目 核心考点 难度
1 Actor 与 Transform 分离 架构设计哲学、组合优于继承 ★★★
2 ChildActorComponent Actor 组合、生命周期管理 ★★
3 技能系统框架设计 数据驱动、流程建模、网络同步 ★★★
4 AOE 命中判定 3D 数学、点积应用、性能优化 ★★
5 Root Motion 动画驱动位移、网络同步 ★★
6 Montage Slot 系统 分层动画、AnimNotify ★★
7 GAS 核心架构 四大组件、技能执行流程 ★★★
8 25 匹马问题 逻辑推理、淘汰法 ★
9 Fisher-Yates 洗牌 概率论、算法正确性 ★
10 背包系统 数据结构、堆叠逻辑、FFastArraySerializer ★★