How to implement custom BP node wildcard pin?

Is there any official documentation on how to do this? I’m getting solutions from AI, but I’d like to see some sort of official implementation so I can actually compare against something functional. I’m planning to implement a latent action node (already know how) to async load assets (already know how) with a payload (already know how), but I’d like the payload to be a wildcard (not sure how). I’m finding some documentation for FWildcardNodeUtils, but there’s not much here on how to use this? Is there an FWildcard or something?

i know only this Reference Guide to Custom Blueprint Nodes | Matt’s Game Dev Notebook it can maybe help you idk.

Thank you, the wildcard pin section looks interesting. I need to be able to combine this with calling a delegate as well and returning that wildcard with a delegate. Seeing a lot of references to CustomThunk in other resources, but nothing seams to really go into enough default or a simple working example lol.

To create a custom Blueprint node with a wildcard pin, use UK2Node as your base class and define a pin with PC_Wildcard type in AllocateDefaultPins(). Then, override NotifyPinConnectionListChanged() to update the pin type when it connects to another pin. This way, the pin adapts dynamically based on what it’s linked to.

this is correct for an input wildcard, if you mean dynamic output then you need a K2Node

1 Like

this page may help Creating a UK2Node returning a reference | Community tutorial

1 Like

I’m calling a latent action inside my custom node. So I’ll need to use UBlueprintAsyncActionBase (which I’ve done before and isn’t a big deal). So I think CustomThunk is my only option here unless there’s a way to have latent actions in a K2Node? I’m basically adding wildcard payload support to a custom AssetAsyncLoad node.

Looks like LoadAsset and LoadAssetClass are already UK2Nodes. Neat so maybe I can just extend those with payload support. This certainly seams like an easier direction to take than implementing CustomThunk. There also seams to be BaseAsyncTask available too if I can’t extend those two, which might work for a latent action it seams.

you’ll still need a customthunk for wildcards even on K2Node (i think) but to answer

you could use an FInstancedStruct

i havent looked into those nodes but you still need to ensure the payload is given directly, otherwise it could be overridden by the time the action completes.

Option B is to construct an object to call the latent action and pass the payload to the object, that way each object has its own payload

The LoadAsset and LoadAssetClass nodes return the loaded class properly on completed event (even if called multiple times) so surely this works to some degree already.

That is what I do in my UBlueprintAsyncActionBase nodes I’ve created. Looking at the UK2Node_AsyncAction this seams like it might be doable. When using RequestAsyncLoad to async load I should be able to just bind a uobject delegate to it and the uobject contains my payload. That should solve this problem I think.

Agreed, likely they are already UObjects so just need to add the input. Let me know how you go, i can show you the CustomThunk process if required.

Most K2Nodes call a static function as the base input

I think I’m going to explore CustomThunk. These K2Nodes seam terrible. They just spawn a bunch of other nodes and chain them together internally (looks like it’s just making macros, but in C++?). I don’t see why I’d do that when I can do the entire process in proper clean C++ and expose with ufuction without this extremely goofy K2Node glue. Reviewed the source of several (including LoadAssetClass) and they’re a nightmare to follow for something as simple as calling the dang asset manager with a delegate.

thats exactly it haha but i little more powerful since it can create events/delegates and variables (expose on spawn)

can you give me an idea of what you want to do exactly, you can take in a wildcard but i dont think you can pass a wildcard to the object as a variable (could be wrong) you may be better off using an FInstancedStruct which would also be much easier

lol, I was just reading up about this. I am absolutely going to use FInstancedStruct instead. This will work perfectly for me as I already have payload structs setup.

Awesome, got it working perfectly. Was very simple to implement using FInstancedStruct. Below is my completed implementation.

AsyncLoadAssetClassPayload.h

/**
* Copyright(C) 2025 | Created by Krileon
*/

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "Delegates/IDelegateInstance.h"
#include "Engine/StreamableManager.h"
#include "StructUtils/InstancedStruct.h"
#include "AsyncLoadAssetClassPayload.generated.h"

UCLASS( Blueprintable )
class ASYNCLOADPAYLOAD_API UAsyncLoadAssetClassPayload : public UBlueprintAsyncActionBase
{
	GENERATED_BODY()

public:
	DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FOnAsyncLoadAssetClassPayloadSignature, UClass*, Class, FInstancedStruct, Payload );

	UPROPERTY( BlueprintAssignable, Category = "AsyncLoadPayload" )
		FOnAsyncLoadAssetClassPayloadSignature Completed;

	UFUNCTION( BlueprintCallable, meta = ( BlueprintInternalUseOnly = "true" ), Category = "AsyncLoadPayload", DisplayName = "Async Load Asset Class w/ Payload" )
		static UAsyncLoadAssetClassPayload* AsyncLoadAssetClassPayload(
			TSoftClassPtr<UObject> AssetClass,
			FInstancedStruct Payload
		);

	virtual void Activate() override;

protected:
	UPROPERTY()
		TSoftClassPtr<UObject> AssetClass;

	UPROPERTY()
		FInstancedStruct Payload;

	void OnCompleted();

	void OnFailed();
};

AsyncLoadAssetClassPayload.cpp

/**
* Copyright(C) 2025 | Created by Krileon
*/

#include "AsyncLoadPayload/Public/AsyncLoadAssetClassPayload.h"
#include "Engine/AssetManager.h"

void UAsyncLoadAssetClassPayload::Activate()
{
	if ( ! AssetClass.IsValid() ) {
		OnFailed();
		return;
	}

	if ( ! AssetClass.ToSoftObjectPath().IsValid() ) {
		OnFailed();
		return;
	}

	UAssetManager::GetStreamableManager().RequestAsyncLoad(
		AssetClass.ToSoftObjectPath(),
		FStreamableDelegate::CreateUObject( this, &UAsyncLoadAssetClassPayload::OnCompleted )
	);
}

void UAsyncLoadAssetClassPayload::OnCompleted()
{
	UClass* LoadedClass = AssetClass.Get();

	Completed.Broadcast( LoadedClass, Payload );

	SetReadyToDestroy();
}

void UAsyncLoadAssetClassPayload::OnFailed()
{
	SetReadyToDestroy();
}

UAsyncLoadAssetClassPayload* UAsyncLoadAssetClassPayload::AsyncLoadAssetClassPayload(
	TSoftClassPtr<UObject> AssetClass,
	FInstancedStruct Payload
) {
	UAsyncLoadAssetClassPayload* BlueprintNode = NewObject<UAsyncLoadAssetClassPayload>();

	BlueprintNode->AssetClass = AssetClass;
	BlueprintNode->Payload = Payload;

	return BlueprintNode;
}

AsyncLoadAssetPayload.h

/**
* Copyright(C) 2025 | Created by Krileon
*/

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "Delegates/IDelegateInstance.h"
#include "Engine/StreamableManager.h"
#include "StructUtils/InstancedStruct.h"
#include "AsyncLoadAssetPayload.generated.h"

UCLASS( Blueprintable )
class ASYNCLOADPAYLOAD_API UAsyncLoadAssetPayload : public UBlueprintAsyncActionBase
{
	GENERATED_BODY()

public:
	DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FOnAsyncLoadAssetPayloadSignature, UObject*, Object, FInstancedStruct, Payload );		

	UPROPERTY( BlueprintAssignable, Category = "AsyncLoadPayload" )
		FOnAsyncLoadAssetPayloadSignature Completed;

	UFUNCTION( BlueprintCallable, meta = ( BlueprintInternalUseOnly = "true" ), Category = "AsyncLoadPayload", DisplayName = "Async Load Asset w/ Payload" )
		static UAsyncLoadAssetPayload* AsyncLoadAssetPayload(
			TSoftObjectPtr<UObject> Asset,
			FInstancedStruct Payload
		);

	virtual void Activate() override;

protected:
	UPROPERTY()
		TSoftObjectPtr<UObject> Asset;

	UPROPERTY()
		FInstancedStruct Payload;

	void OnCompleted();

	void OnFailed();
};

AsyncLoadAssetPayload.cpp

/**
* Copyright(C) 2025 | Created by Krileon
*/

#include "AsyncLoadPayload/Public/AsyncLoadAssetPayload.h"
#include "Engine/AssetManager.h"

void UAsyncLoadAssetPayload::Activate()
{
	if ( ! Asset.IsValid() ) {
		OnFailed();
		return;
	}

	if ( ! Asset.ToSoftObjectPath().IsValid() ) {
		OnFailed();
		return;
	}

	UAssetManager::GetStreamableManager().RequestAsyncLoad(
		Asset.ToSoftObjectPath(),
		FStreamableDelegate::CreateUObject( this, &UAsyncLoadAssetPayload::OnCompleted )
	);
}

void UAsyncLoadAssetPayload::OnCompleted()
{
	UObject* LoadedObject = Asset.Get();

	Completed.Broadcast( LoadedObject, Payload );

	SetReadyToDestroy();
}

void UAsyncLoadAssetPayload::OnFailed()
{
	SetReadyToDestroy();
}

UAsyncLoadAssetPayload* UAsyncLoadAssetPayload::AsyncLoadAssetPayload(
	TSoftObjectPtr<UObject> Asset,
	FInstancedStruct Payload
) {
	UAsyncLoadAssetPayload* BlueprintNode = NewObject<UAsyncLoadAssetPayload>();

	BlueprintNode->Asset = Asset;
	BlueprintNode->Payload = Payload;

	return BlueprintNode;
}

Below is how you’d used it for example.

I made it as a UE plugin. Went ahead and attached it as well. Just drop the folder in your projects plugin folder, run your project, let it compile, and you’re good to go with using the 2 new nodes.

AsyncLoadPayload.zip (6.4 KB)