#pragma once #include "CoreMinimal.h" #include "Kismet/BlueprintFunctionLibrary.h" // #include "Kismet/KismetSystemLibrary.h" // #include "CommonTypes.h" #include "ScriptedAnimation.generated.h" class UEnhancedInputLocalPlayerSubsystem; class UAnimSequenceBase; USTRUCT(BlueprintType) struct INTEGRATION_API FlxWorldClocks { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite) double WorldTime = 0.0; UPROPERTY(EditAnywhere, BlueprintReadWrite) double RealTime = 0.0; }; USTRUCT(BlueprintType) struct INTEGRATION_API FlxScriptedAnimation { GENERATED_BODY() UPROPERTY(EditAnywhere, BlueprintReadWrite) UAnimSequenceBase *Sequence = nullptr; UPROPERTY(EditAnywhere, BlueprintReadWrite) bool ContinueWhenPaused = false; UPROPERTY(EditAnywhere, BlueprintReadWrite) int64 AnimationStepID = 0; UPROPERTY(EditAnywhere, BlueprintReadWrite) double FadeInDuration = 0.0; UPROPERTY(EditAnywhere, BlueprintReadWrite) double FadeOutDuration = 0.0; // StartTime and EndTime. // // StartTime is the world time when the animation was actually started. // That is immutable once the animation is created. However, EndTime is // mutable: initially, it is set to StartTime + Animation.Length. But // script requests that the animation be truncated, the EndTime // may be reduced to implement the truncation. // UPROPERTY(EditAnywhere, BlueprintReadWrite) double StartTime = 0.0; UPROPERTY(EditAnywhere, BlueprintReadWrite) double EndTime = 0.0; // Calculate the progress of a fade. // inline static double CalculateFade(double Offset, double Fade) { if (Offset < 0.0) { return 0.0; } if (Fade > 0.001) { return FMath::Min(Offset / Fade, 1.0); } return 1.0; } // Given an FGameTime, extract the correct clock to use for this animation. // inline double ChooseCorrectClock(const FlxWorldClocks &WorldClocks) const { return ContinueWhenPaused ? WorldClocks.RealTime : WorldClocks.WorldTime; } // Calculate Elapsed Time (unclamped) // // If current time is before the animation's start time, then // elapsed time will be negative. // inline double UnclampedElapsedTime(double CurrentTime) const { return CurrentTime - StartTime; } // Calculate Time Left (unclamped) // // If current time is after the animation's end time, then // time left will be negative. // inline double UnclampedTimeLeft(double CurrentTime) const { return EndTime - CurrentTime; } // Calculate Elapsed Time (clamped) // // If current time is before the animation's start time, then // elapsed time will be zero. // inline double ClampedElapsedTime(double CurrentTime) const { return FMath::Max(0.0, CurrentTime - StartTime); } // Calculate Time Left (clamped) // // If current time is after the animation's end time, then // time left will be zero. // inline double ClampedTimeLeft(double CurrentTime) const { return FMath::Max(0.0, EndTime - CurrentTime); } // Calculate the progress of the fadein. // inline double CalcFadeInAlpha(double CurrentTime) const { return CalculateFade(UnclampedElapsedTime(CurrentTime), FadeInDuration); } // Calculate the progress of the fadeout. // inline double CalcFadeOutAlpha(double CurrentTime) const { return CalculateFade(UnclampedTimeLeft(CurrentTime), FadeOutDuration); } // Calculate the combined alpha of the fade-in-out. // inline double CalcFadeInOutAlpha(double CurrentTime) const { return FMath::Min(CalcFadeInAlpha(CurrentTime), CalcFadeOutAlpha(CurrentTime)); } // Cause an animation to start fading right away. // // Sets up the animation to start fading immediately, by adjusting // the animation's EndTime. If the FadeOut time of the animation // is longer than the allowed fade, that will be reduced. // If the animation was already in the process of fading in // or fading out, then that will be taken into account. // // If you want to chop an animation off abruptly, without any fade, // simply set AllowedFade to 0.0. // void InitiateFadeOut(double CurrentTime, double AllowedFade); }; UCLASS(BlueprintType) class INTEGRATION_API UlxScriptedAnimations : public UObject { GENERATED_BODY() private: UPROPERTY() int KeepCount = 3; UPROPERTY() TArray Animations; // If the number of elements in the array exceeds the count, discard. // void Keep(int n); public: // Allow read-only access anywhere. // const TArray &GetAnimations() const { return Animations; } // Add a scripted animation. // // The step is inserted at the front of the array: the first animation // in the array is always the most recent. All other steps are shifted // back. If the array size exceeds the KeepCount, it is truncated. // // The AnimationStepID parameter can be used to associate a scripted // animation with a step in the animation queue. Typically, you would // set the scripted animation ID equal to the animation queue step ID. // This makes it feasible to garbage collect the scripted animation // if the animation queue step disappears. // // If 'ContinueWhenPaused' is true, then the animation keeps playing // even when the game is paused. Otherwise, it freezes when the game // is paused. // UFUNCTION(BlueprintCallable, Category = "Luprex|Scripted Animations", meta=(WorldContext = "WorldContextObject")) void AddAnimation( UObject *WorldContextObject, UAnimSequenceBase* Sequence, double FadeInTime = 0.2, double FadeOutTime = 0.2, int64 AnimationStepID=0, bool ContinueWhenPaused=false); // Fade all animations whose IDs are not in the 'Keep' list. // // Sometimes, predictive reexecution causes an animation step to // vanish from the animation queue. When that happens, it is // desirable to terminate any scripted animations that were launched // by that animation step. You don't want to cut them off abruptly, // you want to fade them out. // // Whenever luprex calls 'Animation Queue Changed' in a blueprint, // it also automatically runs this garbage collection routine on the // scripted animations. // // Scripted animations with nonpositive IDs are immune to garbage // collection. So if you want an animation step to be not affected, // give it an ID of zero or a negative number. // void FadeGarbage(const UObject *WorldContextObject, TArray KeepIDs); }; UCLASS() class INTEGRATION_API UlxScriptedAnimationLibrary : public UBlueprintFunctionLibrary { GENERATED_BODY() public: // Get all the major 'World Clocks' in a single struct. // UFUNCTION(BlueprintPure, Category = "Utilities|Time", meta=(WorldContext = "WorldContextObject")) static FlxWorldClocks GetAllWorldClocks(const UObject *WorldContextObject); // Get the data to drive Sequence Evaluators and Multi Blend // // To apply scripted animations in an Anim Graph, you will need // three sequence evaluators and a multi-blend node. This // function outputs the input parameters for all of those nodes. // UFUNCTION(BlueprintPure, Category = "Luprex|Scripted Animations", meta=(BlueprintThreadSafe)) static void ScriptedAnimationEvaluatorData(const UlxScriptedAnimations *Animations, const FlxWorldClocks &WorldClocks, UAnimSequenceBase *&Sequence0, float &ExplicitTime0, UAnimSequenceBase *&Sequence1, float &ExplicitTime1, UAnimSequenceBase *&Sequence2, float &ExplicitTime2, float &BaseAlpha, float &Sequence0Alpha, float &Sequence1Alpha, float &Sequence2Alpha); };