2025-10-15 18:08:30 -04:00
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
2025-10-14 20:24:37 -04:00
|
|
|
#include "CoreMinimal.h"
|
2025-10-27 18:18:35 -04:00
|
|
|
#include "Kismet/BlueprintFunctionLibrary.h"
|
2025-10-14 20:24:37 -04:00
|
|
|
// #include "Kismet/KismetSystemLibrary.h"
|
2026-02-09 13:54:00 -05:00
|
|
|
// #include "Common.h"
|
2025-10-14 20:24:37 -04:00
|
|
|
|
|
|
|
|
#include "ScriptedAnimation.generated.h"
|
|
|
|
|
|
|
|
|
|
class UEnhancedInputLocalPlayerSubsystem;
|
|
|
|
|
class UAnimSequenceBase;
|
|
|
|
|
|
|
|
|
|
|
2025-10-27 18:18:35 -04:00
|
|
|
USTRUCT(BlueprintType)
|
|
|
|
|
struct INTEGRATION_API FlxWorldClocks
|
|
|
|
|
{
|
|
|
|
|
GENERATED_BODY()
|
|
|
|
|
|
|
|
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
|
|
|
|
double WorldTime = 0.0;
|
|
|
|
|
|
|
|
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
|
|
|
|
double RealTime = 0.0;
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-14 20:24:37 -04:00
|
|
|
USTRUCT(BlueprintType)
|
|
|
|
|
struct INTEGRATION_API FlxScriptedAnimation
|
|
|
|
|
{
|
|
|
|
|
GENERATED_BODY()
|
|
|
|
|
|
|
|
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
|
|
|
|
UAnimSequenceBase *Sequence = nullptr;
|
|
|
|
|
|
2025-10-27 18:18:35 -04:00
|
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
|
|
|
|
bool ContinueWhenPaused = false;
|
|
|
|
|
|
|
|
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
|
|
|
|
int64 AnimationStepID = 0;
|
2025-10-14 20:24:37 -04:00
|
|
|
|
2025-10-27 18:18:35 -04:00
|
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
2025-10-14 20:24:37 -04:00
|
|
|
double FadeInDuration = 0.0;
|
|
|
|
|
|
2025-10-27 18:18:35 -04:00
|
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
2025-10-14 20:24:37 -04:00
|
|
|
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.
|
|
|
|
|
//
|
|
|
|
|
|
2025-10-27 18:18:35 -04:00
|
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
2025-10-14 20:24:37 -04:00
|
|
|
double StartTime = 0.0;
|
|
|
|
|
|
2025-10-27 18:18:35 -04:00
|
|
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
2025-10-14 20:24:37 -04:00
|
|
|
double EndTime = 0.0;
|
|
|
|
|
|
|
|
|
|
// Calculate the progress of a fade.
|
|
|
|
|
//
|
2025-10-27 18:18:35 -04:00
|
|
|
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;
|
|
|
|
|
}
|
2025-10-14 20:24:37 -04:00
|
|
|
|
|
|
|
|
// Calculate Elapsed Time (unclamped)
|
|
|
|
|
//
|
|
|
|
|
// If current time is before the animation's start time, then
|
|
|
|
|
// elapsed time will be negative.
|
|
|
|
|
//
|
2025-10-27 18:18:35 -04:00
|
|
|
inline double UnclampedElapsedTime(double CurrentTime) const
|
|
|
|
|
{
|
|
|
|
|
return CurrentTime - StartTime;
|
|
|
|
|
}
|
2025-10-14 20:24:37 -04:00
|
|
|
|
|
|
|
|
// Calculate Time Left (unclamped)
|
|
|
|
|
//
|
|
|
|
|
// If current time is after the animation's end time, then
|
|
|
|
|
// time left will be negative.
|
|
|
|
|
//
|
2025-10-27 18:18:35 -04:00
|
|
|
inline double UnclampedTimeLeft(double CurrentTime) const
|
|
|
|
|
{
|
|
|
|
|
return EndTime - CurrentTime;
|
|
|
|
|
}
|
2025-10-14 20:24:37 -04:00
|
|
|
|
|
|
|
|
// Calculate Elapsed Time (clamped)
|
|
|
|
|
//
|
|
|
|
|
// If current time is before the animation's start time, then
|
|
|
|
|
// elapsed time will be zero.
|
|
|
|
|
//
|
2025-10-27 18:18:35 -04:00
|
|
|
inline double ClampedElapsedTime(double CurrentTime) const
|
|
|
|
|
{
|
|
|
|
|
return FMath::Max(0.0, CurrentTime - StartTime);
|
|
|
|
|
}
|
2025-10-14 20:24:37 -04:00
|
|
|
|
|
|
|
|
// Calculate Time Left (clamped)
|
|
|
|
|
//
|
|
|
|
|
// If current time is after the animation's end time, then
|
|
|
|
|
// time left will be zero.
|
|
|
|
|
//
|
2025-10-27 18:18:35 -04:00
|
|
|
inline double ClampedTimeLeft(double CurrentTime) const
|
|
|
|
|
{
|
|
|
|
|
return FMath::Max(0.0, EndTime - CurrentTime);
|
|
|
|
|
}
|
2025-10-14 20:24:37 -04:00
|
|
|
|
|
|
|
|
// Calculate the progress of the fadein.
|
|
|
|
|
//
|
2025-10-27 18:18:35 -04:00
|
|
|
inline double CalcFadeInAlpha(double CurrentTime) const
|
|
|
|
|
{
|
|
|
|
|
return CalculateFade(UnclampedElapsedTime(CurrentTime), FadeInDuration);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-14 20:24:37 -04:00
|
|
|
// Calculate the progress of the fadeout.
|
|
|
|
|
//
|
2025-10-27 18:18:35 -04:00
|
|
|
inline double CalcFadeOutAlpha(double CurrentTime) const
|
|
|
|
|
{
|
|
|
|
|
return CalculateFade(UnclampedTimeLeft(CurrentTime), FadeOutDuration);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-14 20:24:37 -04:00
|
|
|
// Calculate the combined alpha of the fade-in-out.
|
|
|
|
|
//
|
2025-10-27 18:18:35 -04:00
|
|
|
inline double CalcFadeInOutAlpha(double CurrentTime) const
|
|
|
|
|
{
|
|
|
|
|
return FMath::Min(CalcFadeInAlpha(CurrentTime), CalcFadeOutAlpha(CurrentTime));
|
|
|
|
|
}
|
2025-10-14 20:24:37 -04:00
|
|
|
|
|
|
|
|
// 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<FlxScriptedAnimation> 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<FlxScriptedAnimation> &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.
|
|
|
|
|
//
|
2025-10-27 18:18:35 -04:00
|
|
|
// 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.
|
2025-10-14 20:24:37 -04:00
|
|
|
//
|
|
|
|
|
UFUNCTION(BlueprintCallable, Category = "Luprex|Scripted Animations", meta=(WorldContext = "WorldContextObject"))
|
|
|
|
|
void AddAnimation(
|
2025-10-27 18:18:35 -04:00
|
|
|
UObject *WorldContextObject, UAnimSequenceBase* Sequence, double FadeInTime = 0.2, double FadeOutTime = 0.2,
|
|
|
|
|
int64 AnimationStepID=0, bool ContinueWhenPaused=false);
|
|
|
|
|
|
2025-11-18 00:40:43 -05:00
|
|
|
// Calculate time left for a given animation.
|
|
|
|
|
//
|
|
|
|
|
// If there are multiple animations playing with the given ID,
|
|
|
|
|
// returns the MAX time left.
|
|
|
|
|
//
|
|
|
|
|
UFUNCTION(BlueprintCallable, Category = "Luprex|Scripted Animations", meta=(WorldContext = "WorldContextObject"))
|
|
|
|
|
double CalculateTimeLeft(UObject *WorldContextObject, int64 AnimationID=0);
|
|
|
|
|
|
2025-10-27 18:18:35 -04:00
|
|
|
// Fade all animations whose IDs are not in the 'Keep' list.
|
2025-10-14 20:24:37 -04:00
|
|
|
//
|
|
|
|
|
// Sometimes, predictive reexecution causes an animation step to
|
2025-10-27 18:18:35 -04:00
|
|
|
// vanish from the animation queue. When that happens, it is
|
2025-10-14 20:24:37 -04:00
|
|
|
// desirable to terminate any scripted animations that were launched
|
|
|
|
|
// by that animation step. You don't want to cut them off abruptly,
|
2025-10-27 18:18:35 -04:00
|
|
|
// 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.
|
2025-10-14 20:24:37 -04:00
|
|
|
//
|
2025-10-27 18:18:35 -04:00
|
|
|
void FadeGarbage(const UObject *WorldContextObject, TArray<int64> KeepIDs);
|
2025-10-14 20:24:37 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
UCLASS()
|
|
|
|
|
class INTEGRATION_API UlxScriptedAnimationLibrary : public UBlueprintFunctionLibrary
|
|
|
|
|
{
|
|
|
|
|
GENERATED_BODY()
|
|
|
|
|
|
|
|
|
|
public:
|
2025-10-27 18:18:35 -04:00
|
|
|
// Get all the major 'World Clocks' in a single struct.
|
|
|
|
|
//
|
2026-02-13 23:24:18 -05:00
|
|
|
UFUNCTION(BlueprintCallable, Category = "Utilities|Time", meta=(WorldContext = "WorldContextObject"))
|
2025-10-27 18:18:35 -04:00
|
|
|
static FlxWorldClocks GetAllWorldClocks(const UObject *WorldContextObject);
|
|
|
|
|
|
|
|
|
|
// Get the data to drive Sequence Evaluators and Multi Blend
|
2025-10-14 20:24:37 -04:00
|
|
|
//
|
|
|
|
|
// 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))
|
2025-10-27 18:18:35 -04:00
|
|
|
static void ScriptedAnimationEvaluatorData(const UlxScriptedAnimations *Animations,
|
|
|
|
|
const FlxWorldClocks &WorldClocks,
|
2025-10-14 20:24:37 -04:00
|
|
|
UAnimSequenceBase *&Sequence0, float &ExplicitTime0,
|
|
|
|
|
UAnimSequenceBase *&Sequence1, float &ExplicitTime1,
|
|
|
|
|
UAnimSequenceBase *&Sequence2, float &ExplicitTime2,
|
|
|
|
|
float &BaseAlpha, float &Sequence0Alpha, float &Sequence1Alpha, float &Sequence2Alpha);
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|