5.3.2, and still not fixed!
This is a pretty major flaw to have been ignored for over eight years now!
5.3.2, and still not fixed!
This is a pretty major flaw to have been ignored for over eight years now!
BUMP!
I am curious about âWHYâ SpawnAI doesnât have this feature.
My guess is that Epic doesnât focus on NPC creation and focuses more about other/bigger features in UE like MetaHuman, UE improvements/optimisation, etc. . That would explain why BehaviorTree is also so underdeveloped⌠which is all in all realy sad. But that are just guesses, I would like to know the exact reasons behind the missing âspawn on exposeâ feature in âSpawnAIâ.
I expent the last 6 hours trying to make this bp,
I am not an expert on cpp right now but I was able to show the exposed variables when class is selected and the pin of BehaviorTree if you refresh the node or copy/paste it.
the image show two times Bheaviour tree because I tried to se the BehaviorTree with other name, but without luck.
if I connect to the flow, it will complain (after you put 0,0,0 as location of course this is mandatory)
COMPILER ERROR: failed building connection with âOnly exactly matching structures are considered compatible.â at SpawnAIWithExposedVars BC MyChar
I tried with chatgpt, it might be because of this:
The pin is set to be part of the advanced view (`BehaviorTreePin->bAdvancedView = true;`), which means it might be hidden depending on the pin display setting.
So probably something somewhere is pushing Behavior tree to be hidden also, maybe. My cpp knowledge is basic. If somebody make this happen, pls let me know.
I leave here my classes, I invested a lot of time in thisâŚfor nothing, so I will just cast for now.
SGraphNodeSpawnAiWithExposedVarsFromClass.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "KismetNodes/SGraphNodeK2Default.h"
class SGraphNodeSpawnAiWithExposedVarsFromClass : public SGraphNodeK2Default
{
public:
// SGraphNode interface
virtual void CreatePinWidgets() override;
// End of SGraphNode interface
};
SGraphNodeSpawnAiWithExposedVarsFromClass.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "SGraphNodeSpawnAiWithExposedVarsFromClass.h"
#include "EdGraph/EdGraphPin.h"
#include "K2Node_SpawnAiWithExposedVarsFromClass.h"
#include "KismetPins/SGraphPinClass.h"
#include "NodeFactory.h"
void SGraphNodeSpawnAiWithExposedVarsFromClass::CreatePinWidgets()
{
UK2Node_SpawnAiWithExposedVarsFromClass* SpawnAiNode = CastChecked<UK2Node_SpawnAiWithExposedVarsFromClass>(GraphNode);
UEdGraphPin* ClassPin = SpawnAiNode->GetClassPin();
for (auto PinIt = GraphNode->Pins.CreateConstIterator(); PinIt; ++PinIt)
{
UEdGraphPin* CurrentPin = *PinIt;
// Force the BehaviorTreePin to be visible
if (CurrentPin->PinName == FK2Node_SpawnAiWithExposedVarsFromClassHelper::BehaviorTreePinName)
{
CurrentPin->bHidden = false;
}
if ((!CurrentPin->bHidden) && (CurrentPin != ClassPin))
{
TSharedPtr<SGraphPin> NewPin = FNodeFactory::CreatePinWidget(CurrentPin);
check(NewPin.IsValid());
this->AddPin(NewPin.ToSharedRef());
}
else if ((ClassPin == CurrentPin) && (!ClassPin->bHidden || (ClassPin->LinkedTo.Num() > 0)))
{
TSharedPtr<SGraphPinClass> NewPin = SNew(SGraphPinClass, ClassPin);
check(NewPin.IsValid());
this->AddPin(NewPin.ToSharedRef());
}
}
}
K2Node_SpawnAiWithExposedVarsFromClass.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Containers/Array.h"
#include "CoreMinimal.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphNodeUtils.h"
#include "Internationalization/Text.h"
#include "K2Node.h"
#include "K2Node_ConstructObjectFromClass.h"
#include "K2Node_GenericCreateObject.h"
#include "KismetCompilerMisc.h"
#include "Textures/SlateIcon.h"
#include "UObject/ObjectMacros.h"
#include "UObject/UObjectGlobals.h"
#include "K2Node_SpawnAiWithExposedVarsFromClass.generated.h"
class FBlueprintActionDatabaseRegistrar;
class FString;
class UClass;
class UEdGraph;
class UEdGraphPin;
class UObject;
struct FLinearColor;
template <typename KeyType, typename ValueType> struct TKeyValuePair;
UCLASS()
class TOPDOWN_API UK2Node_SpawnAiWithExposedVarsFromClass : public UK2Node_ConstructObjectFromClass
{
GENERATED_UCLASS_BODY()
//~ Begin UEdGraphNode Interface.
virtual void AllocateDefaultPins() override;
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
virtual void GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const override;
virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
virtual FSlateIcon GetIconAndTint(FLinearColor& OutColor) const override;
virtual bool IsCompatibleWithGraph(const UEdGraph* TargetGraph) const override;
virtual void PostPlacedNewNode() override;
//~ End UEdGraphNode Interface.
//~ Begin UObject Interface
virtual void Serialize(FArchive& Ar) override;
virtual void PostLoad() override;
//~ End UObject Interface
//~ Begin UK2Node Interface
virtual bool IsNodeSafeToIgnore() const override { return true; }
virtual void ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& OldPins) override;
virtual void GetNodeAttributes( TArray<TKeyValuePair<FString, FString>>& OutNodeAttributes ) const override;
virtual class FNodeHandlingFunctor* CreateNodeHandler(class FKismetCompilerContext& CompilerContext) const override;
//~ End UK2Node Interface
//~ Begin UK2Node_ConstructObjectFromClass Interface
virtual UClass* GetClassPinBaseClass() const;
virtual bool IsSpawnVarPin(UEdGraphPin* Pin) const override;
//~ End UK2Node_ConstructObjectFromClass Interface
private:
void FixupScaleMethodPin();
/** Get the spawn transform input pin */
UEdGraphPin* GetSpawnTransformPin() const;
/** Get the collision handling method input pin */
UEdGraphPin* GetCollisionHandlingOverridePin() const;
/** Get the collision handling method input pin */
UEdGraphPin* GetScaleMethodPin() const;
UEdGraphPin* TryGetScaleMethodPin() const;
/** Get the actor owner pin */
UEdGraphPin* GetOwnerPin() const;
void MaybeUpdateCollisionPin(TArray<UEdGraphPin*>& OldPins);
};
// Add this struct definition at the end of the file, or after the necessary includes.
struct FK2Node_SpawnAiWithExposedVarsFromClassHelper
{
static const FName SpawnTransformPinName;
static const FName SpawnEvenIfCollidingPinName;
static const FName NoCollisionFailPinName;
static const FName CollisionHandlingOverridePinName;
static const FName TransformScaleMethodPinName;
static const FName OwnerPinName;
static const FName BehaviorTreePinName;
};
K2Node_SpawnAiWithExposedVarsFromClass.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "K2Node_SpawnAiWithExposedVarsFromClass.h"
#include "BehaviorTree/BehaviorTree.h" // Include for UBehaviorTree
#include "Blueprint/AIBlueprintHelperLibrary.h"
#include "Containers/EnumAsByte.h"
#include "Containers/UnrealString.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNodeUtils.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraphSchema_K2.h"
#include "Engine/Blueprint.h"
#include "Engine/EngineTypes.h"
#include "Engine/MemberReference.h"
#include "GameFramework/Actor.h"
#include "HAL/PlatformCrt.h"
#include "HAL/PlatformMath.h"
#include "Internationalization/Internationalization.h"
#include "K2Node.h"
#include "K2Node_CallFunction.h"
#include "K2Node_EnumLiteral.h"
#include "K2Node_Select.h"
#include "Kismet/GameplayStatics.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/CompilerResultsLog.h"
#include "KismetCompiler.h"
#include "KismetCompilerMisc.h"
#include "Math/Transform.h"
#include "Misc/AssertionMacros.h"
#include "Styling/AppStyle.h"
#include "Templates/Casts.h"
#include "Templates/SubclassOf.h"
#include "Templates/UnrealTemplate.h"
#include "UObject/Class.h"
#include "UObject/NameTypes.h"
#include "UObject/Object.h"
#include "UObject/ObjectPtr.h"
#include "UObject/UE5MainStreamObjectVersion.h"
struct FLinearColor;
const FName FK2Node_SpawnAiWithExposedVarsFromClassHelper::SpawnTransformPinName(TEXT("SpawnTransform"));
const FName FK2Node_SpawnAiWithExposedVarsFromClassHelper::SpawnEvenIfCollidingPinName(TEXT("SpawnEvenIfColliding")); // deprecated pin, name kept for backwards compat
const FName FK2Node_SpawnAiWithExposedVarsFromClassHelper::NoCollisionFailPinName(TEXT("bNoCollisionFail")); // deprecated pin, name kept for backwards compat
const FName FK2Node_SpawnAiWithExposedVarsFromClassHelper::CollisionHandlingOverridePinName(TEXT("CollisionHandlingOverride"));
const FName FK2Node_SpawnAiWithExposedVarsFromClassHelper::TransformScaleMethodPinName(TEXT("TransformScaleMethod"));
const FName FK2Node_SpawnAiWithExposedVarsFromClassHelper::OwnerPinName(TEXT("Owner"));
const FName FK2Node_SpawnAiWithExposedVarsFromClassHelper::BehaviorTreePinName(TEXT("BehaviorTree"));
#define LOCTEXT_NAMESPACE "K2Node_SpawnAiWithExposedVarsFromClass"
UK2Node_SpawnAiWithExposedVarsFromClass::UK2Node_SpawnAiWithExposedVarsFromClass(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
NodeTooltip = LOCTEXT("NodeTooltip", "Attempts to spawn a new AI with the specified transform");
}
UClass* UK2Node_SpawnAiWithExposedVarsFromClass::GetClassPinBaseClass() const
{
return AActor::StaticClass();
}
void UK2Node_SpawnAiWithExposedVarsFromClass::AllocateDefaultPins()
{
Super::AllocateDefaultPins();
// Transform pin
UScriptStruct* TransformStruct = TBaseStructure<FTransform>::Get();
UEdGraphPin* TransformPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Struct, TransformStruct, FK2Node_SpawnAiWithExposedVarsFromClassHelper::SpawnTransformPinName);
// Collision handling method pin
UEnum* const CollisionMethodEnum = StaticEnum<ESpawnActorCollisionHandlingMethod>();
UEdGraphPin* const CollisionHandlingOverridePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, CollisionMethodEnum, FK2Node_SpawnAiWithExposedVarsFromClassHelper::CollisionHandlingOverridePinName);
CollisionHandlingOverridePin->DefaultValue = CollisionMethodEnum->GetNameStringByValue(static_cast<int>(ESpawnActorCollisionHandlingMethod::Undefined));
// Pin to set transform scaling behavior
UEnum* const ScaleMethodEnum = StaticEnum<ESpawnActorScaleMethod>();
UEdGraphPin* const ScaleMethodPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, ScaleMethodEnum, FK2Node_SpawnAiWithExposedVarsFromClassHelper::TransformScaleMethodPinName);
ScaleMethodPin->DefaultValue = ScaleMethodEnum->GetNameStringByValue(static_cast<int>(ESpawnActorScaleMethod::SelectDefaultAtRuntime));
ScaleMethodPin->bAdvancedView = true;
// Owner pin
UEdGraphPin* OwnerPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, AActor::StaticClass(), FK2Node_SpawnAiWithExposedVarsFromClassHelper::OwnerPinName);
OwnerPin->bAdvancedView = true;
// Create the original BehaviorTreePin
UEdGraphPin* BehaviorTreePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UBehaviorTree::StaticClass(), FK2Node_SpawnAiWithExposedVarsFromClassHelper::BehaviorTreePinName);
BehaviorTreePin->bAdvancedView = true;
// Create a duplicate BehaviorTreePin with a different name to prevent it from being hidden
UEdGraphPin* BehaviorTreePinDuplicate = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UBehaviorTree::StaticClass(), TEXT("VisibleBehaviorTreePin"));
BehaviorTreePinDuplicate->bAdvancedView = false; // This pin should always be visible
if (ENodeAdvancedPins::NoPins == AdvancedPinDisplay)
{
AdvancedPinDisplay = ENodeAdvancedPins::Hidden;
}
if (ENodeAdvancedPins::NoPins == AdvancedPinDisplay)
{
AdvancedPinDisplay = ENodeAdvancedPins::Hidden;
}
}
void UK2Node_SpawnAiWithExposedVarsFromClass::MaybeUpdateCollisionPin(TArray<UEdGraphPin*>& OldPins)
{
// see if there's a bNoCollisionFail pin
for (UEdGraphPin* Pin : OldPins)
{
if (Pin->PinName == FK2Node_SpawnAiWithExposedVarsFromClassHelper::NoCollisionFailPinName || Pin->PinName == FK2Node_SpawnAiWithExposedVarsFromClassHelper::SpawnEvenIfCollidingPinName)
{
bool bHadOldCollisionPin = true;
if (Pin->LinkedTo.Num() == 0)
{
// no links, use the default value of the pin
bool const bOldCollisionPinValue = (Pin->DefaultValue == FString(TEXT("true")));
UEdGraphPin* const CollisionHandlingOverridePin = GetCollisionHandlingOverridePin();
if (CollisionHandlingOverridePin)
{
UEnum const* const MethodEnum = FindObjectChecked<UEnum>(nullptr, TEXT("/Script/Engine.ESpawnActorCollisionHandlingMethod"), true);
CollisionHandlingOverridePin->DefaultValue =
bOldCollisionPinValue
? MethodEnum->GetNameStringByValue(static_cast<int>(ESpawnActorCollisionHandlingMethod::AlwaysSpawn))
: MethodEnum->GetNameStringByValue(static_cast<int>(ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding));
}
}
else
{
// something was linked. we will just move the links to the new pin
// #note: this will be an invalid linkage and the BP compiler will complain, and that's intentional
// so that users will be able to see and fix issues
UEdGraphPin* const CollisionHandlingOverridePin = GetCollisionHandlingOverridePin();
check(CollisionHandlingOverridePin);
UEnum* const MethodEnum = FindObjectChecked<UEnum>(nullptr, TEXT("/Script/Engine.ESpawnActorCollisionHandlingMethod"), true);
FGraphNodeCreator<UK2Node_EnumLiteral> AlwaysSpawnLiteralCreator(*GetGraph());
UK2Node_EnumLiteral* const AlwaysSpawnLiteralNode = AlwaysSpawnLiteralCreator.CreateNode();
AlwaysSpawnLiteralNode->Enum = MethodEnum;
AlwaysSpawnLiteralNode->NodePosX = NodePosX;
AlwaysSpawnLiteralNode->NodePosY = NodePosY;
AlwaysSpawnLiteralCreator.Finalize();
FGraphNodeCreator<UK2Node_EnumLiteral> AdjustIfNecessaryLiteralCreator(*GetGraph());
UK2Node_EnumLiteral* const AdjustIfNecessaryLiteralNode = AdjustIfNecessaryLiteralCreator.CreateNode();
AdjustIfNecessaryLiteralNode->Enum = MethodEnum;
AdjustIfNecessaryLiteralNode->NodePosX = NodePosX;
AdjustIfNecessaryLiteralNode->NodePosY = NodePosY;
AdjustIfNecessaryLiteralCreator.Finalize();
FGraphNodeCreator<UK2Node_Select> SelectCreator(*GetGraph());
UK2Node_Select* const SelectNode = SelectCreator.CreateNode();
SelectNode->NodePosX = NodePosX;
SelectNode->NodePosY = NodePosY;
SelectCreator.Finalize();
// find pins we want to set and link up
auto FindEnumInputPin = [](UK2Node_EnumLiteral const* Node)
{
for (UEdGraphPin* NodePin : Node->Pins)
{
if (NodePin->PinName == Node->GetEnumInputPinName())
{
return NodePin;
}
}
return (UEdGraphPin*)nullptr;
};
UEdGraphPin* const AlwaysSpawnLiteralNodeInputPin = FindEnumInputPin(AlwaysSpawnLiteralNode);
UEdGraphPin* const AdjustIfNecessaryLiteralInputPin = FindEnumInputPin(AdjustIfNecessaryLiteralNode);
TArray<UEdGraphPin*> SelectOptionPins;
SelectNode->GetOptionPins(SelectOptionPins);
UEdGraphPin* const SelectIndexPin = SelectNode->GetIndexPin();
auto FindResultPin = [](UK2Node const* Node)
{
for (UEdGraphPin* NodePin : Node->Pins)
{
if (EEdGraphPinDirection::EGPD_Output == NodePin->Direction)
{
return NodePin;
}
}
return (UEdGraphPin*)nullptr;
};
UEdGraphPin* const AlwaysSpawnLiteralNodeResultPin = FindResultPin(AlwaysSpawnLiteralNode);
check(AlwaysSpawnLiteralNodeResultPin);
UEdGraphPin* const AdjustIfNecessaryLiteralResultPin = FindResultPin(AdjustIfNecessaryLiteralNode);
check(AdjustIfNecessaryLiteralResultPin);
UEdGraphPin* const OldBoolPin = Pin->LinkedTo[0];
check(OldBoolPin);
//
// now set data and links that we want to set
//
AlwaysSpawnLiteralNodeInputPin->DefaultValue = MethodEnum->GetNameStringByValue(static_cast<int>(ESpawnActorCollisionHandlingMethod::AlwaysSpawn));
AdjustIfNecessaryLiteralInputPin->DefaultValue = MethodEnum->GetNameStringByValue(static_cast<int>(ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding));
OldBoolPin->BreakLinkTo(Pin);
OldBoolPin->MakeLinkTo(SelectIndexPin);
AlwaysSpawnLiteralNodeResultPin->MakeLinkTo(SelectOptionPins[0]);
AdjustIfNecessaryLiteralResultPin->MakeLinkTo(SelectOptionPins[1]);
UEdGraphPin* const SelectOutputPin = SelectNode->GetReturnValuePin();
check(SelectOutputPin);
SelectOutputPin->MakeLinkTo(CollisionHandlingOverridePin);
// tell select node to update its wildcard status
SelectNode->NotifyPinConnectionListChanged(SelectIndexPin);
SelectNode->NotifyPinConnectionListChanged(SelectOptionPins[0]);
SelectNode->NotifyPinConnectionListChanged(SelectOptionPins[1]);
SelectNode->NotifyPinConnectionListChanged(SelectOutputPin);
}
}
}
}
void UK2Node_SpawnAiWithExposedVarsFromClass::ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& OldPins)
{
Super::ReallocatePinsDuringReconstruction(OldPins);
// Ensure the original and duplicate BehaviorTree pins are present
UEdGraphPin* BehaviorTreePin = FindPin(FK2Node_SpawnAiWithExposedVarsFromClassHelper::BehaviorTreePinName);
if (!BehaviorTreePin)
{
BehaviorTreePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UBehaviorTree::StaticClass(), FK2Node_SpawnAiWithExposedVarsFromClassHelper::BehaviorTreePinName);
BehaviorTreePin->bAdvancedView = true;
}
UEdGraphPin* BehaviorTreePinDuplicate = FindPin(TEXT("VisibleBehaviorTreePin"));
if (!BehaviorTreePinDuplicate)
{
BehaviorTreePinDuplicate = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UBehaviorTree::StaticClass(), TEXT("VisibleBehaviorTreePin"));
BehaviorTreePinDuplicate->bAdvancedView = false;
}
// Move existing connections from old pins to the new pins
for (UEdGraphPin* OldPin : OldPins)
{
if (OldPin->PinName == FK2Node_SpawnAiWithExposedVarsFromClassHelper::BehaviorTreePinName ||
OldPin->PinName == TEXT("VisibleBehaviorTreePin"))
{
if (OldPin->LinkedTo.Num() > 0)
{
for (UEdGraphPin* LinkedPin : OldPin->LinkedTo)
{
LinkedPin->Modify(); // Mark the linked pin as modified
LinkedPin->BreakLinkTo(OldPin);
LinkedPin->MakeLinkTo(BehaviorTreePinDuplicate); // Connect to the duplicate pin
}
}
}
}
}
bool UK2Node_SpawnAiWithExposedVarsFromClass::IsSpawnVarPin(UEdGraphPin* Pin) const
{
UEdGraphPin* ParentPin = Pin->ParentPin;
while (ParentPin)
{
if (ParentPin->PinName == FK2Node_SpawnAiWithExposedVarsFromClassHelper::SpawnTransformPinName)
{
return false;
}
ParentPin = ParentPin->ParentPin;
}
return( Super::IsSpawnVarPin(Pin) &&
Pin->PinName != FK2Node_SpawnAiWithExposedVarsFromClassHelper::TransformScaleMethodPinName &&
Pin->PinName != FK2Node_SpawnAiWithExposedVarsFromClassHelper::CollisionHandlingOverridePinName &&
Pin->PinName != FK2Node_SpawnAiWithExposedVarsFromClassHelper::SpawnTransformPinName &&
Pin->PinName != FK2Node_SpawnAiWithExposedVarsFromClassHelper::OwnerPinName );
}
void UK2Node_SpawnAiWithExposedVarsFromClass::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
if (UEdGraphPin* TransformPin = GetSpawnTransformPin())
{
K2Schema->ConstructBasicPinTooltip(*TransformPin, LOCTEXT("TransformPinDescription", "The transform to spawn the Actor with"), TransformPin->PinToolTip);
}
if (UEdGraphPin* CollisionHandlingOverridePin = GetCollisionHandlingOverridePin())
{
K2Schema->ConstructBasicPinTooltip(*CollisionHandlingOverridePin, LOCTEXT("CollisionHandlingOverridePinDescription", "Specifies how to handle collisions at the spawn point. If undefined, uses actor class settings."), CollisionHandlingOverridePin->PinToolTip);
}
if (UEdGraphPin* OwnerPin = GetOwnerPin())
{
K2Schema->ConstructBasicPinTooltip(*OwnerPin, LOCTEXT("OwnerPinDescription", "Can be left empty; primarily used for replication (bNetUseOwnerRelevancy and bOnlyRelevantToOwner), or visibility (PrimitiveComponent's bOwnerNoSee/bOnlyOwnerSee)"), OwnerPin->PinToolTip);
}
return Super::GetPinHoverText(Pin, HoverTextOut);
}
FSlateIcon UK2Node_SpawnAiWithExposedVarsFromClass::GetIconAndTint(FLinearColor& OutColor) const
{
static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.SpawnActor_16x");
return Icon;
}
UEdGraphPin* UK2Node_SpawnAiWithExposedVarsFromClass::GetSpawnTransformPin() const
{
UEdGraphPin* Pin = FindPinChecked(FK2Node_SpawnAiWithExposedVarsFromClassHelper::SpawnTransformPinName);
check(Pin->Direction == EGPD_Input);
return Pin;
}
UEdGraphPin* UK2Node_SpawnAiWithExposedVarsFromClass::GetCollisionHandlingOverridePin() const
{
UEdGraphPin* const Pin = FindPinChecked(FK2Node_SpawnAiWithExposedVarsFromClassHelper::CollisionHandlingOverridePinName);
check(Pin->Direction == EGPD_Input);
return Pin;
}
UEdGraphPin* UK2Node_SpawnAiWithExposedVarsFromClass::GetScaleMethodPin() const
{
UEdGraphPin* const Pin = FindPinChecked(FK2Node_SpawnAiWithExposedVarsFromClassHelper::TransformScaleMethodPinName);
check(Pin->Direction == EGPD_Input);
return Pin;
}
UEdGraphPin* UK2Node_SpawnAiWithExposedVarsFromClass::TryGetScaleMethodPin() const
{
return FindPin(FK2Node_SpawnAiWithExposedVarsFromClassHelper::TransformScaleMethodPinName, EGPD_Input);
}
UEdGraphPin* UK2Node_SpawnAiWithExposedVarsFromClass::GetOwnerPin() const
{
UEdGraphPin* Pin = FindPin(FK2Node_SpawnAiWithExposedVarsFromClassHelper::OwnerPinName);
check(Pin == nullptr || Pin->Direction == EGPD_Input);
return Pin;
}
FText UK2Node_SpawnAiWithExposedVarsFromClass::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
FText NodeTitle = NSLOCTEXT("K2Node", "SpawnAiWithExposedVars_BaseTitle", "Spawn AI with Exposed Vars from Class");
if (TitleType != ENodeTitleType::MenuTitle)
{
if (UEdGraphPin* ClassPin = GetClassPin())
{
if (ClassPin->LinkedTo.Num() > 0)
{
// Blueprint will be determined dynamically, so we don't have the name in this case
NodeTitle = NSLOCTEXT("K2Node", "SpawnAiWithExposedVars_Title_Unknown", "SpawnAIWithExposedVars");
}
else if (ClassPin->DefaultObject == nullptr)
{
NodeTitle = NSLOCTEXT("K2Node", "SpawnAiWithExposedVars_Title_NONE", "SpawnAIWithExposedVars NONE");
}
else
{
if (CachedNodeTitle.IsOutOfDate(this))
{
FText ClassName;
if (UClass* PickedClass = Cast<UClass>(ClassPin->DefaultObject))
{
ClassName = PickedClass->GetDisplayNameText();
}
FFormatNamedArguments Args;
Args.Add(TEXT("ClassName"), ClassName);
// FText::Format() is slow, so we cache this to save on performance
CachedNodeTitle.SetCachedText(FText::Format(NSLOCTEXT("K2Node", "SpawnAiWithExposedVars_Title_Class", "SpawnAIWithExposedVars {ClassName}"), Args), this);
}
NodeTitle = CachedNodeTitle;
}
}
else
{
NodeTitle = NSLOCTEXT("K2Node", "SpawnAiWithExposedVars_Title_NONE", "SpawnAIWithExposedVars NONE");
}
}
return NodeTitle;
}
bool UK2Node_SpawnAiWithExposedVarsFromClass::IsCompatibleWithGraph(const UEdGraph* TargetGraph) const
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(TargetGraph);
return Super::IsCompatibleWithGraph(TargetGraph) && (!Blueprint || (FBlueprintEditorUtils::FindUserConstructionScript(Blueprint) != TargetGraph && Blueprint->GeneratedClass->GetDefaultObject()->ImplementsGetWorld()));
}
void UK2Node_SpawnAiWithExposedVarsFromClass::PostPlacedNewNode()
{
UEdGraphPin* const ScaleMethodPin = GetScaleMethodPin();
const UEnum* const ScaleMethodEnum = StaticEnum<ESpawnActorScaleMethod>();
ScaleMethodPin->DefaultValue = ScaleMethodEnum->GetNameStringByValue(static_cast<int>(ESpawnActorScaleMethod::MultiplyWithRoot));
}
void UK2Node_SpawnAiWithExposedVarsFromClass::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
Ar.UsingCustomVersion(FUE5MainStreamObjectVersion::GUID);
}
void UK2Node_SpawnAiWithExposedVarsFromClass::PostLoad()
{
FixupScaleMethodPin();
Super::PostLoad();
}
void UK2Node_SpawnAiWithExposedVarsFromClass::FixupScaleMethodPin()
{
// if this node is being diffed, don't fix up anything. Keep the legacy pins
const UPackage* Package = GetPackage();
if (Package && Package->HasAnyPackageFlags(PKG_ForDiffing))
{
return;
}
// For the sake of cook determinism, leave the scale method as ESpawnActorScaleMethod::SelectDefaultAtRuntime when cooking.
// This shouldn't impact runtime behavior.
if (IsRunningCookCommandlet())
{
return;
}
if (GetLinkerCustomVersion(FUE5MainStreamObjectVersion::GUID) < FUE5MainStreamObjectVersion::SpawnActorFromClassTransformScaleMethod)
{
if (UEdGraphPin* const ScaleMethodPin = TryGetScaleMethodPin())
{
const UEdGraphPin* const ClassPin = FindPin(TEXT( "Class" ));
const UEnum* const ScaleMethodEnum = StaticEnum<ESpawnActorScaleMethod>();
if (const UClass* Class = Cast<UClass>(ClassPin->DefaultObject))
{
if (const AActor* ActorCDO = Cast<AActor>(Class->ClassDefaultObject))
{
if (const USceneComponent* Root = ActorCDO->GetRootComponent()) // native root component
{
ScaleMethodPin->DefaultValue = ScaleMethodEnum->GetNameStringByValue(static_cast<int>(ESpawnActorScaleMethod::MultiplyWithRoot));
}
else
{
ScaleMethodPin->DefaultValue = ScaleMethodEnum->GetNameStringByValue(static_cast<int>(ESpawnActorScaleMethod::OverrideRootScale));
}
}
}
else
{
// if the class can't be determined during compile time, defer the default value to runtime
ScaleMethodPin->DefaultValue = ScaleMethodEnum->GetNameStringByValue(static_cast<int>(ESpawnActorScaleMethod::SelectDefaultAtRuntime));
}
}
else
{
UE_LOG(LogBlueprint, Warning, TEXT("Blueprint Node '%s' is missing '%s' pin. Proceeding as if it's default is SelectDefaultAtRuntime"),
*GetNodeTitle(ENodeTitleType::FullTitle).ToString(),
*FK2Node_SpawnAiWithExposedVarsFromClassHelper::TransformScaleMethodPinName.ToString()
)
}
}
}
void UK2Node_SpawnAiWithExposedVarsFromClass::GetNodeAttributes( TArray<TKeyValuePair<FString, FString>>& OutNodeAttributes ) const
{
UClass* ClassToSpawn = GetClassToSpawn();
const FString ClassToSpawnStr = ClassToSpawn ? ClassToSpawn->GetName() : TEXT( "InvalidClass" );
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "Type" ), TEXT( "SpawnAiWithExposedVarsFromClass" ) ));
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "Class" ), GetClass()->GetName() ));
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "Name" ), GetName() ));
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "ActorClass" ), ClassToSpawnStr ));
}
FNodeHandlingFunctor* UK2Node_SpawnAiWithExposedVarsFromClass::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const
{
return new FNodeHandlingFunctor(CompilerContext);
}
void UK2Node_SpawnAiWithExposedVarsFromClass::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
Super::ExpandNode(CompilerContext, SourceGraph);
// Fixup the Scale Method Pin
FixupScaleMethodPin();
// Define the function names used
static const FName SpawnAIFromClassFuncName = GET_FUNCTION_NAME_CHECKED(UAIBlueprintHelperLibrary, SpawnAIFromClass);
static const FName WorldContextParamName(TEXT("WorldContextObject"));
static const FName ActorClassParamName(TEXT("PawnClass"));
static const FName BehaviorTreeParamName(TEXT("BehaviorTree"));
static const FName LocationParamName(TEXT("Location"));
static const FName RotationParamName(TEXT("Rotation"));
static const FName CollisionHandlingOverrideParamName(TEXT("bNoCollisionFail"));
static const FName OwnerParamName(TEXT("Owner"));
// Retrieve pins
UEdGraphPin* SpawnNodeExec = GetExecPin();
UEdGraphPin* SpawnNodeTransform = GetSpawnTransformPin();
UEdGraphPin* SpawnNodeCollisionHandlingOverride = GetCollisionHandlingOverridePin();
UEdGraphPin* SpawnWorldContextPin = GetWorldContextPin();
UEdGraphPin* SpawnClassPin = GetClassPin();
UEdGraphPin* SpawnNodeOwnerPin = GetOwnerPin();
UEdGraphPin* SpawnNodeThen = GetThenPin();
UEdGraphPin* SpawnNodeResult = GetResultPin();
UEdGraphPin* SpawnNodeBehaviorTreePin = FindPin(FK2Node_SpawnAiWithExposedVarsFromClassHelper::BehaviorTreePinName);
// Ensure all necessary pins are found
if (!SpawnNodeExec || !SpawnNodeTransform || !SpawnNodeResult || !SpawnClassPin)
{
CompilerContext.MessageLog.Error(*LOCTEXT("NullPinError", "Spawn AI node has missing pins").ToString(), this);
BreakAllNodeLinks();
return;
}
// Create the 'spawn AI' node
UK2Node_CallFunction* CallSpawnAINode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
CallSpawnAINode->FunctionReference.SetExternalMember(SpawnAIFromClassFuncName, UAIBlueprintHelperLibrary::StaticClass());
CallSpawnAINode->AllocateDefaultPins();
// Link the Exec pin
CompilerContext.MovePinLinksToIntermediate(*SpawnNodeExec, *CallSpawnAINode->GetExecPin());
// Link the world context, class, collision handling, and owner pins
if (SpawnWorldContextPin && CallSpawnAINode->FindPin(WorldContextParamName))
{
CompilerContext.MovePinLinksToIntermediate(*SpawnWorldContextPin, *CallSpawnAINode->FindPinChecked(WorldContextParamName));
}
CompilerContext.MovePinLinksToIntermediate(*SpawnClassPin, *CallSpawnAINode->FindPinChecked(ActorClassParamName));
CompilerContext.MovePinLinksToIntermediate(*SpawnNodeCollisionHandlingOverride, *CallSpawnAINode->FindPinChecked(CollisionHandlingOverrideParamName));
CompilerContext.MovePinLinksToIntermediate(*SpawnNodeOwnerPin, *CallSpawnAINode->FindPinChecked(OwnerParamName));
// Link the optional behavior tree pin if connected
if (SpawnNodeBehaviorTreePin && SpawnNodeBehaviorTreePin->LinkedTo.Num() > 0)
{
CompilerContext.MovePinLinksToIntermediate(*SpawnNodeBehaviorTreePin, *CallSpawnAINode->FindPinChecked(BehaviorTreeParamName));
}
// Handle the transform pin: ensure it contains both location and rotation
if (SpawnNodeTransform->LinkedTo.Num() > 0)
{
UEdGraphPin* CallBeginLocationPin = CallSpawnAINode->FindPinChecked(LocationParamName);
UEdGraphPin* CallBeginRotationPin = CallSpawnAINode->FindPinChecked(RotationParamName);
CompilerContext.MovePinLinksToIntermediate(*SpawnNodeTransform, *CallBeginLocationPin);
CompilerContext.MovePinLinksToIntermediate(*SpawnNodeTransform, *CallBeginRotationPin);
}
else
{
CompilerContext.MessageLog.Error(*LOCTEXT("SpawnTransformError", "The current value of the 'Spawn Transform' pin is invalid: 'Spawn Transform' must have an input wired into it.").ToString(), this);
BreakAllNodeLinks();
return;
}
// Link the result pin (spawned AI pawn)
CompilerContext.MovePinLinksToIntermediate(*SpawnNodeResult, *CallSpawnAINode->GetReturnValuePin());
// Link the 'then' pin
CompilerContext.MovePinLinksToIntermediate(*SpawnNodeThen, *CallSpawnAINode->GetThenPin());
// Break any links to the expanded node
BreakAllNodeLinks();
}
#undef LOCTEXT_NAMESPACE
Wuoah dude, thatâs interestening! I wouldnât say you wasted any time for that. Will look into what you did in detail some day.
But still, what bugs me is why the UE developer didnât implement exposed variables for âSpawnAIâ-Function themself. Like, is there any reasoning behind it?