Files
UnrealEngine/Engine/Source/Runtime/MovieSceneTracks/Private/Systems/MovieSceneCameraShakeSystem.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

876 lines
31 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Systems/MovieSceneCameraShakeSystem.h"
#include "Camera/CameraComponent.h"
#include "Camera/CameraModifier_CameraShake.h"
#include "Camera/CameraShakeSourceComponent.h"
#include "EntitySystem/BuiltInComponentTypes.h"
#include "EntitySystem/MovieSceneEntityManager.h"
#include "EntitySystem/MovieSceneEntitySystemLinker.h"
#include "EntitySystem/MovieScenePreAnimatedStateSystem.h"
#include "Evaluation/MovieSceneCameraShakePreviewer.h"
#include "Evaluation/PreAnimatedState/MovieScenePreAnimatedObjectStorage.h"
#include "Evaluation/PreAnimatedState/MovieScenePreAnimatedStateStorage.h"
#include "Evaluation/PreAnimatedState/MovieScenePreAnimatedStorageID.inl"
#include "IMovieScenePlayer.h"
#include "MovieSceneTracksComponentTypes.h"
#include "Sections/MovieSceneCameraShakeSection.h"
#include "UObject/Object.h"
#include "UObject/Package.h"
#if WITH_EDITOR
#include "Editor.h"
#include "LevelEditorViewport.h"
#endif // WITH_EDITOR
#include UE_INLINE_GENERATED_CPP_BY_NAME(MovieSceneCameraShakeSystem)
namespace UE::MovieScene
{
TOptional<float> ComputeCameraShakeDurationOverride(const FMovieSceneContext& Context, const FMovieSceneCameraShakeComponentData& ShakeData)
{
// Get the duration of the shake and compare it to the duration of the section, to know
// if we need to override it. We only override duration if the section is shorter than
// the shake's natural duration... i.e, we only make shakes shorter, not longer.
const FFrameRate& FrameRate = Context.GetFrameRate();
TSubclassOf<UCameraShakeBase> ShakeClass = ShakeData.SectionData.ShakeClass;
FCameraShakeDuration ShakeDuration;
UCameraShakeBase::GetCameraShakeDuration(ShakeClass, ShakeDuration);
TOptional<float> DurationOverride;
const FFrameTime SectionDurationFrames = (ShakeData.SectionEndTime - ShakeData.SectionStartTime);
if (ShakeDuration.IsFixed())
{
const FFrameTime ShakeDurationFrames = Context.GetFrameRate().AsFrameTime(ShakeDuration.Get());
if (ShakeDurationFrames > SectionDurationFrames)
{
DurationOverride = FrameRate.AsSeconds(SectionDurationFrames);
}
}
else
{
DurationOverride = FrameRate.AsSeconds(SectionDurationFrames);
}
return DurationOverride;
}
struct FPreAnimatedCameraShakeTraits : FBoundObjectPreAnimatedStateTraits
{
using KeyType = FObjectKey;
using StorageType = bool;
bool CachePreAnimatedValue(UObject* InKey)
{
return true;
}
void RestorePreAnimatedValue(const FObjectKey& InKey, const bool Unused, const FRestoreStateParams& Params)
{
if (UCameraShakeBase* CameraShake = Cast<UCameraShakeBase>(InKey.ResolveObjectPtr()))
{
if (!CameraShake->IsFinished())
{
CameraShake->StopShake(true);
}
CameraShake->TeardownShake();
}
}
};
struct FPreAnimatedCameraComponentShakeTraits : FBoundObjectPreAnimatedStateTraits
{
using KeyType = FObjectKey;
using StorageType = bool;
bool CachePreAnimatedValue(UObject* InKey)
{
return true;
}
void RestorePreAnimatedValue(const FObjectKey& InKey, const bool Unused, const FRestoreStateParams& Params)
{
if (UCameraComponent* CameraComponent = Cast<UCameraComponent>(InKey.ResolveObjectPtr()))
{
CameraComponent->ClearAdditiveOffset();
CameraComponent->ClearExtraPostProcessBlends();
}
}
};
struct FPreAnimatedCameraSourceShakeTraits : FBoundObjectPreAnimatedStateTraits
{
using KeyType = FObjectKey;
using StorageType = bool;
bool CachePreAnimatedValue(UObject* InKey)
{
return true;
}
void RestorePreAnimatedValue(const FObjectKey& InKey, const bool Unused, const FRestoreStateParams& Params)
{
if (UCameraShakeSourceComponent* ShakeSourceComponent = Cast<UCameraShakeSourceComponent>(InKey.ResolveObjectPtr()))
{
ShakeSourceComponent->StopAllCameraShakes(true);
#if WITH_EDITOR
FCameraShakePreviewerLinkerExtension* PreviewerExtension = Params.Linker->FindExtension<FCameraShakePreviewerLinkerExtension>();
if (PreviewerExtension)
{
if (TSharedPtr<FCameraShakePreviewer> Previewer = PreviewerExtension->FindPreviewer(Params.TerminalInstanceHandle))
{
Previewer->RemoveAllCameraShakesFromSource(ShakeSourceComponent);
}
}
#endif // WITH_EDITOR
}
}
};
struct FPreAnimatedCameraShakeStateStorage : TPreAnimatedStateStorage_ObjectTraits<FPreAnimatedCameraShakeTraits>
{
static TAutoRegisterPreAnimatedStorageID<FPreAnimatedCameraShakeStateStorage> StorageID;
FPreAnimatedStorageID GetStorageType() const override { return StorageID; }
};
TAutoRegisterPreAnimatedStorageID<FPreAnimatedCameraShakeStateStorage> FPreAnimatedCameraShakeStateStorage::StorageID;
struct FPreAnimatedCameraComponentShakeStateStorage : TPreAnimatedStateStorage_ObjectTraits<FPreAnimatedCameraComponentShakeTraits>
{
static TAutoRegisterPreAnimatedStorageID<FPreAnimatedCameraComponentShakeStateStorage> StorageID;
FPreAnimatedStorageID GetStorageType() const override { return StorageID; }
};
TAutoRegisterPreAnimatedStorageID<FPreAnimatedCameraComponentShakeStateStorage> FPreAnimatedCameraComponentShakeStateStorage::StorageID;
struct FPreAnimatedCameraSourceShakeStateStorage : TPreAnimatedStateStorage_ObjectTraits<FPreAnimatedCameraSourceShakeTraits>
{
static TAutoRegisterPreAnimatedStorageID<FPreAnimatedCameraSourceShakeStateStorage> StorageID;
FPreAnimatedStorageID GetStorageType() const override { return StorageID; }
};
TAutoRegisterPreAnimatedStorageID<FPreAnimatedCameraSourceShakeStateStorage> FPreAnimatedCameraSourceShakeStateStorage::StorageID;
#if WITH_EDITOR
TEntitySystemLinkerExtensionID<FCameraShakePreviewerLinkerExtension> FCameraShakePreviewerLinkerExtension::GetExtensionID()
{
static TEntitySystemLinkerExtensionID<FCameraShakePreviewerLinkerExtension> ID = UMovieSceneEntitySystemLinker::RegisterExtension<FCameraShakePreviewerLinkerExtension>();
return ID;
}
TSharedPtr<FCameraShakePreviewerLinkerExtension> FCameraShakePreviewerLinkerExtension::GetOrCreateExtension(UMovieSceneEntitySystemLinker* Linker)
{
if (FCameraShakePreviewerLinkerExtension* PreviewerExtension = Linker->FindExtension<FCameraShakePreviewerLinkerExtension>())
{
return PreviewerExtension->AsShared();
}
TSharedPtr<FCameraShakePreviewerLinkerExtension> NewPreviewerExtension = MakeShared<FCameraShakePreviewerLinkerExtension>(Linker);
Linker->AddExtension(NewPreviewerExtension.Get());
return NewPreviewerExtension;
}
FCameraShakePreviewerLinkerExtension::FCameraShakePreviewerLinkerExtension(UMovieSceneEntitySystemLinker* Linker)
: TSharedEntitySystemLinkerExtension(Linker)
{
}
FCameraShakePreviewerLinkerExtension::~FCameraShakePreviewerLinkerExtension()
{
if (GEditor != nullptr)
{
GEditor->OnLevelViewportClientListChanged().RemoveAll(this);
}
for (TPair<FInstanceHandle, TSharedRef<FCameraShakePreviewer>>& Pair : Previewers)
{
TSharedRef<FCameraShakePreviewer> Previewer = Pair.Value;
Previewer->UnRegisterViewModifiers();
}
Previewers.Reset();
}
TSharedPtr<FCameraShakePreviewer> FCameraShakePreviewerLinkerExtension::FindPreviewer(FInstanceHandle InstanceHandle)
{
if (TSharedRef<FCameraShakePreviewer>* FoundItem = Previewers.Find(InstanceHandle))
{
return FoundItem->ToSharedPtr();
}
return TSharedPtr<FCameraShakePreviewer>();
}
TSharedRef<FCameraShakePreviewer> FCameraShakePreviewerLinkerExtension::GetPreviewer(FInstanceHandle InstanceHandle)
{
if (TSharedRef<FCameraShakePreviewer>* Previewer = Previewers.Find(InstanceHandle))
{
return *Previewer;
}
if (Previewers.IsEmpty() && GEditor != nullptr)
{
// This is our first previewer... let's start listening to viewports changing.
GEditor->OnLevelViewportClientListChanged().AddSP(this, &FCameraShakePreviewerLinkerExtension::OnLevelViewportClientListChanged);
}
UMovieSceneEntitySystemLinker* Linker = WeakLinker.Get();
FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry();
const FSequenceInstance& SequenceInstance = InstanceRegistry->GetInstance(InstanceHandle);
UObject* PlaybackContext = SequenceInstance.GetSharedPlaybackState()->GetPlaybackContext();
UWorld* ContextWorld = PlaybackContext ? PlaybackContext->GetWorld() : nullptr;
TSharedRef<FCameraShakePreviewer> NewPreviewer = MakeShared<FCameraShakePreviewer>(ContextWorld);
NewPreviewer->RegisterViewModifiers([ContextWorld](FLevelEditorViewportClient* LevelVC) -> bool
{
return LevelVC->AllowsCinematicControl() && LevelVC->GetWorld() == ContextWorld;
},
// Pass false to help the Mac compiler along.
false);
Previewers.Add(InstanceHandle, NewPreviewer);
return NewPreviewer;
}
void FCameraShakePreviewerLinkerExtension::UpdateAllPreviewers()
{
UMovieSceneEntitySystemLinker* Linker = WeakLinker.Get();
FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry();
const TSparseArray<FSequenceInstance>& Instances = InstanceRegistry->GetSparseInstances();
for (auto It = Instances.CreateConstIterator(); It; ++It)
{
FInstanceHandle InstanceHandle = It->GetInstanceHandle();
if (TSharedRef<FCameraShakePreviewer>* FoundItem = Previewers.Find(InstanceHandle))
{
TSharedRef<FCameraShakePreviewer> Previewer(*FoundItem);
const FMovieSceneContext& Context = It->GetContext();
const float DeltaTime = Context.GetFrameRate().AsSeconds(Context.GetDelta());
if (DeltaTime > 0.f)
{
const bool bIsPlaying = Context.GetStatus() == EMovieScenePlayerStatus::Playing;
Previewer->Update(DeltaTime, bIsPlaying);
}
else
{
const float ScrubTime = Context.GetFrameRate().AsSeconds(Context.GetTime());
Previewer->Scrub(ScrubTime);
}
}
}
}
bool FCameraShakePreviewerLinkerExtension::HasAnyShake() const
{
TArray<FActiveCameraShakeInfo> TempCameraShakes;
for (const TPair<FInstanceHandle, TSharedRef<FCameraShakePreviewer>>& Pair : Previewers)
{
TSharedRef<FCameraShakePreviewer> Previewer(Pair.Value);
if (Previewer->NumActiveCameraShakes() > 0)
{
return true;
}
}
return false;
}
void FCameraShakePreviewerLinkerExtension::OnLevelViewportClientListChanged()
{
// When viewports change, our shake previewers already correctly unregister from any removed viewport.
// However, we need to automatically register any *new* viewport that fits our requirements.
UMovieSceneEntitySystemLinker* Linker = WeakLinker.Get();
FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry();
for (TPair<FInstanceHandle, TSharedRef<FCameraShakePreviewer>> Pair : Previewers)
{
const FSequenceInstance& SequenceInstance = InstanceRegistry->GetInstance(Pair.Key);
UObject* PlaybackContext = SequenceInstance.GetSharedPlaybackState()->GetPlaybackContext();
UWorld* ContextWorld = PlaybackContext ? PlaybackContext->GetWorld() : nullptr;
TSharedRef<FCameraShakePreviewer> Previewer(Pair.Value);
Previewer->RegisterViewModifiers(
[ContextWorld](FLevelEditorViewportClient* LevelVC)
{
return LevelVC->AllowsCinematicControl() && LevelVC->GetWorld() == ContextWorld;
},
// Ignore duplicate registrations.
true);
}
}
#endif // WITH_EDITOR
} // namespace UE::MovieScene
UMovieSceneCameraShakeInstantiatorSystem::UMovieSceneCameraShakeInstantiatorSystem(const FObjectInitializer& ObjInit)
: Super(ObjInit)
{
using namespace UE::MovieScene;
RelevantComponent = FMovieSceneTracksComponentTypes::Get()->CameraShake;
Phase = ESystemPhase::Instantiation;
if (HasAnyFlags(RF_ClassDefaultObject))
{
const FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get();
DefineComponentConsumer(GetClass(), BuiltInComponents->BoundObject);
// Make sure our shakes aren't stopped/restored before we get a chance to transfer
// them to a re-imported entity.
DefineImplicitPrerequisite(GetClass(), UMovieSceneRestorePreAnimatedStateSystem::StaticClass());
}
}
bool UMovieSceneCameraShakeInstantiatorSystem::IsRelevantImpl(UMovieSceneEntitySystemLinker* InLinker) const
{
return TriggersByInstance.Num() > 0;
}
void UMovieSceneCameraShakeInstantiatorSystem::OnLink()
{
using namespace UE::MovieScene;
PreAnimatedCameraShakeStorage = Linker->PreAnimatedState.GetOrCreateStorage<FPreAnimatedCameraShakeStateStorage>();
PreAnimatedCameraComponentShakeStorage = Linker->PreAnimatedState.GetOrCreateStorage<FPreAnimatedCameraComponentShakeStateStorage>();
PreAnimatedCameraSourceShakeStorage = Linker->PreAnimatedState.GetOrCreateStorage<FPreAnimatedCameraSourceShakeStateStorage>();
#if WITH_EDITOR
if (GEditor != nullptr)
{
// We only need the previewer extension if there's an actual editor.
PreviewerExtension = FCameraShakePreviewerLinkerExtension::GetOrCreateExtension(Linker);
}
// Handle camera shakes being recompiled.
FCoreUObjectDelegates::OnObjectsReplaced.AddUObject(this, &UMovieSceneCameraShakeInstantiatorSystem::OnObjectsReplaced);
#endif // WITH_EDITOR
}
void UMovieSceneCameraShakeInstantiatorSystem::OnUnlink()
{
using namespace UE::MovieScene;
#if WITH_EDITOR
FCoreUObjectDelegates::OnObjectsReplaced.RemoveAll(this);
// Only the two camera shake systems hold pointers to the extension, so it should delete itself
// once both systems are unlinked.
PreviewerExtension = nullptr;
#endif // WITH_EDITOR
if (!ensure(TriggersByInstance.Num() == 0))
{
TriggersByInstance.Reset();
}
}
void UMovieSceneCameraShakeInstantiatorSystem::OnRun(FSystemTaskPrerequisites& InPrerequisites, FSystemSubsequentTasks& Subsequents)
{
using namespace UE::MovieScene;
const FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get();
const FMovieSceneTracksComponentTypes* TrackComponents = FMovieSceneTracksComponentTypes::Get();
FEntityManager& EntityManager = Linker->EntityManager;
FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry();
// Create camera shake instances for new shakes, and start them.
auto VisitNewShakes = [this, BuiltInComponents, &EntityManager, InstanceRegistry](
FMovieSceneEntityID EntityID,
FInstanceHandle InstanceHandle,
UObject* BoundObject,
const FMovieSceneCameraShakeComponentData& ShakeData,
FMovieSceneCameraShakeInstanceData& ShakeInstanceData)
{
const FSequenceInstance& Instance = InstanceRegistry->GetInstance(InstanceHandle);
const FMovieSceneContext& Context = Instance.GetContext();
UObject* PlaybackContext = Instance.GetSharedPlaybackState()->GetPlaybackContext();
TSubclassOf<UCameraShakeBase> ShakeClass = ShakeData.SectionData.ShakeClass;
UCameraShakeSourceComponent* ShakeSourceComponent = Cast<UCameraShakeSourceComponent>(BoundObject);
if (ShakeClass.Get() == nullptr)
{
if (ShakeSourceComponent)
{
ShakeClass = ShakeSourceComponent->CameraShake;
}
}
if (ShakeClass.Get() == nullptr)
{
return;
}
TOptional<float> DurationOverride = ComputeCameraShakeDurationOverride(Context, ShakeData);
const bool bWantsRestoreState = EntityManager.HasComponent(EntityID, BuiltInComponents->Tags.RestoreState);
const FRootInstanceHandle RootInstanceHandle = Instance.GetRootInstanceHandle();
FCachePreAnimatedValueParams CacheParams;
// Start playing the shake.
if (ShakeSourceComponent)
{
bool bStartShake = true;
if (ShakeInstanceData.SectionSignature == ShakeData.SectionSignature && ShakeInstanceData.ShakeInstance.Get() != nullptr && ShakeInstanceData.ShakeInstance->IsActive())
{
// Don't re-create and restart the shake if it was already running with the same
// parameters and this instantiation phase is just re-importing other stuff.
bStartShake = false;
}
PreAnimatedCameraSourceShakeStorage->BeginTrackingEntity(EntityID, bWantsRestoreState, RootInstanceHandle, ShakeSourceComponent);
PreAnimatedCameraSourceShakeStorage->CachePreAnimatedValue(CacheParams, ShakeSourceComponent);
if (bStartShake)
{
FCameraShakeSourceComponentStartParams ComponentParams;
ComponentParams.ShakeClass = ShakeClass;
ComponentParams.Scale = ShakeData.SectionData.PlayScale;
ComponentParams.PlaySpace = ShakeData.SectionData.PlaySpace;
ComponentParams.UserPlaySpaceRot = ShakeData.SectionData.UserDefinedPlaySpace;
ComponentParams.DurationOverride = DurationOverride;
ShakeSourceComponent->StartCameraShake(ComponentParams);
}
ShakeInstanceData.SectionSignature = ShakeData.SectionSignature;
#if WITH_EDITOR
if (PreviewerExtension && bStartShake)
{
// Shake source components start shakes in the world, unlike the other shakes
// (in the `else` clause) who directly affect the bound camera. This means that
// the shake we have just started won't affect the Sequencer preview unless we
// add some shaking ourselves. Let's do that here.
// In fact, in the editor, the above StartCameraShake call generally does nothing
// since there is no player controller.
TSharedRef<FCameraShakePreviewer> Previewer = PreviewerExtension->GetPreviewer(InstanceHandle);
FCameraShakePreviewerAddParams PreviewParams;
PreviewParams.ShakeClass = ShakeClass;
PreviewParams.GlobalStartTime = Context.GetFrameRate().AsSeconds(ShakeData.SectionStartTime);
PreviewParams.SourceComponent = ShakeSourceComponent;
PreviewParams.Scale = ShakeData.SectionData.PlayScale;
PreviewParams.PlaySpace = ShakeData.SectionData.PlaySpace;
PreviewParams.UserPlaySpaceRot = ShakeData.SectionData.UserDefinedPlaySpace;
PreviewParams.DurationOverride = DurationOverride;
// Stop any previous version of this shake.
UCameraShakeBase* OldShakeInstance = ShakeInstanceData.ShakeInstance;
if (OldShakeInstance)
{
Previewer->RemoveCameraShake(OldShakeInstance);
}
ShakeInstanceData.ShakeInstance = Previewer->AddCameraShake(PreviewParams);
ShakeInstanceData.bManagedByPreviewer = true;
}
#endif // WITH_EDITOR
}
else if (UCameraComponent* CameraComponent = MovieSceneHelpers::CameraComponentFromRuntimeObject(BoundObject))
{
bool bStartInstance = true;
UCameraShakeBase* ShakeInstance = nullptr;
if (ShakeInstanceData.SectionSignature == ShakeData.SectionSignature && ShakeInstanceData.ShakeInstance.Get() != nullptr && ShakeInstanceData.ShakeInstance->IsActive())
{
// Don't re-create and restart the shake if it was already running with the same
// parameters and this instantiation phase is just re-importing other stuff.
bStartInstance = false;
ShakeInstance = ShakeInstanceData.ShakeInstance;
}
else
{
UObject* OuterObject = PlaybackContext ? PlaybackContext : GetTransientPackage();
ShakeInstance = NewObject<UCameraShakeBase>(OuterObject, ShakeClass);
}
PreAnimatedCameraShakeStorage->BeginTrackingEntity(EntityID, bWantsRestoreState, RootInstanceHandle, ShakeInstance);
PreAnimatedCameraShakeStorage->CachePreAnimatedValue(CacheParams, ShakeInstance);
PreAnimatedCameraComponentShakeStorage->BeginTrackingEntity(EntityID, bWantsRestoreState, RootInstanceHandle, CameraComponent);
PreAnimatedCameraComponentShakeStorage->CachePreAnimatedValue(CacheParams, CameraComponent);
ShakeInstanceData.ShakeInstance = ShakeInstance;
ShakeInstanceData.SectionSignature = ShakeData.SectionSignature;
ShakeInstanceData.bManagedByPreviewer = false;
if (bStartInstance)
{
FCameraShakeBaseStartParams ShakeParams;
ShakeParams.Scale = ShakeData.SectionData.PlayScale;
ShakeParams.PlaySpace = ShakeData.SectionData.PlaySpace;
ShakeParams.UserPlaySpaceRot = ShakeData.SectionData.UserDefinedPlaySpace;
ShakeParams.DurationOverride = DurationOverride;
ShakeInstance->StartShake(ShakeParams);
}
}
};
FEntityTaskBuilder()
.ReadEntityIDs()
.Read(BuiltInComponents->InstanceHandle)
.Read(BuiltInComponents->BoundObject)
.Read(TrackComponents->CameraShake)
.Write(TrackComponents->CameraShakeInstance)
.FilterAll({ BuiltInComponents->Tags.NeedsLink })
.Iterate_PerEntity(&Linker->EntityManager, VisitNewShakes);
// We don't need to go over expired shakes from NeedsUnlink entities. Either these shakes
// will be stopped be the pre-animated state, and then their component data will be freed
// when the entities are deleted, or they come from KeepState sections and will therefore
// continue running inside the camera manager.
// Now trigger any one-shot shakes.
if (TriggersByInstance.Num() > 0)
{
TriggerOneShotShakes();
}
}
void UMovieSceneCameraShakeInstantiatorSystem::AddShakeTrigger(UE::MovieScene::FInstanceHandle InInstance, const FGuid& ObjectBindingID, const FFrameTime& InTime, const FMovieSceneCameraShakeSourceTrigger& InTrigger)
{
TriggersByInstance.FindOrAdd(InInstance).Add(FTimedTrigger{ ObjectBindingID, InTime, InTrigger });
}
void UMovieSceneCameraShakeInstantiatorSystem::TriggerOneShotShakes()
{
using namespace UE::MovieScene;
FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry();
for (TPair<FInstanceHandle, TArray<FTimedTrigger>>& Pair : TriggersByInstance)
{
const FSequenceInstance& Instance = InstanceRegistry->GetInstance(Pair.Key);
TSharedRef<const FSharedPlaybackState> SharedPlaybackState = Instance.GetSharedPlaybackState();
const FMovieSceneContext& Context = Instance.GetContext();
if (Context.GetDirection() != EPlayDirection::Forwards)
{
return;
}
for (const FTimedTrigger& Trigger : Pair.Value)
{
TArray<UCameraShakeSourceComponent*> ShakeSourceComponents;
if (Trigger.ObjectBindingID.IsValid())
{
TArrayView<TWeakObjectPtr<>> BoundObjects = SharedPlaybackState->FindBoundObjects(Trigger.ObjectBindingID, Instance.GetSequenceID());
for (TWeakObjectPtr<> WeakBoundObject : BoundObjects)
{
if (UObject* BoundObject = WeakBoundObject.Get())
{
if (UCameraShakeSourceComponent* ShakeSourceComponent = Cast<UCameraShakeSourceComponent>(BoundObject))
{
ShakeSourceComponents.Add(ShakeSourceComponent);
}
}
}
}
const float TriggerTime = Context.GetFrameRate().AsSeconds(Trigger.Time);
for (UCameraShakeSourceComponent* ShakeSourceComponent : ShakeSourceComponents)
{
TSubclassOf<UCameraShakeBase> ShakeClass = Trigger.Trigger.ShakeClass;
if (ShakeClass.Get() == nullptr)
{
ShakeClass = ShakeSourceComponent->CameraShake;
}
if (ShakeClass.Get() != nullptr)
{
// Start playing the shake.
ShakeSourceComponent->StartCameraShake(
ShakeClass,
Trigger.Trigger.PlayScale,
Trigger.Trigger.PlaySpace,
Trigger.Trigger.UserDefinedPlaySpace);
#if WITH_EDITOR
if (PreviewerExtension)
{
// Also start playing the shake in our editor preview.
TSharedRef<FCameraShakePreviewer> Previewer = PreviewerExtension->GetPreviewer(Pair.Key);
FCameraShakePreviewerAddParams PreviewParams;
PreviewParams.ShakeClass = ShakeClass;
PreviewParams.GlobalStartTime = TriggerTime;
PreviewParams.SourceComponent = ShakeSourceComponent;
PreviewParams.Scale = Trigger.Trigger.PlayScale;
PreviewParams.PlaySpace = Trigger.Trigger.PlaySpace;
PreviewParams.UserPlaySpaceRot = Trigger.Trigger.UserDefinedPlaySpace;
Previewer->AddCameraShake(PreviewParams);
}
#endif // WITH_EDITOR
}
}
}
}
#if WITH_EDITOR
if (PreviewerExtension && !TriggersByInstance.IsEmpty())
{
// If we have just started new shakes, we might need to forcibly link the shake evaluator system
// so that it can keep the shake previewer ticking every frame (in case that system isn't linked
// because there are no shake sections active right now).
// Once linked, this system will stay relevant and alive for as long as there are active shakes
// in the previewer.
Linker->LinkSystem<UMovieSceneCameraShakeEvaluatorSystem>();
}
#endif
TriggersByInstance.Empty();
}
#if WITH_EDITOR
void UMovieSceneCameraShakeInstantiatorSystem::OnObjectsReplaced(const TMap<UObject*, UObject*>& ReplacementMap)
{
using namespace UE::MovieScene;
const FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get();
const FMovieSceneTracksComponentTypes* TrackComponents = FMovieSceneTracksComponentTypes::Get();
const FInstanceRegistry* InstanceRegistry = Linker->GetInstanceRegistry();
auto ReplaceShakeInstances = [InstanceRegistry, &ReplacementMap](
FInstanceHandle InstanceHandle,
const FMovieSceneCameraShakeComponentData& ShakeData,
FMovieSceneCameraShakeInstanceData& ShakeInstanceData)
{
if (UObject* const * NewShakeInstance = ReplacementMap.Find(ShakeInstanceData.ShakeInstance))
{
ShakeInstanceData.ShakeInstance = Cast<UCameraShakeBase>(*NewShakeInstance);
// Restart the new shake instance, unless it's already managed by a previewer which
// will do this already.
if (ensure(ShakeInstanceData.ShakeInstance) && !ShakeInstanceData.bManagedByPreviewer)
{
const FMovieSceneContext& Context = InstanceRegistry->GetInstance(InstanceHandle).GetContext();
TOptional<float> DurationOverride = ComputeCameraShakeDurationOverride(Context, ShakeData);
FCameraShakeBaseStartParams ShakeParams;
ShakeParams.Scale = ShakeData.SectionData.PlayScale;
ShakeParams.PlaySpace = ShakeData.SectionData.PlaySpace;
ShakeParams.UserPlaySpaceRot = ShakeData.SectionData.UserDefinedPlaySpace;
ShakeParams.DurationOverride = DurationOverride;
ShakeInstanceData.ShakeInstance->StartShake(ShakeParams);
}
}
};
FEntityTaskBuilder()
.Read(BuiltInComponents->InstanceHandle)
.Read(TrackComponents->CameraShake)
.Write(TrackComponents->CameraShakeInstance)
.Iterate_PerEntity(&Linker->EntityManager, ReplaceShakeInstances);
}
#endif
namespace UE::MovieScene
{
struct FAccumulatedShake
{
void AccumulateOffset(const FTransform& InTransformOffset, float InFOVOffset)
{
TotalTransformOffset = TotalTransformOffset * InTransformOffset;
TotalFOVOffset += InFOVOffset;
bApplyTransform = true;
}
void AccumulatePostProcessing(const FPostProcessSettings& InPostProcessSettings, float InWeight)
{
PostProcessSettings.Add({ InPostProcessSettings, InWeight });
bApplyPostProcessing = true;
}
void Apply(UCameraComponent* CameraComponent) const
{
if (bApplyTransform)
{
CameraComponent->ClearAdditiveOffset();
CameraComponent->AddAdditiveOffset(TotalTransformOffset, TotalFOVOffset);
}
if (bApplyPostProcessing)
{
CameraComponent->ClearExtraPostProcessBlends();
for (const TPair<FPostProcessSettings, float>& Pair : PostProcessSettings)
{
CameraComponent->AddExtraPostProcessBlend(Pair.Key, Pair.Value);
}
}
}
private:
bool bApplyTransform = false;
bool bApplyPostProcessing = false;
FTransform TotalTransformOffset;
float TotalFOVOffset = 0.f;
TArray<TTuple<FPostProcessSettings, float>, TInlineAllocator<2>> PostProcessSettings;
};
struct FEvaluateCameraShake
{
const FInstanceRegistry* InstanceRegistry;
TMap<UCameraComponent*, FAccumulatedShake> AccumulatedShakes;
FEvaluateCameraShake(UMovieSceneEntitySystemLinker* InLinker)
: InstanceRegistry(InLinker->GetInstanceRegistry())
{
}
void ForEachAllocation(
const FEntityAllocation* Allocation,
TRead<FInstanceHandle> InstanceHandles,
TRead<UObject*> BoundObjects,
TRead<FMovieSceneCameraShakeComponentData> ShakeComponents,
TRead<FMovieSceneCameraShakeInstanceData> ShakeInstances)
{
const int32 Num = Allocation->Num();
for (int32 Index = 0; Index < Num; ++Index)
{
FInstanceHandle InstanceHandle = InstanceHandles[Index];
const FSequenceInstance& Instance = InstanceRegistry->GetInstance(InstanceHandle);
// Shakes should have been started by the instantiator system.
//
// We don't need to evaluate source components' camera shakes here, as they are ticking
// along by themselves in both the player camera manager (in the game) and the camera shake
// previewer (in the editor). We do however need to tick the camera shakes running directly
// onto camera component bindings.
const FMovieSceneCameraShakeComponentData& ShakeData = ShakeComponents[Index];
const FMovieSceneCameraShakeInstanceData& ShakeInstance = ShakeInstances[Index];
if (UCameraComponent* CameraComponent = MovieSceneHelpers::CameraComponentFromRuntimeObject(BoundObjects[Index]))
{
EvaluateCameraComponentShake(CameraComponent, ShakeData, ShakeInstance, Instance);
}
}
}
void EvaluateCameraComponentShake(
UCameraComponent* CameraComponent,
const FMovieSceneCameraShakeComponentData& ShakeData,
const FMovieSceneCameraShakeInstanceData& ShakeInstance,
const FSequenceInstance& Instance)
{
FMinimalViewInfo POV;
POV.Location = CameraComponent->GetComponentLocation();
POV.Rotation = CameraComponent->GetComponentRotation();
POV.FOV = CameraComponent->FieldOfView;
// Update shake to the new time.
const FMovieSceneContext& Context = Instance.GetContext();
const FFrameTime NewShakeTime = Context.GetTime() - ShakeData.SectionStartTime;
ShakeInstance.ShakeInstance->ScrubAndApplyCameraShake(NewShakeTime / Context.GetFrameRate(), 1.f, POV);
// Grab transform and FOV changes.
FTransform WorldToBaseCamera = CameraComponent->GetComponentToWorld().Inverse();
float BaseFOV = CameraComponent->FieldOfView;
FTransform NewCameraToWorld(POV.Rotation, POV.Location);
float NewFOV = POV.FOV;
FTransform NewCameraToBaseCamera = NewCameraToWorld * WorldToBaseCamera;
float NewFOVToBaseFOV = BaseFOV - NewFOV;
{
// Accumumulate the offsets into the track data for application as part of the track execution token
FAccumulatedShake& AccumulatedShake = AccumulatedShakes.FindOrAdd(CameraComponent);
AccumulatedShake.AccumulateOffset(NewCameraToBaseCamera, NewFOVToBaseFOV);
}
// Grab post process changes.
if (POV.PostProcessBlendWeight > 0.f)
{
FAccumulatedShake& AccumulatedShake = AccumulatedShakes.FindOrAdd(CameraComponent);
AccumulatedShake.AccumulatePostProcessing(POV.PostProcessSettings, POV.PostProcessBlendWeight);
}
}
void PostTask()
{
// Apply accumulated shakes.
for (const TPair<UCameraComponent*, FAccumulatedShake>& Pair : AccumulatedShakes)
{
Pair.Value.Apply(Pair.Key);
}
}
};
} // namespace UE::MovieScene
UMovieSceneCameraShakeEvaluatorSystem::UMovieSceneCameraShakeEvaluatorSystem(const FObjectInitializer& ObjInit)
: Super(ObjInit)
{
using namespace UE::MovieScene;
RelevantComponent = FMovieSceneTracksComponentTypes::Get()->CameraShake;
if (HasAnyFlags(RF_ClassDefaultObject))
{
}
}
bool UMovieSceneCameraShakeEvaluatorSystem::IsRelevantImpl(UMovieSceneEntitySystemLinker* InLinker) const
{
using namespace UE::MovieScene;
#if WITH_EDITOR
if (FCameraShakePreviewerLinkerExtension* Extension = InLinker->FindExtension<FCameraShakePreviewerLinkerExtension>())
{
return Extension->HasAnyShake();
}
#endif // WITH_EDITOR
return false;
}
void UMovieSceneCameraShakeEvaluatorSystem::OnLink()
{
using namespace UE::MovieScene;
#if WITH_EDITOR
PreviewerExtension = FCameraShakePreviewerLinkerExtension::GetOrCreateExtension(Linker);
#endif // WITH_EDITOR
}
void UMovieSceneCameraShakeEvaluatorSystem::OnUnlink()
{
using namespace UE::MovieScene;
#if WITH_EDITOR
// Only the two camera shake systems hold pointers to the extension, so it should delete itself
// once both systems are unlinked.
PreviewerExtension = nullptr;
#endif // WITH_EDITOR
}
void UMovieSceneCameraShakeEvaluatorSystem::OnRun(FSystemTaskPrerequisites& InPrerequisites, FSystemSubsequentTasks& Subsequents)
{
using namespace UE::MovieScene;
const FBuiltInComponentTypes* BuiltInComponents = FBuiltInComponentTypes::Get();
const FMovieSceneTracksComponentTypes* TrackComponents = FMovieSceneTracksComponentTypes::Get();
FEntityTaskBuilder()
.Read(BuiltInComponents->InstanceHandle)
.Read(BuiltInComponents->BoundObject)
.Read(TrackComponents->CameraShake)
.Read(TrackComponents->CameraShakeInstance)
.SetDesiredThread(Linker->EntityManager.GetGatherThread())
.Dispatch_PerAllocation<FEvaluateCameraShake>(
&Linker->EntityManager, InPrerequisites, &Subsequents, Linker);
#if WITH_EDITOR
if (PreviewerExtension)
{
// The previewer only stores the delta time, and only computes shake results when the editor
// later processes viewports. We can therefore safely do this in parallel with the shake
// evaluation above.
PreviewerExtension->UpdateAllPreviewers();
}
#endif // WITH_EDITOR
}