RewindComponent
RewindComponent
#include "RewindComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/StaticMeshComponent.h"
#include "DrawDebugHelpers.h"
#include "Engine/StaticMeshActor.h"
#include "Engine/World.h"
#include "GameFramework/Actor.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/MovementComponent.h"
#include "GameFramework/PawnMovementComponent.h"
#include "RewindCharacter.h"
#include "RewindGameMode.h"
#include "RewindVisualizationComponent.h"
URewindComponent::URewindComponent()
{
PrimaryComponentTick.bCanEverTick = true;
void URewindComponent::BeginPlay()
{
TRACE_CPUPROFILER_EVENT_SCOPE(URewindComponent::BeginPlay);
Super::BeginPlay();
GameMode = Cast<ARewindGameMode>(GetWorld()->GetAuthGameMode());
if (!GameMode)
{
// Disable ticking if we can't find the rewind game mode; no hope of
rewinding
SetComponentTickEnabled(false);
return;
}
if (bIsVisualizingTimeline) { VisualizeTimeline(); }
}
void URewindComponent::OnGlobalRewindStarted()
{
// Attempt to start rewinding; reset TimeSinceSnapshostsChanged if not time
scrubbing
bool bAlreadyManipulatingTime = IsTimeBeingManipulated();
if (TryStartTimeManipulation(bIsRewinding, !bIsTimeScrubbing))
{
// Notify event subscribers that rewind (and possibly time
manipulation) started
OnRewindStarted.Broadcast();
if (!bAlreadyManipulatingTime) { OnTimeManipulationStarted.Broadcast();
}
}
}
void URewindComponent::OnGlobalFastForwardStarted()
{
// Fast forwarding is only allowed when time scrubbing
// Attempt to start fast forwarding; reset TimeSinceSnapshostsChanged if not
time scrubbing
bool bAlreadyManipulatingTime = IsTimeBeingManipulated();
if (bIsTimeScrubbing && TryStartTimeManipulation(bIsFastForwarding, !
bIsTimeScrubbing))
{
// Notify event subscribers that fast forward (and possibly time
manipulation) started
OnFastForwardStarted.Broadcast();
if (!bAlreadyManipulatingTime) { OnTimeManipulationStarted.Broadcast();
}
}
}
void URewindComponent::OnGlobalTimeScrubStarted()
{
// Attempt to start time scrubbing; never reset TimeSinceSnapshostsChanged
for time scrubbing
bool bAlreadyManipulatingTime = IsTimeBeingManipulated();
if (TryStartTimeManipulation(bIsTimeScrubbing, false))
{
// Notify event subscribers that rewind (and possibly time
manipulation) started
OnTimeScrubStarted.Broadcast();
if (!bAlreadyManipulatingTime) { OnTimeManipulationStarted.Broadcast();
}
}
}
void URewindComponent::OnGlobalRewindCompleted()
{
// Attempt to stop rewinding
if (TryStopTimeManipulation(bIsRewinding, !bIsTimeScrubbing, false
/*bResetMovementVelocity*/))
{
// Mark last operation as a rewind for subsequent time scrubbing
bLastTimeManipulationWasRewind = true;
void URewindComponent::OnGlobalFastForwardCompleted()
{
// Attempt to stop fast forwarding
if (TryStopTimeManipulation(bIsFastForwarding, !bIsTimeScrubbing, false
/*bResetMovementVelocity*/))
{
// Mark last operation as a fast forward for subsequent time scrubbing
bLastTimeManipulationWasRewind = false;
void URewindComponent::OnGlobalTimeScrubCompleted()
{
// Attempt to stop time scrubbing; reset movement velocity to match player
expectations
if (TryStopTimeManipulation(bIsTimeScrubbing, false
/*bResetTimeSinceSnapshotsChanged*/, true /*bResetMovementVelocity*/))
{
// Notify event subscribers that time scrubbing (and possibly time
manipulation) completed
OnFastForwardCompleted.Broadcast();
if (!IsTimeBeingManipulated())
{ OnTimeManipulationCompleted.Broadcast(); }
}
}
void URewindComponent::OnGlobalTimelineVisualizationEnabled()
{
bIsVisualizingTimeline = true;
}
void URewindComponent::OnGlobalTimelineVisualizationDisabled()
{
bIsVisualizingTimeline = false;
if (OwnerVisualizationComponent) { OwnerVisualizationComponent-
>ClearInstances(); }
}
// Initialize buffer
TransformAndVelocitySnapshots.Reserve(MaxSnapshots);
TimeSinceSnapshotsChanged += DeltaTime;
// Early out if last snapshot was taken within the desired snapshot cadence
if (TimeSinceSnapshotsChanged < SnapshotFrequencySeconds &&
TransformAndVelocitySnapshots.Num() != 0) { return; }
MovementVelocityAndModeSnapshots.Emplace(TimeSinceSnapshotsChanged,
MovementVelocity, MovementMode);
check(LatestSnapshotIndex == LatestMovementSnapshotIndex);
}
TimeSinceSnapshotsChanged = 0.0f;
}
void URewindComponent::EraseFutureSnapshots()
{
// Pop snapshots until the latest snapshot is the last one in the buffer
while (LatestSnapshotIndex < TransformAndVelocitySnapshots.Num() - 1)
{
TransformAndVelocitySnapshots.Pop();
}
if (bSnapshotMovementVelocityAndMode)
{
// Pop snapshots until the latest snapshot is the last one in the
buffer
while (LatestSnapshotIndex < MovementVelocityAndModeSnapshots.Num() -
1)
{
MovementVelocityAndModeSnapshots.Pop();
}
}
}
UnpauseAnimation();
if (HandleInsufficientSnapshots()) { return; }
ApplySnapshot(MovementVelocityAndModeSnapshots[LatestSnapshotIndex], true
/*bApplyTimeDilationToVelocity*/);
}
return;
}
bReachedEndOfTrack = LatestSnapshotIndex == 0;
}
else
{
// Drop any snapshots that are too old to be relevant
while (LatestSnapshotIndex < TransformAndVelocitySnapshots.Num() - 1 &&
TimeSinceSnapshotsChanged > LatestSnapshotTime)
{
TimeSinceSnapshotsChanged -= LatestSnapshotTime;
LatestSnapshotTime =
TransformAndVelocitySnapshots[LatestSnapshotIndex].TimeSinceLastSnapshot;
++LatestSnapshotIndex;
}
bReachedEndOfTrack = LatestSnapshotIndex ==
TransformAndVelocitySnapshots.Num() - 1;
}
// If we've reached the end of our track, clamp the interpolation and repause
animation
if (bReachedEndOfTrack)
{
TimeSinceSnapshotsChanged = FMath::Min(TimeSinceSnapshotsChanged,
LatestSnapshotTime);
if (bAnimationsPausedAtStartOfTimeManipulation) { PauseAnimation(); }
}
InterpolateAndApplySnapshots(bRewinding);
}
if (HandleInsufficientSnapshots()) { return; }
if (bRewinding)
{
// If we don't have any snapshots in the future, use the latest
snapshot
if (LatestSnapshotIndex == TransformAndVelocitySnapshots.Num() - 1)
{
ApplySnapshot(TransformAndVelocitySnapshots[LatestSnapshotIndex],
false /*bApplyPhysics*/);
if (bSnapshotMovementVelocityAndMode)
{
ApplySnapshot(MovementVelocityAndModeSnapshots[LatestSnapshotIndex], true
/*bApplyTimeDilationToVelocity*/);
}
PauseAnimation();
return;
}
}
InterpolateAndApplySnapshots(bRewinding);
if (FMath::IsNearlyEqual(TimeSinceSnapshotsChanged, LatestSnapshotTime))
{ PauseAnimation(); }
}
return true;
}
// Restore animation
UnpauseAnimation();
ApplySnapshot(MovementVelocityAndModeSnapshots[LatestSnapshotIndex], false
/*bApplyTimeDilationToVelocity*/);
return true;
}
void URewindComponent::PausePhysics()
{
// Pause physics if it's simulating
if (OwnerRootComponent && OwnerRootComponent->BodyInstance.bSimulatePhysics)
{
bPausedPhysics = true;
OwnerRootComponent->SetSimulatePhysics(false);
}
}
void URewindComponent::UnpausePhysics()
{
// Restart physics if simulation was paused
if (!bPausedPhysics) { return; }
check(OwnerRootComponent);
bPausedPhysics = false;
OwnerRootComponent->SetSimulatePhysics(true);
OwnerRootComponent->RecreatePhysicsState();
}
void URewindComponent::PauseAnimation()
{
if (!bPauseAnimationDuringTimeScrubbing) { return; }
check(OwnerSkeletalMesh);
bPausedAnimation = true;
OwnerSkeletalMesh->bPauseAnims = true;
}
void URewindComponent::UnpauseAnimation()
{
if (!bPausedAnimation) { return; }
check(OwnerSkeletalMesh);
bPausedAnimation = false;
OwnerSkeletalMesh->bPauseAnims = false;
}
bool URewindComponent::HandleInsufficientSnapshots()
{
// Nothing to do if no snapshots are available
check(!bSnapshotMovementVelocityAndMode ||
TransformAndVelocitySnapshots.Num() == MovementVelocityAndModeSnapshots.Num());
if (LatestSnapshotIndex < 0 || TransformAndVelocitySnapshots.Num() == 0)
{ return true; }
// Blend and apply transform and velocity snapshots (scoped to avoid variable
shadowing)
{
const FTransformAndVelocitySnapshot& PreviousSnapshot =
TransformAndVelocitySnapshots[PreviousIndex];
const FTransformAndVelocitySnapshot& NextSnapshot =
TransformAndVelocitySnapshots[LatestSnapshotIndex];
ApplySnapshot(
BlendSnapshots(PreviousSnapshot, NextSnapshot,
TimeSinceSnapshotsChanged / NextSnapshot.TimeSinceLastSnapshot),
false /*bApplyPhysics*/);
}
FTransformAndVelocitySnapshot URewindComponent::BlendSnapshots(
const FTransformAndVelocitySnapshot& A,
const FTransformAndVelocitySnapshot& B,
float Alpha)
{
Alpha = FMath::Clamp(Alpha, 0.0f, 1.0f);
FTransformAndVelocitySnapshot BlendedSnapshot;
BlendedSnapshot.Transform.Blend(A.Transform, B.Transform, Alpha);
BlendedSnapshot.LinearVelocity = FMath::Lerp(A.LinearVelocity,
B.LinearVelocity, Alpha);
BlendedSnapshot.AngularVelocityInRadians =
FMath::Lerp(A.AngularVelocityInRadians, B.AngularVelocityInRadians, Alpha);
return BlendedSnapshot;
}
FMovementVelocityAndModeSnapshot URewindComponent::BlendSnapshots(
const FMovementVelocityAndModeSnapshot& A,
const FMovementVelocityAndModeSnapshot& B,
float Alpha)
{
Alpha = FMath::Clamp(Alpha, 0.0f, 1.0f);
FMovementVelocityAndModeSnapshot BlendedSnapshot;
BlendedSnapshot.MovementVelocity = FMath::Lerp(A.MovementVelocity,
B.MovementVelocity, Alpha);
BlendedSnapshot.MovementMode = Alpha < 0.5f ? A.MovementMode :
B.MovementMode;
return BlendedSnapshot;
}
void URewindComponent::VisualizeTimeline()
{
TRACE_CPUPROFILER_EVENT_SCOPE(URewindComponent::VisualizeTimeline);
if (!OwnerVisualizationComponent || !bIsVisualizingTimeline) { return; }
OwnerVisualizationComponent-
>SetInstancesFromSnapshots(TransformAndVelocitySnapshots);
}