Custom PlayMontage node, package problem.

I copied 4 files that makes PlayMontage node:
PlayMontageCallbackProxy.h/cpp
K2Node_PlayMontage.h/cpp

I just changed the names to make my own and added a little code to actual functionality.
The problem now is: I can’t package it. It requires BlueprintGraph module, when i’m adding to Public(or Private doesn’t matter) modules list, then it complains:

UATHelper: Packaging (Windows): Unable to instantiate module 'UnrealEd': Unable to instantiate UnrealEd module for non-editor targets.
UATHelper: Packaging (Windows): (referenced via Target -> SkyCraft.Build.cs -> BlueprintGraph.Build.cs -> KismetCompiler.Build.cs)

BlueprintGraph module is Editor only, so i’m putting in condition for editor-only:

if (Target.bBuildEditor)
{
	PrivateDependencyModuleNames.AddRange(new string[]
	{
		"BlueprintGraph"
	});
}

Now it complains this:

UATHelper: Packaging (Windows): C:\Unreal Engine Projects\SkyCraft\Source\SkyCraft\K2Node_AdianPlayMontage.h(9): Error: Unable to find parent class type for 'UK2Node_AdianPlayMontage' named 'UK2Node_BaseAsyncTask'
PackagingResults: Error: Unable to find parent class type for 'UK2Node_AdianPlayMontage' named 'UK2Node_BaseAsyncTask'

What the hell? I tried numerous methods… I don’t know what to do now!

For clarity, just the same node:

K2Node_AdianPlayMontage.h
// ADIAN Copyrighted

#pragma once

#include "CoreMinimal.h"
#include "K2Node_BaseAsyncTask.h"
#include "K2Node_AdianPlayMontage.generated.h"

UCLASS()
class UK2Node_AdianPlayMontage : public UK2Node_BaseAsyncTask
{
	GENERATED_UCLASS_BODY()

	//~ Begin UEdGraphNode Interface
	virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
	//~ End UEdGraphNode Interface

	//~ Begin UK2Node Interface
	virtual FText GetMenuCategory() const override;
	//~ End UK2Node Interface
};
K2Node_AdianPlayMontage.cpp
// ADIAN Copyrighted

#include "K2Node_AdianPlayMontage.h"
#include "SkyCraft/AdianPlayMontageCallbackProxy.h"

UK2Node_AdianPlayMontage::UK2Node_AdianPlayMontage(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED(UAdianPlayMontageCallbackProxy, CreateProxyObjectForPlayMontage);
	ProxyFactoryClass = UAdianPlayMontageCallbackProxy::StaticClass();
	ProxyClass = UAdianPlayMontageCallbackProxy::StaticClass();
}

FText UK2Node_AdianPlayMontage::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	return FText::FromString("Adian Play Montage");
}

FText UK2Node_AdianPlayMontage::GetMenuCategory() const
{
	return FText::FromString("Animation|Montage");
}
AdianPlayMontageCallbackProxy.h
// ADIAN Copyrighted

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Object.h"
#include "UObject/ScriptMacros.h"
#include "Animation/AnimInstance.h"
#include "AdianPlayMontageCallbackProxy.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMontagePlayDelegate, FName, NotifyName);

UCLASS(MinimalAPI)
class UAdianPlayMontageCallbackProxy : public UObject
{
	GENERATED_UCLASS_BODY()

	// Called when Montage finished playing and wasn't interrupted
	UPROPERTY(BlueprintAssignable)
	FOnMontagePlayDelegate OnCompleted;

	// Called when Montage starts blending out and is not interrupted
	UPROPERTY(BlueprintAssignable)
	FOnMontagePlayDelegate OnBlendOut;

	// Called when Montage has been interrupted (or failed to play)
	UPROPERTY(BlueprintAssignable)
	FOnMontagePlayDelegate OnInterrupted;

	UPROPERTY(BlueprintAssignable)
	FOnMontagePlayDelegate OnNotifyBegin;

	UPROPERTY(BlueprintAssignable)
	FOnMontagePlayDelegate OnNotifyEnd;

	// Called to perform the query internally
	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
	static UAdianPlayMontageCallbackProxy* CreateProxyObjectForPlayMontage(
		class USkeletalMeshComponent* InSkeletalMeshComponent, 
		class UAnimMontage* MontageToPlay, 
		float PlayRate = 1.f, 
		float StartingPosition = 0.f, 
		FName StartingSection = NAME_None,
		bool bShouldStopAllMontages = true);

public:
	//~ Begin UObject Interface
	virtual void BeginDestroy() override;
	//~ End UObject Interface

protected:
	UFUNCTION()
	void OnMontageBlendingOut(UAnimMontage* Montage, bool bInterrupted);

	UFUNCTION()
	void OnMontageEnded(UAnimMontage* Montage, bool bInterrupted);

	UFUNCTION()
	void OnNotifyBeginReceived(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointNotifyPayload);

	UFUNCTION()
	void OnNotifyEndReceived(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointNotifyPayload);

private:
	TWeakObjectPtr<UAnimInstance> AnimInstancePtr;
	int32 MontageInstanceID;
	uint32 bInterruptedCalledBeforeBlendingOut : 1;

	bool IsNotifyValid(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointNotifyPayload) const;
	void UnbindDelegates();

	FOnMontageBlendingOutStarted BlendingOutDelegate;
	FOnMontageEnded MontageEndedDelegate;

protected:
	// Attempts to play a montage with the specified settings. Returns whether it started or not.
	bool AdianPlayMontage(
		class USkeletalMeshComponent* InSkeletalMeshComponent,
		class UAnimMontage* MontageToPlay,
		float PlayRate = 1.f,
		float StartingPosition = 0.f,
		FName StartingSection = NAME_None,
		bool bShouldStopAllMontages = true);
};
AdianPlayMontageCallbackProxy.cpp
// ADIAN Copyrighted

#include "AdianPlayMontageCallbackProxy.h"
#include "Animation/AnimMontage.h"
#include "AssetUserData/AUD_MontageSettings.h"
#include "Components/SkeletalMeshComponent.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(AdianPlayMontageCallbackProxy)

//////////////////////////////////////////////////////////////////////////
// UPlayMontageCallbackProxy

UAdianPlayMontageCallbackProxy::UAdianPlayMontageCallbackProxy(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
	, MontageInstanceID(INDEX_NONE)
	, bInterruptedCalledBeforeBlendingOut(false)
{
}

UAdianPlayMontageCallbackProxy* UAdianPlayMontageCallbackProxy::CreateProxyObjectForPlayMontage(
	class USkeletalMeshComponent* InSkeletalMeshComponent,
	class UAnimMontage* MontageToPlay,
	float PlayRate,
	float StartingPosition,
	FName StartingSection,
	bool bShouldStopAllMontages)
{
	UAdianPlayMontageCallbackProxy* Proxy = NewObject<UAdianPlayMontageCallbackProxy>();
	Proxy->SetFlags(RF_StrongRefOnFrame);
	Proxy->AdianPlayMontage(InSkeletalMeshComponent, MontageToPlay, PlayRate, StartingPosition, StartingSection, bShouldStopAllMontages);
	return Proxy;
}


bool UAdianPlayMontageCallbackProxy::AdianPlayMontage(class USkeletalMeshComponent* InSkeletalMeshComponent, 
	class UAnimMontage* MontageToPlay, 
	float PlayRate, 
	float StartingPosition, 
	FName StartingSection,
	bool bShouldStopAllMontages)
{
	bool bPlayedSuccessfully = false;
	if (InSkeletalMeshComponent)
	{
		if (UAnimInstance* AnimInstance = InSkeletalMeshComponent->GetAnimInstance())
		{
			const float MontageLength = AnimInstance->Montage_Play(MontageToPlay, PlayRate, EMontagePlayReturnType::MontageLength, StartingPosition, bShouldStopAllMontages);
			bPlayedSuccessfully = (MontageLength > 0.f);

			if (bPlayedSuccessfully)
			{
				AnimInstancePtr = AnimInstance;
				if (FAnimMontageInstance* MontageInstance = AnimInstance->GetActiveInstanceForMontage(MontageToPlay))
				{
					MontageInstanceID = MontageInstance->GetInstanceID();
				}

				if (StartingSection != NAME_None)
				{
					AnimInstance->Montage_JumpToSection(StartingSection, MontageToPlay);
				}

				BlendingOutDelegate.BindUObject(this, &UAdianPlayMontageCallbackProxy::OnMontageBlendingOut);
				AnimInstance->Montage_SetBlendingOutDelegate(BlendingOutDelegate, MontageToPlay);

				MontageEndedDelegate.BindUObject(this, &UAdianPlayMontageCallbackProxy::OnMontageEnded);
				AnimInstance->Montage_SetEndDelegate(MontageEndedDelegate, MontageToPlay);

				AnimInstance->OnPlayMontageNotifyBegin.AddDynamic(this, &UAdianPlayMontageCallbackProxy::OnNotifyBeginReceived);
				AnimInstance->OnPlayMontageNotifyEnd.AddDynamic(this, &UAdianPlayMontageCallbackProxy::OnNotifyEndReceived);
			}
		}
	}

	if (!bPlayedSuccessfully)
	{
		OnInterrupted.Broadcast(NAME_None);
	}

	return bPlayedSuccessfully;
}

bool UAdianPlayMontageCallbackProxy::IsNotifyValid(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointNotifyPayload) const
{
	return ((MontageInstanceID != INDEX_NONE) && (BranchingPointNotifyPayload.MontageInstanceID == MontageInstanceID));
}

void UAdianPlayMontageCallbackProxy::OnNotifyBeginReceived(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointNotifyPayload)
{
	if (IsNotifyValid(NotifyName, BranchingPointNotifyPayload))
	{
		OnNotifyBegin.Broadcast(NotifyName);
	}
}

void UAdianPlayMontageCallbackProxy::OnNotifyEndReceived(FName NotifyName, const FBranchingPointNotifyPayload& BranchingPointNotifyPayload)
{
	if (IsNotifyValid(NotifyName, BranchingPointNotifyPayload))
	{
		OnNotifyEnd.Broadcast(NotifyName);
	}
}

void UAdianPlayMontageCallbackProxy::OnMontageBlendingOut(UAnimMontage* Montage, bool bInterrupted)
{
	UAUD_MontageSettings* MontageSettings = Montage->GetAssetUserData<UAUD_MontageSettings>();
	if (MontageSettings && MontageSettings->bEndOnBlendOut)
	{
		if (!bInterrupted)
		{
			OnBlendOut.Broadcast(NAME_None);
		}
		else
		{
			OnInterrupted.Broadcast(NAME_None);
		}
		
		UnbindDelegates();
	}
	else
	{
		if (bInterrupted)
		{
			OnInterrupted.Broadcast(NAME_None);
			bInterruptedCalledBeforeBlendingOut = true;
		}
		else
		{
			OnBlendOut.Broadcast(NAME_None);
		}
	}
}

void UAdianPlayMontageCallbackProxy::OnMontageEnded(UAnimMontage* Montage, bool bInterrupted)
{
	UAUD_MontageSettings* MontageSettings = Montage->GetAssetUserData<UAUD_MontageSettings>();
	if (MontageSettings && MontageSettings->bEndOnBlendOut)
	{
		if (!bInterrupted) OnCompleted.Broadcast(NAME_None);
		return;
	}
	
	if (!bInterrupted)
	{
		OnCompleted.Broadcast(NAME_None);
	}
	else if (!bInterruptedCalledBeforeBlendingOut)
	{
		OnInterrupted.Broadcast(NAME_None);
	}
	
	UnbindDelegates();
}

void UAdianPlayMontageCallbackProxy::UnbindDelegates()
{
	if (UAnimInstance* AnimInstance = AnimInstancePtr.Get())
	{
		AnimInstance->OnPlayMontageNotifyBegin.RemoveDynamic(this, &UAdianPlayMontageCallbackProxy::OnNotifyBeginReceived);
		AnimInstance->OnPlayMontageNotifyEnd.RemoveDynamic(this, &UAdianPlayMontageCallbackProxy::OnNotifyEndReceived);
	}
}

void UAdianPlayMontageCallbackProxy::BeginDestroy()
{
	UnbindDelegates();

	Super::BeginDestroy();
}

I’m using AdianPlayMontage in numerous runtime blueprints.

How can i avoid this dependency issue?

All the node related code is in editor modules, which don’t get built when making a non-editor build
So you probably have to create a new editor only module, and move the node code to it
Your main game module should not have any references to UnrealEd or BlueprintGraph or any other editor only module

1 Like

Yeah, i did it before, (put two K2Nodes files in ProjectEditor module) and it gives warning. It wont be playable when packaged.


Oh wait, or should i put in UncookedOnly module or what? Will it work?

in the past at least when I made nodes I had them in the UncookedOnly module

1 Like

yep, that did the trick! thank you!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.