Как стать автором
Обновить

Делаем свою Blueprint K2 Node в Unreal Engine

Уровень сложностиСложный
Время на прочтение14 мин
Количество просмотров1.3K
Созданная в конце гайда нода
Созданная в конце гайда нода

Иногда надо создать функцию, которая должна быть и доступна в blueprints, и адаптироваться под входные данные. Особенно это касается wildcard.

Можно прибегнуть к ручной прописке рефлексии UFUNCTION. Однако, у этого есть свои ограничения. Для таких случаев в движке есть довольно старый класс – UK2Node. Ниже приведены примеры движковых реализации этого класса.

K2 ноды в Unreal Engine
K2 ноды в Unreal Engine

Что такое K2 Node

ВАЖНО! Работа с нодами должна проходить в uncooked модуле.

Стоит начать с того, что UK2Node это довольно старый класс, в котором всю работу надо писать руками.

Что это означает? Вам надо самим:

  1. Добавить каждый пин.

  2. Обновить тип пина при изменении.

  3. Обновить отображаемый тип пина при изменении.

  4. Зарегистрировать ноду в контекстном меню.

  5. Перемещать/удалять/создавать соединения между пинами.

  6. Прописать название(опционально).

  7. Прописать описание(опционально).

  8. Добавить описание каждого пина(опционально).

Звучит как что-то не очень увлекательное(так и есть), но другого выбора нет. Зато открывается больше возможностей для кастомизации.


Создаем класс

Для примера, создадим ноду, которая будет принимать на вход структуру Input Action Value и сам объект Action Value, а на выходе получать значение нужного нам типа. Если наше действие работает с float, то на выходе мы получим float, если bool, то bool, и т.д.

Приступим.

h. файл
#pragma once

#include "CoreMinimal.h"
#include "K2Node.h"
#include "K2Node_GetInputValue.generated.h"

UCLASS()
class UK2Node_GetInputValue : public UK2Node
{
	GENERATED_BODY()

public:
	//~UEdGraphNode interface
	virtual void PostReconstructNode() override;
	virtual void PinDefaultValueChanged(UEdGraphPin* ChangedPin) override;
	virtual void ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
	//~End of UEdGraphNode interface

	//~UK2Node interface
	virtual void AllocateDefaultPins() override;
	virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
	virtual bool IsNodePure() const override { return true; }

	// Здесь прописываем текст, который будет появляться при наведении на ноду
	virtual FText GetTooltipText() const override
	{
		return NSLOCTEXT("K2Node", "GetInputValue", "Extract input value with type from action.");
	}

	// Пишем отображаемое название нашей функции
	virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override
	{
		return NSLOCTEXT("K2Node", "GetInputValue", "Get Input Value");
	}

	// Пишем название категории, в которой будет находиться наша функция
	virtual FText GetMenuCategory() const override { return NSLOCTEXT("K2Node", "InputCategory", "Enhanced Input"); }
	//~End of UK2Node interface

private:
	void RefreshOutputPinType();

	// Просто для удобства
	UEdGraphPin* GetActionValuePin() const { return FindPinChecked(TEXT("ActionValue")); }
	UEdGraphPin* GetActionPin() const { return FindPinChecked(TEXT("Action")); }
	UEdGraphPin* GetOutputPin() const { return FindPinChecked(TEXT("Value")); }
};

Создаем входные и выходные пины

AllocateDefaultPins
void UK2Node_GetInputValue::AllocateDefaultPins()
{
	/** Creates input pins for ActionValue and Action, and an output pin for Value. */

	// Action value pin
	UEdGraphPin* ActionValuePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Struct, FInputActionValue::StaticStruct(),
	                                        TEXT("ActionValue"));
	ActionValuePin->PinToolTip = TEXT("Value received from the input system for the specified action.");

	// Action object pin
	UEdGraphPin* ActionPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UInputAction::StaticClass(),
	                                   TEXT("Action"));
	ActionPin->PinToolTip = TEXT(
		"Input action to extract the expected value type from (used to determine output type).");

	// Output action value type pin
	UEdGraphPin* ValuePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, TEXT("Value"));
	ValuePin->PinToolTip = TEXT(
		"The extracted value of the input action, matching the expected type (bool, float, or Vector2D).");

	Super::AllocateDefaultPins();
}

Эта функция отвечает за само создание пинов. В ней мы и прописываем входные и выходные параметры. Тут мы сразу указываем их базовый тип, который и будет отображаться при начальном отображении ноды при создании в blueprint graph.

Итак, что у нас тут есть? Мы создали 3 пина:

  1. Входной пин структуры Input Action Value.

  2. Входной пин самого Input Action.

  3. Выходной пин типа wildcard, который мы и будем динамически менять.

Также, сразу даем им описание, которое высветится при наведении на них курсором.

Обновляем отображаемый тип пинов

PostReconstructNode и PinDefaultValueChanged

Эти 2 функции нам нужны именно для пункта про ручное обновление отображаемого типа пинов. Они вызываются при реконстракте и ручном изменении значении пинов в редакторе. Для этих двух методов в private секции дополнительно создана функция RefreshOutputPinType. Т.к. её мы и будем вызывать в обоих случаях.

Обновление пинов
void UK2Node_GetInputValue::PostReconstructNode()
{
	Super::PostReconstructNode();

	RefreshOutputPinType();
}

void UK2Node_GetInputValue::PinDefaultValueChanged(UEdGraphPin* ChangedPin)
{
	if (ChangedPin == GetActionPin())
	{
		RefreshOutputPinType();
	}
}

void UK2Node_GetInputValue::RefreshOutputPinType()
{
	/** Updates the output pin type based on the selected Action's ValueType. */
	UEdGraphPin* OutputPin = GetOutputPin();
	if (!OutputPin) return;

	OutputPin->Modify();

	// Resets pin type before updating
	OutputPin->PinType = FEdGraphPinType();

	const UEdGraphPin* ActionPin = GetActionPin();
	if (!ActionPin || !ActionPin->DefaultObject)
	{
		OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
		return;
	}

	const UInputAction* InputAction = Cast<UInputAction>(ActionPin->DefaultObject);
	if (!InputAction) return;

	switch (InputAction->ValueType)
	{
	case EInputActionValueType::Boolean:
		OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
		break;
	case EInputActionValueType::Axis1D:
		OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Real;
		OutputPin->PinType.PinSubCategory = TEXT("double");
		break;
	case EInputActionValueType::Axis2D:
		OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Struct;
		OutputPin->PinType.PinSubCategoryObject = TBaseStructure<FVector2D>::Get();
		break;
	case EInputActionValueType::Axis3D:
		OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Struct;
		OutputPin->PinType.PinSubCategoryObject = TBaseStructure<FVector>::Get();
		break;
	default:
		OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
		break;
	}

	// Notifies the system about pin type changes
	GetSchema()->ForceVisualizationCacheClear();
	GetGraph()->NotifyGraphChanged();
}

К первым двум функциям особо уже добавить нечего. Разве что в PinDefaultValueChanged мы обновляем наш выходной пин только при изменении нашего объекта Input Action, т.к. с него мы и получим наш выходной тип.

RefreshOutputPinType

Здесь и кроется вся логика отображаемого(важно) выходного значения.

OutputPin->Modify();
OutputPin->PinType = FEdGraphPinType();

Говорим движку, что мы меняем выходной пин(нужно для системы undo/redo), а также сбрасываем его тип перед изменением.

Далее мы пытаемся вытащить из нашего объекта, Input Action, тип его принимаемого значения и на его основе уже устанавливаем отображаемый тип нашего выходного пина. Т.к. это обычный switch, то сделаю акцент лишь на основных трех полях.

OutputPin->PinType.PinCategory
OutputPin->PinType.PinSubCategoryObject
OutputPin->PinType.PinSubCategory

Именно здесь мы и задаем тот самый тип, который потом будет отображен в blueprint graph.

GetSchema()->ForceVisualizationCacheClear();
GetGraph()->NotifyGraphChanged();

Тут мы обновляем "рендер", чтобы наша нода корректно отобразилась после изменений и сообщаем о том, что blueprint изменился и надо бы его скомпилировать.

Регистрируем ноду в контекстном меню

virtual FText GetMenuCategory() const override { return NSLOCTEXT("K2Node", "InputCategory", "Enhanced Input"); }
void UK2Node_GetInputValue::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
	/** Registers this node in the blueprint action database. */
	if (!ActionRegistrar.IsOpenForRegistration(GetClass())) return;

	UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
	check(NodeSpawner != nullptr);

	NodeSpawner->NodeClass = GetClass();

	ActionRegistrar.AddBlueprintAction(GetClass(), NodeSpawner);
}

Эти функции нам нужны исключительно для того, чтобы нашу ноду можно было вызвать в контекстном меню блупринта.

Отображение функции после регистрации
Отображение функции после регистрации

Основная функция

ExpandNode
void UK2Node_GetInputValue::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
	/** Expands the node into a function call from UEnhancedInputLibrary. */
	Super::ExpandNode(CompilerContext, SourceGraph);

	UEdGraphPin* ActionValuePin = GetActionValuePin();
	const UEdGraphPin* ActionPin = GetActionPin();
	UEdGraphPin* OutputPin = GetOutputPin();

	if (!ActionValuePin || !OutputPin)
	{
		CompilerContext.MessageLog.Error(
			*LOCTEXT("MissingPins", "GetInputValue: Missing pins").ToString(), this);
		return;
	}

	if (!ActionPin || !ActionPin->DefaultObject)
	{
		CompilerContext.MessageLog.Error(
			*LOCTEXT("InvalidInputAction", "GetInputValue: Action pin is invalid or not set").ToString(), this);
		return;
	}

	const UInputAction* InputAction = Cast<UInputAction>(ActionPin->DefaultObject);
	if (!InputAction)
	{
		CompilerContext.MessageLog.Error(
			*LOCTEXT("InvalidInputAction", "GetInputValue: Action pin does not contain a valid InputAction").ToString(),
			this);
		return;
	}

	// Determines which function to call based on ValueType
	FName FunctionName;
	FName ActionValueName = TEXT("InValue");
	switch (InputAction->ValueType)
	{
	case EInputActionValueType::Boolean:
		FunctionName = GET_FUNCTION_NAME_CHECKED(UEnhancedInputLibrary, Conv_InputActionValueToBool);
		break;
	case EInputActionValueType::Axis1D:
		FunctionName = GET_FUNCTION_NAME_CHECKED(UEnhancedInputLibrary, Conv_InputActionValueToAxis1D);
		break;
	case EInputActionValueType::Axis2D:
		FunctionName = GET_FUNCTION_NAME_CHECKED(UEnhancedInputLibrary, Conv_InputActionValueToAxis2D);
		break;
	case EInputActionValueType::Axis3D:
		FunctionName = GET_FUNCTION_NAME_CHECKED(UEnhancedInputLibrary, Conv_InputActionValueToAxis3D);
		ActionValueName = TEXT("ActionValue");
		break;
	default:
		CompilerContext.MessageLog.Error(
			*LOCTEXT("UnsupportedType", "GetInputValue: Unsupported Action Value Type").ToString(), this);
		return;
	}

	if (OutputPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard)
	{
		CompilerContext.MessageLog.Error(
			*LOCTEXT("WildcardError", "GetInputValue: Output pin type is still Wildcard!").ToString(), this);
		return;
	}

	// Creates a CallFunction node for the selected function
	UK2Node_CallFunction* GetValueNode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
	if (!GetValueNode)
	{
		CompilerContext.MessageLog.Error(
			*LOCTEXT("NodeSpawnError", "GetInputValue: Failed to create intermediate function node").ToString(), this);
		return;
	}
	GetValueNode->FunctionReference.SetExternalMember(FunctionName, UEnhancedInputLibrary::StaticClass());
	GetValueNode->AllocateDefaultPins();

	// Ensures the function has the correct input pin
	UEdGraphPin* InValuePin = GetValueNode->FindPin(ActionValueName);
	if (!InValuePin)
	{
		CompilerContext.MessageLog.Error(
			*LOCTEXT("MissingInputPin", "GetInputValue: Could not find expected input pin on GetValueNode").ToString(),
			this);
		return;
	}

	// Moves links from ActionValuePin -> InValuePin
	CompilerContext.MovePinLinksToIntermediate(*ActionValuePin, *InValuePin);

	// Moves links from OutputPin -> GetValueNode output
	CompilerContext.MovePinLinksToIntermediate(*OutputPin, *GetValueNode->GetReturnValuePin());

	// Breaks all links on this node since it's no longer needed
	BreakAllNodeLinks();
}

В этой функции мы раскрываем нашу ноду на последовательность других нод(либо даже на одну), с которыми уже и будем соединять наши пины. Да, на самом деле K2 ноды скрытно раскрываются вплоть до целой серии вызовов других. Конкретно в нашем случае мы будем вызывать одну из уже существующих BlueprintInternalUseOnly функций из плагина инпутов.

Итак, что мы делаем:

  1. Проверяем валидность наших пинов.

  2. Выбираем функцию для конвертации.

  3. Создаем ноду этой функции в графе.

  4. Перетаскиваем соединения наших пинов на пины только что размещенной ноды.

  5. Обрываем все связи нашей ноды.

Дополнительная настройка

virtual bool IsNodePure() const override { return true; }

virtual FText GetTooltipText() const override
{
    return NSLOCTEXT("K2Node", "GetInputValue", "Extract input value with type from action.");
}

virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override
{
    return NSLOCTEXT("K2Node", "GetInputValue", "Get Input Value");
}

Тут мы указываем, pure наша нода или impure(имеет execution пины или нет), а также даем отображаемое название вместе с описанием при наведении.

По желанию, можно еще настроить цвета всего и вся.

Работа с цветом
Работа с цветом

Полный код

.h файл(да, еще раз)
#pragma once

#include "CoreMinimal.h"
#include "K2Node.h"
#include "K2Node_GetInputValue.generated.h"

UCLASS()
class UK2Node_GetInputValue : public UK2Node
{
	GENERATED_BODY()

public:
	//~UEdGraphNode interface
	virtual void PostReconstructNode() override;
	virtual void PinDefaultValueChanged(UEdGraphPin* ChangedPin) override;
	virtual void ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
	//~End of UEdGraphNode interface

	//~UK2Node interface
	virtual void AllocateDefaultPins() override;
	virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
	virtual bool IsNodePure() const override { return true; }

	virtual FText GetTooltipText() const override
	{
		return NSLOCTEXT("K2Node", "GetInputValue", "Extract input value with type from action.");
	}

	virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override
	{
		return NSLOCTEXT("K2Node", "GetInputValue", "Get Input Value");
	}

	virtual FText GetMenuCategory() const override { return NSLOCTEXT("K2Node", "InputCategory", "Enhanced Input"); }
	//~End of UK2Node interface

private:
	void RefreshOutputPinType();

	UEdGraphPin* GetActionValuePin() const { return FindPinChecked(TEXT("ActionValue")); }
	UEdGraphPin* GetActionPin() const { return FindPinChecked(TEXT("Action")); }
	UEdGraphPin* GetOutputPin() const { return FindPinChecked(TEXT("Value")); }
};
.cpp файл
#include "K2/K2Node_GetInputValue.h"

#include "BlueprintActionDatabaseRegistrar.h"
#include "BlueprintNodeSpawner.h"
#include "InputAction.h"
#include "InputActionValue.h"
#include "KismetCompiler.h"
#include "K2Node_CallFunction.h"
#include "EnhancedInputLibrary.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(K2Node_GetInputValue)

#define LOCTEXT_NAMESPACE "K2Node"

void UK2Node_GetInputValue::PostReconstructNode()
{
	Super::PostReconstructNode();

	RefreshOutputPinType();
}

void UK2Node_GetInputValue::PinDefaultValueChanged(UEdGraphPin* ChangedPin)
{
	if (ChangedPin == GetActionPin())
	{
		RefreshOutputPinType();
	}
}

void UK2Node_GetInputValue::AllocateDefaultPins()
{
	/** Creates input pins for ActionValue and Action, and an output pin for Value. */

	// Action value pin
	UEdGraphPin* ActionValuePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Struct, FInputActionValue::StaticStruct(),
	                                        TEXT("ActionValue"));
	ActionValuePin->PinToolTip = TEXT("Value received from the input system for the specified action.");

	// Action object pin
	UEdGraphPin* ActionPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UInputAction::StaticClass(),
	                                   TEXT("Action"));
	ActionPin->PinToolTip = TEXT(
		"Input action to extract the expected value type from (used to determine output type).");

	// Output action value type pin
	UEdGraphPin* ValuePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, TEXT("Value"));
	ValuePin->PinToolTip = TEXT(
		"The extracted value of the input action, matching the expected type (bool, float, or Vector2D).");

	Super::AllocateDefaultPins();
}

void UK2Node_GetInputValue::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
	/** Registers this node in the blueprint action database. */
	if (!ActionRegistrar.IsOpenForRegistration(GetClass())) return;

	UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
	check(NodeSpawner != nullptr);

	NodeSpawner->NodeClass = GetClass();

	ActionRegistrar.AddBlueprintAction(GetClass(), NodeSpawner);
}

void UK2Node_GetInputValue::RefreshOutputPinType()
{
	/** Updates the output pin type based on the selected Action's ValueType. */
	UEdGraphPin* OutputPin = GetOutputPin();
	if (!OutputPin) return;

	OutputPin->Modify();

	// Resets pin type before updating
	OutputPin->PinType = FEdGraphPinType();

	const UEdGraphPin* ActionPin = GetActionPin();
	if (!ActionPin || !ActionPin->DefaultObject)
	{
		OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
		return;
	}

	const UInputAction* InputAction = Cast<UInputAction>(ActionPin->DefaultObject);
	if (!InputAction) return;

	switch (InputAction->ValueType)
	{
	case EInputActionValueType::Boolean:
		OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
		break;
	case EInputActionValueType::Axis1D:
		OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Real;
		OutputPin->PinType.PinSubCategory = TEXT("double");
		break;
	case EInputActionValueType::Axis2D:
		OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Struct;
		OutputPin->PinType.PinSubCategoryObject = TBaseStructure<FVector2D>::Get();
		break;
	case EInputActionValueType::Axis3D:
		OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Struct;
		OutputPin->PinType.PinSubCategoryObject = TBaseStructure<FVector>::Get();
		break;
	default:
		OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
		break;
	}

	// Notifies the system about pin type changes
	GetSchema()->ForceVisualizationCacheClear();
	GetGraph()->NotifyGraphChanged();
}

void UK2Node_GetInputValue::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
	/** Expands the node into a function call from UEnhancedInputLibrary. */
	Super::ExpandNode(CompilerContext, SourceGraph);

	UEdGraphPin* ActionValuePin = GetActionValuePin();
	const UEdGraphPin* ActionPin = GetActionPin();
	UEdGraphPin* OutputPin = GetOutputPin();

	if (!ActionValuePin || !OutputPin)
	{
		CompilerContext.MessageLog.Error(
			*LOCTEXT("MissingPins", "GetInputValue: Missing pins").ToString(), this);
		return;
	}

	if (!ActionPin || !ActionPin->DefaultObject)
	{
		CompilerContext.MessageLog.Error(
			*LOCTEXT("InvalidInputAction", "GetInputValue: Action pin is invalid or not set").ToString(), this);
		return;
	}

	const UInputAction* InputAction = Cast<UInputAction>(ActionPin->DefaultObject);
	if (!InputAction)
	{
		CompilerContext.MessageLog.Error(
			*LOCTEXT("InvalidInputAction", "GetInputValue: Action pin does not contain a valid InputAction").ToString(),
			this);
		return;
	}

	// Determines which function to call based on ValueType
	FName FunctionName;
	FName ActionValueName = TEXT("InValue");
	switch (InputAction->ValueType)
	{
	case EInputActionValueType::Boolean:
		FunctionName = GET_FUNCTION_NAME_CHECKED(UEnhancedInputLibrary, Conv_InputActionValueToBool);
		break;
	case EInputActionValueType::Axis1D:
		FunctionName = GET_FUNCTION_NAME_CHECKED(UEnhancedInputLibrary, Conv_InputActionValueToAxis1D);
		break;
	case EInputActionValueType::Axis2D:
		FunctionName = GET_FUNCTION_NAME_CHECKED(UEnhancedInputLibrary, Conv_InputActionValueToAxis2D);
		break;
	case EInputActionValueType::Axis3D:
		FunctionName = GET_FUNCTION_NAME_CHECKED(UEnhancedInputLibrary, Conv_InputActionValueToAxis3D);
		ActionValueName = TEXT("ActionValue");
		break;
	default:
		CompilerContext.MessageLog.Error(
			*LOCTEXT("UnsupportedType", "GetInputValue: Unsupported Action Value Type").ToString(), this);
		return;
	}

	if (OutputPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard)
	{
		CompilerContext.MessageLog.Error(
			*LOCTEXT("WildcardError", "GetInputValue: Output pin type is still Wildcard!").ToString(), this);
		return;
	}

	UE_LOG(LogTemp, Warning, TEXT("OutputPin Type: %s"), *OutputPin->PinType.PinCategory.ToString());

	// Creates a CallFunction node for the selected function
	UK2Node_CallFunction* GetValueNode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
	if (!GetValueNode)
	{
		CompilerContext.MessageLog.Error(
			*LOCTEXT("NodeSpawnError", "GetInputValue: Failed to create intermediate function node").ToString(), this);
		return;
	}
	GetValueNode->FunctionReference.SetExternalMember(FunctionName, UEnhancedInputLibrary::StaticClass());
	GetValueNode->AllocateDefaultPins();

	// Ensures the function has the correct input pin
	UEdGraphPin* InValuePin = GetValueNode->FindPin(ActionValueName);
	if (!InValuePin)
	{
		CompilerContext.MessageLog.Error(
			*LOCTEXT("MissingInputPin", "GetInputValue: Could not find expected input pin on GetValueNode").ToString(),
			this);
		return;
	}

	// Moves links from ActionValuePin -> InValuePin
	CompilerContext.MovePinLinksToIntermediate(*ActionValuePin, *InValuePin);

	// Moves links from OutputPin -> GetValueNode output
	CompilerContext.MovePinLinksToIntermediate(*OutputPin, *GetValueNode->GetReturnValuePin());

	// Breaks all links on this node since it's no longer needed
	BreakAllNodeLinks();
}

#undef LOCTEXT_NAMESPACE
.build.cs файл
using UnrealBuildTool;

public class MyModuleUncooked : ModuleRules
{
	public MyModuleUncooked(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;

		PublicDependencyModuleNames.AddRange(
			new string[]
			{
				"BlueprintGraph",
				"EnhancedInput"
			}
		);

		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
				"Core",
				"CoreUObject",
				"Engine",
				"KismetCompiler",
				"UnrealEd"
			}
		);
	}
}

Итог

Создание кастомной K2 ноды в Unreal Engine — не такая уж тяжелая задача, но очень много мелочей, которые надо учитывать. В этой статье мы прошли весь путь: от идеи до рабочей GetInputValue, которая умеет подхватывать InputAction и возвращать значение нужного типа, не заставляя самим возиться с конвертацией.

Благодаря классу UK2Node можно серьезно расширить функционал блупринтов, однако придется поработать.

Теги:
Хабы:
Всего голосов 22: ↑22 и ↓0+23
Комментарии0

Публикации

Работа

QT разработчик
7 вакансий
Программист C++
93 вакансии

Ближайшие события