Hello,
In our game characters can possess different pawns, and we often change possession behind the scenes during level sequences thus leading to view target changes. In 5.3 this worked just fine, and the view target would be set correctly to the new one after the sequence has ended, probably thanks the code on line 303 in this file https://github.com/EpicGames/UnrealEngine/blob/5\.3/Engine/Source/Runtime/LevelSequence/Private/LevelSequencePlayer.cpp. However, after upgrading to 5.6 this no longer works.
I can see how setting a view target during a level sequence that also sets view targets can be viewed as questionable. So I wonder if this is just a newly introduced bug or this behavior is not supported by design?
Steps to Reproduce
Repro:
- Start a level sequence with camera cuts
- Set a different view target for the camera manager while the sequence is playing
- Wait until the cutscene ends
Expected result:
Camera has the view target that’s been set during the sequence
Actual result:
Camera has the original view target that camera manager had before the level sequence started
Hi! Sorry for the trouble… but yeah, it wasn’t really a supported use-case to change the view target while the Sequencer itself also sets the view target. Things sorted themselves out more by chance than by intent.
The changes in the recent release (and the reason some of the code you mentioned is gone) is because we made all of this use the same system as the rest of the Sequencer for handling “pre-animated values”. That is, we have some basic system for restoring things to what they were before we played (like transforms and other properties and so on). So now the view target uses that same system, and a few unsupported edge cases changed behaviour.
What should happen now is that:
- If you don’t want to return to the pre-cinematic view target, you can set the last Camera Cut section to “Keep State” in its “When Finished” options. That last camera will simply be left as the view target when the Sequencer ends (although of course your Player Camera Manager might do something else).
- If you use “Keep State” with a *spawnable* camera, obviously the camera will be unspawned at the end of the Sequencer by default, but this means the view target will become null and the default behaviour for the Player Camera Manager is then fallback on the current pawn as the view target. So maybe you can also make use of that.
- Similar to the previous point, if the pre-cinematic view target is gone (e.g. it was a previous pawn that has been removed) then Sequencer will set the view target to null and, as mentioned before, the Player Camera Manager will fallback on the current pawn.
Does this help?
Hi!
Thank you very much for the response.
It’s good to know that this behavior was not intended after all.
We ended up making an engine change to bring back the old behavior in order to not remake all the sequences, but we’ll definitely keep this information in mind in the future.
What was the engine change you ended up doing? I may or may not consider doing a little something to help with these use-cases (even though in my opinion this is more of the responsibility of the PlayCameraManager)
Sorry for the late reply, just seen your message.
It’s a bit ugly.
We added a new member variable to FPreAnimatedCameraCutState.
FObjectKey LastCameraObject;Then we had to make a non-const version of the GetCachedValue function because we want to modify that LastCameraObject value during a camera cut. I tried just invalidating the cache and forcing the calling code to create a new cached value but that didn’t work out well, although I don’t remember the details as to why.
StorageType& GetMutableCachedValue(FPreAnimatedStorageIndex StorageIndex)
{
if (ensure(PreAnimatedStorage.IsValidIndex(StorageIndex.Value)))
{
FCachedData& CachedData = PreAnimatedStorage[StorageIndex.Value];
if (CachedData.bInitialized)
{
return CachedData.Value;
}
}
static StorageType Dummy;
return Dummy;
}
And then we replaced this piece of code in MovieSceneCameraCutGameHandler.cpp
if (CameraObject == nullptr)
{
TSharedPtr<FPreAnimatedCameraCutStorage> PreAnimatedStorage = Linker->PreAnimatedState.FindStorage(FPreAnimatedCameraCutStorage::StorageID);
FPreAnimatedStorageIndex StorageIndex = PreAnimatedStorage->FindStorageIndex(0);
if (ensureMsgf(StorageIndex.IsValid(), TEXT("Blending camera back to gameplay but can't find pre-animated camera info!")))
{
FPreAnimatedCameraCutState CachedValue = PreAnimatedStorage->GetCachedValue(StorageIndex);
CameraObject = CachedValue.LastViewTarget.ResolveObjectPtr();
OverrideAspectRatioAxisConstraint = CachedValue.LastAspectRatioAxisConstraint;
}
}
with this one
TSharedPtr<FPreAnimatedCameraCutStorage> PreAnimatedStorage = Linker->PreAnimatedState.FindStorage(FPreAnimatedCameraCutStorage::StorageID);
FPreAnimatedStorageIndex StorageIndex = PreAnimatedStorage->FindStorageIndex(0);
if (ensureMsgf(StorageIndex.IsValid(), TEXT("Blending camera back to gameplay but can't find pre-animated camera info!")))
{
FPreAnimatedCameraCutState& CachedValue = PreAnimatedStorage->GetMutableCachedValue(StorageIndex);
UObject* LastCameraObject = CachedValue.LastCameraObject.ResolveObjectPtr();
if (CameraObject == nullptr)
{
if (LastCameraObject && ViewTarget != LastCameraObject)
{
// last camera object has been set which means this isn't the first update, and now the view target has changed, we assume this to be intentional and replace the cached value
CachedValue.LastViewTarget = ViewTarget;
CameraObject = ViewTarget;
}
else
{
CameraObject = CachedValue.LastViewTarget.ResolveObjectPtr();
}
OverrideAspectRatioAxisConstraint = CachedValue.LastAspectRatioAxisConstraint;
}
else
{
if (ViewTarget != nullptr && ViewTarget != CameraObject && ViewTarget != CachedValue.LastViewTarget.ResolveObjectPtr() && ViewTarget != LastCameraObject)
{
// View target is different from the one provided by the cutscene, and also different from the cached one. The last inequality to the LastCameraObject check
// is required because it takes into account camera cuts inside subsequences, as the initial camera state will be not restored if the cut happens in a subsequence.
CachedValue.LastViewTarget = ViewTarget;
}
}
PreAnimatedStorage->GetMutableCachedValue(StorageIndex).LastCameraObject = CameraObject;
}
I think this doesn’t account for aspect ratio axis constraint changes, but we don’t have that in our game so we ignored it.
Hope this helps!
I see, thanks. I’m surprised that it works, actually -- well, not this code, but the whole concept of it. AFAIK, Level Sequences update pretty early in the engine tick, inside `UWorld::Tick`, via a delegate (see `MovieSceneSequenceTick`). Everything else happens afterwards, all the way to the end of the tick when the camera manager runs and the final camera properties are sent to the renderer. So the only way for Sequencer to see a different view target would be for that view target to be a carry over from someone changing it on the previous frame… which means the previous frame would have been seen through that different view target, no?