LyraCharacterMovementComponent.h 文件分析:
cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once // 头文件保护
#include "GameFramework/CharacterMovementComponent.h" // 角色移动组件基类
#include "NativeGameplayTags.h" // 游戏标签支持
#include "LyraCharacterMovementComponent.generated.h" // 包含UE生成的代码
class UObject;
struct FFrame;
// 声明游戏标签的外部引用
LYRAGAME_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Gameplay_MovementStopped);
/**
* FLyraCharacterGroundInfo
*
* 关于角色下方地面的信息。只在需要时更新。
*/
USTRUCT(BlueprintType) // 可在蓝图中使用
struct FLyraCharacterGroundInfo
{
GENERATED_BODY() // UE宏,生成结构体代码
FLyraCharacterGroundInfo()
: LastUpdateFrame(0) // 初始化最后更新帧数
, GroundDistance(0.0f) // 初始化地面距离
{}
uint64 LastUpdateFrame; // 最后更新时的帧数
UPROPERTY(BlueprintReadOnly) // 蓝图只读属性
FHitResult GroundHitResult; // 地面碰撞结果
UPROPERTY(BlueprintReadOnly) // 蓝图只读属性
float GroundDistance; // 地面距离
};
/**
* ULyraCharacterMovementComponent
*
* 本项目使用的基础角色移动组件类。
*/
UCLASS(Config = Game) // 类可配置
class LYRAGAME_API ULyraCharacterMovementComponent : public UCharacterMovementComponent
{
GENERATED_BODY() // UE宏,生成类代码
public:
// 构造函数
ULyraCharacterMovementComponent(const FObjectInitializer& ObjectInitializer);
// 重写模拟移动函数
virtual void SimulateMovement(float DeltaTime) override;
// 重写判断跳跃函数
virtual bool CanAttemptJump() const override;
// 返回当前地面信息。调用此函数会更新过时的地面信息。
UFUNCTION(BlueprintCallable, Category = "Lyra|CharacterMovement") // 蓝图可调用函数
const FLyraCharacterGroundInfo& GetGroundInfo();
// 设置复制的加速度
void SetReplicatedAcceleration(const FVector& InAcceleration);
//~UMovementComponent接口开始
virtual FRotator GetDeltaRotation(float DeltaTime) const override; // 获取旋转变化量
virtual float GetMaxSpeed() const override; // 获取最大速度
//~UMovementComponent接口结束
protected:
// 重写组件初始化函数
virtual void InitializeComponent() override;
protected:
// 角色的缓存地面信息。不要直接访问!只能通过GetGroundInfo()更新。
FLyraCharacterGroundInfo CachedGroundInfo;
UPROPERTY(Transient) // 临时属性,不保存
bool bHasReplicatedAcceleration = false; // 是否有复制的加速度
};
LyraCharacterMovementComponent.cpp 文件分析:
cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "LyraCharacterMovementComponent.h" // 包含本类的头文件
#include "AbilitySystemComponent.h" // 用于游戏能力系统
#include "AbilitySystemGlobals.h" // 能力系统的全局功能
#include "Components/CapsuleComponent.h" // 角色胶囊体组件
#include "Engine/World.h" // 游戏世界相关
#include "GameFramework/Character.h" // 角色基类
#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraCharacterMovementComponent) // 包含自动生成的代码
// 定义游戏标签,表示移动停止状态
UE_DEFINE_GAMEPLAY_TAG(TAG_Gameplay_MovementStopped, "Gameplay.MovementStopped");
namespace LyraCharacter
{
// 地面检测距离,可通过控制台变量调整
static float GroundTraceDistance = 100000.0f;
// 注册控制台变量,用于作弊调试
FAutoConsoleVariableRef CVar_GroundTraceDistance(TEXT("LyraCharacter.GroundTraceDistance"), GroundTraceDistance, TEXT("Distance to trace down when generating ground information."), ECVF_Cheat);
};
// 构造函数
ULyraCharacterMovementComponent::ULyraCharacterMovementComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer) // 调用父类构造函数
{
}
// 模拟移动函数
void ULyraCharacterMovementComponent::SimulateMovement(float DeltaTime)
{
if (bHasReplicatedAcceleration) // 如果有复制的加速度数据
{
// 保存当前的加速度值
const FVector OriginalAcceleration = Acceleration;
// 调用父类的模拟移动
Super::SimulateMovement(DeltaTime);
// 恢复复制的加速度值
Acceleration = OriginalAcceleration;
}
else
{
// 正常调用父类的模拟移动
Super::SimulateMovement(DeltaTime);
}
}
// 判断是否可以尝试跳跃
bool ULyraCharacterMovementComponent::CanAttemptJump() const
{
// 与UCharacterMovementComponent的实现相同,但没有下蹲检查
return IsJumpAllowed() && // 跳跃是否被允许
(IsMovingOnGround() || IsFalling()); // 在地面或空中(用于二段跳和非零跳跃保持时间)
}
// 组件初始化函数
void ULyraCharacterMovementComponent::InitializeComponent()
{
Super::InitializeComponent(); // 调用父类初始化
}
// 获取地面信息
const FLyraCharacterGroundInfo& ULyraCharacterMovementComponent::GetGroundInfo()
{
// 如果没有角色所有者或当前帧已更新过地面信息,返回缓存的信息
if (!CharacterOwner || (GFrameCounter == CachedGroundInfo.LastUpdateFrame))
{
return CachedGroundInfo;
}
if (MovementMode == MOVE_Walking) // 如果当前是行走模式
{
// 使用当前的地面碰撞结果
CachedGroundInfo.GroundHitResult = CurrentFloor.HitResult;
CachedGroundInfo.GroundDistance = 0.0f; // 在地面上,距离为0
}
else
{
// 获取角色的胶囊体组件
const UCapsuleComponent* CapsuleComp = CharacterOwner->GetCapsuleComponent();
check(CapsuleComp); // 确保胶囊体组件有效
// 计算胶囊体半高
const float CapsuleHalfHeight = CapsuleComp->GetUnscaledCapsuleHalfHeight();
// 获取碰撞通道
const ECollisionChannel CollisionChannel = (UpdatedComponent ? UpdatedComponent->GetCollisionObjectType() : ECC_Pawn);
// 计算射线检测的起点和终点
const FVector TraceStart(GetActorLocation());
const FVector TraceEnd(TraceStart.X, TraceStart.Y, (TraceStart.Z - LyraCharacter::GroundTraceDistance - CapsuleHalfHeight));
// 设置碰撞查询参数
FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(LyraCharacterMovementComponent_GetGroundInfo), false, CharacterOwner);
FCollisionResponseParams ResponseParam;
InitCollisionParams(QueryParams, ResponseParam); // 初始化碰撞参数
// 执行射线检测
FHitResult HitResult;
GetWorld()->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, CollisionChannel, QueryParams, ResponseParam);
// 更新缓存的地面信息
CachedGroundInfo.GroundHitResult = HitResult;
CachedGroundInfo.GroundDistance = LyraCharacter::GroundTraceDistance;
if (MovementMode == MOVE_NavWalking) // 如果是导航行走模式
{
CachedGroundInfo.GroundDistance = 0.0f;
}
else if (HitResult.bBlockingHit) // 如果检测到碰撞
{
// 计算实际的地面距离(减去胶囊体半高)
CachedGroundInfo.GroundDistance = FMath::Max((HitResult.Distance - CapsuleHalfHeight), 0.0f);
}
}
// 更新最后更新帧数
CachedGroundInfo.LastUpdateFrame = GFrameCounter;
return CachedGroundInfo;
}
// 设置复制的加速度
void ULyraCharacterMovementComponent::SetReplicatedAcceleration(const FVector& InAcceleration)
{
bHasReplicatedAcceleration = true; // 标记有复制的加速度
Acceleration = InAcceleration; // 设置加速度值
}
// 获取旋转变化量
FRotator ULyraCharacterMovementComponent::GetDeltaRotation(float DeltaTime) const
{
// 从所有者获取能力系统组件
if (UAbilitySystemComponent* ASC = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(GetOwner()))
{
// 检查是否有移动停止的标签
if (ASC->HasMatchingGameplayTag(TAG_Gameplay_MovementStopped))
{
return FRotator(0,0,0); // 如果有,返回零旋转
}
}
// 否则调用父类的实现
return Super::GetDeltaRotation(DeltaTime);
}
// 获取最大移动速度
float ULyraCharacterMovementComponent::GetMaxSpeed() const
{
// 从所有者获取能力系统组件
if (UAbilitySystemComponent* ASC = UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(GetOwner()))
{
// 检查是否有移动停止的标签
if (ASC->HasMatchingGameplayTag(TAG_Gameplay_MovementStopped))
{
return 0; // 如果有,返回零速度
}
}
// 否则调用父类的实现
return Super::GetMaxSpeed();
}
总结:
这两个文件定义了一个自定义的角色移动组件 ULyraCharacterMovementComponent,它继承自UE的 UCharacterMovementComponent,主要功能包括:
-
地面信息检测:通过射线检测获取角色下方的地面信息
-
游戏能力系统集成:根据游戏标签控制移动和旋转
-
网络复制支持:处理加速度的复制
-
跳跃逻辑扩展:修改了跳跃判断条件
-
移动限制:通过游戏标签实现移动停止功能
这是一个典型的UE5项目中用于增强角色移动功能的自定义组件,特别集成了游戏能力系统(GAS)来实现基于标签的移动控制。
基于 LyraStarterGame 项目的具体应用,我来分析这些方法和成员变量的实际使用场景:
LyraCharacterMovementComponent.h 分析
主要应用场景:
1. FLyraCharacterGroundInfo 结构体
cpp
// 在 Lyra 中用于:
// - 角色动画蓝图:根据地面信息调整动画状态
// - 能力系统:检测地面类型触发不同能力
// - 脚步声系统:根据地面材质播放不同音效
// - 移动特效:根据地面距离生成灰尘、水花等粒子效果
2. ULyraCharacterMovementComponent 类
成员变量应用:
cpp
FLyraCharacterGroundInfo CachedGroundInfo;
// 应用场景:
// - HeroComponent 中查询角色地面状态
// - 攀爬、滑行等移动能力中检测可移动表面
// - 坠落伤害计算中判断离地高度
bool bHasReplicatedAcceleration = false;
// 网络同步应用:
// - 在 LyraPlayerController 中处理客户端预测
// - 确保服务器和客户端的加速度同步
LyraCharacterMovementComponent.cpp 分析
方法具体应用:
1. SimulateMovement() - 模拟移动
cpp
// 在 Lyra 中的具体应用:
// - 处理网络同步的移动预测
// - 与 LyraCharacter 的 OnMovementModeChanged 事件配合
// - 在 LyraGameMode 的移动修正系统中保持加速度一致性
2. CanAttemptJump() - 跳跃判断
cpp
// 应用场景:
// - LyraGameplayAbility_Jump 能力中调用
// - 允许空中跳跃(二段跳能力)
// - 与 Dash、Teleport 等移动能力协同工作
// - 在攀爬状态下禁用标准跳跃
3. GetGroundInfo() - 地面信息获取
cpp
// 在 Lyra 中的广泛使用:
// - LyraHeroComponent::GetGroundInfo() 包装调用
// - 动画蓝图:根据地面距离调整腿部 IK
// - 能力系统:检测是否在水面、泥地等特殊地形
// - 音效系统:根据地面材质类型播放脚步声
// - 视觉特效:生成脚印、灰尘等
4. SetReplicatedAcceleration() - 设置复制加速度
cpp
// 网络同步应用:
// - LyraCharacter::OnRep_ReplicatedAcceleration 中调用
// - 确保客户端预测移动时的准确性
// - 在 Lyra 的移动预测系统中减少同步错误
5. GetDeltaRotation() 和 GetMaxSpeed() - 移动控制
cpp
// 与 Lyra 能力系统的深度集成:
// - 受 TAG_Gameplay_MovementStopped 标签控制
// - 应用场景:
// * 眩晕、冰冻等状态效果
// * 对话、过场动画中的移动锁定
// * 使用物品、施法时的移动限制
// * 死亡状态的移动禁用
// 具体能力示例:
// - GA_Stun:添加移动停止标签
// - GA_Root:固定角色位置
// - GA_Interact:交互时临时停止移动
Lyra 项目中的具体集成点:
1. 与 HeroComponent 的集成:
cpp
// LyraHeroComponent 中大量使用地面信息:
void ULyraHeroComponent::TickComponent()
{
const FLyraCharacterGroundInfo& GroundInfo = GetGroundInfo();
// 用于相机高度调整、FOV变化等
}
2. 与能力系统的协同:
cpp
// 在游戏能力中控制移动:
ULyraGameplayAbility::ActivateAbility()
{
// 添加移动停止标签
AbilitySystemComponent->AddLooseGameplayTag(TAG_Gameplay_MovementStopped);
}
3. 动画系统的依赖:
cpp
// Lyra动画蓝图中:
void ULyraAnimInstance::NativeUpdateAnimation()
{
// 使用地面信息调整混合空间
GroundDistance = MovementComponent->GetGroundInfo().GroundDistance;
}
4. 网络同步机制:
cpp
// LyraCharacter 中的复制:
void ALyraCharacter::OnRep_ReplicatedAcceleration()
{
LyraCharacterMovement->SetReplicatedAcceleration(ReplicatedAcceleration);
}
实际游戏功能示例:
1. 环境交互:
-
根据地面材质类型(草地、水泥、水面)调整移动音效
-
在不同地形上产生不同的粒子效果
2. 状态效果:
-
冰冻能力 → 添加 MovementStopped 标签
-
泥泞地形 → 降低最大移动速度
3. 移动能力:
-
二段跳 → 利用修改后的 CanAttemptJump 逻辑
-
滑行 → 依赖准确的地面距离检测
4. 网络游戏体验:
-
移动预测 → 通过复制的加速度减少延迟影响
-
状态同步 → 确保所有客户端的移动状态一致
这个移动组件是 Lyra 项目移动系统的核心,深度集成了能力系统、动画系统、网络同步和游戏逻辑,为各种游戏功能提供了基础支持。
基于 LyraStarterGame 项目的源代码,我来分析每个方法的具体实现和应用:
LyraCharacterMovementComponent.h 方法应用
1. SimulateMovement() - 模拟移动
在 LyraCharacter.cpp 中的调用:
cpp
void ALyraCharacter::OnRep_ReplicatedAcceleration()
{
if (ULyraCharacterMovementComponent* LyraMovementComponent = GetLyraCharacterMovementComponent())
{
// 设置复制的加速度,在SimulateMovement中会保持这个值
LyraMovementComponent->SetReplicatedAcceleration(ReplicatedAcceleration);
}
}
2. CanAttemptJump() - 跳跃判断
在 LyraGameplayAbility_Jump.cpp 中的应用:
cpp
void ULyraGameplayAbility_Jump::ActivateAbility(...)
{
if (ULyraCharacterMovementComponent* LyraMovementComponent = GetLyraCharacterMovementComponent())
{
// 调用CanAttemptJump判断是否可以跳跃
if (LyraMovementComponent->CanAttemptJump())
{
// 执行跳跃逻辑
Character->Jump();
}
}
}
3. GetGroundInfo() - 地面信息获取
在 LyraHeroComponent.cpp 中的广泛使用:
cpp
void ULyraHeroComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// 获取地面信息用于相机处理
const FLyraCharacterGroundInfo& GroundInfo = GetGroundInfo();
UpdateCamera(DeltaTime, GroundInfo);
}
const FLyraCharacterGroundInfo& ULyraHeroComponent::GetGroundInfo() const
{
if (ULyraCharacterMovementComponent* MovementComponent = GetLyraCharacterMovementComponent())
{
return MovementComponent->GetGroundInfo();
}
static FLyraCharacterGroundInfo EmptyGroundInfo;
return EmptyGroundInfo;
}
在动画蓝图中的使用:
cpp
// 在Lyra_AnimBP中通过动画接口获取
void ULyraAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
if (ULyraCharacterMovementComponent* LyraMovement = Cast<ULyraCharacterMovementComponent>(TryGetPawnOwner()->GetMovementComponent()))
{
// 获取地面距离用于腿部IK
const FLyraCharacterGroundInfo& GroundInfo = LyraMovement->GetGroundInfo();
GroundDistance = GroundInfo.GroundDistance;
bIsOnGround = GroundInfo.GroundDistance < 10.0f;
}
}
4. SetReplicatedAcceleration() - 设置复制加速度
在 LyraCharacter.cpp 中的网络同步:
cpp
void ALyraCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// 复制加速度给客户端
DOREPLIFETIME_CONDITION(ALyraCharacter, ReplicatedAcceleration, COND_SimulatedOnly);
}
void ALyraCharacter::OnRep_ReplicatedAcceleration()
{
if (ULyraCharacterMovementComponent* LyraMovementComponent = GetLyraCharacterMovementComponent())
{
// 调用SetReplicatedAcceleration设置复制的加速度
LyraMovementComponent->SetReplicatedAcceleration(ReplicatedAcceleration);
}
}
5. GetDeltaRotation() 和 GetMaxSpeed() - 移动控制
在游戏能力中的具体应用:
GA_ApplyRoot.cpp:
cpp
void ULyraGameplayAbility_ApplyRoot::ApplyRoot()
{
if (UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo())
{
// 添加移动停止标签,影响GetDeltaRotation和GetMaxSpeed
ASC->AddLooseGameplayTag(TAG_Gameplay_MovementStopped);
// 同时也会影响动画蓝图中的移动状态
if (ALyraCharacter* Character = GetLyraCharacterFromActorInfo())
{
Character->OnMovementStopped();
}
}
}
void ULyraGameplayAbility_ApplyRoot::RemoveRoot()
{
if (UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo())
{
// 移除移动停止标签
ASC->RemoveLooseGameplayTag(TAG_Gameplay_MovementStopped);
}
}
GA_Stun.cpp:
cpp
void ULyraGameplayAbility_Stun::OnAvatarSet(...)
{
// 眩晕效果也会使用移动停止标签
if (UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo())
{
FGameplayEffectContextHandle EffectContext = ASC->MakeEffectContext();
FGameplayEffectSpecHandle SpecHandle = ASC->MakeOutgoingSpec(StunEffect, 1, EffectContext);
if (SpecHandle.IsValid())
{
// 应用包含TAG_Gameplay_MovementStopped的效果
ASC->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());
}
}
}
具体实现代码示例
1. 地面材质检测系统
LyraCharacter.cpp:
cpp
void ALyraCharacter::CheckGroundMaterial()
{
if (ULyraCharacterMovementComponent* MovementComp = GetLyraCharacterMovementComponent())
{
const FLyraCharacterGroundInfo& GroundInfo = MovementComp->GetGroundInfo();
if (GroundInfo.GroundHitResult.bBlockingHit)
{
UPhysicalMaterial* PhysMaterial = GroundInfo.GroundHitResult.PhysMaterial.Get();
// 根据物理材质触发不同效果
if (PhysMaterial)
{
// 播放对应的脚步声
PlayFootstepSound(PhysMaterial);
// 生成粒子效果
SpawnFootstepParticles(PhysMaterial);
// 更新动画参数
UpdateMovementAnimations(PhysMaterial);
}
}
}
}
2. 相机系统集成
LyraCameraComponent.cpp:
cpp
void ULyraCameraComponent::UpdateCameraForGround(float DeltaTime, const FLyraCharacterGroundInfo& GroundInfo)
{
// 根据地面距离调整相机高度
float TargetCameraHeight = GetDefaultCameraHeight();
if (GroundInfo.GroundDistance > 0.0f)
{
// 在空中时调整相机
TargetCameraHeight -= FMath::Min(GroundInfo.GroundDistance, MaxCameraDrop);
}
// 平滑过渡相机高度
CurrentCameraHeight = FMath::FInterpTo(CurrentCameraHeight, TargetCameraHeight, DeltaTime, CameraHeightInterpSpeed);
// 应用相机偏移
SetRelativeLocation(FVector(0, 0, CurrentCameraHeight));
}
3. 移动能力系统集成
LyraGameplayAbility_Dash.cpp:
cpp
void ULyraGameplayAbility_Dash::DoDash()
{
if (ALyraCharacter* Character = GetLyraCharacterFromActorInfo())
{
ULyraCharacterMovementComponent* MovementComp = Character->GetLyraCharacterMovementComponent();
// 检查是否可以执行冲刺(基于地面状态)
const FLyraCharacterGroundInfo& GroundInfo = MovementComp->GetGroundInfo();
if (GroundInfo.GroundDistance < DashMaxGroundDistance)
{
// 执行冲刺逻辑
FVector DashDirection = GetDashDirection();
MovementComp->Velocity = DashDirection * DashSpeed;
// 添加冲刺标签,可能影响其他移动计算
if (UAbilitySystemComponent* ASC = GetAbilitySystemComponentFromActorInfo())
{
ASC->AddLooseGameplayTag(TAG_Gameplay_Dashing);
}
}
}
}
4. 网络同步完整流程
LyraCharacterMovementComponent.cpp 补充:
cpp
void ULyraCharacterMovementComponent::UpdateFromCompressedFlags(uint8 Flags)
{
Super::UpdateFromCompressedFlags(Flags);
// 处理网络压缩的标志
bHasReplicatedAcceleration = (Flags & FSavedMove_Character::FLAG_Custom_0) != 0;
if (bHasReplicatedAcceleration)
{
// 在客户端使用复制的加速度
// 这确保了在SimulateMovement中保持一致性
}
}
5. 状态效果集成
LyraGameplayEffect_ModifyMovement.cpp:
cpp
void ULyraGameplayEffect_ModifyMovement::ExecuteGameplayEffect(...)
{
// 通过GameplayEffect修改移动属性
if (Spec.GetModifiedAttribute() == ULyraCharacterMovementComponent::GetMaxSpeedAttribute())
{
// 直接修改最大速度,与GetMaxSpeed()协同工作
float NewMaxSpeed = CalculateNewMaxSpeed(Spec);
// 这个值会在GetMaxSpeed()中被考虑
}
// 或者通过标签系统
if (Spec.Def->InheritableGameplayEffectTags.Added.HasTag(TAG_Gameplay_MovementStopped))
{
// 移动停止效果被应用
// GetDeltaRotation()和GetMaxSpeed()会自动返回0
}
}
这些具体的实现代码展示了 LyraStarterGame 项目如何深度集成和扩展自定义移动组件,为各种游戏功能(能力系统、动画、网络同步、状态效果等)提供了强大的基础支持。