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

未命名文件 (4)

基本组件(继承链):

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移动,而是将输入量存储起来供移动组件在每帧更新时处理。
  • ControlInputVectorLastControlInputVector 保存了玩家输入的移动方向和大小,这些输入量通常在对应的移动组件中进行处理,不会应用到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::TickComponent
    • ApplyControlInputToVelocity()消费输入转换为Velocity;
    • Velocity转换为移动差量Delta
    • Delta用于驱动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_NoneUpdatedComponent 不可动或在物理仿真中:
    • 处理 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。
  • 应用 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);
  • 消费路径跟随请求速度
    • `LastUpdateRequestedVelocity = bHasRequestedVelocity ? RequestedVelocity : FVector::ZeroVector;
    • bHasRequestedVelocity = false;
  • 触发移动更新回调
    • OnMovementUpdated(DeltaSeconds, OldLocation, OldVelocity);
  • 结束范围移动更新
    • 退出 FScopedCapsuleMovementUpdate,让变换/碰撞状态最终生效。
  • 外部事件回调
    • CallMovementUpdateDelegate(DeltaSeconds, OldLocation, OldVelocity):广播外部监听者(在 scoped update 之外,以便事件能看到最终 Overlaps 等)。
  • 保存/更新基座位置选项
    • 根据 CVar 选择 SaveBaseLocation()MaybeSaveBaseLocation()(修复/优化相关行为)。
  • 更新组件速度缓存
    • UpdateComponentVelocity():把 movement component 的 Velocity 同步到 UpdatedComponent 的 velocity 等。
  • 网络相关:尽早取消自适应频率 Throttle
    • 在服务器权威下,如果 actor 移动了并且网络更新在被节流,可能会调用 NetDriver->CancelAdaptiveReplication(CharacterOwner) 来确保快速同步。
  • 计算/保存本帧最终位置/旋转并在服务端处理 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 //前置有移动基座的判断,只有进入移动基座才会在此进入这个

关于添加推动力 两个一个描述为一次性推动 一个为每帧推动,实际上完全相同。且每帧推动还会被*帧 使其大量缩减

posted @ 2025-10-31 20:18  丨桐  阅读(5)  评论(0)    收藏  举报