diff --git a/Content/Characters/Mannequins/Animations/ABP_Manny.uasset b/Content/Characters/Mannequins/Animations/ABP_Manny.uasset index 63f76caa..98563fc9 100644 --- a/Content/Characters/Mannequins/Animations/ABP_Manny.uasset +++ b/Content/Characters/Mannequins/Animations/ABP_Manny.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ecf396c3199b1c670e4ff6616cb34e5a1b9dc034021d6d910eb7347fecb0200e -size 413277 +oid sha256:c6f196551cd5f7efda68a41c7f8f3398b36a14ee8bd59ad371bb82bc2b67ebe9 +size 403792 diff --git a/Content/Tangibles/TAN_Character.uasset b/Content/Tangibles/TAN_Character.uasset index 75f930ad..cf749d2f 100644 --- a/Content/Tangibles/TAN_Character.uasset +++ b/Content/Tangibles/TAN_Character.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:982ad39fdafc41fbe887b86e4e2a7a21f8491e1a4c81b7852301d22eb608b1ad -size 342981 +oid sha256:2e4eac77be662465181a233723da17a88968fcbfca69be4dfa5682e7068180d1 +size 323402 diff --git a/Source/Integration/AnimQueue.h b/Source/Integration/AnimQueue.h index 54e980f5..515263d3 100644 --- a/Source/Integration/AnimQueue.h +++ b/Source/Integration/AnimQueue.h @@ -30,7 +30,7 @@ public: // The hash of the animation step, a 63-bit unique identifier. // - UPROPERTY(VisibleAnywhere, BlueprintReadOnly) + UPROPERTY() int64 Hash; // The Body contains all the key-value pairs in an encoded form. To @@ -74,7 +74,7 @@ class INTEGRATION_API UlxAnimationStepLibrary : public UBlueprintFunctionLibrary GENERATED_BODY() public: - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Animation Step") + UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step") static FString AnimationStepDebugString(const FlxAnimationStep& step); // Stores the key-value pairs in properties of the target object. @@ -101,26 +101,29 @@ public: UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = "Luprex|Animation Step") static void UnpackAnimationStep(bool &bChanged, FString &Action, const FlxAnimationStep& step, UObject* target, const FString& VariableNamePrefix = TEXT("aq")); - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Animation Step") + UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step") static bool AnimationStepEqual(const FlxAnimationStep &StepA, const FlxAnimationStep &StepB); - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Animation Step") + UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step") static bool AnimationStepIsIdle(const FlxAnimationStep &step); - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Animation Step") + UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step") static FVector AnimationStepGetVector(const FlxAnimationStep& step, const FString& name); - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Animation Step") + UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step") static double AnimationStepGetFloat(const FlxAnimationStep& step, const FString& name); - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Animation Step") + UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step") static FString AnimationStepGetString(const FlxAnimationStep& step, const FString& name); - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Animation Step") + UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step") static FName AnimationStepGetName(const FlxAnimationStep& step, const FString& name); - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Animation Step") + UFUNCTION(BlueprintPure, Category = "Luprex|Animation Step") static bool AnimationStepGetBool(const FlxAnimationStep& step, const FString& name); + + UFUNCTION(BlueprintPure, meta = (BlueprintAutocast), Category = "Luprex|Animation Step") + static int64 AnimationStepID(const FlxAnimationStep& step) { return step.Hash; } }; //////////////////////////////////////////////// diff --git a/Source/Integration/ScriptedAnimation.cpp b/Source/Integration/ScriptedAnimation.cpp index 233e18e2..dd84d9da 100644 --- a/Source/Integration/ScriptedAnimation.cpp +++ b/Source/Integration/ScriptedAnimation.cpp @@ -1,59 +1,5 @@ #include "ScriptedAnimation.h" - -//////////////////////////////////////////////////////////// -// -// Routines to calculate the current state of an FlxScriptedAnimation. -// -//////////////////////////////////////////////////////////// - - -double FlxScriptedAnimation::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; -} - -double FlxScriptedAnimation::UnclampedElapsedTime(double CurrentTime) const -{ - return CurrentTime - StartTime; -} - -double FlxScriptedAnimation::UnclampedTimeLeft(double CurrentTime) const -{ - return EndTime - CurrentTime; -} - -double FlxScriptedAnimation::ClampedElapsedTime(double CurrentTime) const -{ - return FMath::Max(0.0, CurrentTime - StartTime); -} - -double FlxScriptedAnimation::ClampedTimeLeft(double CurrentTime) const -{ - return FMath::Max(0.0, EndTime - CurrentTime); -} - -double FlxScriptedAnimation::CalcFadeInAlpha(double CurrentTime) const -{ - return CalculateFade(UnclampedElapsedTime(CurrentTime), FadeInDuration); -} - -double FlxScriptedAnimation::CalcFadeOutAlpha(double CurrentTime) const -{ - return CalculateFade(UnclampedTimeLeft(CurrentTime), FadeOutDuration); -} - -double FlxScriptedAnimation::CalcFadeInOutAlpha(double CurrentTime) const -{ - return FMath::Min(CalcFadeInAlpha(CurrentTime), CalcFadeOutAlpha(CurrentTime)); -} +#include "Engine/Engine.h" void FlxScriptedAnimation::InitiateFadeOut(double CurrentTime, double AllowedFade) { @@ -81,43 +27,48 @@ void UlxScriptedAnimations::Keep(int n) } void UlxScriptedAnimations::AddAnimation( - UObject* WorldContextObject, UAnimSequenceBase* Sequence, double FadeInTime, double FadeOutTime, int64 AqHash) + UObject* WorldContextObject, UAnimSequenceBase* Sequence, double FadeInTime, double FadeOutTime, + int64 AnimationStepID, bool ContinueWhenPaused) { check(KeepCount >= 1); FlxScriptedAnimation Result; // Get World Time - UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject); - double WorldTime = World ? World->GetTimeSeconds() : 0.0; + FlxWorldClocks Clocks = UlxScriptedAnimationLibrary::GetAllWorldClocks(WorldContextObject); + double CurrentTime = ContinueWhenPaused ? Clocks.RealTime : Clocks.WorldTime; // Get the animation Length. double Length = (Sequence ? static_cast(Sequence->GetPlayLength()) : 0.0); // Fill the static setup fields - Result.Sequence = Sequence; - Result.AqHash = AqHash; - Result.FadeInDuration = FadeInTime; - Result.FadeOutDuration = FadeOutTime; - Result.StartTime = WorldTime; - Result.EndTime = WorldTime + Length; + Result.Sequence = Sequence; + Result.ContinueWhenPaused = ContinueWhenPaused; + Result.AnimationStepID = AnimationStepID; + Result.FadeInDuration = FadeInTime; + Result.FadeOutDuration = FadeOutTime; + Result.StartTime = CurrentTime; + Result.EndTime = CurrentTime + Length; Keep(KeepCount - 1); Animations.Insert(Result, 0); } -void UlxScriptedAnimations::FadeGarbage(TArray Hashes, double CurrentTime) +void UlxScriptedAnimations::FadeGarbage(const UObject *WorldContextObject, TArray KeepIDs) { + FlxWorldClocks Clocks = UlxScriptedAnimationLibrary::GetAllWorldClocks(WorldContextObject); for (int i = 0; i < Animations.Num(); i++) { FlxScriptedAnimation &Anim = Animations[i]; - if ((Anim.AqHash != 0) && (!Hashes.Contains(Anim.AqHash))) + if ((Anim.AnimationStepID > 0) && (!KeepIDs.Contains(Anim.AnimationStepID))) { - Anim.InitiateFadeOut(CurrentTime, 0.2); + Anim.InitiateFadeOut(Anim.ChooseCorrectClock(Clocks), 0.2); } } } -void UlxScriptedAnimationLibrary::ScriptedAnimationEvaluatorData(const UlxScriptedAnimations *Animations, double CurrentTime, +void UlxScriptedAnimationLibrary::ScriptedAnimationEvaluatorData( + const UlxScriptedAnimations *Animations, + const FlxWorldClocks &WorldClocks, UAnimSequenceBase *&Sequence0, float &ExplicitTime0, UAnimSequenceBase *&Sequence1, float &ExplicitTime1, UAnimSequenceBase *&Sequence2, float &ExplicitTime2, @@ -140,6 +91,7 @@ void UlxScriptedAnimationLibrary::ScriptedAnimationEvaluatorData(const UlxScript if (Anims.Num() > 0) { const FlxScriptedAnimation &Anim = Anims[0]; + double CurrentTime = Anim.ChooseCorrectClock(WorldClocks); Sequence0 = Anim.Sequence; ExplicitTime0 = Anim.ClampedElapsedTime(CurrentTime); Sequence0Alpha = Anim.CalcFadeInOutAlpha(CurrentTime); @@ -147,6 +99,7 @@ void UlxScriptedAnimationLibrary::ScriptedAnimationEvaluatorData(const UlxScript if (Anims.Num() > 1) { const FlxScriptedAnimation &Anim = Anims[1]; + double CurrentTime = Anim.ChooseCorrectClock(WorldClocks); Sequence1 = Anim.Sequence; ExplicitTime1 = Anim.ClampedElapsedTime(CurrentTime); Sequence1Alpha = Anim.CalcFadeInOutAlpha(CurrentTime); @@ -154,6 +107,7 @@ void UlxScriptedAnimationLibrary::ScriptedAnimationEvaluatorData(const UlxScript if (Anims.Num() > 2) { const FlxScriptedAnimation &Anim = Anims[2]; + double CurrentTime = Anim.ChooseCorrectClock(WorldClocks); Sequence2 = Anim.Sequence; ExplicitTime2 = Anim.ClampedElapsedTime(CurrentTime); Sequence2Alpha = Anim.CalcFadeInOutAlpha(CurrentTime); @@ -175,3 +129,14 @@ void UlxScriptedAnimationLibrary::ScriptedAnimationEvaluatorData(const UlxScript } } +FlxWorldClocks UlxScriptedAnimationLibrary::GetAllWorldClocks(const UObject *WorldContextObject) +{ + UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject); + FlxWorldClocks Result; + if (World != nullptr) + { + Result.WorldTime = World->GetTimeSeconds(); + Result.RealTime = World->GetRealTimeSeconds(); + } + return Result; +} diff --git a/Source/Integration/ScriptedAnimation.h b/Source/Integration/ScriptedAnimation.h index c902b514..a6e3894d 100644 --- a/Source/Integration/ScriptedAnimation.h +++ b/Source/Integration/ScriptedAnimation.h @@ -2,9 +2,9 @@ #pragma once #include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" // #include "Kismet/KismetSystemLibrary.h" // #include "CommonTypes.h" -// #include "Kismet/BlueprintFunctionLibrary.h" #include "ScriptedAnimation.generated.h" @@ -12,6 +12,18 @@ 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 { @@ -20,13 +32,16 @@ struct INTEGRATION_API FlxScriptedAnimation UPROPERTY(EditAnywhere, BlueprintReadWrite) UAnimSequenceBase *Sequence = nullptr; - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay) - int64 AqHash = 0; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + bool ContinueWhenPaused = false; - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay) + UPROPERTY(EditAnywhere, BlueprintReadWrite) + int64 AnimationStepID = 0; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) double FadeInDuration = 0.0; - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay) + UPROPERTY(EditAnywhere, BlueprintReadWrite) double FadeOutDuration = 0.0; // StartTime and EndTime. @@ -38,55 +53,94 @@ struct INTEGRATION_API FlxScriptedAnimation // may be reduced to implement the truncation. // - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay) + UPROPERTY(EditAnywhere, BlueprintReadWrite) double StartTime = 0.0; - UPROPERTY(EditAnywhere, BlueprintReadWrite, AdvancedDisplay) + UPROPERTY(EditAnywhere, BlueprintReadWrite) double EndTime = 0.0; // Calculate the progress of a fade. // - static double CalculateFade(double Offset, double 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. // - double UnclampedElapsedTime(double CurrentTime) const; + 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. // - double UnclampedTimeLeft(double CurrentTime) const; + 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. // - double ClampedElapsedTime(double CurrentTime) const; + 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. // - double ClampedTimeLeft(double CurrentTime) const; + inline double ClampedTimeLeft(double CurrentTime) const + { + return FMath::Max(0.0, EndTime - CurrentTime); + } // Calculate the progress of the fadein. // - double CalcFadeInAlpha(double CurrentTime) const; - + inline double CalcFadeInAlpha(double CurrentTime) const + { + return CalculateFade(UnclampedElapsedTime(CurrentTime), FadeInDuration); + } + // Calculate the progress of the fadeout. // - double CalcFadeOutAlpha(double CurrentTime) const; - + inline double CalcFadeOutAlpha(double CurrentTime) const + { + return CalculateFade(UnclampedTimeLeft(CurrentTime), FadeOutDuration); + } + // Calculate the combined alpha of the fade-in-out. // - double CalcFadeInOutAlpha(double CurrentTime) const; + inline double CalcFadeInOutAlpha(double CurrentTime) const + { + return FMath::Min(CalcFadeInAlpha(CurrentTime), CalcFadeOutAlpha(CurrentTime)); + } // Cause an animation to start fading right away. // @@ -130,25 +184,38 @@ public: // 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 Aq Hash parameter can be used to associate a scripted animation - // with a step in the animation queue. This makes it feasible to garbage - // collect the scripted animation if the animation queue step disappears. + // 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 AqHash=0); - - // Fade animations whose corresponding animation queue step is dead. + 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 usually + // 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. This function causes all animations that - // are owned by a dead animation step to begin fading. + // 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. // - UFUNCTION(BlueprintCallable, Category = "Luprex|Scripted Animations", meta=(WorldContext = "WorldContextObject")) - void FadeGarbage(TArray Hashes, double CurrentTime); + void FadeGarbage(const UObject *WorldContextObject, TArray KeepIDs); }; UCLASS() @@ -156,18 +223,21 @@ class INTEGRATION_API UlxScriptedAnimationLibrary : public UBlueprintFunctionLib { GENERATED_BODY() -private: - - public: - // Get the data to drive Sequence Evaluators and Multi Blend + // 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, double CurrentTime, + static void ScriptedAnimationEvaluatorData(const UlxScriptedAnimations *Animations, + const FlxWorldClocks &WorldClocks, UAnimSequenceBase *&Sequence0, float &ExplicitTime0, UAnimSequenceBase *&Sequence1, float &ExplicitTime1, UAnimSequenceBase *&Sequence2, float &ExplicitTime2, diff --git a/Source/Integration/Tangible.cpp b/Source/Integration/Tangible.cpp index 16d4b369..3d437241 100644 --- a/Source/Integration/Tangible.cpp +++ b/Source/Integration/Tangible.cpp @@ -123,7 +123,8 @@ void UlxTangible::MaybeExecuteAnimStateChanged() { bool AnyChange = AnimTracker.IsChanged(); - while (AnimTracker.IsChanged()) { + while (AnimTracker.IsChanged()) + { if (limit == 0) break; limit -= 1; AnimTracker.ClearChanged(); @@ -164,11 +165,22 @@ void UlxTangible::MaybeExecuteAnimStateChanged() { FString DS = UlxAnimationStepLibrary::AnimationStepDebugString(*Step); UE_LOG(LogLuprex, Warning, TEXT("Timeout - blueprint didn't finish animation: %s"), *DS); AnimTracker.FinishedAnimation(PendingAnimationHash); + AnyChange = true; } AutoUpdatePosition(); PendingAnimationHash = 0; PendingAnimationTimeout = 0.0; } + + // The following code garbage collects any scripted animations that + // were created by animation steps that are no longer in the animation + // queue. This is intended to handle the case that predictive reexecution + // incorrectly predicts an animation step, and then the animation step + // goes away during of difference transmission. + if (AnyChange && (ScriptedAnimations != nullptr)) + { + ScriptedAnimations->FadeGarbage(this, AnimTracker.GetHashes()); + } } FVector UlxTangible::GetLocation() const @@ -233,7 +245,8 @@ void UlxTangible::AutoUpdatePosition() } } -void UlxTangible::FinishedAnimation(AActor *target, const FlxAnimationStep &step, bool AutoUpdate) { +void UlxTangible::FinishedAnimation(AActor *target, const FlxAnimationStep &step, bool AutoUpdate) +{ UlxTangible *tan = GetActorTangibleOrLog(target); if (tan == nullptr) return; tan->AnimTracker.FinishedAnimation(step.Hash); @@ -242,7 +255,8 @@ void UlxTangible::FinishedAnimation(AActor *target, const FlxAnimationStep &step UE_LOG(LogLuprex, Display, TEXT("Animation Finished: %s"), *DebugString); } -bool UlxTangible::AnimationStepIsFinished(AActor *target, const FlxAnimationStep &step) { +bool UlxTangible::AnimationStepIsFinished(AActor *target, const FlxAnimationStep &step) +{ UlxTangible *tan = GetActorTangibleOrLog(target); if (tan == nullptr) return true; return tan->AnimTracker.IsFinished(step.Hash);