From a60d34b6859d2ede0826de8c0de6626e1b29e265 Mon Sep 17 00:00:00 2001 From: LindyHopperGT <91915878+LindyHopperGT@users.noreply.github.com> Date: Wed, 3 Jun 2026 11:30:39 -0700 Subject: [PATCH] Flow Sequencer changes Adds UMKTFlowNodeAddOn_LevelSequenceActorBinding, a Flow node add-on that binds a named Level Sequence actor slot to an actor resolved from a blackboard key before playback. Supports multiplayer via replicated binding state on AFlowLevelSequenceActor. New IMKTPlayLevelSequenceAddOnInterface: pre-play hook (OnSequencePlayerCreated) for add-ons to apply binding overrides after player creation MKTFlowNode_PlayLevelSequenceAtStageMarker: accepts IMKTPlayLevelSequenceAddOnInterface children, calls the hook, exposes GetSequenceActor() AFlowLevelSequenceActor: AddBinding()/ClearAllBindings() with replicated BindingEntries so binding overrides propagate to clients --- .../LevelSequence/FlowLevelSequenceActor.cpp | 51 ++++++++++++++++++- .../LevelSequence/FlowLevelSequencePlayer.cpp | 2 +- .../IFlowPlayLevelSequenceAddOnInterface.cpp | 12 +++++ .../Actor/FlowNode_PlayLevelSequence.cpp | 37 ++++++++++++-- .../LevelSequence/FlowLevelSequenceActor.h | 25 +++++++++ .../LevelSequence/FlowLevelSequencePlayer.h | 3 +- .../IFlowPlayLevelSequenceAddOnInterface.h | 40 +++++++++++++++ .../Nodes/Actor/FlowNode_PlayLevelSequence.h | 8 +++ 8 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 Source/Flow/Private/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.cpp create mode 100644 Source/Flow/Public/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.h diff --git a/Source/Flow/Private/LevelSequence/FlowLevelSequenceActor.cpp b/Source/Flow/Private/LevelSequence/FlowLevelSequenceActor.cpp index 88b0f35c..d99f05ce 100644 --- a/Source/Flow/Private/LevelSequence/FlowLevelSequenceActor.cpp +++ b/Source/Flow/Private/LevelSequence/FlowLevelSequenceActor.cpp @@ -1,10 +1,10 @@ // Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors #include "LevelSequence/FlowLevelSequenceActor.h" +#include "FlowLogChannels.h" #include "LevelSequence/FlowLevelSequencePlayer.h" #include "Net/UnrealNetwork.h" #include "Runtime/Launch/Resources/Version.h" - #include "DefaultLevelSequenceInstanceData.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowLevelSequenceActor) @@ -20,6 +20,7 @@ void AFlowLevelSequenceActor::GetLifetimeReplicatedProps(TArray& OutActor ) { if (LevelSequence == nullptr) diff --git a/Source/Flow/Private/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.cpp b/Source/Flow/Private/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.cpp new file mode 100644 index 00000000..8942d1dc --- /dev/null +++ b/Source/Flow/Private/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.cpp @@ -0,0 +1,12 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#include "LevelSequence/IFlowPlayLevelSequenceAddOnInterface.h" + +#include "AddOns/FlowNodeAddOn.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(IFlowPlayLevelSequenceAddOnInterface) + +bool IFlowPlayLevelSequenceAddOnInterface::ImplementsInterfaceSafe(const UFlowNodeAddOn* AddOnTemplate) +{ + return IsValid(AddOnTemplate) && AddOnTemplate->Implements(); +} diff --git a/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp b/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp index 3cd94789..e07b68a3 100644 --- a/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp +++ b/Source/Flow/Private/Nodes/Actor/FlowNode_PlayLevelSequence.cpp @@ -5,7 +5,10 @@ #include "FlowAsset.h" #include "FlowLogChannels.h" #include "FlowSubsystem.h" +#include "AddOns/FlowNodeAddOn.h" +#include "LevelSequence/FlowLevelSequenceActor.h" #include "LevelSequence/FlowLevelSequencePlayer.h" +#include "LevelSequence/IFlowPlayLevelSequenceAddOnInterface.h" #if WITH_EDITOR #include "MovieScene/MovieSceneFlowTrack.h" @@ -13,7 +16,6 @@ #endif #include "LevelSequence.h" -#include "LevelSequenceActor.h" #include "VisualLogger/VisualLogger.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowNode_PlayLevelSequence) @@ -148,17 +150,29 @@ void UFlowNode_PlayLevelSequence::InitializeInstance() { Super::InitializeInstance(); + SequenceActor = nullptr; + // Cache Play Rate set by user CachedPlayRate = PlaybackSettings.PlayRate; } +EFlowAddOnAcceptResult UFlowNode_PlayLevelSequence::AcceptFlowNodeAddOnChild_Implementation( + const UFlowNodeAddOn* AddOnTemplate, + const TArray& AdditionalAddOnsToAssumeAreChildren) const +{ + if (IFlowPlayLevelSequenceAddOnInterface::ImplementsInterfaceSafe(AddOnTemplate)) + { + return EFlowAddOnAcceptResult::TentativeAccept; + } + + return Super::AcceptFlowNodeAddOnChild_Implementation(AddOnTemplate, AdditionalAddOnsToAssumeAreChildren); +} + void UFlowNode_PlayLevelSequence::CreatePlayer() { LoadedSequence = Sequence.LoadSynchronous(); if (LoadedSequence) { - ALevelSequenceActor* SequenceActor; - AActor* OwningActor = TryGetRootFlowActorOwner(); // Apply AActor::CustomTimeDilation from owner of the Root Flow @@ -178,6 +192,17 @@ void UFlowNode_PlayLevelSequence::CreatePlayer() SequencePlayer->SetFlowEventReceiver(this); } + // Notify add-ons so they can apply binding overrides before Play() is called + if (AFlowLevelSequenceActor* FlowSequenceActor = SequenceActor.Get()) + { + ForEachAddOnForClass([this, FlowSequenceActor, OwningActor](UFlowNodeAddOn& AddOn) + { + IFlowPlayLevelSequenceAddOnInterface* Interface = CastChecked(&AddOn); + Interface->OnSequencePlayerCreated(*FlowSequenceActor, OwningActor); + return EFlowForEachAddOnFunctionReturnValue::Continue; + }); + } + const FFrameRate FrameRate = LoadedSequence->GetMovieScene()->GetTickResolution(); const FFrameNumber PlaybackStartFrame = LoadedSequence->GetMovieScene()->GetPlaybackRange().GetLowerBoundValue(); StartTime = FQualifiedFrameTime(FFrameTime(PlaybackStartFrame, 0.0f), FrameRate).AsSeconds(); @@ -319,6 +344,12 @@ void UFlowNode_PlayLevelSequence::Cleanup() SequencePlayer = nullptr; } + if (IsValid(SequenceActor) && SequenceActor->HasAuthority()) + { + SequenceActor->Destroy(); + } + SequenceActor = nullptr; + LoadedSequence = nullptr; StartTime = 0.0f; ElapsedTime = 0.0f; diff --git a/Source/Flow/Public/LevelSequence/FlowLevelSequenceActor.h b/Source/Flow/Public/LevelSequence/FlowLevelSequenceActor.h index 99c6d5cf..6144b52a 100644 --- a/Source/Flow/Public/LevelSequence/FlowLevelSequenceActor.h +++ b/Source/Flow/Public/LevelSequence/FlowLevelSequenceActor.h @@ -6,6 +6,19 @@ class ULevelSequence; +/** Single actor binding override entry, replicated from server to clients. */ +USTRUCT() +struct FFlowSequenceBindingEntry +{ + GENERATED_BODY() + + UPROPERTY() + FName BindingTag; + + UPROPERTY() + TObjectPtr BoundActor; +}; + /** * Custom ALevelSequenceActor is needed to override ULevelSequencePlayer class. */ @@ -24,7 +37,19 @@ class FLOW_API AFlowLevelSequenceActor : public ALevelSequenceActor void SetPlaybackSettings(FMovieSceneSequencePlaybackSettings NewPlaybackSettings); void SetReplicatedLevelSequenceAsset(ULevelSequence* Asset); + /** Server only. Adds a binding override that replicates to clients via OnRep_BindingEntries. */ + void AddBinding(FName Tag, AActor* Actor); + + /** Server only. Clears all binding overrides and replicates the cleared state to clients. */ + void ClearAllBindings(); + protected: UFUNCTION() void OnRep_ReplicatedLevelSequenceAsset(); + + UPROPERTY(ReplicatedUsing = OnRep_BindingEntries) + TArray BindingEntries; + + UFUNCTION() + void OnRep_BindingEntries(); }; diff --git a/Source/Flow/Public/LevelSequence/FlowLevelSequencePlayer.h b/Source/Flow/Public/LevelSequence/FlowLevelSequencePlayer.h index 038125d1..525b6754 100644 --- a/Source/Flow/Public/LevelSequence/FlowLevelSequencePlayer.h +++ b/Source/Flow/Public/LevelSequence/FlowLevelSequencePlayer.h @@ -4,6 +4,7 @@ #include "LevelSequencePlayer.h" #include "FlowLevelSequencePlayer.generated.h" +class AFlowLevelSequenceActor; class UFlowNode; /** @@ -29,7 +30,7 @@ class FLOW_API UFlowLevelSequencePlayer : public ULevelSequencePlayer AActor* TransformOriginActor, const bool bReplicates, const bool bAlwaysRelevant, - ALevelSequenceActor*& OutActor); + TObjectPtr& OutActor); void SetFlowEventReceiver(UFlowNode* FlowNode) { FlowEventReceiver = FlowNode; } diff --git a/Source/Flow/Public/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.h b/Source/Flow/Public/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.h new file mode 100644 index 00000000..d98c8272 --- /dev/null +++ b/Source/Flow/Public/LevelSequence/IFlowPlayLevelSequenceAddOnInterface.h @@ -0,0 +1,40 @@ +// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors + +#pragma once + +#include "UObject/Interface.h" + +#include "IFlowPlayLevelSequenceAddOnInterface.generated.h" + +class AActor; +class AFlowLevelSequenceActor; +class UFlowNodeAddOn; + +/** + * Interface for add-ons that want to apply setup (e.g. actor binding overrides) to a + * level sequence actor immediately after the sequence player is created, before Play() is called. + * + * Attach to any flow node that spawns an AFlowLevelSequenceActor and supports this interface. + */ +UINTERFACE(MinimalAPI) +class UFlowPlayLevelSequenceAddOnInterface : public UInterface +{ + GENERATED_BODY() +}; + +class FLOW_API IFlowPlayLevelSequenceAddOnInterface +{ + GENERATED_BODY() + +public: + + static bool ImplementsInterfaceSafe(const UFlowNodeAddOn* AddOnTemplate); + + /** + * Called after the sequence player is created, before Play() is invoked. + * + * @param SequenceActor The spawned AFlowLevelSequenceActor. Never null when called. + * @param FlowOwner The actor that owns the flow graph. May be null. + */ + virtual void OnSequencePlayerCreated(AFlowLevelSequenceActor& SequenceActor, AActor* FlowOwner) {} +}; diff --git a/Source/Flow/Public/Nodes/Actor/FlowNode_PlayLevelSequence.h b/Source/Flow/Public/Nodes/Actor/FlowNode_PlayLevelSequence.h index 02eaa1b5..19914c08 100644 --- a/Source/Flow/Public/Nodes/Actor/FlowNode_PlayLevelSequence.h +++ b/Source/Flow/Public/Nodes/Actor/FlowNode_PlayLevelSequence.h @@ -10,6 +10,7 @@ #include "Nodes/FlowNode.h" #include "FlowNode_PlayLevelSequence.generated.h" +class AFlowLevelSequenceActor; class UFlowLevelSequencePlayer; DECLARE_MULTICAST_DELEGATE(FFlowNodeLevelSequenceEvent); @@ -75,6 +76,9 @@ class FLOW_API UFlowNode_PlayLevelSequence UPROPERTY() TObjectPtr SequencePlayer; + UPROPERTY() + TObjectPtr SequenceActor; + /* Play Rate set by the user in PlaybackSettings. */ float CachedPlayRate; @@ -106,6 +110,10 @@ class FLOW_API UFlowNode_PlayLevelSequence virtual void FlushContent() override; // -- + // UFlowNodeBase + virtual EFlowAddOnAcceptResult AcceptFlowNodeAddOnChild_Implementation(const UFlowNodeAddOn* AddOnTemplate, const TArray& AdditionalAddOnsToAssumeAreChildren) const override; + // -- + virtual void InitializeInstance() override; void CreatePlayer();