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

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

yep, that did the trick! thank you!