// Copyright 2025 https://yuewu.dev/en All Rights Reserved. #include "Settings/GMS_SettingObjectLibrary.h" #include "Locomotions/GMS_AnimLayer.h" #include "Animation/BlendSpace.h" #include "Locomotions/GMS_AnimLayer_Additive.h" #include "Locomotions/GMS_AnimLayer_Overlay.h" #include "Locomotions/GMS_AnimLayer_SkeletalControls.h" #include "Locomotions/GMS_AnimLayer_States.h" #include "Locomotions/GMS_AnimLayer_View_Default.h" #include "Misc/DataValidation.h" #include "Utility/GMS_Utility.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(GMS_SettingObjectLibrary) #pragma region CommonSettings #if WITH_EDITOR #include "UObject/ObjectSaveContext.h" void UGMS_MovementDefinition::PreSave(FObjectPreSaveContext SaveContext) { Super::PreSave(SaveContext); } EDataValidationResult UGMS_MovementDefinition::IsDataValid(class FDataValidationContext& Context) const { for (const TTuple& Pair : MovementSets) { if (Pair.Value.ControlSetting == nullptr) { Context.AddError(FText::FromString(FString::Format(TEXT("ControlSetting is required on {0}!!!"), {Pair.Key.GetTagName().ToString()}))); } if (Pair.Value.bUseInstancedStatesSetting && Pair.Value.AnimLayerSetting_States && Pair.Value.AnimLayerSetting_States->IsDataValid(Context) == EDataValidationResult::Invalid) { return EDataValidationResult::Invalid; } if (Pair.Value.bUseInstancedOverlaySetting && Pair.Value.AnimLayerSetting_Overlay && Pair.Value.AnimLayerSetting_Overlay->IsDataValid(Context) == EDataValidationResult::Invalid) { return EDataValidationResult::Invalid; } if (Pair.Value.AnimLayerSetting_Additive && Pair.Value.AnimLayerSetting_Additive->IsDataValid(Context) == EDataValidationResult::Invalid) { return EDataValidationResult::Invalid; } if (Pair.Value.AnimLayerSetting_View && Pair.Value.AnimLayerSetting_View->IsDataValid(Context) == EDataValidationResult::Invalid) { return EDataValidationResult::Invalid; } if (Pair.Value.AnimLayerSetting_SkeletalControls && Pair.Value.AnimLayerSetting_SkeletalControls->IsDataValid(Context) == EDataValidationResult::Invalid) { return EDataValidationResult::Invalid; } } return Super::IsDataValid(Context); } #endif FGameplayTag UGMS_MovementControlSetting_Default::MatchStateTagBySpeed(float Speed, float Threshold) const { for (const FGMS_MovementStateSetting& MovementState : MovementStates) { if (MovementState.Speed > 0.0f && MovementState.Speed < Speed + Threshold) { return MovementState.Tag; } } return FGameplayTag::EmptyTag; } bool UGMS_MovementControlSetting_Default::GetStateByIndex(const int32& Index, FGMS_MovementStateSetting& OutSetting) const { if (MovementStates.IsValidIndex(Index)) { OutSetting = MovementStates[Index]; return true; } return false; } bool UGMS_MovementControlSetting_Default::GetStateBySpeedLevel(const int32& Level, FGMS_MovementStateSetting& OutSetting) const { if (SpeedLevelToArrayIndex.Contains(Level)) { OutSetting = MovementStates[SpeedLevelToArrayIndex[Level]]; return true; } return false; } bool UGMS_MovementControlSetting_Default::GetStateByTag(const FGameplayTag& Tag, FGMS_MovementStateSetting& OutSetting) const { if (auto Setting = GetMovementStateSetting(Tag)) { OutSetting = *Setting; return true; } return false; } const FGMS_MovementStateSetting* UGMS_MovementControlSetting_Default::GetMovementStateSetting(const FGameplayTag& Tag) const { if (!Tag.IsValid()) { return nullptr; } return MovementStates.FindByPredicate([Tag](const FGMS_MovementStateSetting& Setting) { return Setting.Tag.IsValid() && Setting.Tag == Tag; }); } const FGMS_MovementStateSetting* UGMS_MovementControlSetting_Default::GetMovementStateSetting(const FGameplayTag& Tag, bool bHasFallback) const { if (auto Setting = GetMovementStateSetting(Tag)) { return Setting; } if (bHasFallback) { checkf(!MovementStates.IsEmpty(), TEXT("%s: MovementStates can't be empty!"), *GetNameSafe(this)) return &MovementStates.Last(); } return nullptr; } #pragma endregion #pragma region ControlSettings #if WITH_EDITOR float UGMS_MovementControlSetting_Default::MigrateRotationInterpolationSpeed(float Old) { if (Old <= 0.0f) { return 0.0f; } // larger old value, smaller new value. return FMath::GetMappedRangeValueClamped(FVector2f{0.0f, 20.0f}, FVector2f{0.3f, 0.1f}, Old); } void UGMS_MovementControlSetting_Default::PreSave(FObjectPreSaveContext SaveContext) { Super::PreSave(SaveContext); SpeedLevelToArrayIndex.Empty(); MovementStates.Sort([](const FGMS_MovementStateSetting& A, const FGMS_MovementStateSetting& B) { return A.SpeedLevel < B.SpeedLevel; }); for (int i = 0; i < MovementStates.Num(); ++i) { FGMS_MovementStateSetting& Setting = MovementStates[i]; Setting.EditorFriendlyName = FString::Format(TEXT("State({0}) SpeedLevel({1}) Speed({2})"), {UGMS_Utility::GetSimpleTagName(Setting.Tag).ToString(), Setting.SpeedLevel, Setting.Speed}); SpeedLevelToArrayIndex.Emplace(Setting.SpeedLevel, i); } // Migration code for GMS1.5, TODO remove in 1.6 PRAGMA_DISABLE_DEPRECATION_WARNINGS if (MovementStates.Num() > 0) { const FGMS_MovementStateSetting& LastMovementState = MovementStates[MovementStates.Num() - 1]; // migrate velocity direction setting. if (!VelocityDirectionSetting.IsValid()) { if (LastMovementState.VelocityDirectionSetting.DirectionMode != EGMS_VelocityDirectionMode_DEPRECATED::TurningCircle) { FGMS_VelocityDirectionSetting_Default Temp; Temp.bEnableRotationWhenNotMoving = LastMovementState.VelocityDirectionSetting.bEnableRotationWhenNotMoving; Temp.TargetYawAngleRotationSpeed = LastMovementState.TargetYawAngleRotationSpeed; Temp.RotationInterpolationSpeed = MigrateRotationInterpolationSpeed(LastMovementState.RotationInterpolationSpeed); Temp.bOrientateToMoveInputIntent = LastMovementState.VelocityDirectionSetting.DirectionMode == EGMS_VelocityDirectionMode_DEPRECATED::OrientToInputDirection; VelocityDirectionSetting.InitializeAs(Temp); } else if (LastMovementState.VelocityDirectionSetting.DirectionMode == EGMS_VelocityDirectionMode_DEPRECATED::TurningCircle) { FGMS_VelocityDirectionSetting_RateBased Temp; Temp.bEnableRotationWhenNotMoving = LastMovementState.VelocityDirectionSetting.bEnableRotationWhenNotMoving; Temp.TurnRate = LastMovementState.VelocityDirectionSetting.TurningRate; VelocityDirectionSetting.InitializeAs(Temp); } } // migrate view direction setting. if (!ViewDirectionSetting.IsValid()) { if (LastMovementState.ViewDirectionSetting.DirectionMode == EGMS_ViewDirectionMode_DEPRECATED::Aiming) { FGMS_ViewDirectionSetting_Aiming Temp; Temp.bEnableRotationWhenNotMoving = LastMovementState.ViewDirectionSetting.bRotateToViewDirectionWhileNotMoving; Temp.TargetYawAngleRotationSpeed = LastMovementState.TargetYawAngleRotationSpeed; Temp.RotationInterpolationSpeed = MigrateRotationInterpolationSpeed(LastMovementState.RotationInterpolationSpeed); Temp.MinAimingYawAngleLimit = LastMovementState.ViewDirectionSetting.MinAimingYawAngleLimit; ViewDirectionSetting.InitializeAs(Temp); } else if (LastMovementState.ViewDirectionSetting.DirectionMode == EGMS_ViewDirectionMode_DEPRECATED::Default) { FGMS_ViewDirectionSetting_Default Temp; Temp.bEnableRotationWhenNotMoving = LastMovementState.ViewDirectionSetting.bRotateToViewDirectionWhileNotMoving; Temp.TargetYawAngleRotationSpeed = LastMovementState.TargetYawAngleRotationSpeed; Temp.RotationInterpolationSpeed = MigrateRotationInterpolationSpeed(LastMovementState.RotationInterpolationSpeed); ViewDirectionSetting.InitializeAs(Temp); } } } PRAGMA_ENABLE_DEPRECATION_WARNINGS // Safety guard if still invalid. if (!VelocityDirectionSetting.IsValid()) { VelocityDirectionSetting.InitializeAs(FGMS_VelocityDirectionSetting_Default()); } if (!ViewDirectionSetting.IsValid()) { ViewDirectionSetting.InitializeAs(FGMS_ViewDirectionSetting_Default()); } } EDataValidationResult UGMS_MovementControlSetting_Default::IsDataValid(class FDataValidationContext& Context) const { for (int32 i = 0; i < MovementStates.Num(); i++) { const FGMS_MovementStateSetting& MRSetting = MovementStates[i]; if (!MRSetting.Tag.IsValid()) { Context.AddError(FText::FromString(FString::Format(TEXT("Invalid tag at index({0}) of MovementStates"), {i}))); return EDataValidationResult::Invalid; } if (MRSetting.AllowedRotationModes.IsEmpty()) { Context.AddError(FText::FromString( FString::Format(TEXT("AllowedRotationModes at index({0}) of MovementStates can't be empty!"), {i}))); return EDataValidationResult::Invalid; } } if (!ViewDirectionSetting.IsValid()) { Context.AddError(FText::FromString(TEXT("Invalid view direction setting"))); return EDataValidationResult::Invalid; } if (ViewDirectionSetting.IsValid() && ViewDirectionSetting.GetScriptStruct() == FGMS_ViewDirectionSetting::StaticStruct()) { Context.AddError(FText::FromString(FString::Format(TEXT("View direction setting({0}) was deprecated!"), {FGMS_ViewDirectionSetting::StaticStruct()->GetName()}))); return EDataValidationResult::Invalid; } if (!VelocityDirectionSetting.IsValid()) { Context.AddError(FText::FromString(TEXT("Invalid velocity direction setting"))); return EDataValidationResult::Invalid; } if (VelocityDirectionSetting.IsValid() && VelocityDirectionSetting.GetScriptStruct() == FGMS_VelocityDirectionSetting::StaticStruct()) { Context.AddError(FText::FromString(FString::Format(TEXT("Velocity direction setting({0}) was deprecated!"), {FGMS_VelocityDirectionSetting::StaticStruct()->GetName()}))); return EDataValidationResult::Invalid; } return Super::IsDataValid(Context); } #endif #pragma endregion