[UE学习笔记][基于源码] 控制器、Pawn、相机的控制关系
基于 UE 5.7 引擎源码
理清 Controller / PlayerController / Pawn / Character / CharacterMovementComponent / SpringArm / Camera / PlayerCameraManager 之间"谁持有什么、谁改谁"。
先盘点状态变量 ,再讲过程怎么读写它们------从"数据"到"流程"建立心智模型。
1. 核心状态变量
只列与"控制 / 移动 / 视点"直接相关的字段。
1.1 AController(基类)
抽象的"控制者",只有一个 Pawn 引用、一个朝向意图。
| 字段 | 类型 | 含义 | 默认 |
|---|---|---|---|
Pawn |
TObjectPtr<APawn> |
当前控制的 Pawn(通过 Possess/UnPossess 改) |
nullptr |
Character |
TObjectPtr<ACharacter> |
Pawn 是 Character 时的别名 | nullptr |
PlayerState |
TObjectPtr<APlayerState> |
玩家信息(昵称、分数等),可复制 | nullptr |
ControlRotation |
FRotator |
核心:玩家"想看哪儿"的朝向,权威源 | (0,0,0) |
1.2 APlayerController(在 AController 之上加了什么)
人类玩家专用,新增"输入"和"相机管理"两组字段。
| 字段 | 类型 | 含义 | 默认 |
|---|---|---|---|
PlayerInput |
TObjectPtr<UPlayerInput> |
按键 / 轴的"原始账本",输入流入口 | 由 OverridePlayerInputClass 或 InputSettings.DefaultPlayerInputClass Spawn |
RotationInput |
FRotator |
本帧鼠标 / 手柄累积的"旋转增量",每帧消费后清零 | (0,0,0) |
PlayerCameraManager |
TObjectPtr<APlayerCameraManager> |
视点管理器,每个 PlayerController 一个 | PostInitializeComponents 中 Spawn |
PlayerCameraManagerClass |
TSubclassOf<APlayerCameraManager> |
Spawn 时用哪个类 | APlayerCameraManager::StaticClass() |
bAutoManageActiveCameraTarget |
bool |
Possess 时是否自动把新 Pawn 设为 ViewTarget | true |
MyHUD |
TObjectPtr<AHUD> |
UI 入口 | nullptr |
1.3 APawn
被控制对象的基类,承担"被谁控制"和"要往哪儿走"两件事的存储。
| 字段 | 类型 | 含义 | 默认 |
|---|---|---|---|
Controller |
TObjectPtr<AController> |
反向指针 :谁在控制我(由 PossessedBy 设置) |
nullptr |
PlayerState |
TObjectPtr<APlayerState> |
与 Controller.PlayerState 同步的镜像 |
nullptr |
bUseControllerRotationPitch |
uint32:1 |
Pawn.Rotation.Pitch 是否每帧同步成 Controller.ControlRotation.Pitch |
false |
bUseControllerRotationYaw |
uint32:1 |
同上,Yaw | false |
bUseControllerRotationRoll |
uint32:1 |
同上,Roll | false |
BaseEyeHeight |
float |
计算视点位置时的眼高偏移 | 64 |
ControlInputVector |
FVector |
本帧累积的"移动输入向量"(世界空间),Movement Tick 后清零 | (0,0,0) |
LastControlInputVector |
FVector |
上一帧的副本,给查询用 | (0,0,0) |
Pawn 自己没有 ControlRotation 。
Pawn.GetViewRotation()默认实现 =Controller ? Controller->GetControlRotation() : ActorRotation。
1.4 ACharacter(在 APawn 之上加了什么)
把"角色三件套"持有起来,并加了跳跃状态。
| 字段 | 类型 | 含义 | 默认 |
|---|---|---|---|
CapsuleComponent |
TObjectPtr<UCapsuleComponent> |
碰撞胶囊(RootComponent) | 构造时创建 |
Mesh |
TObjectPtr<USkeletalMeshComponent> |
角色骨骼网格 | 构造时创建 |
CharacterMovement |
TObjectPtr<UCharacterMovementComponent> |
位移引擎 | 构造时创建 |
bPressedJump |
uint8:1 |
玩家是否按住跳键 | false |
JumpKeyHoldTime |
float |
按住时长 | 0 |
JumpMaxHoldTime |
float |
最大允许按住时长 | 0 |
JumpMaxCount |
int32 |
最多连跳次数 | 1 |
JumpCurrentCount |
int32 |
已跳次数 | 0 |
ACharacter构造里会把bUseControllerRotationYaw改回 true------所以默认 Character 的身体跟着鼠标转。
1.5 UCharacterMovementComponent
真正"动起来"的地方,持有速度 / 加速度 / 移动模式 / 朝向开关。
| 字段 | 类型 | 含义 | 默认 |
|---|---|---|---|
CharacterOwner |
TObjectPtr<ACharacter> |
反向指针 | 自动绑定 |
UpdatedComponent(基类) |
USceneComponent* |
实际被挪动的组件(= Character 的 Capsule) | Capsule |
Velocity(基类) |
FVector |
当前速度 | (0,0,0) |
Acceleration |
FVector |
当前加速度(来自 ControlInputVector 归一化方向 × MaxAcceleration) |
(0,0,0) |
MovementMode |
EMovementMode |
Walking / Falling / Flying / Swimming / Custom | MOVE_None(Possess 后由 SetDefaultMovementMode 切到 DefaultLandMovementMode,默认 MOVE_Walking) |
bOrientRotationToMovement |
uint8:1 |
角色朝向移动方向 (覆盖 bUseControllerDesiredRotation) |
false |
bUseControllerDesiredRotation |
uint8:1 |
角色平滑转向 ControlRotation |
false |
RotationRate |
FRotator |
上述两种转向的速度上限(度/秒) | (0, 360, 0) |
JumpZVelocity |
float |
跳跃初速 | 420 |
MaxWalkSpeed |
float |
行走最大速 | 600 |
GravityScale |
float |
重力倍率 | 1.0 |
1.6 USpringArmComponent
只决定"相机末端的位置和朝向",自身不渲染。
| 字段 | 类型 | 含义 | 默认 |
|---|---|---|---|
TargetArmLength |
float |
吊臂自然长度 | 300 |
SocketOffset |
FVector |
末端 socket 偏移(最常用,决定相机位置) | (0,0,0) |
TargetOffset |
FVector |
起点的世界空间偏移 | (0,0,0) |
bUsePawnControlRotation |
uint32:1 |
吊臂朝向 = Pawn.GetViewRotation()(即 ControlRotation) |
false |
bInheritPitch |
uint32:1 |
是否继承父组件 Pitch | true |
bInheritYaw |
uint32:1 |
是否继承父组件 Yaw | true |
bInheritRoll |
uint32:1 |
是否继承父组件 Roll | true |
bDoCollisionTest |
uint32:1 |
被遮挡时是否回弹 | true |
bEnableCameraLag |
uint32:1 |
位置是否平滑跟随 | false |
bEnableCameraRotationLag |
uint32:1 |
旋转是否平滑跟随 | false |
1.7 UCameraComponent
只决定镜头参数(FOV / 后处理等),位置由父级(通常是 SpringArm 末端)决定。
| 字段 | 类型 | 含义 | 默认 |
|---|---|---|---|
FieldOfView |
float |
透视 FOV(度) | 90 |
AspectRatio |
float |
宽高比 | 1.777778 |
bConstrainAspectRatio |
uint8:1 |
是否强制宽高比(不匹配时加黑边) | false |
PostProcessSettings |
FPostProcessSettings |
后处理 | 默认结构 |
bUsePawnControlRotation |
uint8:1 |
没有 SpringArm 时,自己跟控制器旋转 | false |
1.8 APlayerCameraManager
视点的"出口":选定 ViewTarget、调它的 CalcCamera、缓存 POV、跑修饰器栈。
| 字段 | 类型 | 含义 | 默认 |
|---|---|---|---|
PCOwner |
TObjectPtr<APlayerController> |
反向指针 | Spawn 时绑定 |
ViewTarget |
FTViewTarget |
当前观察对象(一般 = 当前 Pawn)+ 缓存的 POV | 空 |
PendingViewTarget |
FTViewTarget |
切换中的目标(用于混合) | 空 |
CameraStyle |
FName |
"Default" / "Fixed" / "FreeCam" 等模式 | "Default" |
DefaultFOV |
float |
没有 CameraComponent 时的兜底 FOV | 90 |
CameraCachePrivate |
FCameraCacheEntry |
上一帧算出的 POV 缓存 | 空 |
ModifierList |
TArray<UCameraModifier*> |
后处理混合栈(震屏等) | 空 |
2. 重要动作和开关------发生了什么
按"动作 / 开关"逐节展开,每节回答"它到底改了什么状态、玩家看到什么差异"。
时序图说明:
🔓表示虚函数(C++ 可重写)。
2.1 Add Yaw Input 发生了什么
这一步只做累加 ------把鼠标增量加到 APlayerController.RotationInput,不立即生效。
APlayerController APawn 蓝图 APlayerController APawn 蓝图 RotationInput.Yaw += Val Add Controller Yaw Input(Val) 1 AddYawInput(Val) 2
调用本身就到这里。真正"消费 RotationInput 写入 ControlRotation、并把朝向同步给 Pawn / SpringArm" 的过程在后续小节展开------这里只是生产端。
2.2 启用 Pawn 的 bUseControllerRotationPitch / Yaw / Roll 会发生什么
让 Pawn 的身体 跟着 ControlRotation 转。这一节顺便把 2.1 累积起来的 RotationInput 是怎么、什么时候被消费 也画出来------UpdateRotation 内部一气呵成。
APawn APlayerCameraManager APlayerController APawn APlayerCameraManager APlayerController TG_PrePhysics 局部 ViewRot = GetControlRotation() 局部 DeltaRot = RotationInput 跑 ModifierList 链 OutViewRot += OutDeltaRot;OutDeltaRot = 0 LimitViewPitch / Yaw / Roll 直接 return,不写 ActorRotation 关掉的轴回退到 ActorRotation 当前值 alt [三个 bUseControllerRotation* 全为 false] [任一为 true] TickActor 末尾:RotationInput = ZeroRotator 🔓 TickActor(DeltaTime) 1 🔓 PlayerTick(DeltaTime) 2 🔓 UpdateRotation(DeltaTime) 3 🔓 ProcessViewRotation(DeltaTime, ViewRot&, DeltaRot&) 4 返回(ViewRot 已按引用更新) 5 SetControlRotation(ViewRot) 6 🔓 FaceRotation(ViewRot) 7 SetActorRotation(ViewRot) 8
2.3 启用 SpringArm 的 bUsePawnControlRotation / bInheritPitch / Yaw / Roll 会发生什么
AController APawn USpringArmComponent AController APawn USpringArmComponent TG_PostPhysics DesiredRot = GetComponentRotation()(默认基底) DesiredRot = ControlRotation alt [bUsePawnControlRotation == true] DesiredRot.Pitch = RelativeRotation.Pitch alt [!bInheritPitch] DesiredRot.Yaw = RelativeRotation.Yaw alt [!bInheritYaw] DesiredRot.Roll = RelativeRotation.Roll alt [!bInheritRoll] alt [!IsUsingAbsoluteRotation()] 用 DesiredRot 计算 SocketTransform,末端 Camera 跟随 🔓 TickComponent(DeltaTime) 1 🔓 UpdateDesiredArmLocation(...) 2 GetTargetRotation() 3 🔓 GetViewRotation() 4 GetControlRotation() 5 ControlRotation 6 返回 ControlRotation 7 返回 DesiredRot 8
典型用法:
| 配置 | 表现 |
|---|---|
关掉 bInheritPitch |
相机水平面跟玩家转,但俯仰恒定(俯视 / 仰视固定相机) |
关掉 bInheritYaw |
相机朝固定方向,玩家在画面内自由转身(横版固定视角) |
| 全部关掉 | 相机姿态完全由 SpringArm 自身 RelativeRotation 决定,跟谁都不跟 |
2.4 Add Movement Input 发生了什么
UCharacterMovementComponent APawn 蓝图 UCharacterMovementComponent APawn 蓝图 ControlInputVector += WorldAccel TG_PrePhysics LastControlInputVector = ControlInputVector ControlInputVector = ZeroVector 用 InputVector 算 Acceleration / Velocity / 写 Capsule 位置 🔓 Add Movement Input(WorldDir, Scale) 1 🔓 AddInputVector(WorldDir * Scale) 2 Internal_AddMovementInput(WorldAccel) 3 🔓 TickComponent(DeltaTime) 4 ConsumeInputVector() 5 Internal_ConsumeMovementInputVector() 6 返回 InputVector 7 🔓 PerformMovement(DeltaSeconds) 8
关键 :AddYawInput 是 PlayerController 的成员(人类玩家专属 ),AddMovementInput 是 Pawn 的成员(AI 也用同一个 )------这就是为什么 AI 和玩家的"走路"汇合在 ControlInputVector 这一层,而"看哪儿"对 AI 而言走的是另一套(直接给 Controller SetControlRotation)。