diff --git a/Source/UHLStateTree/Private/Net/UHLMontageReplicatorObject.cpp b/Source/UHLStateTree/Private/Net/UHLMontageReplicatorObject.cpp deleted file mode 100644 index 6ed75c8..0000000 --- a/Source/UHLStateTree/Private/Net/UHLMontageReplicatorObject.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// Pavel Penkov 2025 All Rights Reserved. - -#include "Net/UHLMontageReplicatorObject.h" -#include "Animation/AnimInstance.h" -#include "Engine/ActorChannel.h" - -void UUHLMontageReplicatorObject::Initialize(AActor* InOwner) -{ - Owner = InOwner; -} - -int32 UUHLMontageReplicatorObject::GetFunctionCallspace(UFunction* Function, FFrame* Stack) -{ - return Owner ? Owner->GetFunctionCallspace(Function, Stack) : FunctionCallspace::Local; -} - -bool UUHLMontageReplicatorObject::CallRemoteFunction(UFunction* Function, void* Params, FOutParmRec* OutParms, FFrame* Stack) -{ - if (Owner) - { - UNetDriver* NetDriver = Owner->GetNetDriver(); - if (NetDriver) - { - NetDriver->ProcessRemoteFunction(Owner, Function, Params, OutParms, Stack, this); - return true; - } - } - return false; -} - -void UUHLMontageReplicatorObject::Multicast_PlayMontage_Implementation( - USkeletalMeshComponent* Mesh, - UAnimMontage* Montage, - float PlayRate, - float StartPosition, - FName StartSection) -{ - if (!Mesh || !Montage) return; - if (UAnimInstance* AnimInstance = Mesh->GetAnimInstance()) - { - AnimInstance->Montage_Play(Montage, PlayRate, EMontagePlayReturnType::MontageLength, StartSection != NAME_None ? 0.0f : StartPosition, true); - if (StartSection != NAME_None) - { - AnimInstance->Montage_JumpToSection(StartSection, Montage); - } - } -} - -void UUHLMontageReplicatorObject::Multicast_StopAllMontages_Implementation(USkeletalMeshComponent* Mesh, float BlendOutTime) -{ - if (!Mesh) return; - if (UAnimInstance* AnimInstance = Mesh->GetAnimInstance()) - { - AnimInstance->StopAllMontages(BlendOutTime); - } -} - - diff --git a/Source/UHLStateTree/Private/Tasks/UHLSTTask_PlayAnimMontage.cpp b/Source/UHLStateTree/Private/Tasks/UHLSTTask_PlayAnimMontage.cpp index 1b9a0e6..015d0b3 100644 --- a/Source/UHLStateTree/Private/Tasks/UHLSTTask_PlayAnimMontage.cpp +++ b/Source/UHLStateTree/Private/Tasks/UHLSTTask_PlayAnimMontage.cpp @@ -2,10 +2,10 @@ #include "Tasks/UHLSTTask_PlayAnimMontage.h" +#include "StateTreeAsyncExecutionContext.h" #include "StateTreeExecutionContext.h" #include "GameFramework/Character.h" #include "Animation/AnimInstance.h" -#include "Net/UHLMontageReplicatorObject.h" #include "StateTreeLinker.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(UHLSTTask_PlayAnimMontage) @@ -21,42 +21,179 @@ USkeletalMeshComponent* FUHLSTTask_PlayAnimMontage::ResolveMesh(const FInstanceD return InstanceData.Character ? InstanceData.Character->GetMesh() : nullptr; } -bool FUHLSTTask_PlayAnimMontage::IsMontagePlaying(USkeletalMeshComponent* Mesh, const UAnimMontage* Montage) const +bool FUHLSTTask_PlayAnimMontage::PlayMontage( + FStateTreeExecutionContext& Context, + FInstanceDataType& InstanceData, + USkeletalMeshComponent* Mesh) const { - if (!Mesh) return false; + bool bPlayedSuccessfully = false; + float MontageLength = -1.0f; UAnimInstance* AnimInstance = Mesh->GetAnimInstance(); - if (!AnimInstance) return false; - return AnimInstance->Montage_IsPlaying(Montage); -} - -static void UHL_BindMontageDelegates( - FStateTreeExecutionContext& Context, - UAnimInstance* AnimInstance, - UAnimMontage* Montage, - FUHLSTTask_PlayAnimMontage::FInstanceDataType& InstanceData) -{ - if (!AnimInstance || !Montage) return; - InstanceData.BoundAnimInstance = AnimInstance; - FOnMontageEnded Ended; - Ended.BindLambda([&InstanceData](UAnimMontage* InMontage, bool bInterrupted) + // If a starting section is provided, jump to it; otherwise use starting position + if (Mesh == InstanceData.Character->GetMesh()) { - InstanceData.bInterruptedTriggered |= bInterrupted; - InstanceData.bCompletedTriggered |= !bInterrupted; - }); - AnimInstance->Montage_SetEndDelegate(Ended, Montage); + // Playing on Character's main mesh will replicate to simulated proxies when executed on the server + MontageLength = InstanceData.Character->PlayAnimMontage(InstanceData.AnimMontage, InstanceData.PlayRate); + if (InstanceData.StartingSection != NAME_None) + { + AnimInstance->Montage_JumpToSection(InstanceData.StartingSection, InstanceData.AnimMontage); + } + else if (InstanceData.StartingPosition > 0.0f) + { + AnimInstance->Montage_SetPosition(InstanceData.AnimMontage, InstanceData.StartingPosition); + } + } + else + { + MontageLength = AnimInstance->Montage_Play(InstanceData.AnimMontage, InstanceData.PlayRate, EMontagePlayReturnType::MontageLength, InstanceData.StartingSection != NAME_None ? 0.0f : InstanceData.StartingPosition, true); + if (InstanceData.StartingSection != NAME_None) + { + AnimInstance->Montage_JumpToSection(InstanceData.StartingSection, InstanceData.AnimMontage); + } + } + + const FStateTreeWeakExecutionContext WeakContext = Context.MakeWeakExecutionContext(); - FOnMontageBlendingOutStarted BlendOut; - BlendOut.BindLambda([&InstanceData](UAnimMontage* InMontage, bool bInterrupted) + Mesh->GetWorld()->GetTimerManager().SetTimerForNextTick([WeakContext, AnimInstance]() { - InstanceData.bBlendOutTriggered = true; - InstanceData.bInterruptedTriggered |= bInterrupted; + if (!AnimInstance) + { + return; + } + + FStateTreeStrongExecutionContext StrongContext = WeakContext.MakeStrongExecutionContext(); + if (!StrongContext.IsValid()) + { + return; + } + + FUHLSTTask_PlayAnimMontage::FInstanceDataType* InstanceDataPtr = StrongContext.GetInstanceDataPtr(); + if (!InstanceDataPtr) + { + return; + } + + const bool bBindBlendOut = InstanceDataPtr->bFinishTaskOnBlendOut; + const bool bBindEnd = InstanceDataPtr->bFinishTaskOnCompleted || InstanceDataPtr->bFinishTaskOnInterrupted; + if (!bBindBlendOut && !bBindEnd) + { + return; + } + + InstanceDataPtr->BoundAnimInstance = AnimInstance; + + if (bBindBlendOut) + { + FOnMontageBlendingOutStarted BlendOutDelegate; + BlendOutDelegate.BindLambda( + [WeakContext](UAnimMontage* Montage, bool bInterrupted) + { + FStateTreeStrongExecutionContext StrongContext = WeakContext.MakeStrongExecutionContext(); + if (!StrongContext.IsValid()) + { + return; + } + + FUHLSTTask_PlayAnimMontage::FInstanceDataType* InstanceDataPtr = StrongContext.GetInstanceDataPtr(); + if (!InstanceDataPtr) + { + return; + } + + if (Montage != InstanceDataPtr->AnimMontage) + { + return; + } + + if (InstanceDataPtr->bBlendOutTriggered) + { + return; + } + + InstanceDataPtr->bBlendOutTriggered = true; + InstanceDataPtr->bInterruptedTriggered |= bInterrupted; + InstanceDataPtr->bCompletedTriggered |= !bInterrupted; + + const bool bShouldSucceed = InstanceDataPtr->bSucceededResult && !bInterrupted; + const EStateTreeFinishTaskType FinishType = bShouldSucceed ? EStateTreeFinishTaskType::Succeeded : EStateTreeFinishTaskType::Failed; + WeakContext.FinishTask(FinishType); + } + ); + + AnimInstance->Montage_SetBlendingOutDelegate(BlendOutDelegate, InstanceDataPtr->AnimMontage); + } + + if (bBindEnd) + { + FOnMontageEnded EndDelegate; + EndDelegate.BindLambda( + [WeakContext](UAnimMontage* Montage, bool bInterrupted) + { + FStateTreeStrongExecutionContext StrongContext = WeakContext.MakeStrongExecutionContext(); + if (!StrongContext.IsValid()) + { + return; + } + + FUHLSTTask_PlayAnimMontage::FInstanceDataType* InstanceDataPtr = StrongContext.GetInstanceDataPtr(); + if (!InstanceDataPtr) + { + return; + } + + if (Montage != InstanceDataPtr->AnimMontage) + { + return; + } + + if (InstanceDataPtr->bBlendOutTriggered) + { + return; + } + + if (bInterrupted) + { + if (!InstanceDataPtr->bFinishTaskOnInterrupted || InstanceDataPtr->bInterruptedTriggered) + { + return; + } + + InstanceDataPtr->bInterruptedTriggered = true; + + const bool bShouldSucceed = InstanceDataPtr->bSucceededResult && !bInterrupted; + const EStateTreeFinishTaskType FinishType = bShouldSucceed ? EStateTreeFinishTaskType::Succeeded : EStateTreeFinishTaskType::Failed; + WeakContext.FinishTask(FinishType); + return; + } + + if (!InstanceDataPtr->bFinishTaskOnCompleted || InstanceDataPtr->bCompletedTriggered) + { + return; + } + + InstanceDataPtr->bCompletedTriggered = true; + + const bool bShouldSucceed = InstanceDataPtr->bSucceededResult; + const EStateTreeFinishTaskType FinishType = bShouldSucceed ? EStateTreeFinishTaskType::Succeeded : EStateTreeFinishTaskType::Failed; + WeakContext.FinishTask(FinishType); + } + ); + + AnimInstance->Montage_SetEndDelegate(EndDelegate, InstanceDataPtr->AnimMontage); + } }); - AnimInstance->Montage_SetBlendingOutDelegate(BlendOut, Montage); + + bPlayedSuccessfully = (MontageLength > 0.f); + return bPlayedSuccessfully; } EStateTreeRunStatus FUHLSTTask_PlayAnimMontage::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const { FInstanceDataType& InstanceData = Context.GetInstanceData(*this); + InstanceData.bCompletedTriggered = false; + InstanceData.bInterruptedTriggered = false; + InstanceData.bBlendOutTriggered = false; + InstanceData.BoundAnimInstance.Reset(); if (!InstanceData.Character || !InstanceData.AnimMontage) { return EStateTreeRunStatus::Failed; @@ -83,76 +220,15 @@ EStateTreeRunStatus FUHLSTTask_PlayAnimMontage::EnterState(FStateTreeExecutionCo } else { - if (InstanceData.Character->HasAuthority()) - { - UUHLMontageReplicatorObject* Replicator = NewObject(InstanceData.Character); - Replicator->Initialize(InstanceData.Character); - InstanceData.Character->AddReplicatedSubObject(Replicator); - Replicator->Multicast_StopAllMontages(Mesh, 0.25f); - } - else - { - AnimInstance->StopAllMontages(0.25f); - } + AnimInstance->StopAllMontages(0.25f); } } - - // If a starting section is provided, jump to it; otherwise use starting position - if (Mesh == InstanceData.Character->GetMesh()) - { - // Playing on Character's main mesh will replicate to simulated proxies when executed on the server - InstanceData.Character->PlayAnimMontage(InstanceData.AnimMontage, InstanceData.PlayRate); - if (InstanceData.StartingSection != NAME_None) - { - AnimInstance->Montage_JumpToSection(InstanceData.StartingSection, InstanceData.AnimMontage); - } - else if (InstanceData.StartingPosition > 0.0f) - { - AnimInstance->Montage_SetPosition(InstanceData.AnimMontage, InstanceData.StartingPosition); - } - } - else - { - if (InstanceData.Character->HasAuthority()) - { - UUHLMontageReplicatorObject* Replicator = NewObject(InstanceData.Character); - Replicator->Initialize(InstanceData.Character); - InstanceData.Character->AddReplicatedSubObject(Replicator); - Replicator->Multicast_PlayMontage( - Mesh, - InstanceData.AnimMontage, - InstanceData.PlayRate, - InstanceData.StartingPosition, - InstanceData.StartingSection); - } - else - { - AnimInstance->Montage_Play(InstanceData.AnimMontage, InstanceData.PlayRate, EMontagePlayReturnType::MontageLength, InstanceData.StartingSection != NAME_None ? 0.0f : InstanceData.StartingPosition, true); - if (InstanceData.StartingSection != NAME_None) - { - AnimInstance->Montage_JumpToSection(InstanceData.StartingSection, InstanceData.AnimMontage); - } - } - } - - // Bind delegates for completion/interrupt/BlendOut and request transition directly - UHL_BindMontageDelegates(Context, AnimInstance, InstanceData.AnimMontage, InstanceData); + + bool bSuccessPlayMontage = PlayMontage(Context, InstanceData, Mesh); return EStateTreeRunStatus::Running; } -EStateTreeRunStatus FUHLSTTask_PlayAnimMontage::Tick(FStateTreeExecutionContext& Context, const float DeltaTime) const -{ - FInstanceDataType& InstanceData = Context.GetInstanceData(*this); - if ((InstanceData.bFinishTaskOnCompleted && InstanceData.bCompletedTriggered) - || (InstanceData.bFinishTaskOnInterrupted && InstanceData.bInterruptedTriggered) - || (InstanceData.bFinishTaskOnBlendOut && InstanceData.bBlendOutTriggered)) - { - return InstanceData.bSucceededResult ? EStateTreeRunStatus::Succeeded : EStateTreeRunStatus::Failed; - } - return FStateTreeTaskCommonBase::Tick(Context, DeltaTime); -} - void FUHLSTTask_PlayAnimMontage::ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const { FInstanceDataType& InstanceData = Context.GetInstanceData(*this); diff --git a/Source/UHLStateTree/Public/Net/UHLMontageReplicatorObject.h b/Source/UHLStateTree/Public/Net/UHLMontageReplicatorObject.h deleted file mode 100644 index 2a0e886..0000000 --- a/Source/UHLStateTree/Public/Net/UHLMontageReplicatorObject.h +++ /dev/null @@ -1,43 +0,0 @@ -// Pavel Penkov 2025 All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Object.h" -#include "Animation/AnimMontage.h" -#include "Components/SkeletalMeshComponent.h" -#include "UHLMontageReplicatorObject.generated.h" - -UCLASS() -class UHLSTATETREE_API UUHLMontageReplicatorObject : public UObject -{ - GENERATED_BODY() - -public: - UUHLMontageReplicatorObject() {} - - // Required so the UObject can take part in networking - virtual bool IsSupportedForNetworking() const override { return true; } - virtual int32 GetFunctionCallspace(UFunction* Function, FFrame* Stack) override; - virtual bool CallRemoteFunction(UFunction* Function, void* Params, FOutParmRec* OutParms, FFrame* Stack) override; - virtual UWorld* GetWorld() const override { return Owner ? Owner->GetWorld() : nullptr; } - - void Initialize(AActor* InOwner); - - UFUNCTION(NetMulticast, Reliable) - void Multicast_PlayMontage( - USkeletalMeshComponent* Mesh, - UAnimMontage* Montage, - float PlayRate, - float StartPosition, - FName StartSection); - - UFUNCTION(NetMulticast, Reliable) - void Multicast_StopAllMontages(USkeletalMeshComponent* Mesh, float BlendOutTime); - -private: - UPROPERTY() - TObjectPtr Owner = nullptr; -}; - - diff --git a/Source/UHLStateTree/Public/Tasks/UHLSTTask_PlayAnimMontage.h b/Source/UHLStateTree/Public/Tasks/UHLSTTask_PlayAnimMontage.h index 54c9ff9..353be6e 100644 --- a/Source/UHLStateTree/Public/Tasks/UHLSTTask_PlayAnimMontage.h +++ b/Source/UHLStateTree/Public/Tasks/UHLSTTask_PlayAnimMontage.h @@ -91,8 +91,6 @@ struct UHLSTATETREE_API FUHLSTTask_PlayAnimMontage : public FStateTreeTaskCommon virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); } virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; - // Lightweight tick: only checks flags set by montage delegates to finish the task. - virtual EStateTreeRunStatus Tick(FStateTreeExecutionContext& Context, const float DeltaTime) const override; virtual void ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override; #if WITH_EDITOR @@ -103,7 +101,14 @@ struct UHLSTATETREE_API FUHLSTTask_PlayAnimMontage : public FStateTreeTaskCommon private: USkeletalMeshComponent* ResolveMesh(const FInstanceDataType& InstanceData) const; - bool IsMontagePlaying(USkeletalMeshComponent* Mesh, const UAnimMontage* Montage) const; + + /** + * PlayMontage and bind callback + */ + bool PlayMontage( + FStateTreeExecutionContext& Context, + FInstanceDataType& InstanceData, + USkeletalMeshComponent* InSkeletalMeshComponent) const; };