// Copyright Epic Games, Inc. All Rights Reserved. #include "BlueprintComponentNodeSpawner.h" #include "Engine/Blueprint.h" #include "GameFramework/Actor.h" #include "Engine/BlueprintGeneratedClass.h" #include "EdGraphSchema_K2.h" #include "K2Node_AddComponent.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "Styling/SlateIconFinder.h" #include "BlueprintNodeTemplateCache.h" #include "ComponentAssetBroker.h" #include "ComponentTypeRegistry.h" #define LOCTEXT_NAMESPACE "BlueprintComponenetNodeSpawner" /******************************************************************************* * Static UBlueprintComponentNodeSpawner Helpers ******************************************************************************/ namespace BlueprintComponentNodeSpawnerImpl { static FText GetDefaultMenuCategory(TSubclassOf const ComponentClass); } //------------------------------------------------------------------------------ static FText BlueprintComponentNodeSpawnerImpl::GetDefaultMenuCategory(TSubclassOf const ComponentClass) { FText ClassGroup; TArray ClassGroupNames; ComponentClass->GetClassGroupNames(ClassGroupNames); static FText const DefaultClassGroup(LOCTEXT("DefaultClassGroup", "Common")); // 'Common' takes priority over other class groups if (ClassGroupNames.Contains(DefaultClassGroup.ToString()) || (ClassGroupNames.Num() == 0)) { ClassGroup = DefaultClassGroup; } else { ClassGroup = FText::FromString(ClassGroupNames[0]); } return FText::Format(LOCTEXT("ComponentCategory", "Add Component|{0}"), ClassGroup); } /******************************************************************************* * UBlueprintComponentNodeSpawner ******************************************************************************/ //------------------------------------------------------------------------------ UBlueprintComponentNodeSpawner* UBlueprintComponentNodeSpawner::Create(const FComponentTypeEntry& Entry) { UClass* ComponentClass = Entry.ComponentClass; if (ComponentClass == nullptr) { // unloaded class, must be blueprint created. Create an entry. We'll load the class when we spawn the node: UBlueprintComponentNodeSpawner* NodeSpawner = NewObject(GetTransientPackage()); NodeSpawner->ComponentClass = nullptr; NodeSpawner->NodeClass = UK2Node_AddComponent::StaticClass(); NodeSpawner->ComponentName = Entry.ComponentName; NodeSpawner->ComponentAssetName = Entry.ComponentAssetName; FBlueprintActionUiSpec& MenuSignature = NodeSpawner->DefaultMenuSignature; FText const ComponentTypeName = FText::FromString(Entry.ComponentName); MenuSignature.MenuName = FText::Format(LOCTEXT("AddComponentMenuName", "Add {0}"), ComponentTypeName); MenuSignature.Category = LOCTEXT("BlueprintComponentCategory", "Custom"); MenuSignature.Tooltip = FText::Format(LOCTEXT("AddComponentTooltip", "Spawn a {0}"), ComponentTypeName); // add at least one character, so that PrimeDefaultUiSpec() doesn't // attempt to query the template node if (MenuSignature.Keywords.IsEmpty()) { MenuSignature.Keywords = FText::FromString(TEXT(" ")); } MenuSignature.Icon = FSlateIconFinder::FindIconForClass(nullptr); MenuSignature.DocLink = TEXT("Shared/GraphNodes/Blueprint/UK2Node_AddComponent"); MenuSignature.DocExcerptTag = TEXT("AddComponent"); return NodeSpawner; } if (!FKismetEditorUtilities::IsClassABlueprintSpawnableComponent(ComponentClass)) { // loaded class that is marked as abstract or not spawnable, don't create an entry: return nullptr; } if (ComponentClass->ClassWithin && ComponentClass->ClassWithin != UObject::StaticClass()) { // we can't support 'Within' markup on components at this time (core needs to be aware of non-CDO archetypes // that have within markup, and BP system needs to property use RF_ArchetypeObject on template objects) return nullptr; } UClass* const AuthoritativeClass = ComponentClass->GetAuthoritativeClass(); UBlueprintComponentNodeSpawner* NodeSpawner = NewObject(GetTransientPackage()); NodeSpawner->ComponentClass = AuthoritativeClass; NodeSpawner->NodeClass = UK2Node_AddComponent::StaticClass(); FBlueprintActionUiSpec& MenuSignature = NodeSpawner->DefaultMenuSignature; FText const ComponentTypeName = AuthoritativeClass->GetDisplayNameText(); MenuSignature.MenuName = FText::Format(LOCTEXT("AddComponentMenuName", "Add {0}"), ComponentTypeName); MenuSignature.Category = BlueprintComponentNodeSpawnerImpl::GetDefaultMenuCategory(AuthoritativeClass); MenuSignature.Tooltip = FText::Format(LOCTEXT("AddComponentTooltip", "Spawn a {0}"), ComponentTypeName); MenuSignature.Keywords = AuthoritativeClass->GetMetaDataText(*FBlueprintMetadata::MD_FunctionKeywords.ToString(), TEXT("UObjectKeywords"), AuthoritativeClass->GetFullGroupName(false)); // add at least one character, so that PrimeDefaultUiSpec() doesn't // attempt to query the template node if (MenuSignature.Keywords.IsEmpty()) { MenuSignature.Keywords = FText::FromString(TEXT(" ")); } MenuSignature.Icon = FSlateIconFinder::FindIconForClass(AuthoritativeClass); MenuSignature.DocLink = TEXT("Shared/GraphNodes/Blueprint/UK2Node_AddComponent"); MenuSignature.DocExcerptTag = AuthoritativeClass->GetName(); return NodeSpawner; } //------------------------------------------------------------------------------ UBlueprintComponentNodeSpawner::UBlueprintComponentNodeSpawner(FObjectInitializer const& ObjectInitializer) : Super(ObjectInitializer) { } //------------------------------------------------------------------------------ FBlueprintNodeSignature UBlueprintComponentNodeSpawner::GetSpawnerSignature() const { FBlueprintNodeSignature SpawnerSignature(NodeClass); SpawnerSignature.AddSubObject(ComponentClass.Get()); return SpawnerSignature; } //------------------------------------------------------------------------------ // Evolved from a combination of FK2ActionMenuBuilder::CreateAddComponentAction() // and FEdGraphSchemaAction_K2AddComponent::PerformAction(). UEdGraphNode* UBlueprintComponentNodeSpawner::Invoke(UEdGraph* ParentGraph, FBindingSet const& Bindings, FVector2D const Location) const { UClass* ComponentType = ComponentClass; auto PostSpawnLambda = [ComponentType](UEdGraphNode* NewNode, bool bIsTemplateNode, FCustomizeNodeDelegate UserDelegate) { UK2Node_AddComponent* AddCompNode = CastChecked(NewNode); UBlueprint* Blueprint = AddCompNode->GetBlueprint(); UFunction* AddComponentFunc = FindFieldChecked(AActor::StaticClass(), UK2Node_AddComponent::GetAddComponentFunctionName()); AddCompNode->FunctionReference.SetFromField(AddComponentFunc, !bIsTemplateNode && FBlueprintEditorUtils::IsActorBased(Blueprint)); AddCompNode->TemplateType = ComponentType; UserDelegate.ExecuteIfBound(NewNode, bIsTemplateNode); }; FCustomizeNodeDelegate PostSpawnDelegate = FCustomizeNodeDelegate::CreateLambda(PostSpawnLambda, CustomizeNodeDelegate); // let SpawnNode() allocate default pins (so we can modify them) UK2Node_AddComponent* NewNode = Super::SpawnNode(NodeClass, ParentGraph, FBindingSet(), Location, PostSpawnDelegate); if (NewNode->Pins.Num() == 0) { NewNode->AllocateDefaultPins(); } // set the return type to be the type of the template UEdGraphPin* ReturnPin = NewNode->GetReturnValuePin(); if (ReturnPin != nullptr) { if (ComponentClass != nullptr) { ReturnPin->PinType.PinSubCategoryObject = ComponentType; } else { ReturnPin->PinType.PinSubCategoryObject = UActorComponent::StaticClass(); } } bool const bIsTemplateNode = FBlueprintNodeTemplateCache::IsTemplateOuter(ParentGraph); if (!bIsTemplateNode) { TSubclassOf Class = ComponentClass; if (Class == nullptr) { const ELoadFlags LoadFlags = LOAD_None; UBlueprint* LoadedObject = LoadObject(NULL, *ComponentAssetName, NULL, LoadFlags, NULL); if (LoadedObject == nullptr) { return nullptr; } Class = TSubclassOf(Cast(LoadedObject->GeneratedClass)); if (Class == nullptr) { return nullptr; } } UBlueprint* Blueprint = NewNode->GetBlueprint(); FName DesiredComponentName = NewNode->MakeNewComponentTemplateName(Blueprint->GeneratedClass, Class); UActorComponent* ComponentTemplate = NewObject(Blueprint->GeneratedClass, Class, DesiredComponentName, RF_ArchetypeObject | RF_Public | RF_Transactional); Blueprint->ComponentTemplates.Add(ComponentTemplate); // set the name of the template as the default for the TemplateName param UEdGraphPin* TemplateNamePin = NewNode->GetTemplateNamePinChecked(); if (TemplateNamePin != nullptr) { TemplateNamePin->DefaultValue = ComponentTemplate->GetName(); } NewNode->ReconstructNode(); } // apply bindings, after we've setup the template pin ApplyBindings(NewNode, Bindings); return NewNode; } //------------------------------------------------------------------------------ FBlueprintActionUiSpec UBlueprintComponentNodeSpawner::GetUiSpec(FBlueprintActionContext const& Context, FBindingSet const& Bindings) const { UEdGraph* TargetGraph = (Context.Graphs.Num() > 0) ? Context.Graphs[0] : nullptr; FBlueprintActionUiSpec MenuSignature = PrimeDefaultUiSpec(TargetGraph); if (Bindings.Num() > 0) { FText AssetName; { FBindingObject Binding = *Bindings.CreateConstIterator(); if (Binding.IsValid()) { AssetName = FText::FromName(Binding.GetFName()); } } FText const ComponentTypeName = FText::FromName(ComponentClass->GetFName()); MenuSignature.MenuName = FText::Format(LOCTEXT("AddBoundComponentMenuName", "Add {0} (as {1})"), AssetName, ComponentTypeName); MenuSignature.Tooltip = FText::Format(LOCTEXT("AddBoundComponentTooltip", "Spawn {0} using {1}"), ComponentTypeName, AssetName); } DynamicUiSignatureGetter.ExecuteIfBound(Context, Bindings, &MenuSignature); return MenuSignature; } //------------------------------------------------------------------------------ bool UBlueprintComponentNodeSpawner::IsBindingCompatible(FBindingObject BindingCandidate) const { bool bCanBindWith = false; if (BindingCandidate.IsUObject() && BindingCandidate.Get()->IsAsset()) { TArray< TSubclassOf > ComponentClasses = FComponentAssetBrokerage::GetComponentsForAsset(BindingCandidate.Get()); bCanBindWith = ComponentClasses.Contains(ComponentClass); } return bCanBindWith; } //------------------------------------------------------------------------------ bool UBlueprintComponentNodeSpawner::BindToNode(UEdGraphNode* Node, FBindingObject Binding) const { bool bSuccessfulBinding = false; UK2Node_AddComponent* AddCompNode = CastChecked(Node); UActorComponent* ComponentTemplate = AddCompNode->GetTemplateFromNode(); if (ComponentTemplate != nullptr) { check(!Binding.IsValid() || Binding.IsUObject()); // FProp bSuccessfulBinding = FComponentAssetBrokerage::AssignAssetToComponent(ComponentTemplate, Binding.Get()); AddCompNode->ReconstructNode(); } return bSuccessfulBinding; } //------------------------------------------------------------------------------ TSubclassOf UBlueprintComponentNodeSpawner::GetComponentClass() const { return ComponentClass; } #undef LOCTEXT_NAMESPACE