I found a way to fix this for 5.5 and lower. It will involve some C++, if you’re using Unreal source build, just add this into FStateTreeTransitionRequest Request; in RequestTransition function
Request.SourceStateTree = CachedFrameStateTree.Get();
Request.SourceRootState = CachedFrameRootState;
Or if not, you can make a base class for the task and use my custom transition function
// © 2025 Team Bacon. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/StateTreeTaskBlueprintBase.h"
#include "BrawlStateTreeTaskBlueprintBase.generated.h"
/**
* Base class for Blueprint based State Tree Task
* This class fixes an issue in UE5.5 and earlier where RequestTransition called from Blueprint tasks
*/
UCLASS()
class PROJECTBRAWL_API UBrawlStateTreeTaskBlueprintBase : public UStateTreeTaskBlueprintBase
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "StateTree", meta = (HideSelfPin = "true", DisplayName = "Brawl StateTree Request Transition"))
void BrawlRequestTransition(const FStateTreeStateLink& TargetState, const EStateTreeTransitionPriority Priority = EStateTreeTransitionPriority::Normal);
#if (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION <= 5)
public:
virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) override;
virtual void ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) override;
protected:
void BrawlSetCachedInstanceDataFromContext(const FStateTreeExecutionContext& Context) const;
void BrawlClearCachedInstanceData() const;
private:
/** Cached instance data while the node is active. */
mutable TWeakPtr<FStateTreeInstanceStorage> WeakInstanceStorage;
/** Cached owner while the node is active. */
// It's TObjectPtr in base class so we only need TWeakObjectPtr here, the base class will keep it alive
mutable TWeakObjectPtr<UObject> CachedOwner = nullptr;
/** Cached State Tree of owning execution frame. */
// It's TObjectPtr in base class so we only need TWeakObjectPtr here, the base class will keep it alive
mutable TWeakObjectPtr<const UStateTree> CachedFrameStateTree = nullptr;
/** Cached root state of owning execution frame. */
mutable FStateTreeStateHandle CachedFrameRootState;
/** Cached State where the node is processed on. */
mutable FStateTreeStateHandle CachedState;
#endif
};
// © 2025 Team Bacon. All Rights Reserved.
#include "AI/ST/BrawlStateTreeTaskBlueprintBase.h"
#include "StateTreeExecutionContext.h"
void UBrawlStateTreeTaskBlueprintBase::BrawlRequestTransition(const FStateTreeStateLink& TargetState,
const EStateTreeTransitionPriority Priority)
{
#if (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION <= 5)
TSharedPtr<FStateTreeInstanceStorage> InstanceStorage = WeakInstanceStorage.Pin();
if (InstanceStorage == nullptr || CachedOwner == nullptr)
{
UE_VLOG_UELOG(this, LogStateTree, Error, TEXT("Trying to call SendEvent() while node is not active. Use SendEvent() on UStateTreeComponent instead for sending signals externally."));
return;
}
FStateTreeTransitionRequest Request;
Request.SourceState = CachedState;
Request.TargetState = TargetState.StateHandle;
Request.Priority = Priority;
// The fix
Request.SourceStateTree = CachedFrameStateTree.Get();
Request.SourceRootState = CachedFrameRootState;
InstanceStorage->AddTransitionRequest(CachedOwner.Get(), Request);
#else
// Fixed in 5.6, fallback to base implementation.
RequestTransition(TargetState, Priority);
#endif
}
#if (ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION <= 5)
EStateTreeRunStatus UBrawlStateTreeTaskBlueprintBase::EnterState(FStateTreeExecutionContext& Context,
const FStateTreeTransitionResult& Transition)
{
BrawlSetCachedInstanceDataFromContext(Context);
return Super::EnterState(Context, Transition);
}
void UBrawlStateTreeTaskBlueprintBase::ExitState(FStateTreeExecutionContext& Context,
const FStateTreeTransitionResult& Transition)
{
BrawlClearCachedInstanceData();
Super::ExitState(Context, Transition);
}
void UBrawlStateTreeTaskBlueprintBase::BrawlSetCachedInstanceDataFromContext(const FStateTreeExecutionContext& Context) const
{
if (FStateTreeInstanceData* InstanceData = Context.GetMutableInstanceData())
{
WeakInstanceStorage = InstanceData->GetWeakMutableStorage();
}
CachedState = Context.GetCurrentlyProcessedState();
CachedOwner = Context.GetOwner();
const FStateTreeExecutionFrame* CurrentlyProcessedFrame = Context.GetCurrentlyProcessedFrame();
check(CurrentlyProcessedFrame);
CachedFrameStateTree = CurrentlyProcessedFrame->StateTree;
CachedFrameRootState = CurrentlyProcessedFrame->RootState;
}
void UBrawlStateTreeTaskBlueprintBase::BrawlClearCachedInstanceData() const
{
WeakInstanceStorage = nullptr;
CachedState = FStateTreeStateHandle::Invalid;
CachedOwner = nullptr;
CachedFrameStateTree = nullptr;
CachedFrameRootState = FStateTreeStateHandle::Invalid;
}
#endif