You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This represents UE4/Main @17774255, Release-5.0 @17791557 and Dev-PerfTest @17789485 [CL 17794212 by aurel cordonnier in ue5-release-engine-test branch]
503 lines
15 KiB
C++
503 lines
15 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Animation/UMGSequencePlayer.h"
|
|
#include "MovieScene.h"
|
|
#include "UMGPrivate.h"
|
|
#include "Animation/WidgetAnimation.h"
|
|
#include "MovieSceneTimeHelpers.h"
|
|
#include "Evaluation/MovieScenePlayback.h"
|
|
#include "EntitySystem/MovieSceneEntitySystemLinker.h"
|
|
#include "Animation/UMGSequenceTickManager.h"
|
|
|
|
extern TAutoConsoleVariable<bool> CVarUserWidgetUseParallelAnimation;
|
|
|
|
UUMGSequencePlayer::UUMGSequencePlayer(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
PlayerStatus = EMovieScenePlayerStatus::Stopped;
|
|
TimeCursorPosition = FFrameTime(0);
|
|
PlaybackSpeed = 1;
|
|
bRestoreState = false;
|
|
Animation = nullptr;
|
|
bIsEvaluating = false;
|
|
bCompleteOnPostEvaluation = false;
|
|
UserTag = NAME_None;
|
|
}
|
|
|
|
void UUMGSequencePlayer::InitSequencePlayer(UWidgetAnimation& InAnimation, UUserWidget& InUserWidget)
|
|
{
|
|
Animation = &InAnimation;
|
|
UserWidget = &InUserWidget;
|
|
|
|
UMovieScene* MovieScene = Animation->GetMovieScene();
|
|
|
|
// Cache the time range of the sequence to determine when we stop
|
|
Duration = UE::MovieScene::DiscreteSize(MovieScene->GetPlaybackRange());
|
|
AnimationResolution = MovieScene->GetTickResolution();
|
|
AbsolutePlaybackStart = UE::MovieScene::DiscreteInclusiveLower(MovieScene->GetPlaybackRange());
|
|
}
|
|
|
|
UMovieSceneEntitySystemLinker* UUMGSequencePlayer::ConstructEntitySystemLinker()
|
|
{
|
|
UUserWidget* Widget = UserWidget.Get();
|
|
if (ensure(Widget) && !EnumHasAnyFlags(Animation->GetFlags(), EMovieSceneSequenceFlags::BlockingEvaluation))
|
|
{
|
|
if (!ensure(Widget->AnimationTickManager))
|
|
{
|
|
// @todo: There should be no possible way that the animation tick manager is null here, but there is a very low-rate
|
|
// crash caused by it being null that is very hard to track down, so patching with a band-aid for now.
|
|
Widget->AnimationTickManager = UUMGSequenceTickManager::Get(Widget);
|
|
Widget->AnimationTickManager->AddWidget(Widget);
|
|
}
|
|
|
|
return Widget->AnimationTickManager->GetLinker();
|
|
}
|
|
|
|
return UMovieSceneEntitySystemLinker::CreateLinker(Widget ? Widget->GetWorld() : nullptr);
|
|
}
|
|
|
|
void UUMGSequencePlayer::Tick(float DeltaTime)
|
|
{
|
|
if ( PlayerStatus == EMovieScenePlayerStatus::Playing )
|
|
{
|
|
FFrameTime DeltaFrameTime = (bIsPlayingForward ? DeltaTime * PlaybackSpeed : -DeltaTime * PlaybackSpeed) * AnimationResolution;
|
|
|
|
FFrameTime LastTimePosition = TimeCursorPosition;
|
|
TimeCursorPosition += DeltaFrameTime;
|
|
|
|
// Check if we crossed over bounds
|
|
const bool bCrossedLowerBound = TimeCursorPosition < FFrameTime(0);
|
|
const bool bCrossedUpperBound = TimeCursorPosition >= FFrameTime(Duration);
|
|
const bool bCrossedEndTime = bIsPlayingForward
|
|
? LastTimePosition < EndTime && EndTime <= TimeCursorPosition
|
|
: LastTimePosition > EndTime && EndTime >= TimeCursorPosition;
|
|
|
|
// Increment the num loops if we crossed any bounds.
|
|
if (bCrossedLowerBound || bCrossedUpperBound || (bCrossedEndTime && NumLoopsCompleted >= NumLoopsToPlay - 1))
|
|
{
|
|
NumLoopsCompleted++;
|
|
}
|
|
|
|
// Did the animation complete
|
|
const bool bCompleted = NumLoopsToPlay != 0 && NumLoopsCompleted >= NumLoopsToPlay;
|
|
|
|
// Handle times and see if we need to loop or ping-pong. If looping/ping-ponging,
|
|
// we update the sequence for the last little bit before it happens.
|
|
const FFrameTime LastValidFrame(Duration-1, 0.99999994f);
|
|
if (bCrossedLowerBound)
|
|
{
|
|
if (bCompleted)
|
|
{
|
|
TimeCursorPosition = FFrameTime(0);
|
|
}
|
|
else
|
|
{
|
|
UpdateInternal(LastTimePosition, FFrameTime(0), false);
|
|
|
|
if (PlayMode == EUMGSequencePlayMode::PingPong)
|
|
{
|
|
bIsPlayingForward = !bIsPlayingForward;
|
|
TimeCursorPosition = FMath::Abs(TimeCursorPosition);
|
|
LastTimePosition = FFrameTime(0);
|
|
}
|
|
else
|
|
{
|
|
TimeCursorPosition += FFrameTime(Duration);
|
|
LastTimePosition = LastValidFrame;
|
|
}
|
|
}
|
|
}
|
|
else if (bCrossedUpperBound)
|
|
{
|
|
if (bCompleted)
|
|
{
|
|
TimeCursorPosition = LastValidFrame;
|
|
}
|
|
else
|
|
{
|
|
UpdateInternal(LastTimePosition, LastValidFrame, false);
|
|
|
|
if (PlayMode == EUMGSequencePlayMode::PingPong)
|
|
{
|
|
bIsPlayingForward = !bIsPlayingForward;
|
|
TimeCursorPosition = LastValidFrame - (TimeCursorPosition - FFrameTime(Duration));
|
|
LastTimePosition = LastValidFrame;
|
|
}
|
|
else
|
|
{
|
|
TimeCursorPosition = TimeCursorPosition - FFrameTime(Duration);
|
|
LastTimePosition = FFrameTime(0);
|
|
}
|
|
}
|
|
}
|
|
else if (bCrossedEndTime)
|
|
{
|
|
if (bCompleted)
|
|
{
|
|
TimeCursorPosition = EndTime;
|
|
}
|
|
}
|
|
|
|
bCompleteOnPostEvaluation = bCompleted;
|
|
|
|
const bool bHasJumped = (bCrossedLowerBound || bCrossedUpperBound || bCrossedEndTime);
|
|
UpdateInternal(LastTimePosition, TimeCursorPosition, bHasJumped);
|
|
}
|
|
}
|
|
|
|
void UUMGSequencePlayer::UpdateInternal(FFrameTime LastTimePosition, FFrameTime NextTimePosition, bool bHasJumped)
|
|
{
|
|
if (RootTemplateInstance.IsValid())
|
|
{
|
|
UMovieScene* MovieScene = Animation->GetMovieScene();
|
|
|
|
FMovieSceneContext Context(
|
|
FMovieSceneEvaluationRange(
|
|
AbsolutePlaybackStart + TimeCursorPosition,
|
|
AbsolutePlaybackStart + LastTimePosition,
|
|
AnimationResolution),
|
|
PlayerStatus);
|
|
Context.SetHasJumped(bHasJumped);
|
|
|
|
UMovieSceneSequence* MovieSceneSequence = RootTemplateInstance.GetSequence(MovieSceneSequenceID::Root);
|
|
const bool bIsSequenceBlocking = EnumHasAnyFlags(MovieSceneSequence->GetFlags(), EMovieSceneSequenceFlags::BlockingEvaluation);
|
|
|
|
const bool bIsRunningWithTickManager = CVarUserWidgetUseParallelAnimation.GetValueOnGameThread();
|
|
|
|
if (bIsRunningWithTickManager)
|
|
{
|
|
UUserWidget* Widget = UserWidget.Get();
|
|
UUMGSequenceTickManager* TickManager = Widget ? ToRawPtr(Widget->AnimationTickManager) : nullptr;
|
|
|
|
if (!TickManager)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!bIsSequenceBlocking)
|
|
{
|
|
// Queue an evaluation of this player's widget animation, to be evaluated later by our
|
|
// global tick manager as part of a glorious multi-threaded job fest.
|
|
FMovieSceneEntitySystemRunner& Runner = TickManager->GetRunner();
|
|
Runner.QueueUpdate(Context, RootTemplateInstance.GetRootInstanceHandle());
|
|
// WARNING: the evalution hasn't run yet so don't run any stateful code after this point
|
|
// unless you know it's OK to do so. Most likely, you want to run stateful code in the
|
|
// PostEvaluation method, or queue up a latent action.
|
|
}
|
|
else
|
|
{
|
|
// Synchronous evaluation. Sucks for performance.
|
|
RootTemplateInstance.Evaluate(Context, *this);
|
|
// The latent actions will be run by the tick manager.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Synchronous evaluation. Sucks for performance.
|
|
RootTemplateInstance.Evaluate(Context, *this);
|
|
ApplyLatentActions();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UUMGSequencePlayer::PlayInternal(double StartAtTime, double EndAtTime, int32 InNumLoopsToPlay, EUMGSequencePlayMode::Type InPlayMode, float InPlaybackSpeed, bool bInRestoreState)
|
|
{
|
|
RootTemplateInstance.Initialize(*Animation, *this, nullptr);
|
|
|
|
if (bInRestoreState)
|
|
{
|
|
RootTemplateInstance.EnableGlobalPreAnimatedStateCapture();
|
|
}
|
|
|
|
bRestoreState = bInRestoreState;
|
|
PlaybackSpeed = FMath::Abs(InPlaybackSpeed);
|
|
PlayMode = InPlayMode;
|
|
|
|
FFrameTime LastValidFrame(Duration-1, 0.99999994f);
|
|
|
|
if (PlayMode == EUMGSequencePlayMode::Reverse)
|
|
{
|
|
// When playing in reverse count subtract the start time from the end.
|
|
TimeCursorPosition = LastValidFrame - StartAtTime * AnimationResolution;
|
|
}
|
|
else
|
|
{
|
|
TimeCursorPosition = StartAtTime * AnimationResolution;
|
|
}
|
|
|
|
// Clamp the start time and end time to be within the bounds
|
|
TimeCursorPosition = FMath::Clamp(TimeCursorPosition, FFrameTime(0), LastValidFrame);
|
|
EndTime = FMath::Clamp(EndAtTime * AnimationResolution, FFrameTime(0), LastValidFrame);
|
|
|
|
if ( PlayMode == EUMGSequencePlayMode::PingPong )
|
|
{
|
|
// When animating in ping-pong mode double the number of loops to play so that a loop is a complete forward/reverse cycle.
|
|
NumLoopsToPlay = 2 * InNumLoopsToPlay;
|
|
}
|
|
else
|
|
{
|
|
NumLoopsToPlay = InNumLoopsToPlay;
|
|
}
|
|
|
|
NumLoopsCompleted = 0;
|
|
bIsPlayingForward = InPlayMode != EUMGSequencePlayMode::Reverse;
|
|
|
|
PlayerStatus = EMovieScenePlayerStatus::Playing;
|
|
|
|
UUserWidget* Widget = UserWidget.Get();
|
|
UUMGSequenceTickManager* TickManager = Widget ? ToRawPtr(Widget->AnimationTickManager) : nullptr;
|
|
|
|
// Playback assumes the start frame has already been evaulated, so we also want to evaluate any events on the start frame here.
|
|
if (TickManager && RootTemplateInstance.IsValid())
|
|
{
|
|
const FMovieSceneContext Context(FMovieSceneEvaluationRange(AbsolutePlaybackStart + TimeCursorPosition, AbsolutePlaybackStart + TimeCursorPosition, AnimationResolution), PlayerStatus);
|
|
|
|
// We queue an update instead of immediately flushing the entire linker so that we don't incur a cascade of flushes on frames when multiple animations are played
|
|
// In rare cases where the linker must be flushed immediately PreTick, the queue should be manually flushed
|
|
|
|
FMovieSceneEntitySystemRunner& Runner = TickManager->GetRunner();
|
|
Runner.QueueUpdate(Context, RootTemplateInstance.GetRootInstanceHandle());
|
|
}
|
|
}
|
|
|
|
void UUMGSequencePlayer::Play(float StartAtTime, int32 InNumLoopsToPlay, EUMGSequencePlayMode::Type InPlayMode, float InPlaybackSpeed, bool bInRestoreState)
|
|
{
|
|
if (NeedsQueueLatentAction())
|
|
{
|
|
QueueLatentAction(FMovieSceneSequenceLatentActionDelegate::CreateUObject(
|
|
this, &UUMGSequencePlayer::Play, StartAtTime, InNumLoopsToPlay, InPlayMode, InPlaybackSpeed, bInRestoreState));
|
|
return;
|
|
}
|
|
|
|
PlayInternal(StartAtTime, 0.0, InNumLoopsToPlay, InPlayMode, InPlaybackSpeed, bInRestoreState);
|
|
}
|
|
|
|
void UUMGSequencePlayer::PlayTo(float StartAtTime, float EndAtTime, int32 InNumLoopsToPlay, EUMGSequencePlayMode::Type InPlayMode, float InPlaybackSpeed, bool bInRestoreState)
|
|
{
|
|
if (NeedsQueueLatentAction())
|
|
{
|
|
QueueLatentAction(FMovieSceneSequenceLatentActionDelegate::CreateUObject(
|
|
this, &UUMGSequencePlayer::PlayTo, StartAtTime, EndAtTime, InNumLoopsToPlay, InPlayMode, InPlaybackSpeed, bInRestoreState));
|
|
return;
|
|
}
|
|
|
|
PlayInternal(StartAtTime, EndAtTime, InNumLoopsToPlay, InPlayMode, InPlaybackSpeed, bInRestoreState);
|
|
}
|
|
|
|
void UUMGSequencePlayer::Pause()
|
|
{
|
|
if (NeedsQueueLatentAction())
|
|
{
|
|
QueueLatentAction(FMovieSceneSequenceLatentActionDelegate::CreateUObject(this, &UUMGSequencePlayer::Pause));
|
|
return;
|
|
}
|
|
|
|
// Purposely don't trigger any OnFinished events
|
|
PlayerStatus = EMovieScenePlayerStatus::Stopped;
|
|
|
|
// Evaluate the sequence at its current time, with a status of 'stopped' to ensure that animated state pauses correctly. (ie. audio sounds should stop/pause)
|
|
const FMovieSceneContext Context(FMovieSceneEvaluationRange(AbsolutePlaybackStart + TimeCursorPosition, AbsolutePlaybackStart + TimeCursorPosition, AnimationResolution), PlayerStatus);
|
|
if (RootTemplateInstance.HasEverUpdated())
|
|
{
|
|
UUserWidget* Widget = UserWidget.Get();
|
|
UUMGSequenceTickManager* TickManager = Widget ? ToRawPtr(Widget->AnimationTickManager) : nullptr;
|
|
|
|
if (TickManager)
|
|
{
|
|
FMovieSceneEntitySystemRunner& Runner = TickManager->GetRunner();
|
|
Runner.QueueUpdate(Context, RootTemplateInstance.GetRootInstanceHandle());
|
|
}
|
|
}
|
|
}
|
|
|
|
void UUMGSequencePlayer::Reverse()
|
|
{
|
|
if (PlayerStatus == EMovieScenePlayerStatus::Playing)
|
|
{
|
|
bIsPlayingForward = !bIsPlayingForward;
|
|
}
|
|
}
|
|
|
|
void UUMGSequencePlayer::Stop()
|
|
{
|
|
if (NeedsQueueLatentAction())
|
|
{
|
|
QueueLatentAction(FMovieSceneSequenceLatentActionDelegate::CreateUObject(this, &UUMGSequencePlayer::Stop));
|
|
return;
|
|
}
|
|
|
|
PlayerStatus = EMovieScenePlayerStatus::Stopped;
|
|
|
|
UUserWidget* Widget = UserWidget.Get();
|
|
UUMGSequenceTickManager* TickManager = Widget ? ToRawPtr(Widget->AnimationTickManager) : nullptr;
|
|
|
|
if (TickManager && RootTemplateInstance.IsValid())
|
|
{
|
|
if (RootTemplateInstance.HasEverUpdated())
|
|
{
|
|
const FMovieSceneContext Context(FMovieSceneEvaluationRange(AbsolutePlaybackStart, AnimationResolution), PlayerStatus);
|
|
RootTemplateInstance.Evaluate(Context, *this);
|
|
}
|
|
else
|
|
{
|
|
TickManager->ClearLatentActions(this);
|
|
LatentActions.Empty();
|
|
}
|
|
RootTemplateInstance.Finish(*this);
|
|
}
|
|
|
|
if (bRestoreState)
|
|
{
|
|
RestorePreAnimatedState();
|
|
}
|
|
|
|
UserWidget->OnAnimationFinishedPlaying(*this);
|
|
OnSequenceFinishedPlayingEvent.Broadcast(*this);
|
|
|
|
TimeCursorPosition = FFrameTime(0);
|
|
}
|
|
|
|
void UUMGSequencePlayer::SetNumLoopsToPlay(int32 InNumLoopsToPlay)
|
|
{
|
|
if (PlayMode == EUMGSequencePlayMode::PingPong)
|
|
{
|
|
NumLoopsToPlay = (2 * InNumLoopsToPlay);
|
|
}
|
|
else
|
|
{
|
|
NumLoopsToPlay = InNumLoopsToPlay;
|
|
}
|
|
}
|
|
|
|
void UUMGSequencePlayer::SetPlaybackSpeed(float InPlaybackSpeed)
|
|
{
|
|
PlaybackSpeed = InPlaybackSpeed;
|
|
}
|
|
|
|
EMovieScenePlayerStatus::Type UUMGSequencePlayer::GetPlaybackStatus() const
|
|
{
|
|
return PlayerStatus;
|
|
}
|
|
|
|
UObject* UUMGSequencePlayer::GetPlaybackContext() const
|
|
{
|
|
return UserWidget.Get();
|
|
}
|
|
|
|
TArray<UObject*> UUMGSequencePlayer::GetEventContexts() const
|
|
{
|
|
TArray<UObject*> EventContexts;
|
|
if (UserWidget.IsValid())
|
|
{
|
|
EventContexts.Add(UserWidget.Get());
|
|
}
|
|
return EventContexts;
|
|
}
|
|
|
|
void UUMGSequencePlayer::SetPlaybackStatus(EMovieScenePlayerStatus::Type InPlaybackStatus)
|
|
{
|
|
PlayerStatus = InPlaybackStatus;
|
|
}
|
|
|
|
void UUMGSequencePlayer::PreEvaluation(const FMovieSceneContext& Context)
|
|
{
|
|
bIsEvaluating = true;
|
|
}
|
|
|
|
void UUMGSequencePlayer::PostEvaluation(const FMovieSceneContext& Context)
|
|
{
|
|
bIsEvaluating = false;
|
|
|
|
if (bCompleteOnPostEvaluation)
|
|
{
|
|
bCompleteOnPostEvaluation = false;
|
|
|
|
PlayerStatus = EMovieScenePlayerStatus::Stopped;
|
|
|
|
if (RootTemplateInstance.IsValid())
|
|
{
|
|
RootTemplateInstance.Finish(*this);
|
|
}
|
|
|
|
if (bRestoreState)
|
|
{
|
|
RestorePreAnimatedState();
|
|
}
|
|
|
|
UserWidget->OnAnimationFinishedPlaying(*this);
|
|
OnSequenceFinishedPlayingEvent.Broadcast(*this);
|
|
}
|
|
}
|
|
|
|
bool UUMGSequencePlayer::NeedsQueueLatentAction() const
|
|
{
|
|
return bIsEvaluating;
|
|
}
|
|
|
|
void UUMGSequencePlayer::QueueLatentAction(FMovieSceneSequenceLatentActionDelegate Delegate)
|
|
{
|
|
if (CVarUserWidgetUseParallelAnimation.GetValueOnGameThread())
|
|
{
|
|
UUserWidget* Widget = UserWidget.Get();
|
|
UUMGSequenceTickManager* TickManager = Widget ? ToRawPtr(Widget->AnimationTickManager) : nullptr;
|
|
|
|
if (ensure(TickManager))
|
|
{
|
|
TickManager->AddLatentAction(Delegate);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LatentActions.Add(Delegate);
|
|
}
|
|
}
|
|
|
|
void UUMGSequencePlayer::ApplyLatentActions()
|
|
{
|
|
if (CVarUserWidgetUseParallelAnimation.GetValueOnGameThread())
|
|
{
|
|
UUserWidget* Widget = UserWidget.Get();
|
|
UUMGSequenceTickManager* TickManager = Widget ? ToRawPtr(Widget->AnimationTickManager) : nullptr;
|
|
if (TickManager)
|
|
{
|
|
TickManager->RunLatentActions();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (LatentActions.Num() > 0)
|
|
{
|
|
const FMovieSceneSequenceLatentActionDelegate& Delegate = LatentActions[0];
|
|
Delegate.ExecuteIfBound();
|
|
LatentActions.RemoveAt(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UUMGSequencePlayer::RemoveEvaluationData()
|
|
{
|
|
using namespace UE::MovieScene;
|
|
|
|
UMovieSceneEntitySystemLinker* Linker = RootTemplateInstance.GetEntitySystemLinker();
|
|
FSequenceInstance* SequenceInstance = RootTemplateInstance.FindInstance(MovieSceneSequenceID::Root);
|
|
|
|
if (SequenceInstance && Linker)
|
|
{
|
|
SequenceInstance->Ledger.UnlinkEverything(Linker);
|
|
SequenceInstance->InvalidateCachedData(Linker);
|
|
}
|
|
}
|
|
|
|
void UUMGSequencePlayer::TearDown()
|
|
{
|
|
RootTemplateInstance.BeginDestroy();
|
|
}
|
|
|
|
void UUMGSequencePlayer::BeginDestroy()
|
|
{
|
|
RootTemplateInstance.BeginDestroy();
|
|
|
|
Super::BeginDestroy();
|
|
}
|
|
|