2025-11-17 15:11:34 +08:00
|
|
|
|
// Fill out your copyright notice in the Description page of Project Settings.
|
|
|
|
|
|
|
|
|
|
|
|
#include "DialogueAssetEditor.h"
|
|
|
|
|
|
#include "DialogueGraph.h"
|
|
|
|
|
|
#include "DialogueGraphSchema.h"
|
|
|
|
|
|
#include "DialogueEditorMode.h"
|
|
|
|
|
|
#include "DialogueGraphPanelNodeFactory.h"
|
|
|
|
|
|
#include "DialogueGraphPanelPinFactory.h"
|
|
|
|
|
|
#include "EdGraphUtilities.h"
|
|
|
|
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
|
|
|
|
#include "GraphEditorActions.h"
|
|
|
|
|
|
#include "Framework/Commands/GenericCommands.h"
|
|
|
|
|
|
#include "IDetailsView.h"
|
|
|
|
|
|
#include "Node/DialogueGraphNode_Base.h"
|
|
|
|
|
|
#include "Node/DialogueGraphNode_NPC.h"
|
|
|
|
|
|
#include "Node/DialogueGraphNode_Player.h"
|
|
|
|
|
|
#include "Node/DialogueGraphNode_Root.h"
|
|
|
|
|
|
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE "DialogueEditorToolkit"
|
|
|
|
|
|
|
|
|
|
|
|
const FName DialogueEditorAppName = FName(TEXT("DialogueEditorApp"));
|
|
|
|
|
|
|
|
|
|
|
|
void FDialogueAssetEditor::OnClose()
|
|
|
|
|
|
{
|
|
|
|
|
|
SaveGraphData();
|
|
|
|
|
|
DialogueBeingEdited->SetPreSaveListener(nullptr);
|
|
|
|
|
|
FWorkflowCentricApplication::OnClose();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FDialogueAssetEditor::FDialogueAssetEditor()
|
|
|
|
|
|
: DialogueGraph(nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FDialogueAssetEditor::~FDialogueAssetEditor()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FText FDialogueAssetEditor::GetBaseToolkitName() const
|
|
|
|
|
|
{
|
|
|
|
|
|
return IsValid(DialogueBeingEdited) ? FText::FromString(DialogueBeingEdited->GetName()) : LOCTEXT("AppLabel", "Dialogue Editor");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void FDialogueAssetEditor::InitDialogueEditor(const EToolkitMode::Type Mode,
|
|
|
|
|
|
const TSharedPtr<IToolkitHost>& InitToolkitHost, TObjectPtr<UDialogueAsset> InitDialogueAsset)
|
|
|
|
|
|
{
|
|
|
|
|
|
DialogueBeingEdited = InitDialogueAsset;
|
|
|
|
|
|
//修改过程中保存
|
|
|
|
|
|
DialogueBeingEdited->SetPreSaveListener([this] () { SaveGraphData();; });
|
|
|
|
|
|
// 注册命令
|
|
|
|
|
|
FGenericCommands::Register();
|
|
|
|
|
|
FGraphEditorCommands::Register();
|
|
|
|
|
|
BindGraphCommands();
|
|
|
|
|
|
|
|
|
|
|
|
//注册节点的创建
|
|
|
|
|
|
FEdGraphUtilities::RegisterVisualNodeFactory(MakeShareable(new FDialogueGraphPanelNodeFactory()));
|
|
|
|
|
|
FEdGraphUtilities::RegisterVisualPinFactory(MakeShareable(new FDialogueGraphPanelPinFactory()));
|
|
|
|
|
|
|
|
|
|
|
|
// 创建图表
|
|
|
|
|
|
DialogueGraph = FBlueprintEditorUtils::CreateNewGraph(
|
|
|
|
|
|
DialogueBeingEdited.Get(),
|
|
|
|
|
|
NAME_None,
|
|
|
|
|
|
UDialogueGraph::StaticClass(),
|
|
|
|
|
|
UDialogueGraphSchema::StaticClass()
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
TArray<UObject*> ObjectsToEdit;
|
|
|
|
|
|
ObjectsToEdit.Add(InitDialogueAsset);
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化编辑器
|
|
|
|
|
|
InitAssetEditor(
|
|
|
|
|
|
Mode,
|
|
|
|
|
|
InitToolkitHost,
|
|
|
|
|
|
DialogueEditorAppName,
|
|
|
|
|
|
FTabManager::FLayout::NullLayout,
|
|
|
|
|
|
true,
|
|
|
|
|
|
true,
|
|
|
|
|
|
ObjectsToEdit
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加应用模式
|
|
|
|
|
|
AddApplicationMode(TEXT("DialogueEditorMode"), MakeShareable(new FDialogueEditorMode(SharedThis(this))));
|
|
|
|
|
|
SetCurrentMode(TEXT("DialogueEditorMode"));
|
|
|
|
|
|
|
|
|
|
|
|
//加载资源中的数据节点
|
|
|
|
|
|
LoadGraphData();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FDialogueAssetEditor::SetDetailsView(TSharedPtr<IDetailsView> InDetailsView)
|
|
|
|
|
|
{
|
|
|
|
|
|
DetailsView = InDetailsView;
|
|
|
|
|
|
if (DetailsView.IsValid())
|
|
|
|
|
|
{
|
|
|
|
|
|
DetailsView->OnFinishedChangingProperties().AddSP(this, &FDialogueAssetEditor::OnNodeDetailViewPropertiesUpdated);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FDialogueAssetEditor::OnGraphSelectionChanged(const FGraphPanelSelectionSet& Selection)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!DetailsView.IsValid())
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
if (UDialogueGraphNode_Base* SelectedNode = GetSelectedNode(Selection))
|
|
|
|
|
|
{
|
|
|
|
|
|
DetailsView->SetObject(SelectedNode->GetDialogueNodeData());
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
DetailsView->SetObject(nullptr);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FDialogueAssetEditor::SaveGraphData()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!DialogueBeingEdited || !DialogueGraph)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TArray<std::pair<FGuid, FGuid>> Connections;
|
|
|
|
|
|
TMap<FGuid, UDialogueRuntimePin*> IdToPinMap;
|
|
|
|
|
|
DialogueBeingEdited->NodeDatas.Empty();
|
|
|
|
|
|
for (UEdGraphNode* EditorNode : DialogueGraph->Nodes)
|
|
|
|
|
|
{
|
|
|
|
|
|
UDialogueRuntimeNode* RuntimeNode = NewObject<UDialogueRuntimeNode>(DialogueBeingEdited);
|
|
|
|
|
|
RuntimeNode->Position = FVector2D(EditorNode->NodePosX, EditorNode->NodePosY);
|
|
|
|
|
|
|
|
|
|
|
|
for (UEdGraphPin* EditorPin : EditorNode->Pins)
|
|
|
|
|
|
{
|
|
|
|
|
|
UDialogueRuntimePin* RuntimePin = NewObject<UDialogueRuntimePin>(RuntimeNode);
|
|
|
|
|
|
RuntimePin->PinName = EditorPin->PinName;
|
|
|
|
|
|
RuntimePin->PinId = EditorPin->PinId;
|
|
|
|
|
|
RuntimePin->Parent = RuntimeNode;
|
|
|
|
|
|
RuntimePin->Direction = EditorPin->Direction;
|
|
|
|
|
|
|
|
|
|
|
|
if (EditorPin->HasAnyConnections())
|
|
|
|
|
|
{
|
|
|
|
|
|
std::pair<FGuid, FGuid> Connection = std::make_pair(EditorPin->PinId, EditorPin->LinkedTo[0]->PinId);
|
|
|
|
|
|
Connections.Add(Connection);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
IdToPinMap.Add(EditorPin->PinId, RuntimePin);
|
|
|
|
|
|
if (RuntimePin->Direction == EGPD_Input)
|
|
|
|
|
|
{
|
|
|
|
|
|
RuntimeNode->InputPin = RuntimePin;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
RuntimeNode->OutputPins.Add(RuntimePin);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
UDialogueGraphNode_Base* EditorDialogueNode = Cast<UDialogueGraphNode_Base>(EditorNode);
|
|
|
|
|
|
RuntimeNode->NodeData = DuplicateObject(EditorDialogueNode->GetDialogueNodeData(), DialogueBeingEdited);
|
|
|
|
|
|
RuntimeNode->NodeType = EditorDialogueNode->GetDialogueNodeType();
|
|
|
|
|
|
|
|
|
|
|
|
DialogueBeingEdited->NodeDatas.Add(RuntimeNode);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (std::pair<FGuid, FGuid> Connection : Connections)
|
|
|
|
|
|
{
|
|
|
|
|
|
UDialogueRuntimePin* Pin1 = IdToPinMap[Connection.first];
|
|
|
|
|
|
UDialogueRuntimePin* Pin2 = IdToPinMap[Connection.second];
|
|
|
|
|
|
if (!Pin1->Connections.Contains(Pin2))
|
|
|
|
|
|
{
|
|
|
|
|
|
Pin1->Connections.Add(Pin2);
|
2025-11-18 15:37:21 +08:00
|
|
|
|
//按照横向位置进行排序
|
|
|
|
|
|
Pin1->Connections.Sort([](const UDialogueRuntimePin& A, const UDialogueRuntimePin& B)
|
|
|
|
|
|
{
|
|
|
|
|
|
return A.Parent->Position.X < B.Parent->Position.X;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-11-17 15:11:34 +08:00
|
|
|
|
if (Pin1->Direction == EGPD_Input)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!Pin2->Connections.Contains(Pin1))
|
|
|
|
|
|
{
|
|
|
|
|
|
Pin2->Connections.Add(Pin1);
|
2025-11-18 15:37:21 +08:00
|
|
|
|
//按照横向位置进行排序
|
|
|
|
|
|
Pin2->Connections.Sort([](const UDialogueRuntimePin& A, const UDialogueRuntimePin& B)
|
|
|
|
|
|
{
|
|
|
|
|
|
return A.Parent->Position.X < B.Parent->Position.X;
|
|
|
|
|
|
});
|
2025-11-17 15:11:34 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//标记数据更新
|
|
|
|
|
|
DialogueBeingEdited->Modify();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FDialogueAssetEditor::LoadGraphData()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (DialogueBeingEdited->NodeDatas.Num() == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
//空节点,创建默认Root节点
|
|
|
|
|
|
DialogueGraph->GetSchema()->CreateDefaultNodesForGraph(*DialogueGraph);
|
|
|
|
|
|
UDialogueRuntimeNode* RootNode = NewObject<UDialogueRuntimeNode>();
|
|
|
|
|
|
RootNode->NodeType = EDialogueNodeType::Root;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
//读取节点信息,创建pin及连线
|
|
|
|
|
|
TArray<std::pair<FGuid, FGuid>> Connections;
|
|
|
|
|
|
TMap<FGuid, UEdGraphPin*> IdToPinMap;
|
|
|
|
|
|
for (UDialogueRuntimeNode* RuntimeNode: DialogueBeingEdited->NodeDatas)
|
|
|
|
|
|
{
|
|
|
|
|
|
UDialogueGraphNode_Base* EditorNode = nullptr;
|
|
|
|
|
|
switch (RuntimeNode->NodeType)
|
|
|
|
|
|
{
|
|
|
|
|
|
case EDialogueNodeType::Root:
|
|
|
|
|
|
EditorNode = NewObject<UDialogueGraphNode_Root>(DialogueGraph);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case EDialogueNodeType::NPC:
|
|
|
|
|
|
EditorNode = NewObject<UDialogueGraphNode_NPC>(DialogueGraph);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case EDialogueNodeType::Player:
|
|
|
|
|
|
EditorNode = NewObject<UDialogueGraphNode_Player>(DialogueGraph);
|
|
|
|
|
|
break;
|
|
|
|
|
|
default: ;
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorNode->CreateNewGuid();
|
|
|
|
|
|
EditorNode->SetPostion(RuntimeNode->Position);
|
|
|
|
|
|
if (RuntimeNode->NodeData)
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorNode->SetDialogueNodeData(DuplicateObject(RuntimeNode->NodeData, EditorNode));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorNode->InitializeNodeData();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (RuntimeNode->InputPin)
|
|
|
|
|
|
{
|
|
|
|
|
|
UDialogueRuntimePin* InputPin = RuntimeNode->InputPin;
|
|
|
|
|
|
FName Category = TEXT("Inputs");
|
|
|
|
|
|
UEdGraphPin* EditorPin = EditorNode->CreatePin((EEdGraphPinDirection)InputPin->Direction, Category, InputPin->PinName);
|
|
|
|
|
|
EditorPin->PinId = InputPin->PinId;
|
|
|
|
|
|
|
|
|
|
|
|
if (InputPin->Connections.Num() > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Connections.Add(std::make_pair(InputPin->PinId, InputPin->Connections[0]->PinId));
|
|
|
|
|
|
}
|
|
|
|
|
|
IdToPinMap.Add(InputPin->PinId, EditorPin);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (UDialogueRuntimePin* RuntimePin : RuntimeNode->OutputPins)
|
|
|
|
|
|
{
|
|
|
|
|
|
FName Category = TEXT("Outputs");
|
|
|
|
|
|
UEdGraphPin* EditorPin = EditorNode->CreatePin((EEdGraphPinDirection)RuntimePin->Direction, Category, RuntimePin->PinName);
|
|
|
|
|
|
EditorPin->PinId = RuntimePin->PinId;
|
|
|
|
|
|
|
|
|
|
|
|
if (RuntimePin->Connections.Num() > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Connections.Add(std::make_pair(RuntimePin->PinId, RuntimePin->Connections[0]->PinId));
|
|
|
|
|
|
}
|
|
|
|
|
|
IdToPinMap.Add(RuntimePin->PinId, EditorPin);
|
|
|
|
|
|
}
|
|
|
|
|
|
DialogueGraph->AddNode(EditorNode, true, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (std::pair<FGuid, FGuid> Connection : Connections)
|
|
|
|
|
|
{
|
|
|
|
|
|
UEdGraphPin* FromPin = IdToPinMap[Connection.first];
|
|
|
|
|
|
UEdGraphPin* ToPin = IdToPinMap[Connection.second];
|
|
|
|
|
|
FromPin->LinkedTo.Add(ToPin);
|
|
|
|
|
|
ToPin->LinkedTo.Add(FromPin);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FDialogueAssetEditor::BindGraphCommands()
|
|
|
|
|
|
{
|
|
|
|
|
|
ToolkitCommands->MapAction(FGenericCommands::Get().Delete,
|
|
|
|
|
|
FExecuteAction::CreateSP(this, &FDialogueAssetEditor::DeleteSelectedNodes),
|
|
|
|
|
|
FCanExecuteAction::CreateSP(this, &FDialogueAssetEditor::CanDeleteNodes));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FDialogueAssetEditor::DeleteSelectedNodes()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!GraphEditor.IsValid())
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
const FScopedTransaction Transaction(FGenericCommands::Get().Delete->GetDescription());
|
|
|
|
|
|
GraphEditor->GetCurrentGraph()->Modify();
|
|
|
|
|
|
|
|
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
|
|
GraphEditor->ClearSelectionSet();
|
|
|
|
|
|
|
|
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
UEdGraphNode* EdNode = Cast<UEdGraphNode>(*NodeIt);
|
|
|
|
|
|
if (EdNode == nullptr || !EdNode->CanUserDeleteNode())
|
|
|
|
|
|
continue;;
|
|
|
|
|
|
|
|
|
|
|
|
if (UDialogueGraphNode_Base* EdNode_Node = Cast<UDialogueGraphNode_Base>(EdNode))
|
|
|
|
|
|
{
|
|
|
|
|
|
EdNode_Node->Modify();
|
|
|
|
|
|
|
|
|
|
|
|
const UEdGraphSchema* Schema = EdNode_Node->GetSchema();
|
|
|
|
|
|
if (Schema != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
Schema->BreakNodeLinks(*EdNode_Node);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EdNode_Node->DestroyNode();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
EdNode->Modify();
|
|
|
|
|
|
EdNode->DestroyNode();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool FDialogueAssetEditor::CanDeleteNodes()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!GraphEditor.IsValid())
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
|
|
|
|
|
|
{
|
|
|
|
|
|
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
|
|
|
|
|
|
if (Node && Node->CanUserDeleteNode())
|
|
|
|
|
|
{
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
UDialogueGraphNode_Base* FDialogueAssetEditor::GetSelectedNode(const FGraphPanelSelectionSet& Selection)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (UObject* Obj : Selection)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (UDialogueGraphNode_Base* Node = Cast<UDialogueGraphNode_Base>(Obj))
|
|
|
|
|
|
{
|
|
|
|
|
|
return Node;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FDialogueAssetEditor::OnNodeDetailViewPropertiesUpdated(const FPropertyChangedEvent& Event)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (GraphEditor.IsValid())
|
|
|
|
|
|
{
|
|
|
|
|
|
GraphEditor->NotifyGraphChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|