【UEGamePlay】- 3C篇 : Character(一)

基本组件(继承链):
UActorCompoent - 负责组件的生命周期管理、激活/停用、与Actor的绑定等
USceneCompoent - 具有变换并支持附件(组件依附),但没有渲染或碰撞功能。
UPrimitiveCompoent - 具有渲染和物理信息,可以实现Overlap
UMovementComponent - 具备基本移动功能和接口
UProjectileMovementComponent - 抛射物移动
UNavMovementComponent - 路径寻找和导航功能,可实现代理AI移动
UPawnMovementComponent - 提供输入累积,Owner和移动相关接口
UCharacterMovementComponent - 提供了丰富的角色移动功能
UFloatingPawnMovement - 运动组件,为Pawn提供简单运动
基础移动组件(泛用型):
USceneComponent
+FTransform ComponentToWorld 组件相对世界的变换/基本位置信息 //关键能力
-----------------------------------
MoveComponent() //核心移动入口,负责位置/旋转更新 可选择sweep。
MoveComponentImpl() //虚函数分发
UpdateComponentToWorldWithParent() //更新 +ComponentToWorld
UMovementComponent
+USceneComponent UpdatedComponent //
+UPrimitiveComponent UpdatedPrimitive;
+FVector Velocity //速度,表示角色当前的移动速度和方向。
----------------------------------------------
SafeMoveUpdatedComponent() //安全移动更新的组件,尝试移动组件并处理碰撞和阻挡,确保不会进入不可通过的区域。
MoveUpdatedComponent() //移动更新的组件,直接移动组件到目标位置,不处理碰撞。
MoveUpdatedComponentImpl() //虚函数分发
GetPenetrationAdjustment() //计算移动调整,尝试摆脱失败移动带来的渗透/基于FHitResult,返回能解决穿透的Delta增量
ResolvePenetration() //移動失敗后,多次退回尝试移出物体的穿透范围。遵循平面约束
HandleImpact() // 碰撞响应
ComputeSlideVector() //计算滑动下的位移
SlideAlongSurface() //尝试沿表面滑动
GetMaxSpeed() //获取最大速度,返回角色在当前状态下的最大移动速度。
UpdateComponentVelocity() //更新组件速度
GetPhysicsVolume() //获取物理体积,返回角色当前所在的物理体积,如水、空气等。
UNavMovementComponent
+FNavAgentProperties NavAgentProps //导航代理属性,包含角色在导航网格中的相关信息,如大小、能否通过特定区域等。
+FMovementProperties MovementState //运动状态标签,用于标识角色的运动能力和状态。
uint8 bCanCrouch
uint8 bCanJump
uint8 bCanWalk
uint8 bCanSwim
uint8 bCanFly
基于GamePlay视角下的底层核心移动函数(此函数为不同移动组件衍生中包裹的核心):
USceneComponent::MoveComponent()
virtual MoveComponentImpl() //虚函数转发
ConditionalUpdateComponentToWorld()
UpdateComponentToWorld()
UpdateComponentToWorldWithParent()
更新变量:+ComponentToWorld
InternalSetWorldLocationAndRotation
UpdateComponentToWorldWithParent()
更新变量:+ComponentToWorld
移动组件处理移动的核心调用链(主要用于处理不同的物理运动状态下的移动)
UMovementComponent::SafeMoveUpdatedComponent()
UMovementComponent::MoveUpdatedComponent()
UMovementComponent::MoveUpdatedComponentImpl()
+UpdatedComponent -> USceneComponent::MoveComponent();
tips:移动组件必须控制一个能够存在于世界上的组件。移动组件属于UActorCompoent的子类。本身不具备坐标,其主要功能是用于控制成员变量USceneComponent UpdatedComponent进行移动
UMovementComponent::SafeMoveUpdatedComponent(const FVector& Delta, const FRotator& NewRotation, bool bSweep, FHitResult& OutHit, ETeleportType Teleport)
USceneComponent::MoveComponent(const FVector& Delta, const FQuat& NewRotation, bool bSweep, FHitResult* Hit=NULL, EMoveComponentFlags MoveFlags = MOVECOMP_NoFlags, ETeleportType Teleport = ETeleportType::None);
衍生移动组件(角色特化型)
//角色特化型移动组件一般用于包含“物理”特性的可操控主体。例如Pawn,Character
Pawn && PawnMovementComponent
PawnMovementComponent的设计上用于为 Pawn 提供更新移动的能力(接口/辅助),主要目的还是为了子组件铺垫所提供一些Pawn其关联的移动的基本功能。提供了一种通用的方式来累积和读取方向输入(并不实现具体的移动行为):
UPawnMovementComponent
+TObjectPtr<class APawn> PawnOwner;
-----------------------------------
AddInputVector() //接受输入方向 自身不做处理,内部转发回Owner处理
ConsumeInputVector() //消耗输入方向 自身不做处理,内部转发回Owner处理
APawn
+FVector ControlInputVector //待处理输入
+FVector LastControlInputVector //正在处理输入
+bUseControllerRotationPitch //使用控制器旋转
+bUseControllerRotationRoll //使用控制器旋转
+bUseControllerRotationYaw //使用控制器旋转
----------------------------------------
SetupPlayerInputComponent() //设置玩家输入组件
CreatePlayerInputComponent()//创建玩家输入组件
AddMovementInput()
//内部最终调用为 APawn::Internal_AddMovementInput();
//内部包含拥有PawnMovementComponent的转发,但是实际最终调用相同(转发回溯)
ConsumeMovementInputVector()
//内部最终调用为APawn::Internal_ConsumeMovementInputVector();
//内部包含拥有PawnMovementComponent的转发,但是实际最终调用相同(转发回溯)
AddControllerPitchInput()
//内部转发至AController::AddPitchInput();
AddControllerYawInput()
//内部转发至AController::AddYawInput();
AddControllerRollInput()
//内部转发至AController::AddRollInput();
GetLastMovementInputVector()//返回LastControlInputVector
GetPendingMovementInputVector()//返回ControlInputVector
FaceRotation()//将Pawn的旋转更新为指定的旋转,假定为ControlRotation,遵循控制器旋转设置
GetViewRotation()// 返回视图旋转 通常是控制器旋转
UpdateNavAgent()//更新导航代理,确保导航代理属性与角色状态同步。
MoveIgnoreActorAdd()//添加忽略的移动演员,将指定的演员添加到移动忽略列表中,避免与之碰撞
GetMovementComponent()//此函数为了通用性用了消耗量比较大的查找类去搜寻,可以进行重载变得更快捷
AddMovementInput()用于收集和存储移动输入。主要用于改变Pawn中的ControlInputVector以及LastControlInputVector。本身不会直接导致Pawn移动,而是将输入量存储起来供移动组件在每帧更新时处理。ControlInputVector,LastControlInputVector保存了玩家输入的移动方向和大小,这些输入量通常在对应的移动组件中进行处理,不会应用到Pawn的移动上。- 基础Pawn类中并没有直接附加
UPawnMovementComponent组件。需要自行添加 UPawnMovementComponent组件并不实现具体的移动行为,其组件含义为提供输入累积,Owner 关联和一些移动相关的工具函数ControlInputVector->LastControlInputVector(目的是防止在帧之间控制输入的累积)
输入-移动/旋转流程
Pawn中不实现具体的移动逻辑,但是存在已经实现的旋转逻辑
旋转流程:
APlayerController::TickActor
PlayerTick
UpdateRotation
APawn::FaceRotation
如果启用以下设置中任意一项
+bUseControllerRotationPitch //使用控制器旋转
+bUseControllerRotationRoll //使用控制器旋转
+bUseControllerRotationYaw //使用控制器旋转
在APawn::FaceRotation中Pawn的旋转会被启用的对应控制器旋转控制(直接SetActorRotation())
如果不是自由相机,尽可能不要使用这个方式,尤其是在Character这种精细化的角色子类。
DefaultPawn(Pawn衍生/模板)
Character 和 DefaultPawn 等子类会自动处理此输入并移动。
以DefaultPawn举例,由输入导致的最终更新位于TickComponent中SafeMoveUpdatedComponent();
DefaultPawn作为基本实现应用类的简易角色类,是其余角色类的基本模板
UFloatingPawnMovement //运动组件,提供速度,加速度限制,没有实现重力
ApplyControlInputToVelocity()//负责将存储的输入量转换为加速度或速度。这一步将输入量应用到Pawn的速度上,决定Pawn的移动方向和速度。
ADefaultPawn
+UFloatingPawnMovement MovementComponent //移动组件
+UStaticMeshComponent MeshComponent //
输入-移动旋转流程
- 旋转由父类Pawn的FaceRotation提供,默认启用三项控制器旋转设置
UFloatingPawnMovement::TickComponentApplyControlInputToVelocity()消费输入转换为Velocity;Velocity转换为移动差量DeltaDelta用于驱动SafeMoveUpdatedComponent()实现实际的移动HandleImpact()和SlideAlongSurface()处理滑动以及物理阻挡UpdateComponentVelocity()更新组件速度
Character (包含基础运动学的网络同步角色)
UCharacterMovementComponent
+FVector Acceleration //加速度 由输入向量更新
+FFindFloorResult CurrentFloor //当前地面信息
+enum EMovementMode MovementMode //当前移动模式
+float Mass //质量,用于计算加速度以及受力
+bEnableScopedMovementUpdates //是否启动范围内更新优化性能
+FNetworkPredictionData_Client_Character* ClientPredictionData //客户端角色移动预测数据
+FNetworkPredictionData_Server_Character* ServerPredictionData //服务器角色移动预测数据
--------------------------------------------------
MoveAutonomous() //处理自主移动,即在不受其他外力干扰的情况下,角色根据输入指令进行移动的逻辑。
MoveAlongFloor() //在地面上移动的逻辑,确保角色沿着地面正常行走,并处理地形变化。
UpdateBasedRotation() //随着底座的旋转更新控制器的视图旋转
StartNewPhysics() //启动新的物理处理周期,根据角色当前的移动模式和状态,调用相应的物理处理函数。
PhysWalking() //处理角色在步行模式下的物理计算,包括地面摩擦力、重力等因素。
PhysNavWalking() //处理角色在导航网格上步行的物理计算,通常用于AI角色的路径导航。
PhysFalling() //处理角色在下落模式下的物理计算,包括重力加速度和落地检测。
PhysFlying() //处理角色在飞行模式下的物理计算,包括飞行速度和方向控制。
PhysSwimming() //处理角色在游泳模式下的物理计算,包括水的浮力和阻力。
PhysCustom() //处理自定义模式下的物理计算,允许开发者实现自定义的移动逻辑。
CallServerMove() //在客户端上调用,向服务器发送移动请求,确保客户端和服务器上的角色位置同步。
ReplicateMoveToServer() //复制移动数据到服务器,确保服务器上的角色位置和客户端的一致。
ClientUpdatePositionAfterServerUpdate() //在服务器更新后,客户端更新角色位置,处理可能的位移校正。
SimulatedTick() //在模拟角色(非本地控制的角色)上调用,用于处理模拟的移动和物理更新。
TickCharacterPose() //每帧更新角色的姿态,确保角色动画和物理状态的同步
ACharacter
+FBasedMovementInfo BasedMovement //保存角色当前所站立的“基础”对象的信息,用于处理角色在移动平台上或其他移动基础上的相对位置和旋转。
+FBasedMovementInfo ReplicatedBasedMovement //复制的基于移动的信息,用于网络同步,确保客户端和服务器上的基于移动状态一致。
+uint8 ReplicatedMovementMode //复制的移动模式,用于网络同步,确保客户端和服务器上的移动模式一致。
+float ReplicatedServerLastTransformUpdateTimeStamp //复制的服务器最后变换更新时间戳,用于网络同步,记录服务器上最后一次变换更新的时间。
+TObjectPtr<UCharacterMovementComponent> CharacterMovement
+TObjectPtr<USkeletalMeshComponent> Mesh
+TObjectPtr<UCapsuleComponent> CapsuleComponent
FBasedMovementinfo //保存有关角色所站立的“基础”对象的信息的结构。
+TObjectPtr<UPrimitiveComponent> MovementBase //指向角色所站立的基础对象的指针,通常是一个移动平台或其他物理对象
+FName BoneName //基础对象上的骨骼名称,如果角色站立在一个骨骼网格的特定骨骼上,则记录该骨骼的名称。
+FVector_NetQuantize100 Location //角色相对于基础对象的位置,使用网络量化格式确保高效同步。
+FRotator Rotation //角色相对于基础对象的旋转。
+bool bServerHasBaseComponent //表示服务器是否拥有基础组件。
+bool bRelativeRotation //表示角色是否相对于基础对象进行旋转。
+bool bServerHasVelocity //表示服务器是否拥有基础对象的速度信息。
输入-移动旋转流程(玩家)
//以下是接受控制器输入状态的角色,也就是玩家角色的流程
常规的第三人称/第一人称 角色移动逻辑:
AddControllerPitch/Yaw/RollInput //添加对应的控制器旋转
更新ControlRotation
AddMovementInput //添加移动向量
移动输入向量被转换为加速度向量,后续在各类动力学模式中被处理为速度进行移动
通常根据ControlRotation转换移动向量 //例如攀爬等方向为特殊需要单独处理
UCharacterMovementComponent::TickComponent()
InputVector = ConsumeInputVector(){return APawn::LastControlInputVector}
UCharacterMovementComponent::ControlledCharacterMove(InputVector, DeltaTime)
Acceleration = ScaleInputAcceleration(ConstrainInputAcceleration(InputVector));
PerformMovement() //完整的移动处理流程
FScopedMovementUpdate // 延迟更新
StartNewPhysics() //在这里处理不同状态下的移动逻辑
switch (MovementMode){ PhysWalking PhysNavWalking PhysFalling PhyFlying PhysSwimming PhysCustom }
CalcVelocity() //根据加速度计算速度
SafeMoveUpdatedComponent() //核心移动逻辑(每个分支均会包括)
PhysicsRotation()//核心旋转逻辑。
ComputeOrientToMovementRotation()
// 根据当前运动计算目标旋转,当使用bOrientRotationToMovement时,基于加速度计算旋转
正常在地面上的流程栈帧:
![[6bbc850572a1f714064411937e50411e.png]]
PerformMovement
处理绝大部分情况下的每帧运动,是CharacterMovementComponent的核心,其中包含范围更新移动代码块。此代码块为PerformMovement的核心
tips:以目的做流程,子列表为核心函数
处理流程如下
- 计时/前置验证
SCOPE_CYCLE_COUNTER(性能统计)HasValidData()/GetWorld()前置有效性检查
- 瞬移检测
- 更新 bTeleportedSinceLastUpdate 用于在地面上瞬移后强制检测地面
- 早退(不能移动的情况)
- 若
MovementMode == MOVE_None或UpdatedComponent不可动或在物理仿真中: - 处理 root motion 的消耗(若不是客户端更新且不忽略根运动),清空/消费 root motion,清除累计力,然后返回。
- 若
- 基于移动的地面检查准备
bForceNextFloorCheck |= (IsMovingOnGround() && bTeleportedSinceLastUpdate);如果我们在地上且发生瞬移,强制下一帧做地面检查。
- root motion 的增量调整
CurrentRootMotion.LastPreAdditiveVelocity += Adjustment;
- 保存旧状态(调试用)
- 开始范围移动更新(性能/一致性保障)
FScopedCapsuleMovementUpdate ScopedMovementUpdate(UpdatedComponent, bEnableScopedMovementUpdates);减少重复性碰撞检测
- 根据基座运动更新或者延迟更新位置
MaybeUpdateBasedMovement(DeltaSeconds)UpdateBasedMovement(DeltaSeconds)UpdateBasedRotation(FinalRotation, PawnDeltaRotation.Rotator());
- 清理无效的 RootMotion Source
CurrentRootMotion.CleanUpInvalidRootMotion(DeltaSeconds, *CharacterOwner, *this);
- 应用累计外力
ApplyAccumulatedForces(DeltaSeconds):把外部施加到 character 的力(launch、external forces)转换为速度/影响。
- 更新角色状态(移动前)
UpdateCharacterStateBeforeMovement(DeltaSeconds);移动前修正状态 //源码中为蹲伏状态的切换
- 检查MOVE_NavWalking
TryToLeaveNavWalking();查询是否需要做离开导航步行状态的转换
- 处理延迟的 Launch
HandlePendingLaunch() Character::LaunchCharacter()被延迟执行时在此生效ClearAccumulatedForces清除累计力
- 准备/收集 Root Motion(在物理前)
- 存在根运动且不由客户端更新:
- `IsPlayingRootMotion()
TickCharacterPose(),并转换动画 local->world。
CurrentRootMotion.PrepareRootMotion(...):准备合成/累积来自非动画来源的 root motion。
- `IsPlayingRootMotion()
- 存在根运动且不由客户端更新:
- 应用 Root Motion 到 Velocity
HasAnimRootMotion()animation root motion 转成世界空间速度,覆盖Velocity- `RootMotionParams = ConvertLocalRootMotionToWorld()
AnimRootMotionVelocity = CalcAnimRootMotionVelocity()Velocity = ConstrainAnimRootMotionVelocity()
else- 若
HasOverrideVelocity,合成 override velocity 到Velocity。
- 若
- NaN 校验
- 宏
ensureMsgf(!Velocity.ContainsNaN()确保速度合法,避免灾难性崩溃
- 宏
- 清除跳跃输入与计数
CharacterOwner->ClearJumpInput(DeltaSeconds);NumJumpApexAttempts = 0;
- 主要物理移动调用
StartNewPhysics(DeltaSeconds, 0)真正移动角色地方,存在基本的动力学
- 再次验证有效性
- 移动后更新角色
UpdateCharacterStateAfterMovement(DeltaSeconds);移动后修正状态 //源码中为蹲伏状态的切换
- 旋转处理
PhysicsRotation(DeltaSeconds);
- 应用 Root Motion 的旋转
- `HasAnimRootMotion()
MoveUpdatedComponent(FVector::ZeroVector, NewActorRotationQuat, true)}
CurrentRootMotion.HasActiveRootMotionSources()MoveUpdatedComponent(FVector::ZeroVector, NewActorRotationQuat, true);
- `HasAnimRootMotion()
- 消费路径跟随请求速度
- `LastUpdateRequestedVelocity = bHasRequestedVelocity ? RequestedVelocity : FVector::ZeroVector;
bHasRequestedVelocity = false;
- 触发移动更新回调
OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity);
- 结束范围移动更新
- 退出
FScopedCapsuleMovementUpdate,让变换/碰撞状态最终生效。
- 退出
- 外部事件回调
CallMovementUpdateDelegate(DeltaSeconds, OldLocation, OldVelocity):广播外部监听者(在 scoped update 之外,以便事件能看到最终 Overlaps 等)。
- 保存/更新基座位置选项
- 根据 CVar 选择
SaveBaseLocation()或MaybeSaveBaseLocation()(修复/优化相关行为)。
- 根据 CVar 选择
- 更新组件速度缓存
UpdateComponentVelocity():把 movement component 的Velocity同步到 UpdatedComponent 的 velocity 等。
- 网络相关:尽早取消自适应频率 Throttle
- 在服务器权威下,如果 actor 移动了并且网络更新在被节流,可能会调用
NetDriver->CancelAdaptiveReplication(CharacterOwner)来确保快速同步。
- 在服务器权威下,如果 actor 移动了并且网络更新在被节流,可能会调用
- 计算/保存本帧最终位置/旋转并在服务端处理 timestamp
NewLocation = UpdatedComponent->GetComponentLocation(),NewRotation = UpdatedComponent->GetComponentQuat()。若 server 且 transform 改变,保存ServerLastTransformUpdateTimeStamp(有条件使用客户端时间戳)。
- 保存 LastUpdateLocation/Rotation/Velocity
LastUpdateLocation = NewLocation; LastUpdateRotation = NewRotation; LastUpdateVelocity = Velocity;—— 用于下一帧比较/差分和网络预测。
工程要点:
在CharacterMovementComponent中处理旋转的是PhysicsRotation:其核心变量为DesiredRotation
由Orient Rotation to movement和bUseControllerDesiredRotation决定DesiredRotation计算方式
ComputeOrientToMovementRotation();//根据当前运动计算目标旋转,当使用bOrientRotationToMovement时,基于加速度计算旋转
UpdateBasedRotation :当角色所依附的 Base(比如旋转的平台/载具/重力基座)发生旋转 时,同步调整角色相关的朝向(主要是 Controller 的 ControlRotation / 视角)并根据设置决定是否把基座的 roll (翻滚)应用到角色/摄像机上。它在 PerformMovement 中计算出 pawn 因基座旋转产生的增量旋转(delta) 后被调用,允许移动组件修正最终的角色朝向或对控制器视角做额外处理。
旋转基座处理的调用栈
PerformMovement
MaybeUpdateBasedMovement
UpdateBasedMovement
UpdateBasedRotation //前置有移动基座的判断,只有进入移动基座才会在此进入这个
关于添加推动力 两个一个描述为一次性推动 一个为每帧推动,实际上完全相同。且每帧推动还会被*帧 使其大量缩减

浙公网安备 33010602011771号