UE行为树编辑器图文笔记

对UE的编辑器实现有点好奇,于是从比较熟悉的行为树编辑器着手分析。以下为阅读UE源码后的个人理解,如有错误请指正。

编辑器基础

扩展编辑器的几种方式

  • MenuBar 菜单栏
  • ToolBar 工具栏
  • DetailCustomization 自定义细节面板,支持两种方式:IDetailCustomization自定义整个细节面板、 IPropertyTypeCustomization只对特定类型字段进行自定义。
  • GraphNode 节点的扩展。例如对现有蓝图节点的扩充。
  • Custom Asset Editor 自定义一个新的资源类型并拥有独立的编辑器。
  • Custom Edit Mode 自定义编辑模式。类似于:地形编辑模式、植被编辑模式......
  • Editor Utility Widgets
  • Scripted Actions:Create Blueprints that you can invoke by right-clicking an Asset in the Content Browser or an Actor in the Level.

推荐视频教程:

编辑器核心概念及关键类

  • Menu 菜单栏

  • ToolBar 工具栏

  • Tab tab页

  • TabManager tab页管理器

  • Layout 布局

    • FLayoutNode 布局节点,有一个尺寸系数。
    • FSplitter 布局切分,可横向或者纵向进行切分。继承自FLayoutNode
    • FArea 继承自FSplitter
    • FStack 可以承载多个tab页,但只有一个在前台(激活态)。继承自FLayoutNode
  • FToolkitManager

    单例,管理所有编辑器实例。主要函数:FindEditorForAsset尝试找到资源所对应的编辑器。

  • UAssetEditorSubsystem

    资源编辑器子系统,记录了当前所有正在编辑的资源。主要函数:OpenEditorForAsset打开资源编辑器、FindEditorForAsset查找资源对应的编辑器、GetAllEditedAssets获取所有当前编辑中的资源......

其它

  • UEditorAssetSubsystem:暴露资源相关工具方法给蓝图侧
  • UEditorActorSubsystem:暴露关卡中Actor相关工具方法给蓝图侧

行为树编辑器代码分析

行为树编辑器代码目录结构如下:

  • FBehaviorTreeEditorModule
    行为树编辑器模块类,提供了创建行为树编辑器的接口,在模块启动的时候注册了对detail面板的自定义、注册了行为树节点对应可视节点的工厂方法、创建了菜单扩展管理器以及工具栏扩展管理器。
  • FBehaviorTreeDebugger
    行为树调试器:提供断点的添加、移除,单步步进等。待进一步详细分析。

行为树编辑器逻辑层(Model)

  • 资源定义
    • UAssetDefinition_BehaviorTree:行为树的资源定义,定义了资源在Content Drawer中的名字的显示、颜色、分类、被双击所执行的操作、文件对比。
    • UAssetDefinition_Blackboard:黑板的资源定义,同上。
  • 工厂类
    • UBehaviorTreeFactory行为树工厂类
    • UBlackboardDataFactory黑板工厂类
  • UBehaviorTreeGraph行为树图,也就是我们平常编辑的行为树的逻辑表示。
  • UBehaviorTreeGraphNode行为树图中节点的基类
    • UBehaviorTreeGraphNode_Root行为树图的根节点
    • UBehaviorTreeGraphNode_Composite行为树图中复合节点
    • UBehaviorTreeGraphNode_SimpleParallel
    • UBehaviorTreeGraphNode_Decorator
    • UBehaviorTreeGraphNode_Service
    • UBehaviorTreeGraphNode_Task
    • UBehaviorTreeGraphNode_SubtreeTask
    • UBehaviorTreeGraphNode_CompositeDecorator复合装饰器节点,里面包含了一个Graph,从而可以通过与或非来组合重利用已有装饰器
      • UBehaviorTreeDecoratorGraph复合装饰器节点的Graph
      • UEdGraphSchema_BehaviorTreeDecorator定义了UBehaviorTreeDecoratorGraph的行为模式
      • UBehaviorTreeDecoratorGraphNode复合装饰器Graph中节点基类
      • UBehaviorTreeDecoratorGraphNode_Logic逻辑节点(与、或、非),继承自UBehaviorTreeDecoratorGraphNode
      • UBehaviorTreeDecoratorGraphNode_Decorator装饰器节点,继承自UBehaviorTreeDecoratorGraphNode
  • UEdGraphSchema_BehaviorTree定义了UBehaviorTreeGraph的行为模式,例如:右键菜单有哪些选项、两个pin是否能够连接、节点的右键菜单列表、连接两个pin......

行为树编辑器控制层(Controller)

  • FBehaviorTreeEditor
  • BehaviorTreeEditorCommands文件中定义了一些命令:行为树的搜索、新建黑板、调试相关的命令(步进、暂停、恢复、停止......)等。
  • BehaviorTreeEditorModes:行为树编辑器的两个工作模式(行为树模式、黑板模式)。模式类中定义了当前模式下Tab的默认布局、注册模式中所有Tab的工厂方法、菜单及工具栏的扩展器。
    • 行为树模式类FBehaviorTreeEditorApplicationMode
    • 黑板模式类FBlackboardEditorApplicationMode
  • BehaviorTreeEditorTabs定义了所有Tab的标识符:行为树Tab、行为树属性面板Tab、行为树搜索面板Tab、黑板属性面板Tab、黑板Key面板。
  • BehaviorTreeEditorTabFactories文件:包含了几个Tab的工厂方法类。
    • FBlackboardEditorSummoner黑板编辑页的工厂类
    • FBlackboardDetailsSummoner黑板detail面板的工厂类
    • FBehaviorTreeDetailsSummoner行为树detail面板的工厂类
    • FBehaviorTreeSearchSummoner行为树搜索面板的工厂类
    • FBTGraphEditorSummoner行为树编辑面板的工厂类
    • FBlackboardSummoner黑板View面板(方便运行中查看黑板的值)的工厂类
  • FBehaviorTreeEditorToolbar负责工具栏

行为树编辑器UI层(View)

  • SBehaviorTreeBlackboardEditor黑板Key编辑面板。
  • SBehaviorTreeBlackboardView黑板条目展示。
  • SGraphNode_BehaviorTree负责单个行为树节点的绘制
  • SGraphNode_Decorator负责复合UBehaviorTreeDecoratorGraphNode_Decorator节点的绘制。
  • BehaviorTreeColors定义了各类节点的颜色、Pin在不同状态下的颜色、调试相关的颜色...
  • BehaviorTreeConnectionDrawingPolicy:行为树种节点之间连线的绘制策略(是否绘制流动的泡泡效果、线的粗线、颜色等)
  • DetailCustomizations文件夹:定制的一些细节面板(黑板detail面板、BlackboardKeySelector)
  • FindInBT负责行为树中的搜索,包括搜索逻辑以及UI。
  • SBehaviorTreeDiff行为树对比界面。

实现细节

入口

从行为树资源被双击打开编辑器作为切入口。

UAssetDefinition_BehaviorTree

c++ 复制代码
EAssetCommandResult UAssetDefinition_BehaviorTree::OpenAssets(const FAssetOpenArgs& OpenArgs) const
{
	// 省略其余代码.....
		if (!bFoundExisting)
		{
			FBehaviorTreeEditorModule& BehaviorTreeEditorModule = FModuleManager::GetModuleChecked<FBehaviorTreeEditorModule>("BehaviorTreeEditor");
			BehaviorTreeEditorModule.CreateBehaviorTreeEditor(Mode, OpenArgs.ToolkitHost, BehaviorTree); // 向行为树编辑器模块儿求情创建行为树编辑器
		}
	}
	return EAssetCommandResult::Handled;
}

行为树编辑器模块创建了FBehaviorTreeEditor

c++ 复制代码
TSharedRef<IBehaviorTreeEditor> FBehaviorTreeEditorModule::CreateBehaviorTreeEditor( const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, UObject* Object )
{
    if (!ClassCache.IsValid())
    {
        // 监听行为树蓝图节点类UBTTask_BlueprintBase、UBTDecorator_BlueprintBase、UBTService_BlueprintBase      
    }
    TSharedRef< FBehaviorTreeEditor > NewBehaviorTreeEditor( new FBehaviorTreeEditor() );
    NewBehaviorTreeEditor->InitBehaviorTreeEditor( Mode, InitToolkitHost, Object );
    return NewBehaviorTreeEditor;  
}

FBehaviorTreeEditor定义

c++ 复制代码
class BEHAVIORTREEEDITOR_API FBehaviorTreeEditor : public IBehaviorTreeEditor, 
public FAIGraphEditor, public FNotifyHook
{
    //...
}

编辑器初始化

FBehaviorTreeEditor的初始化函数InitBehaviorTreeEditor

c++ 复制代码
void FBehaviorTreeEditor::InitBehaviorTreeEditor( const EToolkitMode::Type Mode, 
const TSharedPtr< class IToolkitHost >& InitToolkitHost, UObject* InObject ){
// DocumentManager初始化(DocumentManager主要记录了双击行为树节点打开的蓝图面板)
// 工具栏构建器ToolbarBuilder的创建
// 命令注册(行为树相关命令、行为树调试相关命令、黑板相关命令)
// 基类FAssetEditorToolkit的初始化InitAssetEditor
//  预创建Detail面板
// 行为树调试器的创建和初始化
// 扩展菜单栏、工具栏
// 创建并添加黑板模式FBlackboardEditorApplicationMode、行为树模式FBehaviorTreeEditorApplicationMode
// 预创建黑板编辑器面板
// 命令到回调的映射
// 设置当前的工作模式(行为树模型、黑板模式),注册TabFactory,从编辑器布局ini配置文件中读取布局信息,恢复UI布局(这里会调用前面注册的各个Tab的工厂方法)。
// 重新生成菜单栏、工具栏
}

Tab工厂类被调用时的堆栈:

设置当前运行模式的时候会恢复模式对应的编辑器UI布局,而布局包含了一组tab标识符,根据tab标识符找到之前注册的tab工厂类,再由工厂类的CreateTabBody函数来创建tab面板。当所有tab面板都创建完毕的时候,编辑器也就完成了创建。

行为树编辑面板

行为树编辑面板的创建代码在FBehaviorTreeEditor::CreateGraphEditorWidget。

C++ 复制代码
/** Create new tab for the supplied graph - don't call this directly, call SExplorer->FindTabForGraph.*/
TSharedRef<SGraphEditor> FBehaviorTreeEditor::CreateGraphEditorWidget(UEdGraph* InGraph)
{
	check(InGraph != NULL);
	
	if (!GraphEditorCommands.IsValid())
	{
		CreateCommandList();

		GraphEditorCommands->MapAction( FGraphEditorCommands::Get().RemoveExecutionPin,
			FExecuteAction::CreateSP( this, &FBehaviorTreeEditor::OnRemoveInputPin ),
			FCanExecuteAction::CreateSP( this, &FBehaviorTreeEditor::CanRemoveInputPin )
			);

		GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AddExecutionPin,
			FExecuteAction::CreateSP( this, &FBehaviorTreeEditor::OnAddInputPin ),
			FCanExecuteAction::CreateSP( this, &FBehaviorTreeEditor::CanAddInputPin )
			);

		// Debug actions
		GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AddBreakpoint,
			FExecuteAction::CreateSP( this, &FBehaviorTreeEditor::OnAddBreakpoint ),
			FCanExecuteAction::CreateSP( this, &FBehaviorTreeEditor::CanAddBreakpoint ),
			FIsActionChecked(),
			FIsActionButtonVisible::CreateSP( this, &FBehaviorTreeEditor::CanAddBreakpoint )
			);

		GraphEditorCommands->MapAction( FGraphEditorCommands::Get().RemoveBreakpoint,
			FExecuteAction::CreateSP( this, &FBehaviorTreeEditor::OnRemoveBreakpoint ),
			FCanExecuteAction::CreateSP( this, &FBehaviorTreeEditor::CanRemoveBreakpoint ),
			FIsActionChecked(),
			FIsActionButtonVisible::CreateSP( this, &FBehaviorTreeEditor::CanRemoveBreakpoint )
			);

		GraphEditorCommands->MapAction( FGraphEditorCommands::Get().EnableBreakpoint,
			FExecuteAction::CreateSP( this, &FBehaviorTreeEditor::OnEnableBreakpoint ),
			FCanExecuteAction::CreateSP( this, &FBehaviorTreeEditor::CanEnableBreakpoint ),
			FIsActionChecked(),
			FIsActionButtonVisible::CreateSP( this, &FBehaviorTreeEditor::CanEnableBreakpoint )
			);

		GraphEditorCommands->MapAction( FGraphEditorCommands::Get().DisableBreakpoint,
			FExecuteAction::CreateSP( this, &FBehaviorTreeEditor::OnDisableBreakpoint ),
			FCanExecuteAction::CreateSP( this, &FBehaviorTreeEditor::CanDisableBreakpoint ),
			FIsActionChecked(),
			FIsActionButtonVisible::CreateSP( this, &FBehaviorTreeEditor::CanDisableBreakpoint )
			);

		GraphEditorCommands->MapAction( FGraphEditorCommands::Get().ToggleBreakpoint,
			FExecuteAction::CreateSP( this, &FBehaviorTreeEditor::OnToggleBreakpoint ),
			FCanExecuteAction::CreateSP( this, &FBehaviorTreeEditor::CanToggleBreakpoint ),
			FIsActionChecked(),
			FIsActionButtonVisible::CreateSP( this, &FBehaviorTreeEditor::CanToggleBreakpoint )
			);
	}

	SGraphEditor::FGraphEditorEvents InEvents;
	InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateSP(this, &FBehaviorTreeEditor::OnSelectedNodesChanged);
	InEvents.OnNodeDoubleClicked = FSingleNodeEvent::CreateSP(this, &FBehaviorTreeEditor::OnNodeDoubleClicked);
	InEvents.OnTextCommitted = FOnNodeTextCommitted::CreateSP(this, &FBehaviorTreeEditor::OnNodeTitleCommitted);

	// Make title bar
	TSharedRef<SWidget> TitleBarWidget = 
		SNew(SBorder)
		.BorderImage( FAppStyle::GetBrush( TEXT("Graph.TitleBackground") ) )
		.HAlign(HAlign_Fill)
		[
			SNew(SHorizontalBox)
			+SHorizontalBox::Slot()
			.HAlign(HAlign_Center)
			.FillWidth(1.f)
			[
				SNew(STextBlock)
				.Text(TitleText)
				.TextStyle( FAppStyle::Get(), TEXT("GraphBreadcrumbButtonText") )
			]
		];

	// Make full graph editor
	const bool bGraphIsEditable = InGraph->bEditable;
	return SNew(SGraphEditor)
		.AdditionalCommands(GraphEditorCommands)
		.IsEditable(this, &FBehaviorTreeEditor::InEditingMode, bGraphIsEditable)
		.Appearance(this, &FBehaviorTreeEditor::GetGraphAppearance)
		.TitleBar(TitleBarWidget)
		.GraphToEdit(InGraph)
		.GraphEvents(InEvents)
		.AutoExpandActionMenu(true);
}

可以看到行为树编辑面板复用的SGraphEditor。

节点以及连线的绘制在SGraphEditor子UI SGraphPanel的OnPaint中,代码比较长这里就不放了。

SGraphNode_BehaviorTree负责行为树中单个节点的绘制。

相关推荐
偶尔微微一笑1 小时前
AI网络渗透kali应用(gptshell)
linux·人工智能·python·自然语言处理·编辑器
晓数2 小时前
【硬核干货】JetBrains AI Assistant 干货笔记
人工智能·笔记·jetbrains·ai assistant
timing9942 小时前
LVGL在VScode的WSL2中仿真
ide·vscode·编辑器
我的golang之路果然有问题2 小时前
速成GO访问sql,个人笔记
经验分享·笔记·后端·sql·golang·go·database
lwewan2 小时前
26考研——存储系统(3)
c语言·笔记·考研
搞机小能手2 小时前
六个能够白嫖学习资料的网站
笔记·学习·分类
nongcunqq3 小时前
爬虫练习 js 逆向
笔记·爬虫
汐汐咯3 小时前
终端运行java出现???
笔记
无敌小茶5 小时前
Linux学习笔记之环境变量
linux·笔记
帅云毅6 小时前
Web3.0的认知补充(去中心化)
笔记·学习·web3·去中心化·区块链