Skip to content

Commit

Permalink
Add support for linking BehaviorTree nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
zosiu authored Jun 20, 2023
1 parent 9b95099 commit 7881fae
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 71 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ For nodes that have a jump target, you'll see an additional `Copy 'Open node' UR

Clicking on links like these will open up the (jump target of the) linked node in the editor.

## Linking BehaviorTree nodes
This feature is only available in UE 5.3+, if you are using a source build, make sure you have [this commit](https://github.com/EpicGames/UnrealEngine/commit/6bb52ead8294554e5bdb60f637729eae397942d8).

Once you've set up RedTalaria, you should be able to right click on any BehaviorTree node and see a new `Copy 'Focus node' URL` option:

[<img src="README_link_behavior_tree_node_focus.png?raw=true">](README_link_behavior_tree_node_focus.png?raw=true)

Clicking on links like these will open up the asset in the editor and focus on the linked node.

## Linking Levels at a specific camera position
Once you've set up RedTalaria, under the Viewport options menu you should see a new `Copy camera coords URL` option (it will only be active for perspective viewports):

Expand Down
Binary file added README_link_behavior_tree_node_focus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
150 changes: 79 additions & 71 deletions RedTalaria.uplugin
Original file line number Diff line number Diff line change
@@ -1,73 +1,81 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.1.0",
"FriendlyName": "RedTalaria",
"Description": "A collection of Hermes endpoints. Hermes icons created by max.icons - Flaticon : https://www.flaticon.com/free-icons/hermes",
"Category": "Other",
"CreatedBy": "CD PROJEKT S.A.",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "RedTalariaUrls",
"Type": "Runtime",
"LoadingPhase": "Default"
},
{
"Name": "RedTalaria",
"Type": "EditorNoCommandlet",
"LoadingPhase": "Default",
"WhitelistPlatforms": [
"Win64"
]
},
{
"Name": "RedHermesGraphNodeEndpoint",
"Type": "EditorNoCommandlet",
"LoadingPhase": "Default",
"WhitelistPlatforms": [
"Win64"
]
},
{
"Name": "RedHermesBlueprintNodeEndpoint",
"Type": "EditorNoCommandlet",
"LoadingPhase": "PostEngineInit",
"WhitelistPlatforms": [
"Win64"
]
},
{
"Name": "RedHermesLevelAtCameraCoordsEndpoint",
"Type": "EditorNoCommandlet",
"LoadingPhase": "PostEngineInit",
"WhitelistPlatforms": [
"Win64"
]
},
{
"Name": "RedHermesContentFolderEndpoint",
"Type": "EditorNoCommandlet",
"LoadingPhase": "Default",
"WhitelistPlatforms": [
"Win64"
]
}
],
"Plugins": [
{
"Name": "HermesCore",
"Enabled": true,
"WhitelistPlatforms": [
"Win64"
]
}
]
"FileVersion": 3,
"Version": 1,
"VersionName": "1.1.0",
"FriendlyName": "RedTalaria",
"Description": "A collection of Hermes endpoints. Hermes icons created by max.icons - Flaticon : https://www.flaticon.com/free-icons/hermes",
"Category": "Other",
"CreatedBy": "CD PROJEKT S.A.",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "RedTalariaUrls",
"Type": "Runtime",
"LoadingPhase": "Default"
},
{
"Name": "RedTalaria",
"Type": "EditorNoCommandlet",
"LoadingPhase": "Default",
"WhitelistPlatforms": [
"Win64"
]
},
{
"Name": "RedHermesGraphNodeEndpoint",
"Type": "EditorNoCommandlet",
"LoadingPhase": "Default",
"WhitelistPlatforms": [
"Win64"
]
},
{
"Name": "RedHermesBlueprintNodeEndpoint",
"Type": "EditorNoCommandlet",
"LoadingPhase": "PostEngineInit",
"WhitelistPlatforms": [
"Win64"
]
},
{
"Name": "RedHermesBehaviorTreeNodeEndpoint",
"Type": "EditorNoCommandlet",
"LoadingPhase": "PostEngineInit",
"WhitelistPlatforms": [
"Win64"
]
},
{
"Name": "RedHermesLevelAtCameraCoordsEndpoint",
"Type": "EditorNoCommandlet",
"LoadingPhase": "PostEngineInit",
"WhitelistPlatforms": [
"Win64"
]
},
{
"Name": "RedHermesContentFolderEndpoint",
"Type": "EditorNoCommandlet",
"LoadingPhase": "Default",
"WhitelistPlatforms": [
"Win64"
]
}
],
"Plugins": [
{
"Name": "HermesCore",
"Enabled": true,
"WhitelistPlatforms": [
"Win64"
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) CD PROJEKT S.A.

#include "RedHermesBehaviorTreeNodeEndpoint.h"

#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 3
#include "BehaviorTreeGraphNode.h"
#include "Editor.h"
#include "IBehaviorTreeEditor.h"
#include "RedHermesGraphNodeEndpoint.h"
#include "RedHermesGraphNodeEndpointEditorExtension.h"
#include "BehaviorTree/BehaviorTree.h"
#include "EdGraph/EdGraph.h"
#include "Toolkits/ToolkitManager.h"
#endif

#define LOCTEXT_NAMESPACE "FRedHermesBehaviorTreeNodeEndpointModule"

void FRedHermesBehaviorTreeNodeEndpointModule::StartupModule()
{
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 3
if (const auto RedHermesGraphNodeEndpoint = GEditor->GetEditorSubsystem<URedHermesGraphNodeEndpoint>())
{
OnRequestDelegateHandle = RedHermesGraphNodeEndpoint->OnRequest().AddStatic(&FRedHermesBehaviorTreeNodeEndpointModule::HandleRequest);
}

if (const auto NodeExtensionSubsystem = GEditor->GetEditorSubsystem<URedHermesGraphNodeEndpointEditorExtension>())
{
OnNodeMenuExtensionHookRequestDelegateHandle = NodeExtensionSubsystem->OnNodeMenuExtensionHookRequest().AddStatic(&FRedHermesBehaviorTreeNodeEndpointModule::ProvideNodeExtensionHook);
}
#endif
}

void FRedHermesBehaviorTreeNodeEndpointModule::ShutdownModule()
{
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 3
if (OnRequestDelegateHandle.IsValid())
{
if (GEditor != nullptr)
{
if (const auto RedHermesGraphNodeEndpoint = GEditor->GetEditorSubsystem<URedHermesGraphNodeEndpoint>())
{
RedHermesGraphNodeEndpoint->OnRequest().Remove(OnRequestDelegateHandle);
}
OnRequestDelegateHandle.Reset();
}
}

if (OnNodeMenuExtensionHookRequestDelegateHandle.IsValid())
{
if (GEditor != nullptr)
{
if (const auto NodeExtensionSubsystem = GEditor->GetEditorSubsystem<URedHermesGraphNodeEndpointEditorExtension>())
{
NodeExtensionSubsystem->OnNodeMenuExtensionHookRequest().Remove(OnNodeMenuExtensionHookRequestDelegateHandle);
}
OnNodeMenuExtensionHookRequestDelegateHandle.Reset();
}
}
#endif
}

#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 3
UEdGraphNode* FRedHermesBehaviorTreeNodeEndpointModule::GetNodeInAsset(FGuid NodeGuid, const UObject* Asset)
{

if (const auto BehaviorTreeObj = Cast<UBehaviorTree>(Asset))
{
for (UEdGraphNode* Node : BehaviorTreeObj->BTGraph->Nodes)
{
if (Node->NodeGuid == NodeGuid)
{
return Node;
}
}
}

return nullptr;
}

void FRedHermesBehaviorTreeNodeEndpointModule::HandleRequest(FGuid NodeGuid, UObject* Asset, bool bOpenJumpTarget)
{
if (UEdGraphNode* Node = GetNodeInAsset(NodeGuid, Asset))
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Asset);
TSharedPtr<IToolkit> FoundAssetEditor = FToolkitManager::Get().FindEditorForAsset(Asset);
if (FoundAssetEditor.IsValid())
{
auto BehaviorTreeEditor = StaticCastSharedPtr<IBehaviorTreeEditor>(FoundAssetEditor);
BehaviorTreeEditor->FocusWindow();
bOpenJumpTarget ? BehaviorTreeEditor->DoubleClickNode(Node) : BehaviorTreeEditor->FocusAttentionOnNode(Node);
}
}
}

void FRedHermesBehaviorTreeNodeEndpointModule::ProvideNodeExtensionHook(const UEdGraphNode* Node, const UEdGraph* /*Graph*/, TSet<FName>& ExtensionHooks)
{
if (Node->IsA<UBehaviorTreeGraphNode>())
{
ExtensionHooks.Emplace(FName(TEXT("BehaviorTreeGraphSchemaNodeActions")));
}
}
#endif

#undef LOCTEXT_NAMESPACE

IMPLEMENT_MODULE(FRedHermesBehaviorTreeNodeEndpointModule, RedHermesBehaviorTreeNodeEndpoint)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) CD PROJEKT S.A.

#pragma once

// The engine commit that is needed for this to work: https://github.com/EpicGames/UnrealEngine/commit/6bb52ead8294554e5bdb60f637729eae397942d8 .

#include "Modules/ModuleInterface.h"
#include "Runtime/Launch/Resources/Version.h"

#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 3
#include "Delegates/IDelegateInstance.h"
#include "UObject/NameTypes.h"

class UObject;
class UEdGraphNode;
class UEdGraph;
#endif

class FRedHermesBehaviorTreeNodeEndpointModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;

private:
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 3
FDelegateHandle OnRequestDelegateHandle;
FDelegateHandle OnNodeMenuExtensionHookRequestDelegateHandle;

static UEdGraphNode* GetNodeInAsset(FGuid NodeGuid, const UObject* Asset);
static void HandleRequest(FGuid NodeGuid, UObject* Asset, bool bOpenJumpTarget);
static void ProvideNodeExtensionHook(const UEdGraphNode* Node, const UEdGraph* Graph, TSet<FName>& ExtensionHooks);
#endif
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) CD PROJEKT S.A.

using UnrealBuildTool;

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

PublicDependencyModuleNames.AddRange(
new[]
{
"Core",
"AIModule",
}
);

PrivateDependencyModuleNames.AddRange(
new[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"UnrealEd",
"RedHermesGraphNodeEndpoint",
"AIGraph",
"BehaviorTreeEditor",
"EditorFramework",
}
);
}
}

0 comments on commit 7881fae

Please sign in to comment.