Files
UnrealEngineUWP/Engine/Source/Runtime/LevelSequence/Private/LevelSequencePlayer.cpp
Max Chen 39a92c1fa2 Copying //UE4/Dev-Sequencer to //UE4/Main
==========================
MAJOR FEATURES + CHANGES
==========================

Change 2859626 on 2016/02/08 by Max.Preussner

	Editor: Added SaveAs functionality to content asset editors

Change 2859666 on 2016/02/08 by Max.Chen

	Sequencer: Fix crash in CheckForWorldGCLeaks when loading a new map because spawnables are left behind.

	#jira  UE-25616

Change 2859685 on 2016/02/08 by Max.Chen

	Sequencer: Add prompt to save sub level sequences if they are dirty

	#jira UE-26510

Change 2859715 on 2016/02/08 by Thomas.Sarkanen

	Adding actor spawning recording

	Actors are queued for record on spawn then added to the list like manually-specifed ones.
	Changed almost everything about UActorRecording. We now record on a per-component basis, with property tracks encapsulated in each actor recording. Much effort is expended to make sure that the correct components are owned by their respective actors, as we can add and remove components at runtime, but they must be created up-front in the UMovieScene Blueprints. We go as far as to add our own SCS nodes to make sure components are correctly spawned.
	Fixed infinite loop in FSequencer::AddSpawnable.
	Fixed visibility track instance to work with scene components as well as actors.
	Fixed particle track instance to work on UParticleSystemComponent rather than just AEmitters.
	Added particle recorder.
	Moved animation recording into an animation property recorder rather than having it as a special case. This still uses the animation recorder under the hood.
	Moved old-style Matinee animation control into FMovieSceneSkeletalAnimationTrackInstance & made this work on USkeletalMeshComponents directly, rather than via the old interface.
	Exposed SetMatineeAnimPositionInner and PreviewMatineeSetAnimPositionInner in FAnimMontageInstance so those utility functions can be used externally to Engine.
	Added a predicate version of UMovieScene::FindPossessable.
	Exposed UMovieSceneParticleSection::AddKey externally via MOVIESCENETRACKS_API so I can programmatically add keys.
	Fixed a crash in FScalableFloatDetails::CustomizeHeader when selecting PIE projectiles in Orion.
	Moved all recorders over to recording Actors or Components & store UObjects instead of AActors.
	Allowed skeletal animation tracks on components as well as actors.

Change 2862675 on 2016/02/10 by Max.Chen

	Sequencer: Add option to link the sequencer curve editor with the sequencer timeline.

	Under General Options->Link Curve Editor Time Range. The default is false, so the sequencer and curve editor have separate time ranges.

	#jira UE-25933

Change 2862699 on 2016/02/11 by Max.Chen

	Sequencer: Added a playback status of jumping which the AudioTrack and Skeletal Mesh Track (anim notifies) ignores for updates. This is used to updating thumbnail at certain times.

	#jira UE-26447, UE-26671

Change 2862712 on 2016/02/11 by Max.Chen

	Sequencer: Fix spawnables firing off their particles. Disable auto activate on spawnable components

	#jira UE-26390

Change 2862719 on 2016/02/11 by Max.Preussner

	Editor: Refactored detail customizations for colors, rotators, vectors

	- broke color and rotator customizations out into their own files
	- added vector customizations (placeholder)
	- cleaned up localization namespaces, forward declarations

Change 2866454 on 2016/02/14 by Max.Preussner

	Sequencer: Removed ULevelEditorSequencerSettings; moved default settings into INI

Change 2866455 on 2016/02/14 by Thomas.Sarkanen

	Sequence recorder can now record replays

	Added extra edtior-only UI to the replay playback controls to record sequences. Curretnly very placeholder: only records the entire sequence and provides no feedback in the UI if it is recording.
	Fixed bindings to recorded objects not working in various circumstances. Added the ability to manually create a binding.
	Recompiled actor blueprints post-record if we added components.
	Fixed a null ptr dereference in FOrionTeamUIInfo::Update.
	Removed tolerances when reducing tracks - they are now 'very small'.
	Added actor filter so actors of certain classes can be recorded.

Change 2866458 on 2016/02/14 by Max.Chen

	Sequencer: Fix anim notifies that fire at shot cuts. Anim notifies are fired from the last position to the current position. When jumping cuts, we want the delta to be 0 so that the anim notifies before the shot are not fired off.

	#jira UE-26390, UE-26671

Change 2866459 on 2016/02/14 by Max.Chen

	Sequencer: Add option to toggle visibility of combined keys

Change 2866466 on 2016/02/14 by Frank.Fella

	Sequencer - Add a track for controlling streamed level visibilty and remove visibility code from the master level blueprint.

Change 2866470 on 2016/02/14 by Max.Chen

	Sequencer: Add return value to indicate data has changed when a section has been added. This fixes a bug where creating a new section doesn't seem to add a key.

	#jira UE-26837

Change 2866481 on 2016/02/14 by Max.Preussner

	Sequencer: Implemented Presets for adding tracks automatically based on actor type (UE-24513)

	#Jira: UE-24513

Change 2866482 on 2016/02/14 by Max.Chen

	Sequencer: Allow for any actor that has a camera component to be a camera cut.

	#jira UE-26777

Change 2866484 on 2016/02/14 by Thomas.Sarkanen

	Added in/out times to sequence recording

	Also added the optional ability to record different actor types (heroes, projectiles, minions).

Change 2866495 on 2016/02/14 by Max.Chen

	Sequencer: Need to limit camera control to the section bounds of the camera cut otherwise, control won't be relinquished back to player at the end of the playback.

	#jira UE-26886

[CL 2874647 by Max Chen in Main branch]
2016-02-19 21:36:27 -05:00

357 lines
9.0 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "LevelSequencePCH.h"
#include "LevelSequencePlayer.h"
#include "MovieScene.h"
#include "MovieSceneSequence.h"
#include "MovieSceneSequenceInstance.h"
#include "LevelSequenceSpawnRegister.h"
struct FTickAnimationPlayers : public FTickableGameObject
{
TArray<TWeakObjectPtr<ULevelSequencePlayer>> ActiveInstances;
virtual bool IsTickable() const override
{
return ActiveInstances.Num() != 0;
}
virtual TStatId GetStatId() const override
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FTickAnimationPlayers, STATGROUP_Tickables);
}
virtual void Tick(float DeltaSeconds) override
{
for (int32 Index = 0; Index < ActiveInstances.Num();)
{
if (auto* Player = ActiveInstances[Index].Get())
{
Player->Update(DeltaSeconds);
++Index;
}
else
{
ActiveInstances.RemoveAt(Index, 1, false);
}
}
}
};
struct FAutoDestroyAnimationTicker
{
FAutoDestroyAnimationTicker()
{
FCoreDelegates::OnPreExit.AddLambda([&]{
Impl.Reset();
});
}
void Add(ULevelSequencePlayer* Player)
{
if (!Impl.IsValid())
{
Impl.Reset(new FTickAnimationPlayers);
}
Impl->ActiveInstances.Add(Player);
}
TUniquePtr<FTickAnimationPlayers> Impl;
} GAnimationPlayerTicker;
/* ULevelSequencePlayer structors
*****************************************************************************/
ULevelSequencePlayer::ULevelSequencePlayer(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, LevelSequence(nullptr)
, bIsPlaying(false)
, TimeCursorPosition(0.0f)
, StartTime(0.f)
, EndTime(0.f)
, CurrentNumLoops(0)
, bHasCleanedUpSequence(false)
{
SpawnRegister = MakeShareable(new FLevelSequenceSpawnRegister);
}
/* ULevelSequencePlayer interface
*****************************************************************************/
ULevelSequencePlayer* ULevelSequencePlayer::CreateLevelSequencePlayer(UObject* WorldContextObject, ULevelSequence* InLevelSequence, FLevelSequencePlaybackSettings Settings)
{
if (InLevelSequence == nullptr)
{
return nullptr;
}
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject);
check(World != nullptr);
ULevelSequencePlayer* NewPlayer = NewObject<ULevelSequencePlayer>(GetTransientPackage(), NAME_None, RF_Transient);
check(NewPlayer != nullptr);
NewPlayer->Initialize(InLevelSequence, World, Settings);
// Automatically tick this player
GAnimationPlayerTicker.Add(NewPlayer);
return NewPlayer;
}
bool ULevelSequencePlayer::IsPlaying() const
{
return bIsPlaying;
}
void ULevelSequencePlayer::Pause()
{
bIsPlaying = false;
}
void ULevelSequencePlayer::Stop()
{
bIsPlaying = false;
TimeCursorPosition = PlaybackSettings.PlayRate < 0.f ? GetLength() : 0.f;
CurrentNumLoops = 0;
// todo: Trigger an event?
}
void ULevelSequencePlayer::Play()
{
if ((LevelSequence == nullptr) || !World.IsValid())
{
return;
}
// @todo Sequencer playback: Should we recreate the instance every time?
// We must not recreate the instance since it holds stateful information (such as which objects it has spawned). Recreating the instance would break any
if (!RootMovieSceneInstance.IsValid())
{
RootMovieSceneInstance = MakeShareable(new FMovieSceneSequenceInstance(*LevelSequence));
RootMovieSceneInstance->RefreshInstance(*this);
}
bIsPlaying = true;
bHasCleanedUpSequence = false;
UpdateMovieSceneInstance(TimeCursorPosition, TimeCursorPosition);
}
void ULevelSequencePlayer::PlayLooping(int32 NumLoops)
{
PlaybackSettings.LoopCount = NumLoops;
Play();
}
float ULevelSequencePlayer::GetPlaybackPosition() const
{
return TimeCursorPosition;
}
void ULevelSequencePlayer::SetPlaybackPosition(float NewPlaybackPosition)
{
float LastTimePosition = TimeCursorPosition;
TimeCursorPosition = NewPlaybackPosition;
OnCursorPositionChanged();
UpdateMovieSceneInstance(TimeCursorPosition, LastTimePosition);
}
float ULevelSequencePlayer::GetLength() const
{
return EndTime - StartTime;
}
float ULevelSequencePlayer::GetPlayRate() const
{
return PlaybackSettings.PlayRate;
}
void ULevelSequencePlayer::SetPlayRate(float PlayRate)
{
PlaybackSettings.PlayRate = PlayRate;
}
void ULevelSequencePlayer::SetPlaybackRange( const float NewStartTime, const float NewEndTime )
{
StartTime = NewStartTime;
EndTime = FMath::Max(NewEndTime, StartTime);
TimeCursorPosition = FMath::Clamp(TimeCursorPosition, 0.f, GetLength());
}
void ULevelSequencePlayer::OnCursorPositionChanged()
{
float Length = GetLength();
// Handle looping or stopping
if (TimeCursorPosition >= Length || TimeCursorPosition < 0)
{
if (PlaybackSettings.LoopCount < 0 || CurrentNumLoops < PlaybackSettings.LoopCount)
{
++CurrentNumLoops;
const float Overplay = FMath::Fmod(TimeCursorPosition, Length);
TimeCursorPosition = Overplay < 0 ? Length + Overplay : Overplay;
SpawnRegister->ForgetExternallyOwnedSpawnedObjects(*this);
}
else
{
// Stop playing without modifying the playback position
// @todo: trigger an event?
bIsPlaying = false;
CurrentNumLoops = 0;
}
}
}
/* ULevelSequencePlayer implementation
*****************************************************************************/
void ULevelSequencePlayer::Initialize(ULevelSequence* InLevelSequence, UWorld* InWorld, const FLevelSequencePlaybackSettings& Settings)
{
LevelSequence = InLevelSequence;
World = InWorld;
PlaybackSettings = Settings;
if (UMovieScene* MovieScene = LevelSequence->GetMovieScene())
{
TRange<float> PlaybackRange = MovieScene->GetPlaybackRange();
SetPlaybackRange(PlaybackRange.GetLowerBoundValue(), PlaybackRange.GetUpperBoundValue());
}
// Ensure everything is set up, ready for playback
Stop();
}
/* IMovieScenePlayer interface
*****************************************************************************/
void ULevelSequencePlayer::GetRuntimeObjects(TSharedRef<FMovieSceneSequenceInstance> MovieSceneInstance, const FGuid& ObjectId, TArray<UObject*>& OutObjects) const
{
UObject* FoundObject = MovieSceneInstance->FindObject(ObjectId, *this);
if (FoundObject)
{
OutObjects.Add(FoundObject);
}
}
void ULevelSequencePlayer::UpdateCameraCut(UObject* CameraObject, UObject* UnlockIfCameraObject) const
{
// skip missing player controller
APlayerController* PC = World->GetGameInstance()->GetFirstLocalPlayerController();
if (PC == nullptr)
{
return;
}
// skip same view target
AActor* ViewTarget = PC->GetViewTarget();
if (CameraObject == ViewTarget)
{
return;
}
// skip unlocking if the current view target differs
ACameraActor* UnlockIfCameraActor = Cast<ACameraActor>(UnlockIfCameraObject);
if ((CameraObject == nullptr) && (UnlockIfCameraActor != ViewTarget))
{
return;
}
// override the player controller's view target
ACameraActor* CameraActor = Cast<ACameraActor>(CameraObject);
FViewTargetTransitionParams TransitionParams;
PC->SetViewTarget(CameraActor, TransitionParams);
if (PC->PlayerCameraManager)
{
PC->PlayerCameraManager->bClientSimulatingViewTarget = (CameraActor != nullptr);
PC->PlayerCameraManager->bGameCameraCutThisFrame = true;
}
}
void ULevelSequencePlayer::SetViewportSettings(const TMap<FViewportClient*, EMovieSceneViewportParams>& ViewportParamsMap)
{
}
void ULevelSequencePlayer::GetViewportSettings(TMap<FViewportClient*, EMovieSceneViewportParams>& ViewportParamsMap) const
{
}
EMovieScenePlayerStatus::Type ULevelSequencePlayer::GetPlaybackStatus() const
{
return bIsPlaying ? EMovieScenePlayerStatus::Playing : EMovieScenePlayerStatus::Stopped;
}
void ULevelSequencePlayer::SetPlaybackStatus(EMovieScenePlayerStatus::Type InPlaybackStatus)
{
}
void ULevelSequencePlayer::AddOrUpdateMovieSceneInstance(UMovieSceneSection& MovieSceneSection, TSharedRef<FMovieSceneSequenceInstance> InstanceToAdd)
{
}
void ULevelSequencePlayer::RemoveMovieSceneInstance(UMovieSceneSection& MovieSceneSection, TSharedRef<FMovieSceneSequenceInstance> InstanceToRemove)
{
}
TSharedRef<FMovieSceneSequenceInstance> ULevelSequencePlayer::GetRootMovieSceneSequenceInstance() const
{
return RootMovieSceneInstance.ToSharedRef();
}
UObject* ULevelSequencePlayer::GetPlaybackContext() const
{
return World.Get();
}
void ULevelSequencePlayer::Update(const float DeltaSeconds)
{
float LastTimePosition = TimeCursorPosition;
if (bIsPlaying)
{
TimeCursorPosition += DeltaSeconds * PlaybackSettings.PlayRate;
OnCursorPositionChanged();
UpdateMovieSceneInstance(TimeCursorPosition, LastTimePosition);
}
else if (!bHasCleanedUpSequence && TimeCursorPosition >= GetLength())
{
UpdateMovieSceneInstance(TimeCursorPosition, LastTimePosition);
bHasCleanedUpSequence = true;
SpawnRegister->DestroyAllOwnedObjects(*this);
}
}
void ULevelSequencePlayer::UpdateMovieSceneInstance(float CurrentPosition, float PreviousPosition)
{
if(RootMovieSceneInstance.IsValid())
{
EMovieSceneUpdateData UpdateData(CurrentPosition + StartTime, PreviousPosition + StartTime);
RootMovieSceneInstance->Update(UpdateData, *this);
#if WITH_EDITOR
OnLevelSequencePlayerUpdate.Broadcast(*this, CurrentPosition, PreviousPosition);
#endif
}
}
IMovieSceneSpawnRegister& ULevelSequencePlayer::GetSpawnRegister()
{
return *SpawnRegister;
}