Good day. Although I’m not sure if CoreRedirects will be useful to redirect specific functions in a BPFL, it’s possible to achieve what you want by writing an editor utility function that automatically finds and updates blueprint nodes.
Put the following function in an editor module (to avoid packaging problems later) with “UnrealEd” and “BlueprintGraph” added to Build.cs:
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "BlueprintRefactorUtils.generated.h"
UCLASS()
class ABILITIESLAB_API UBlueprintRefactorUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
// Searches all loaded blueprints to find function call nodes of some name,
// then replaces them to call the new function. Signatures must match.
// Blueprints must have been loaded, and must be compiled afterwards.
// bPrintOnly controls whether to replace nodes or just print results.
UFUNCTION(BlueprintCallable, meta=(CallInEditor))
static void ReplaceFunction(TSubclassOf<UBlueprintFunctionLibrary> OldLib, const FName OldFunctionName,
TSubclassOf<UBlueprintFunctionLibrary> NewLib, const FName NewFunctionName, const bool bPrintOnly);
};
#include "BlueprintRefactorUtils.h"
#include "K2Node_CallFunction.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "GenericPlatform/GenericPlatformMisc.h"
void UBlueprintRefactorUtils::ReplaceFunction(TSubclassOf<UBlueprintFunctionLibrary> OldLib, const FName OldFunctionName, TSubclassOf<UBlueprintFunctionLibrary> NewLib, const FName NewFunctionName, const bool bPrintOnly)
{
UFunction* OldFunc = OldLib->FindFunctionByName(OldFunctionName);
if (!OldFunc)
{
FString Msg = FString::Printf(TEXT("Did not find old function: %s"), *OldFunctionName.ToString());
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Msg));
return;
}
UFunction* NewFunc = NewLib->FindFunctionByName(NewFunctionName);
if (!NewFunc)
{
FString Msg = FString::Printf(TEXT("Did not find new function: %s"), * NewFunctionName.ToString());
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Msg));
return;
}
if (!OldFunc->IsSignatureCompatibleWith(NewFunc))
{
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Found function signatures were incompatible"));
return;
}
UFunction* SkelFunction = nullptr;
UBlueprint* OldLibBP = nullptr;
if (UBlueprintGeneratedClass* BpClassOwner = Cast<UBlueprintGeneratedClass>(OldLib))
{
OldLibBP = CastChecked<UBlueprint>(BpClassOwner->ClassGeneratedBy.Get(), ECastCheckedType::NullAllowed);
if (OldLibBP && OldLibBP->SkeletonGeneratedClass)
{
SkelFunction = OldLibBP->SkeletonGeneratedClass->FindFunctionByName(OldFunctionName);
}
}
if (!SkelFunction)
{
FString Msg = FString::Printf(TEXT("Did not find old function on skeleton class: %s"), *OldFunctionName.ToString());
return;
}
TArray<UObject*> FoundObjects;
GetObjectsOfClass(UBlueprint::StaticClass(), FoundObjects);
int32 NumTotalChanges = 0;
for (UObject* Obj : FoundObjects)
{
UBlueprint* BP = CastChecked<UBlueprint>(Obj);
// Don't do find and replace in the old BP library itself
if (BP == OldLibBP)
{
continue;
}
// Gather all graphs to process. This does NOT cover all possible
// blueprint graphs. There may be special graphs on other asset
// types like animation blueprints that aren't processed with this.
TArray<TObjectPtr<UEdGraph>> GraphsToProcess;
GraphsToProcess.Append(BP->EventGraphs);
GraphsToProcess.Append(BP->FunctionGraphs);
GraphsToProcess.Append(BP->UbergraphPages);
GraphsToProcess.Append(BP->MacroGraphs);
// Process the graphs of the current BP
int32 NumFileChanges = 0;
for (TObjectPtr<UEdGraph>& Graph : GraphsToProcess)
{
// Find and process all function call nodes
TArray<UK2Node_CallFunction*> CallFunctionNodes;
Graph->GetNodesOfClass<UK2Node_CallFunction>(CallFunctionNodes);
for (UK2Node_CallFunction* CallFuncNode : CallFunctionNodes)
{
// Find the function currently being called by the node
UFunction* NodeFunc = CallFuncNode->GetTargetFunction();
if (!NodeFunc)
{
continue;
}
// Check if the function is the one we want to replace
if (NodeFunc == OldFunc || NodeFunc == SkelFunction)
{
// Replace the function
if (!bPrintOnly)
{
CallFuncNode->SetFromFunction(NewFunc);
CallFuncNode->ReconstructNode();
}
++NumFileChanges;
}
}
}
if (NumFileChanges > 0)
{
UE_LOG(LogTemp, Warning, TEXT("%d nodes %s in blueprint '%s'"), NumFileChanges, bPrintOnly ? TEXT("found") : TEXT("changed"), *BP->GetPackage()->GetPathName());
if (!bPrintOnly)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
}
NumTotalChanges += NumFileChanges;
}
}
// Print total
if (!bPrintOnly)
{
FString Msg = FString::Printf(TEXT("Replaced %s -> %s in %d places"), *OldFunctionName.ToString(), *NewFunctionName.ToString(), NumTotalChanges);
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Msg));
}
else
{
FString Msg = FString::Printf(TEXT("Found %s -> %s to replace in %d places"), *OldFunctionName.ToString(), *NewFunctionName.ToString(), NumTotalChanges);
FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Msg));
}
}
And add this to the editor module’s Build.cs:
if (Target.bBuildEditor)
{
PrivateDependencyModuleNames.AddRange(new string[] { "BlueprintGraph", "UnrealEd" });
}
Then use the function in the following way:
- Launch the editor
- Open the old blueprint
- Find References > By Class Member (All) on the function to replace
- Open all the blueprints that reference the old function. Note: this step is because my utility function will only process loaded blueprints, and it could be very heavy to load all blueprints. So here we’re leveraging that blueprint search can be used to load only the relevant BPs.
- Create an editor utility widget with a button that will run the above functions. Pass in the old BP class + function, the new BPFL class + function. Set PrintOnly to true if you want to just see first how many hits you’d find, or false to do actual replacement.
Be sure to back up assets before doing this, and test thoroughly afterwards. Be aware that I just wrote this today, for this specific use case. Besides checking that the new function is being called at runtime, of course, also check the following:
- Whether Find References on modified assets after resaving still result in a reference to the old library. It shouldn’t if all function calls have been updated.
- Whether Find References > By Class Member (All) on the old BPFL function still finds the modified assets
And check if there are other types of blueprint graphs/nodes to update. The above function only replaces the basic ‘Call Function’ node types in common BP graphs, but other graph types might not be covered.
[Attachment Removed]