If I had to choose one recent experience in working with UE that felt the most meaningless and torturous, it would definitely be trying to find a resource in the ContentBrowser based on a long string path.
The inability to quickly jump directly to a directory or resource makes me feel like I’m wasting my life every time I have to navigate deeply nested folders one step at a time.
To address this pain point, I wrote a small tool that significantly alleviates this manual anxiety. I named it j2
(jump to), which is a minimalist ContentBrowser jump tool, open-source on GitHub: hxhb/JumpTo.
This article will introduce the usage of this project, the thought process behind conceptualizing the solution, and the step-by-step code implementation. Although the functionality itself is a very simple plugin, the discovery, analysis, and solution process for the actual pain points is worth documenting.
j2
What is j2?
j2 is a tool that allows you to quickly locate directories or resources in the ContentBrowser through the Console in UE without having to manually click through each folder level, enabling direct jumps via commands.
Usage
The plugin provides a command named j2
(jump to), which can be invoked via the Console (` key):
Parameters can be passed for directories:
1 | j2 /Game/StarterContent/Maps |
Or resource paths:
1 | j2 /Game/StarterContent/Textures/T_Concrete_Panels_N |
It also supports paths for resources copied from the ContentBrowser:
1 | j2 /Script/Engine.Texture2D'/Game/StarterContent/Textures/T_Concrete_Panels_N.T_Concrete_Panels_N' |
When the passed parameter is a resource path, it jumps to the directory containing the resource and selects it.
History
Since it’s based on the Console, it can naturally save execution history, allowing quick switching between recorded commands:
Note: The Console execution history is stored in [PROJECT_DIR]/Saved/Config/ConsoleHistory.ini
.
1 | [ConsoleHistory] |
Implementation Principle
Requirement Analysis
The code for j2 is very simple; I finished writing it in half an hour. Although the functionality is straightforward, a requirement analysis was still necessary to understand what features to implement and what support was needed from the engine, thus documenting my thought process in creating the tool.
- Only runs in the Editor; no need for Runtime modules.
- Invoke commands via Console, executing functions and passing parameters.
- Extract parameters from Console commands.
- Check if parameters are valid paths or resources.
- Determine the type of incoming path: is it a directory or resource (PackageName or Copy Reference).
- Locate the directory or resource path within the current ContentBrowser window.
Invoking Commands
You can use FAutoConsoleCommand
to define a command and pass in a callback function:
1 | static FAutoConsoleCommand J2Cmd( |
The callback is a function with the prototype void(const TArray< FString >&)
, which can retrieve parameters passed from the Console.
Then it can be recognized in the Console:
Accepting Parameters
Parameters can be obtained from the callback function bound to the FAutoConsoleCommand
object:
1 | void J2(const TArray<FString>& Args) |
Since only one path needs to be jumped to, we only take the first parameter.
Validating Parameters
Incoming parameters need to be checked to determine whether they are valid resources or paths.
Resources
For resources, we need to check both LongPackageName and Copy Reference formats simultaneously, requiring two checks.
If the input is a LongPackageName, its format is /Game/XXXX/YYYY
, so we can check it directly using FPackageName
‘s DoesPackageExist
function:
1 | bool bPackageExist = FPackageName::DoesPackageExist(JumpTo); |
If it’s in the Copy Reference
format, it looks like:
1 | Class'/Game/XXX/YYY.YYYY' |
Validation can be performed using a regular expression with the pattern '[^']*/[^']*'
:
1 | TArray<FString> OutRegStrings; |
IsMatchRegExp
is a function I implemented in the plugin based on the engine’s FRegexMatcher
, used to check if a string matches a specific regex and can also extract the actual resource path from the Copy Reference.
It can also be used in all logical scenarios requiring regex matching; I used it in ResScanner for naming and path regex matching. The complete code is as follows:
1 | bool UFlibJumpToHelper::IsMatchRegExp(const FString& Str, const FString& RegExp,TArray<FString>& OutMatchStrs) |
Paths
For parameters passed as paths, we need to check whether they are valid resource directories to determine if the jump should proceed.
The concept of RootPaths comes into play here. When the engine project and plugin are registered, the content directory is added to RootPaths, allowing recognition of resources from different modules.
For example:
1 | /Engine/xxx |
All these forms are based on the respective module’s RootPath. Of course, I want j2 to jump to resources in any module, not limited to /Game
or /Engine
.
This requires a universal method to check if a resource directory exists, but I couldn’t find a directly usable function, so I devised an alternative approach.
In UE, FPackageName
has a function TryConvertLongPackageNameToFilename
, which can convert the resource’s LongPackageName to the disk path of the uasset.
1 | FString AssetAbsPath; |
This will return:
1 | E:\UnrealProjects\BlankUE5\Content\StarterContent\Textures\T_Burst_M.uasset |
Its purpose is to transform the asset path in the engine into the corresponding uasset path on disk, though it’s not absolute. Moreover, converting a directory path can be achieved by controlling parameters.
For example, to get the disk path of /Game/StarterContent
:
1 | FString StarterContentAbsPath; |
Just treat the directory as a resource and pass an empty Extension
parameter. It will convert to the absolute path:
1 | E:\UnrealProjects\BlankUE5\Content\StarterContent |
Then use FPaths::DirectoryExists
to check if the absolute path exists, which allows for checking all RootPath directories in the engine.
Jumping to a Path
In the Content Browser, right-clicking a directory gives a Show In New Content Browser
option:
What I want is a function like this, but controlled by code regarding the path opened.
I examined the engine code, and the functionality is implemented with:
1 | // Engine\Source\Editor\ContentBrowser\Private\SContentBrowser.cpp |
The SyncBrowserToItems
function is what we need.
A brief look at the defining class FContentBrowserSingleton
revealed an even better-suited function, SyncBrowserToFolders
, which only requires a string to be passed. The prototype is as follows:
1 | virtual void SyncBrowserToFolders(const TArray<FString>& FolderList, bool bAllowLockedBrowsers = false, bool bFocusContentBrowser = true, const FName& InstanceName = FName(), bool bNewSpawnBrowser = false); |
We can directly call this in the J2 function and pass the path received from the Console to FolderList
.
Note: Be sure to remove any trailing slashes from the path, such as
/Game/XXXX/
.
1 | while(JumpTo.RemoveFromEnd(TEXT("/"))){}; |
This enables locating the specified directory in the current ContentBrowser. If the current editor layout does not contain a ContentBrowser, one will be automatically created.
If you want to open in a new ContentBrowser panel, you can pass true
for the bNewSpawnBrowser
parameter.
Jumping to a Resource
Jumping to a resource has some differences compared to jumping to a path; essentially, it first jumps to the resource’s directory and then navigates to the resource within the current directory.
In the class FContentBrowserSingleton
, there’s a similar interface:
1 | virtual void SyncBrowserToAssets(const TArray<struct FAssetData>& AssetDataList, bool bAllowLockedBrowsers = false, bool bFocusContentBrowser = true, const FName& InstanceName = FName(), bool bNewSpawnBrowser = false); |
Similar to opening a directory but requires passing the resource’s AssetData
.
Since the LongPackageName and Copy Reference we pass are full resource paths, we can retrieve the corresponding FAssetData
from the AssetRegistry.
1 | FAssetData GetAssetDataByLongPackageName(FName LongPackageNames) |
Note: If the input is in LongPackageName format, it needs to be converted to
ObjectPath
format.
Then we can call SyncBrowserToAssets
(also ensure that the obtained FAssetData
is valid):
1 | FAssetData AssetData = GetAssetDataByLongPackageName(*JumpTo); |
This achieves the effect of jumping to the resource in the ContentBrowser.
Conclusion
With this plugin, I no longer worry about finding resources from paths. Even if someone sends a long string of paths, I can jump to it by simply copying and pasting, which is incredibly satisfying.
Additionally, this serves as a case study in plugin development, tracing the entire process from discovering the need, to conceptualizing a solution, and finally implementing it. It reflects my daily problem-solving approach in development.
Only by truly discovering issues and analyzing them can we approach resolving them as a process of handling specific points while making full use of the existing features of the engine to achieve a smooth outcome.