// Copyright 2025 https://yuewu.dev/en All Rights Reserved. #include "GMS_MovementSystemComponent.h" #include "GameFramework/Pawn.h" #include "Engine/World.h" #include "TimerManager.h" #include "GameplayTagAssetInterface.h" #include "GameFramework/Actor.h" #include "GameFramework/Pawn.h" #include "Animation/AnimInstance.h" #include "Misc/DataValidation.h" #include "Net/Core/PushModel/PushModel.h" #include "Net/UnrealNetwork.h" #include "Settings/GMS_SettingObjectLibrary.h" #include "Utility/GMS_Log.h" #include "Utility/GMS_Math.h" #include "Utility/GMS_Vector.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_MovementSystemComponent) UGMS_MovementSystemComponent::UGMS_MovementSystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { PrimaryComponentTick.bCanEverTick = true; bWantsInitializeComponent = true; } void UGMS_MovementSystemComponent::PostLoad() { Super::PostLoad(); #if WITH_EDITOR PRAGMA_DISABLE_DEPRECATION_WARNINGS if (!MovementDefinitions.IsEmpty()) { MovementDefinition = MovementDefinitions.Last(); MovementDefinitions.Empty(); } PRAGMA_ENABLE_DEPRECATION_WARNINGS #endif } void UGMS_MovementSystemComponent::InitializeComponent() { Super::InitializeComponent(); OwnerPawn = Cast(GetOwner()); check(OwnerPawn) if (OwnerPawn) { // Set some default values here to ensure that the animation instance and the // camera component can read the most up-to-date values during their initialization. SetReplicatedViewRotation(OwnerPawn->GetViewRotation().GetNormalized(), false); ViewState.Rotation = ReplicatedViewRotation; ViewState.PreviousYawAngle = UE_REAL_TO_FLOAT(OwnerPawn->GetViewRotation().Yaw); const auto YawAngle{UE_REAL_TO_FLOAT(OwnerPawn->GetActorRotation().Yaw)}; LocomotionState.InputYawAngle = YawAngle; LocomotionState.VelocityYawAngle = YawAngle; } } void UGMS_MovementSystemComponent::BeginPlay() { Super::BeginPlay(); if (GetOwner()->GetClass()->ImplementsInterface(UGameplayTagAssetInterface::StaticClass())) { SetGameplayTagsProvider(GetOwner()); } else { TArray Components = GetOwner()->GetComponentsByInterface(UGameplayTagAssetInterface::StaticClass()); if (Components.IsValidIndex(0)) { SetGameplayTagsProvider(Components[0]); } } } void UGMS_MovementSystemComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); FDoRepLifetimeParams Parameters; Parameters.bIsPushBased = true; Parameters.Condition = COND_SkipOwner; DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, LocomotionMode, Parameters) DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, OverlayMode, Parameters) DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, MovementSet, Parameters) DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, MovementDefinition, Parameters) DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, ReplicatedViewRotation, Parameters) DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, DesiredVelocityYawAngle, Parameters) DOREPLIFETIME_WITH_PARAMS_FAST(ThisClass, OwnedTags, Parameters) } UGMS_MovementSystemComponent* UGMS_MovementSystemComponent::GetMovementSystemComponent(const AActor* Actor) { return Actor != nullptr ? Actor->FindComponentByClass() : nullptr; } bool UGMS_MovementSystemComponent::K2_FindMovementComponent(const AActor* Actor, UGMS_MovementSystemComponent*& Instance) { if (Actor == nullptr) { return false; } Instance = GetMovementSystemComponent(Actor); return Instance != nullptr; } bool UGMS_MovementSystemComponent::K2_FindMovementComponentExt(const AActor* Actor, TSubclassOf DesiredClass, UGMS_MovementSystemComponent*& Instance) { if (DesiredClass) { Instance = GetMovementSystemComponent(Actor); return Instance != nullptr && Instance->GetClass()->IsChildOf(DesiredClass); } return false; } FGameplayTagContainer UGMS_MovementSystemComponent::GetGameplayTags() const { if (IGameplayTagAssetInterface* TagAssetInterface = Cast(GameplayTagsProvider)) { FGameplayTagContainer RetTags; TagAssetInterface->GetOwnedGameplayTags(RetTags); if (!OwnedTags.IsEmpty()) { RetTags.AppendTags(OwnedTags); } RetTags.AddTagFast(GetMovementState()); RetTags.AddTagFast(GetRotationMode()); return RetTags; } return OwnedTags; } void UGMS_MovementSystemComponent::SetGameplayTagsProvider(UObject* Provider) { if (!IsValid(Provider)) { GMS_CLOG(Warning, "Passed invalid GameplayTagsProvider."); return; } if (IGameplayTagAssetInterface* TagAssetInterface = Cast(Provider)) { GameplayTagsProvider = Provider; } else { GMS_CLOG(Warning, "Passed in GameplayTagsProvider(%s) Doesn't implement GameplayTagAssetInterface, it can't provide gameplay tags.", *Provider->GetName()); } } #pragma region GameplayTags void UGMS_MovementSystemComponent::AddGameplayTag(FGameplayTag TagToAdd) { AddGameplayTag(TagToAdd, true); } void UGMS_MovementSystemComponent::RemoveGameplay(FGameplayTag TagToRemove) { RemoveGameplayTag(TagToRemove, true); } void UGMS_MovementSystemComponent::SetGameplayTags(FGameplayTagContainer TagsToSet) { SetGameplayTags(TagsToSet, true); } void UGMS_MovementSystemComponent::AddGameplayTag(const FGameplayTag& TagToAdd, bool bSendRpc) { if (GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy) { return; } OwnedTags.AddTag(TagToAdd); MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, OwnedTags, this) if (bSendRpc) { if (GetOwner()->GetLocalRole() >= ROLE_Authority) { ClientAddGameplayTag(TagToAdd); } else { ServerAddGameplayTag(TagToAdd); } } } void UGMS_MovementSystemComponent::RemoveGameplayTag(const FGameplayTag& TagToRemove, bool bSendRpc) { if (GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy) { return; } OwnedTags.RemoveTag(TagToRemove); MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, OwnedTags, this) if (bSendRpc) { if (GetOwner()->GetLocalRole() >= ROLE_Authority) { ClientRemoveGameplayTag(TagToRemove); } else { ServerRemoveGameplayTag(TagToRemove); } } } void UGMS_MovementSystemComponent::SetGameplayTags(const FGameplayTagContainer& TagsToSet, bool bSendRpc) { if (GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy) { return; } OwnedTags = TagsToSet; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, OwnedTags, this) if (bSendRpc) { if (GetOwner()->GetLocalRole() >= ROLE_Authority) { ClientSetGameplayTags(TagsToSet); } else { ServerSetGameplayTags(TagsToSet); } } } void UGMS_MovementSystemComponent::ClientAddGameplayTag_Implementation(const FGameplayTag& TagToAdd) { AddGameplayTag(TagToAdd, false); } void UGMS_MovementSystemComponent::ServerAddGameplayTag_Implementation(const FGameplayTag& TagToAdd) { AddGameplayTag(TagToAdd, false); } void UGMS_MovementSystemComponent::ClientRemoveGameplayTag_Implementation(const FGameplayTag& TagToRemove) { RemoveGameplayTag(TagToRemove, false); } void UGMS_MovementSystemComponent::ServerRemoveGameplayTag_Implementation(const FGameplayTag& TagToRemove) { RemoveGameplayTag(TagToRemove, false); } void UGMS_MovementSystemComponent::ClientSetGameplayTags_Implementation(const FGameplayTagContainer& TagsToSet) { SetGameplayTags(TagsToSet, false); } void UGMS_MovementSystemComponent::ServerSetGameplayTags_Implementation(const FGameplayTagContainer& TagsToSet) { SetGameplayTags(TagsToSet, false); } #pragma endregion GameplayTags #pragma region Locomotion const FGMS_LocomotionState& UGMS_MovementSystemComponent::GetLocomotionState() const { return LocomotionState; } TScriptInterface UGMS_MovementSystemComponent::GetTrajectoryPredictor() const { return nullptr; } bool UGMS_MovementSystemComponent::IsCrouching() const { return false; } float UGMS_MovementSystemComponent::GetMaxSpeed() const { return 0.0; } float UGMS_MovementSystemComponent::GetMaxAcceleration() const { return 1000.0f; } bool UGMS_MovementSystemComponent::IsMovingOnGround() const { return true; } void UGMS_MovementSystemComponent::SetDesiredVelocityYawAngle(float NewDesiredVelocityYawAngle) { COMPARE_ASSIGN_AND_MARK_PROPERTY_DIRTY(ThisClass, DesiredVelocityYawAngle, NewDesiredVelocityYawAngle, this); } void UGMS_MovementSystemComponent::ServerSetDesiredVelocityYawAngle_Implementation(float NewDesiredVelocityYawAngle) { SetDesiredVelocityYawAngle(NewDesiredVelocityYawAngle); } const FGameplayTag& UGMS_MovementSystemComponent::GetLocomotionMode() const { return LocomotionMode; } const FGMS_MovementBaseState& UGMS_MovementSystemComponent::GetMovementBase() const { return MovementBase; } void UGMS_MovementSystemComponent::SetLocomotionMode(const FGameplayTag& NewLocomotionMode) { if (LocomotionMode != NewLocomotionMode) { const auto PreviousLocomotionMode{LocomotionMode}; LocomotionMode = NewLocomotionMode; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, LocomotionMode, this) OnLocomotionModeChanged(PreviousLocomotionMode); } } void UGMS_MovementSystemComponent::OnReplicated_LocomotionMode(const FGameplayTag& PreviousLocomotionMode) { OnLocomotionModeChanged(PreviousLocomotionMode); } void UGMS_MovementSystemComponent::OnLocomotionModeChanged_Implementation(const FGameplayTag& PreviousLocomotionMode) { GMS_CLOG(Verbose, "locomotion mode changed from %s to %s", *PreviousLocomotionMode.ToString(), *LocomotionMode.ToString()); OnLocomotionModeChangedEvent.Broadcast(PreviousLocomotionMode); } #pragma endregion void UGMS_MovementSystemComponent::RefreshMovementBase() { } #pragma region MovementSet const FGameplayTag& UGMS_MovementSystemComponent::GetMovementSet() const { return MovementSet; } const FGMS_MovementSetSetting& UGMS_MovementSystemComponent::GetMovementSetSetting() const { return MovementSetSetting; } const FGMS_MovementStateSetting& UGMS_MovementSystemComponent::GetMovementStateSetting() const { return MovementStateSetting; } const UGMS_MovementControlSetting_Default* UGMS_MovementSystemComponent::GetControlSetting() const { return ControlSetting; } int32 UGMS_MovementSystemComponent::GetNumOfMovementStateSettings() const { return ControlSetting->MovementStates.Num(); } TSoftObjectPtr UGMS_MovementSystemComponent::GetMovementDefinition() const { return MovementDefinition; } TSoftObjectPtr UGMS_MovementSystemComponent::GetPrevMovementDefinition() const { return PrevMovementDefinition; } const UGMS_MovementDefinition* UGMS_MovementSystemComponent::GetLoadedMovementDefinition() const { return MovementDefinition.IsValid() ? MovementDefinition.Get() : nullptr; } void UGMS_MovementSystemComponent::SetMovementSet(const FGameplayTag& NewMovementSet) { SetMovementSet(NewMovementSet, true); } void UGMS_MovementSystemComponent::SetMovementDefinition(TSoftObjectPtr NewDefinition) { if (NewDefinition.IsNull()) { return; } if (NewDefinition != MovementDefinition) { auto LoadedDefinition = NewDefinition.LoadSynchronous(); if (LoadedDefinition->MovementSets.Contains(MovementSet)) { InternalSetMovementDefinition(NewDefinition, true); } } } void UGMS_MovementSystemComponent::LocalSetMovementDefinition(TSoftObjectPtr NewDefinition) { InternalSetMovementDefinition(NewDefinition, false); } void UGMS_MovementSystemComponent::PushAvailableMovementDefinition(TSoftObjectPtr NewDefinition, bool bPopCurrent) { InternalSetMovementDefinition(NewDefinition, true); } void UGMS_MovementSystemComponent::PopAvailableMovementDefinition() { // PopMovementDefinition(true); } void UGMS_MovementSystemComponent::InternalSetMovementDefinition(const TSoftObjectPtr NewDefinition, bool bSendRpc) { if (!NewDefinition.IsNull() && NewDefinition != MovementDefinition) { PrevMovementDefinition = MovementDefinition; MovementDefinition = NewDefinition; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, MovementDefinition, this) OnMovementSetChanged(MovementSet); if (bSendRpc) { if (GetOwner()->GetLocalRole() >= ROLE_Authority) { ClientSetMovementDefinition(NewDefinition); } else { ServerSetMovementDefinition(NewDefinition); } } } } void UGMS_MovementSystemComponent::ClientSetMovementDefinition_Implementation(const TSoftObjectPtr& NewDefinition) { InternalSetMovementDefinition(NewDefinition, false); } void UGMS_MovementSystemComponent::ServerSetMovementDefinition_Implementation(const TSoftObjectPtr& NewDefinition) { InternalSetMovementDefinition(NewDefinition, false); } void UGMS_MovementSystemComponent::OnReplicated_MovementDefinition() { OnMovementSetChanged(MovementSet); } void UGMS_MovementSystemComponent::SetMovementSet(const FGameplayTag& NewMovementSet, bool bSendRpc) { if (MovementSet == NewMovementSet || GetOwner()->GetLocalRole() <= ROLE_SimulatedProxy) { return; } const auto PreviousMovementSet{MovementSet}; MovementSet = NewMovementSet; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, MovementSet, this) OnMovementSetChanged(PreviousMovementSet); if (bSendRpc) { if (GetOwner()->GetLocalRole() >= ROLE_Authority) { ClientSetMovementSet(MovementSet); } else { ServerSetMovementSet(MovementSet); } } } void UGMS_MovementSystemComponent::ClientSetMovementSet_Implementation(const FGameplayTag& NewMovementSet) { SetMovementSet(NewMovementSet, false); } void UGMS_MovementSystemComponent::ServerSetMovementSet_Implementation(const FGameplayTag& NewMovementSet) { SetMovementSet(NewMovementSet, false); } void UGMS_MovementSystemComponent::OnReplicated_MovementSet(const FGameplayTag& PreviousMovementSet) { OnMovementSetChanged(PreviousMovementSet); } void UGMS_MovementSystemComponent::RefreshMovementSetSetting() { const UGMS_MovementDefinition* LoadedDefinition = MovementDefinition.LoadSynchronous(); if (!LoadedDefinition) { GMS_CLOG(Warning, "Missing valid movement definition!") return; } if (!LoadedDefinition->MovementSets.Contains(MovementSet)) { GMS_CLOG(Warning, "No movement set(%s) found in movement definition(%s)!", *MovementSet.ToString(), *GetNameSafe(LoadedDefinition)) return; } MovementSetSetting = MovementDefinition->MovementSets[MovementSet]; RefreshControlSetting(); // bool bFoundMovementSet{false}; // for (int32 i = MovementDefinitions.Num() - 1; i >= 0; i--) // { // if (MovementDefinitions[i].IsNull()) // { // continue; // } // if (!MovementDefinitions[i].IsValid()) // { // MovementDefinitions[i].LoadSynchronous(); // } // if (MovementDefinitions[i]->MovementSets.Contains(MovementSet)) // { // MovementDefinition = MovementDefinitions[i].Get(); // MovementSetSetting = MovementDefinition->MovementSets[MovementSet]; // bFoundMovementSet = true; // RefreshControlSetting(); // break; // } // } // if (!bFoundMovementSet) // { // GMS_CLOG(Error, "No movement set(%s) found in movement definitions!", *MovementSet.ToString()) // } } void UGMS_MovementSystemComponent::RefreshControlSetting() { const UGMS_MovementControlSetting_Default* NewSetting = MovementSetSetting.bControlSettingPerOverlayMode && MovementSetSetting.ControlSettings.Contains(OverlayMode) ? MovementSetSetting.ControlSettings[OverlayMode] : MovementSetSetting.ControlSetting; if (NewSetting != nullptr && !NewSetting->MovementStates.IsEmpty()) { ControlSetting = NewSetting; RefreshMovementStateSetting(); ApplyMovementSetting(); } else { ControlSetting = nullptr; GMS_CLOG(Error, "Empty MovementState settings are found in the movement set(%s) of definition(%s), which is not allowed!", *MovementSet.ToString(), *MovementDefinition->GetName()) } } void UGMS_MovementSystemComponent::OnMovementSetChanged_Implementation(const FGameplayTag& PreviousMovementSet) { RefreshMovementSetSetting(); OnMovementSetChangedEvent.Broadcast(PreviousMovementSet); } const FGameplayTag& UGMS_MovementSystemComponent::GetDesiredMovementState() const { return FGameplayTag::EmptyTag; } void UGMS_MovementSystemComponent::RefreshMovementStateSetting() { if (!IsValid(ControlSetting)) { return; } FGMS_MovementStateSetting NewStateSetting; if (!ControlSetting->GetStateByTag(GetMovementState(), NewStateSetting)) { checkf(!ControlSetting->MovementStates.IsEmpty(), TEXT("Found empty MovementState Settings on %s!"), *ControlSetting->GetName()) NewStateSetting = ControlSetting->MovementStates.Last(); SetDesiredMovement(NewStateSetting.Tag); GMS_CLOG(Verbose, "No MovementState setting for current movement state(%s), Change desired last one(%s) in list.", *GetMovementState().ToString(), *NewStateSetting.Tag.ToString()); } MovementStateSetting = NewStateSetting; if (bRespectAllowedRotationModesSettings) { if (!MovementStateSetting.AllowedRotationModes.Contains(GetDesiredRotationMode())) { FGameplayTag AdjustedRotationMode = MovementStateSetting.AllowedRotationModes.Last(); GMS_CLOG(Warning, "current movement state(%s) doesn't allow current desired rotation mode(%s), adjusted to:%s", *GetMovementState().ToString(), *GetDesiredRotationMode().ToString(), *AdjustedRotationMode.ToString()); SetDesiredRotationMode(AdjustedRotationMode); } } } void UGMS_MovementSystemComponent::ApplyMovementSetting() { } #pragma endregion #pragma region MovementState void UGMS_MovementSystemComponent::SetDesiredMovement(const FGameplayTag& NewDesiredMovement) { } void UGMS_MovementSystemComponent::CycleDesiredMovementState(bool bForward) { if (GetNumOfMovementStateSettings() == 1) { return; } int32 Index = ControlSetting->MovementStates.IndexOfByKey(GetMovementState()); if (Index == INDEX_NONE) { return; } if (bForward && ControlSetting->MovementStates.IsValidIndex(Index + 1)) { SetDesiredMovement(ControlSetting->MovementStates[Index + 1].Tag); } if (!bForward && ControlSetting->MovementStates.IsValidIndex(Index - 1)) { SetDesiredMovement(ControlSetting->MovementStates[Index - 1].Tag); } } const FGameplayTag& UGMS_MovementSystemComponent::GetMovementState() const { return FGameplayTag::EmptyTag; } int32 UGMS_MovementSystemComponent::GetSpeedLevel() const { return MovementStateSetting.SpeedLevel; } float UGMS_MovementSystemComponent::GetMappedMovementSpeedLevel(float Speed) const { if (!ControlSetting) { return 0.0f; } float SpeedLevelAmount = ControlSetting->MovementStates.Last().SpeedLevel; for (int32 i = ControlSetting->MovementStates.Num() - 2; i >= 0; i--) { const FGMS_MovementStateSetting& Max = ControlSetting->MovementStates[i + 1]; const FGMS_MovementStateSetting& Min = ControlSetting->MovementStates[i]; // In current range. if (Min.Speed > 0 && Speed >= Min.Speed && Speed <= Max.Speed) { SpeedLevelAmount = FMath::GetMappedRangeValueClamped(FVector2f{Min.Speed, Max.Speed}, {static_cast(Min.SpeedLevel), static_cast(Max.SpeedLevel)}, Speed); } } GMS_CLOG(VeryVerbose, "Mapped speed(%f) to speed level(%f)", Speed, SpeedLevelAmount) return SpeedLevelAmount; } void UGMS_MovementSystemComponent::OnMovementStateChanged_Implementation(const FGameplayTag& PreviousMovementState) { RefreshMovementStateSetting(); ApplyMovementSetting(); OnMovementStateChangedEvent.Broadcast(PreviousMovementState); } #pragma endregion #pragma region Input FVector UGMS_MovementSystemComponent::GetMovementIntent() const { checkf(0, TEXT("Not implemented")); return FVector::ZeroVector; } void UGMS_MovementSystemComponent::RefreshInput(float DeltaTime) { LocomotionState.bHasInput = GetMovementIntent().SizeSquared() > UE_KINDA_SMALL_NUMBER; if (LocomotionState.bHasInput) { LocomotionState.InputYawAngle = UE_REAL_TO_FLOAT(UGMS_Vector::DirectionToAngleXY(GetMovementIntent())); } } void UGMS_MovementSystemComponent::TurnAtRate(float Direction) { if (Direction != 0 && GetRotationMode() == GMS_RotationModeTags::VelocityDirection && OwnerPawn->GetLocalRole() >= ROLE_AutonomousProxy) { if (const auto Setting = ControlSetting->VelocityDirectionSetting.GetPtr()) { const float TurnRate = IsValid(Setting->TurnRateSpeedCurve) ? Setting->TurnRateSpeedCurve->GetFloatValue(FMath::Max(1.0f, GetMappedMovementSpeedLevel(UE_REAL_TO_FLOAT(LocomotionState.Velocity.Size2D())))) : Setting->TurnRate; float YawDelta = Direction * TurnRate * GetWorld()->GetDeltaSeconds(); if (FMath::Abs(YawDelta) > UE_SMALL_NUMBER) { float TargetYawAngle = FMath::UnwindDegrees(OwnerPawn->GetActorRotation().Yaw + YawDelta); // 或者,方法2:使用 RotateAngleAxis(如果需要保持四元数精度) // FQuat DeltaRotation = FQuat(FVector::UpVector, FMath::DegreesToRadians(YawDelta)); // FQuat NewRotation = DeltaRotation * LocomotionState.RotationQuaternion; // NewDesiredVelocityYawAngle = NewRotation.Rotator().Yaw; // FRotator RotationDelta = FRotator(0,Direction * GetVelocityDirectionSetting().TurningRate * DeltaTime,0); // // NewDesiredVelocityYawAngle = (RotationDelta.Quaternion() * LocomotionState.RotationQuaternion).Rotator().Yaw; SetDesiredVelocityYawAngle(TargetYawAngle); if (OwnerPawn->GetLocalRole() < ROLE_Authority) { ServerSetDesiredVelocityYawAngle(TargetYawAngle); } } } } } #pragma endregion #pragma region ViewSystem const FGMS_ViewState& UGMS_MovementSystemComponent::GetViewState() const { return ViewState; } void UGMS_MovementSystemComponent::SetReplicatedViewRotation(const FRotator& NewViewRotation, bool bSendRpc) { if (!ReplicatedViewRotation.Equals(NewViewRotation)) { ReplicatedViewRotation = NewViewRotation; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, ReplicatedViewRotation, this) if (bSendRpc && GetOwner()->GetLocalRole() == ROLE_AutonomousProxy) { ServerSetReplicatedViewRotation(ReplicatedViewRotation); } } } void UGMS_MovementSystemComponent::ServerSetReplicatedViewRotation_Implementation(const FRotator& NewViewRotation) { SetReplicatedViewRotation(NewViewRotation, false); } void UGMS_MovementSystemComponent::OnReplicated_ReplicatedViewRotation() { ViewState.Rotation = MovementBase.bHasRelativeRotation ? (MovementBase.Rotation * ReplicatedViewRotation.Quaternion()).Rotator() : ReplicatedViewRotation; } #pragma endregion #pragma region Rotation Mode const FGameplayTag& UGMS_MovementSystemComponent::GetDesiredRotationMode() const { return FGameplayTag::EmptyTag; } void UGMS_MovementSystemComponent::SetDesiredRotationMode(const FGameplayTag& NewDesiredRotationMode) { } const FGameplayTag& UGMS_MovementSystemComponent::GetRotationMode() const { return FGameplayTag::EmptyTag; } void UGMS_MovementSystemComponent::OnRotationModeChanged_Implementation(const FGameplayTag& PreviousRotationMode) { ApplyMovementSetting(); OnRotationModeChangedEvent.Broadcast(PreviousRotationMode); } #pragma endregion #pragma region OverlayMode const FGameplayTag& UGMS_MovementSystemComponent::GetOverlayMode() const { return OverlayMode; } void UGMS_MovementSystemComponent::SetOverlayMode(const FGameplayTag& NewOverlayMode) { SetOverlayMode(NewOverlayMode, true); } void UGMS_MovementSystemComponent::SetOverlayMode(const FGameplayTag& NewOverlayMode, bool bSendRpc) { if (OverlayMode == NewOverlayMode || OwnerPawn->GetLocalRole() <= ROLE_SimulatedProxy) { return; } const auto PreviousOverlayMode{OverlayMode}; OverlayMode = NewOverlayMode; MARK_PROPERTY_DIRTY_FROM_NAME(ThisClass, OverlayMode, this) OnOverlayModeChanged(PreviousOverlayMode); if (bSendRpc) { if (OwnerPawn->GetLocalRole() >= ROLE_Authority) { ClientSetOverlayMode(OverlayMode); } else { ServerSetOverlayMode(OverlayMode); } } } void UGMS_MovementSystemComponent::ClientSetOverlayMode_Implementation(const FGameplayTag& NewOverlayMode) { SetOverlayMode(NewOverlayMode, false); } void UGMS_MovementSystemComponent::ServerSetOverlayMode_Implementation(const FGameplayTag& NewOverlayMode) { SetOverlayMode(NewOverlayMode, false); } void UGMS_MovementSystemComponent::OnReplicated_OverlayMode(const FGameplayTag& PreviousOverlayMode) { OnOverlayModeChanged(PreviousOverlayMode); } void UGMS_MovementSystemComponent::OnOverlayModeChanged_Implementation(const FGameplayTag& PreviousOverlayMode) { if (MovementSetSetting.bControlSettingPerOverlayMode) { RefreshControlSetting(); } OnOverlayModeChangedEvent.Broadcast(PreviousOverlayMode); } #pragma endregion #if WITH_EDITOR EDataValidationResult UGMS_MovementSystemComponent::IsDataValid(class FDataValidationContext& Context) const { if (IsTemplate() && AnimGraphSetting == nullptr) { Context.AddError(FText::FromString("AnimGraphSetting is required!")); return EDataValidationResult::Invalid; } return Super::IsDataValid(Context); } #endif