claude-code-arcane 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +104 -55
- package/agents/career/career-strategist.md +88 -0
- package/agents/career/cv-reviewer.md +81 -0
- package/agents/career/hiring-manager.md +68 -0
- package/agents/career/mock-interviewer.md +76 -0
- package/agents/freelance/client-screener.md +84 -0
- package/agents/freelance/discovery-call.md +65 -0
- package/agents/freelance/pipeline-strategist.md +99 -0
- package/agents/freelance/proposal-reviewer.md +82 -0
- package/agents/game/ue-blueprint-specialist.md +125 -0
- package/agents/game/ue-gas-specialist.md +133 -0
- package/agents/game/ue-umg-specialist.md +131 -0
- package/dist/cli.js +57 -13
- package/docs/RELEASE-SETUP.md +1 -1
- package/docs/SKILLS-CATALOG.md +67 -4
- package/docs/USER-GUIDE.md +77 -72
- package/docs/agent-hierarchy.md +21 -1
- package/docs/presentations/build_arcane_deck.py +2 -2
- package/docs/presentations/build_arcane_deck_12.py +2 -2
- package/hooks/detect-unreal.sh +90 -0
- package/package.json +2 -2
- package/profiles/freelance.yaml +43 -0
- package/profiles/job-hunt.yaml +5 -2
- package/profiles/unreal-dev.yaml +97 -0
- package/rules/drafter-reviewer.md +16 -3
- package/rules/freelance-guardrails.md +53 -0
- package/rules/gamedev/unreal-code.md +125 -0
- package/skills/career-registry/SKILL.md +95 -0
- package/skills/career-registry/scripts/career_registry.py +488 -0
- package/skills/client-screen/SKILL.md +83 -0
- package/skills/client-screen/references/red-flags.md +95 -0
- package/skills/cover-letter/SKILL.md +4 -3
- package/skills/cv-tailor/SKILL.md +11 -2
- package/skills/freelance-deliver/SKILL.md +90 -0
- package/skills/freelance-hunt/SKILL.md +126 -0
- package/skills/freelance-hunt/references/templates/Connects.md +39 -0
- package/skills/freelance-hunt/references/templates/Contrato.md +60 -0
- package/skills/freelance-hunt/references/templates/Propuesta.md +66 -0
- package/skills/freelance-kickoff/SKILL.md +94 -0
- package/skills/freelance-pipeline/SKILL.md +109 -0
- package/skills/freelance-pipeline/scripts/import_history.py +425 -0
- package/skills/freelance-profile/SKILL.md +93 -0
- package/skills/freelance-proposal/SKILL.md +112 -0
- package/skills/freelance-scan/SKILL.md +196 -0
- package/skills/freelance-scan/references/dedup.md +35 -0
- package/skills/freelance-scan/references/platforms.md +167 -0
- package/skills/freelance-scan/scripts/freelance_search.py +1077 -0
- package/skills/install-mcp/SKILL.md +135 -46
- package/skills/install-mcp/references/unreal-operations.md +55 -0
- package/skills/interview-prep/SKILL.md +11 -3
- package/skills/job-aplicar/SKILL.md +5 -1
- package/skills/job-hunt/SKILL.md +17 -0
- package/skills/job-outcome/SKILL.md +15 -1
- package/skills/job-upskill/SKILL.md +10 -2
- package/skills/ue-actor-component-architecture/SKILL.md +509 -0
- package/skills/ue-actor-component-architecture/references/actor-lifecycle.md +301 -0
- package/skills/ue-actor-component-architecture/references/component-types.md +592 -0
- package/skills/ue-ai-navigation/SKILL.md +526 -0
- package/skills/ue-ai-navigation/references/behavior-tree-patterns.md +392 -0
- package/skills/ue-ai-navigation/references/behavior-tree-workflow.md +107 -0
- package/skills/ue-ai-navigation/references/custom-bttask.md +104 -0
- package/skills/ue-ai-navigation/references/eqs-reference.md +423 -0
- package/skills/ue-animation-system/SKILL.md +521 -0
- package/skills/ue-animation-system/references/anim-notify-reference.md +486 -0
- package/skills/ue-animation-system/references/locomotion-setup.md +550 -0
- package/skills/ue-async-threading/SKILL.md +433 -0
- package/skills/ue-async-threading/references/thread-safety-guide.md +213 -0
- package/skills/ue-async-threading/references/threading-patterns.md +345 -0
- package/skills/ue-audio-system/SKILL.md +527 -0
- package/skills/ue-audio-system/references/audio-setup-patterns.md +700 -0
- package/skills/ue-blueprints/SKILL.md +134 -0
- package/skills/ue-blueprints/references/communication.md +82 -0
- package/skills/ue-character-movement/SKILL.md +475 -0
- package/skills/ue-character-movement/references/cmc-extension-patterns.md +335 -0
- package/skills/ue-character-movement/references/movement-pipeline.md +189 -0
- package/skills/ue-cpp-foundations/SKILL.md +533 -0
- package/skills/ue-cpp-foundations/references/components-and-gc.md +107 -0
- package/skills/ue-cpp-foundations/references/container-patterns.md +544 -0
- package/skills/ue-cpp-foundations/references/delegate-patterns.md +497 -0
- package/skills/ue-cpp-foundations/references/gameplay-classes-cpp.md +136 -0
- package/skills/ue-cpp-foundations/references/property-specifiers.md +306 -0
- package/skills/ue-data-assets-tables/SKILL.md +527 -0
- package/skills/ue-data-assets-tables/references/asset-loading-patterns.md +306 -0
- package/skills/ue-data-assets-tables/references/data-driven-design.md +378 -0
- package/skills/ue-editor-tools/SKILL.md +523 -0
- package/skills/ue-editor-tools/references/detail-customization-patterns.md +395 -0
- package/skills/ue-editor-tools/references/editor-module-setup.md +480 -0
- package/skills/ue-game-features/SKILL.md +477 -0
- package/skills/ue-game-features/references/experience-system.md +222 -0
- package/skills/ue-game-features/references/game-feature-patterns.md +308 -0
- package/skills/ue-gameplay-abilities/SKILL.md +518 -0
- package/skills/ue-gameplay-abilities/references/ability-task-reference.md +391 -0
- package/skills/ue-gameplay-abilities/references/gameplay-effect-reference.md +397 -0
- package/skills/ue-gameplay-abilities/references/gas-setup-patterns.md +369 -0
- package/skills/ue-gameplay-framework/SKILL.md +466 -0
- package/skills/ue-gameplay-framework/references/framework-class-map.md +243 -0
- package/skills/ue-input-system/SKILL.md +394 -0
- package/skills/ue-input-system/references/cpp-setup.md +131 -0
- package/skills/ue-input-system/references/enhanced-input-workflow.md +117 -0
- package/skills/ue-input-system/references/input-action-reference.md +731 -0
- package/skills/ue-mass-entity/SKILL.md +476 -0
- package/skills/ue-mass-entity/references/mass-entity-patterns.md +319 -0
- package/skills/ue-mass-entity/references/mass-fragment-reference.md +119 -0
- package/skills/ue-materials-rendering/SKILL.md +526 -0
- package/skills/ue-materials-rendering/references/material-parameter-reference.md +216 -0
- package/skills/ue-materials-rendering/references/post-process-settings.md +383 -0
- package/skills/ue-module-build-system/SKILL.md +487 -0
- package/skills/ue-module-build-system/references/build-cs-reference.md +418 -0
- package/skills/ue-module-build-system/references/common-build-errors.md +381 -0
- package/skills/ue-naming-conventions/SKILL.md +142 -0
- package/skills/ue-naming-conventions/references/asset-name-modifiers.md +233 -0
- package/skills/ue-naming-conventions/references/content-structure.md +83 -0
- package/skills/ue-networking-replication/SKILL.md +452 -0
- package/skills/ue-networking-replication/references/replication-patterns.md +526 -0
- package/skills/ue-networking-replication/references/rpc-decision-guide.md +393 -0
- package/skills/ue-niagara-effects/SKILL.md +531 -0
- package/skills/ue-niagara-effects/references/niagara-data-interfaces.md +491 -0
- package/skills/ue-niagara-effects/references/niagara-parameter-types.md +153 -0
- package/skills/ue-niagara-effects/references/niagara-workflow.md +115 -0
- package/skills/ue-packaging/SKILL.md +116 -0
- package/skills/ue-physics-collision/SKILL.md +423 -0
- package/skills/ue-physics-collision/references/collision-channel-setup.md +218 -0
- package/skills/ue-physics-collision/references/trace-patterns.md +499 -0
- package/skills/ue-procedural-generation/SKILL.md +505 -0
- package/skills/ue-procedural-generation/references/pcg-node-reference.md +381 -0
- package/skills/ue-procedural-generation/references/procedural-mesh-patterns.md +940 -0
- package/skills/ue-project-context/SKILL.md +305 -0
- package/skills/ue-sequencer-cinematics/SKILL.md +506 -0
- package/skills/ue-sequencer-cinematics/references/sequencer-patterns.md +629 -0
- package/skills/ue-serialization-savegames/SKILL.md +524 -0
- package/skills/ue-serialization-savegames/references/save-system-architecture.md +705 -0
- package/skills/ue-state-trees/SKILL.md +489 -0
- package/skills/ue-state-trees/references/state-tree-mass-integration.md +184 -0
- package/skills/ue-state-trees/references/state-tree-patterns.md +321 -0
- package/skills/ue-testing-debugging/SKILL.md +520 -0
- package/skills/ue-testing-debugging/references/automation-test-patterns.md +387 -0
- package/skills/ue-testing-debugging/references/profiling-commands.md +357 -0
- package/skills/ue-ui-umg-slate/SKILL.md +431 -0
- package/skills/ue-ui-umg-slate/references/common-ui-setup.md +484 -0
- package/skills/ue-ui-umg-slate/references/widget-types.md +487 -0
- package/skills/ue-world-level-streaming/SKILL.md +518 -0
- package/skills/ue-world-level-streaming/references/streaming-patterns.md +414 -0
- package/templates/gamedev/unreal-project-context.md +107 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ue-editor-tools
|
|
3
|
+
description: "Editor utility widgets, Blutility, detail customization, property editors, editor subsystems"
|
|
4
|
+
category: "gamedev"
|
|
5
|
+
argument-hint: "[tool o customization]"
|
|
6
|
+
user-invocable: true
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Bash, Write, Edit
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# UE Editor Tools
|
|
11
|
+
|
|
12
|
+
You are an expert in extending the Unreal Editor with custom tools and workflows.
|
|
13
|
+
|
|
14
|
+
Ask before writing: this skill edits editor module code, utility widgets and detail customizations. Propose the change and get approval before using Write or Edit.
|
|
15
|
+
|
|
16
|
+
## Context
|
|
17
|
+
|
|
18
|
+
Read `docs/unreal/project-context.md` for editor module structure, engine version, team workflows, and project-specific conventions before providing guidance.
|
|
19
|
+
|
|
20
|
+
## Before You Start
|
|
21
|
+
|
|
22
|
+
Ask which area the user needs if not clear:
|
|
23
|
+
- **Editor Utility Widget** — UMG panel run from editor right-click
|
|
24
|
+
- **Blutility** — UAssetActionUtility or UActorActionUtility scripted actions
|
|
25
|
+
- **Detail Customization** — Custom property panel (IDetailCustomization / IPropertyTypeCustomization)
|
|
26
|
+
- **Custom Editor Mode** — Viewport mode with specialized interaction (FEdMode)
|
|
27
|
+
- **Asset Type Actions** — Content Browser integration for a custom asset type
|
|
28
|
+
- **Editor Subsystem** — Editor-lifetime singleton (UEditorSubsystem)
|
|
29
|
+
- **Menu / Toolbar Extension** — UToolMenus additions to main menu, toolbars, context menus
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Editor Module Setup
|
|
34
|
+
|
|
35
|
+
All editor-extending code must live in a module with `"Type": "Editor"`. Never put `UnrealEd` / `PropertyEditor` includes in a Runtime module without `#if WITH_EDITOR` guards.
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{ "Name": "MyGameEditor", "Type": "Editor", "LoadingPhase": "PostEngineInit" }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```csharp
|
|
42
|
+
// MyGameEditor.Build.cs — key dependencies
|
|
43
|
+
PrivateDependencyModuleNames.AddRange(new string[] {
|
|
44
|
+
"Core", "CoreUObject", "Engine", "UnrealEd",
|
|
45
|
+
"Slate", "SlateCore", "EditorStyle",
|
|
46
|
+
"PropertyEditor", // IDetailCustomization, IPropertyTypeCustomization
|
|
47
|
+
"EditorSubsystem", // UEditorSubsystem
|
|
48
|
+
"Blutility", // UEditorUtilityWidget, UAssetActionUtility
|
|
49
|
+
"ToolMenus", // UToolMenus
|
|
50
|
+
"AssetTools", // FAssetTypeActions_Base
|
|
51
|
+
"MyGame"
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
Module skeleton — every registration in `StartupModule` must be mirrored in `ShutdownModule`:
|
|
55
|
+
```cpp
|
|
56
|
+
IMPLEMENT_MODULE(FMyGameEditorModule, MyGameEditor)
|
|
57
|
+
|
|
58
|
+
void FMyGameEditorModule::StartupModule()
|
|
59
|
+
{
|
|
60
|
+
FPropertyEditorModule& PropMod =
|
|
61
|
+
FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
62
|
+
PropMod.RegisterCustomClassLayout(
|
|
63
|
+
UMyDataAsset::StaticClass()->GetFName(),
|
|
64
|
+
FOnGetDetailCustomizationInstance::CreateStatic(
|
|
65
|
+
&FMyDataAssetCustomization::MakeInstance));
|
|
66
|
+
PropMod.NotifyCustomizationModuleChanged();
|
|
67
|
+
|
|
68
|
+
UToolMenus::RegisterStartupCallback(
|
|
69
|
+
FSimpleMulticastDelegate::FDelegate::CreateRaw(
|
|
70
|
+
this, &FMyGameEditorModule::RegisterMenus));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
void FMyGameEditorModule::ShutdownModule()
|
|
74
|
+
{
|
|
75
|
+
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
|
|
76
|
+
{
|
|
77
|
+
FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor")
|
|
78
|
+
.UnregisterCustomClassLayout(UMyDataAsset::StaticClass()->GetFName());
|
|
79
|
+
}
|
|
80
|
+
UToolMenus::UnRegisterStartupCallback(this);
|
|
81
|
+
if (UToolMenus* TM = UToolMenus::TryGet()) { TM->UnregisterOwner(this); }
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
> Full boilerplate with asset type actions, editor modes, and factory: `references/editor-module-setup.md`
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Editor Utility Widgets
|
|
90
|
+
|
|
91
|
+
`UEditorUtilityWidget` (from `EditorUtilityWidget.h`) extends `UUserWidget` for editor-only UMG panels. Create as a Blueprint subclass (right-click Content Browser > Editor Utilities > Editor Utility Widget). **Run**: right-click the widget asset > Run Editor Utility Widget. Or subclass in C++:
|
|
92
|
+
|
|
93
|
+
```cpp
|
|
94
|
+
#pragma once
|
|
95
|
+
#include "EditorUtilityWidget.h"
|
|
96
|
+
#include "MyEditorUtilityWidget.generated.h"
|
|
97
|
+
|
|
98
|
+
UCLASS()
|
|
99
|
+
class MYGAMEEDITOR_API UMyEditorUtilityWidget : public UEditorUtilityWidget
|
|
100
|
+
{
|
|
101
|
+
GENERATED_BODY()
|
|
102
|
+
public:
|
|
103
|
+
UFUNCTION(BlueprintCallable, Category = "My Tools")
|
|
104
|
+
void BatchRenameSelectedAssets(const FString& Prefix);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// .cpp
|
|
108
|
+
void UMyEditorUtilityWidget::BatchRenameSelectedAssets(const FString& Prefix)
|
|
109
|
+
{
|
|
110
|
+
for (UObject* Asset : UEditorUtilityLibrary::GetSelectedAssets())
|
|
111
|
+
{
|
|
112
|
+
if (Asset)
|
|
113
|
+
{
|
|
114
|
+
const FString Src = Asset->GetOutermost()->GetName(); // e.g. /Game/Folder/OldName
|
|
115
|
+
UEditorAssetLibrary::RenameAsset(Src, FPaths::GetPath(Src) / Prefix + TEXT("_") + Asset->GetName());
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Key `UEditorUtilityLibrary` functions (from `EditorUtilityLibrary.h`):
|
|
122
|
+
|
|
123
|
+
| Function | Purpose |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `GetSelectedAssets()` | Currently selected Content Browser assets |
|
|
126
|
+
| `GetSelectedAssetsOfClass(UClass*)` | Filter selection by class |
|
|
127
|
+
| `UEditorAssetLibrary::DeleteAsset(Path)` | Delete asset; `RenameAsset` for move/rename |
|
|
128
|
+
| `GetSelectionSet()` | Selected level actors |
|
|
129
|
+
| `SyncBrowserToFolders(TArray<FString>)` | Sync content browser view |
|
|
130
|
+
|
|
131
|
+
**Content browser filters**: Use `FARFilter` with `IAssetRegistry::GetAssets` for programmatic asset queries by class, path, or tags.
|
|
132
|
+
|
|
133
|
+
Open a widget programmatically:
|
|
134
|
+
|
|
135
|
+
```cpp
|
|
136
|
+
GEditor->GetEditorSubsystem<UEditorUtilitySubsystem>()
|
|
137
|
+
->SpawnAndRegisterTab(LoadObject<UEditorUtilityWidgetBlueprint>(
|
|
138
|
+
nullptr, TEXT("/Game/EditorWidgets/BP_MyTool")));
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Blutility: Scripted Actions
|
|
144
|
+
|
|
145
|
+
### UAssetActionUtility — Asset Right-Click Actions
|
|
146
|
+
|
|
147
|
+
Any `UFUNCTION(CallInEditor)` on a `UAssetActionUtility` subclass appears in the Content Browser context menu. Set `SupportedClasses` in Class Defaults to filter by asset type.
|
|
148
|
+
|
|
149
|
+
```cpp
|
|
150
|
+
#pragma once
|
|
151
|
+
#include "AssetActionUtility.h" // Engine/Source/Editor/Blutility/Classes/AssetActionUtility.h
|
|
152
|
+
#include "MyAssetActionUtility.generated.h"
|
|
153
|
+
|
|
154
|
+
UCLASS()
|
|
155
|
+
class MYGAMEEDITOR_API UMyAssetActionUtility : public UAssetActionUtility
|
|
156
|
+
{
|
|
157
|
+
GENERATED_BODY()
|
|
158
|
+
public:
|
|
159
|
+
UFUNCTION(CallInEditor, Category = "My Tools")
|
|
160
|
+
void SetTextureCompressionToUI()
|
|
161
|
+
{
|
|
162
|
+
for (UObject* Asset : UEditorUtilityLibrary::GetSelectedAssets())
|
|
163
|
+
{
|
|
164
|
+
if (UTexture2D* Tex = Cast<UTexture2D>(Asset))
|
|
165
|
+
{
|
|
166
|
+
Tex->CompressionSettings = TC_EditorIcon;
|
|
167
|
+
Tex->MarkPackageDirty();
|
|
168
|
+
Tex->PostEditChange();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### UActorActionUtility — Actor Right-Click Actions
|
|
176
|
+
|
|
177
|
+
Same pattern for level actors. Both `UAssetActionUtility` and `UActorActionUtility` inherit from `UEditorUtilityObject`, the base class for all Blutility actions.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Detail Customizations
|
|
182
|
+
|
|
183
|
+
### Class Customization — IDetailCustomization
|
|
184
|
+
|
|
185
|
+
```cpp
|
|
186
|
+
class FMyDataAssetCustomization : public IDetailCustomization
|
|
187
|
+
{
|
|
188
|
+
public:
|
|
189
|
+
static TSharedRef<IDetailCustomization> MakeInstance();
|
|
190
|
+
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
|
|
191
|
+
// Override the TSharedPtr overload too — store WeakBuilder for async ForceRefreshDetails
|
|
192
|
+
virtual void CustomizeDetails(const TSharedPtr<IDetailLayoutBuilder>& DetailBuilder) override;
|
|
193
|
+
private:
|
|
194
|
+
TWeakPtr<IDetailLayoutBuilder> WeakBuilder;
|
|
195
|
+
};
|
|
196
|
+
// In CustomizeDetails: EditCategory, AddProperty, AddCustomRow, HideCategory
|
|
197
|
+
// Register: PropMod.RegisterCustomClassLayout(UMyClass::StaticClass()->GetFName(), ...MakeInstance)
|
|
198
|
+
// Unregister in ShutdownModule: PropMod.UnregisterCustomClassLayout(...)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Struct Customization — IPropertyTypeCustomization
|
|
202
|
+
|
|
203
|
+
```cpp
|
|
204
|
+
class FMyStructCustomization : public IPropertyTypeCustomization
|
|
205
|
+
{
|
|
206
|
+
public:
|
|
207
|
+
static TSharedRef<IPropertyTypeCustomization> MakeInstance();
|
|
208
|
+
virtual void CustomizeHeader(TSharedRef<IPropertyHandle> Handle,
|
|
209
|
+
FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& Utils) override;
|
|
210
|
+
virtual void CustomizeChildren(TSharedRef<IPropertyHandle> Handle,
|
|
211
|
+
IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& Utils) override;
|
|
212
|
+
};
|
|
213
|
+
// Register: PropMod.RegisterCustomPropertyTypeLayout(FMyStruct::StaticStruct()->GetFName(), ...MakeInstance)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
> Full implementations, Slate row patterns, IPropertyHandle read/write, NameContent/ValueContent: `references/detail-customization-patterns.md`
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Custom Editor Modes
|
|
221
|
+
|
|
222
|
+
`FEdMode` (from `EdMode.h`) provides specialized viewport interaction. Register globally; only one active per viewport at a time.
|
|
223
|
+
|
|
224
|
+
```cpp
|
|
225
|
+
// MyEditorMode.h
|
|
226
|
+
class FMyEditorMode : public FEdMode
|
|
227
|
+
{
|
|
228
|
+
public:
|
|
229
|
+
static const FEditorModeID EM_MyMode;
|
|
230
|
+
FMyEditorMode();
|
|
231
|
+
virtual void Enter() override;
|
|
232
|
+
virtual void Exit() override;
|
|
233
|
+
virtual bool HandleClick(FEditorViewportClient*, HHitProxy*,
|
|
234
|
+
const FViewportClick&) override;
|
|
235
|
+
virtual bool MouseMove(FEditorViewportClient* ViewportClient,
|
|
236
|
+
FViewport* Viewport, int32 X, int32 Y) override;
|
|
237
|
+
// Use View->DeprojectFVector2D (FSceneView) for world-space ray from pixel coords
|
|
238
|
+
virtual void Render(const FSceneView*, FViewport*,
|
|
239
|
+
FPrimitiveDrawInterface*) override;
|
|
240
|
+
virtual bool UsesToolkits() const override { return true; }
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// MyEditorMode.cpp
|
|
244
|
+
const FEditorModeID FMyEditorMode::EM_MyMode = TEXT("EM_MyEditorMode");
|
|
245
|
+
|
|
246
|
+
FMyEditorMode::FMyEditorMode()
|
|
247
|
+
{
|
|
248
|
+
Info = FEditorModeInfo(EM_MyMode,
|
|
249
|
+
FText::FromString("My Editor Mode"), FSlateIcon(), /*bVisible=*/true);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
void FMyEditorMode::Enter()
|
|
253
|
+
{
|
|
254
|
+
FEdMode::Enter();
|
|
255
|
+
if (!Toolkit.IsValid())
|
|
256
|
+
{
|
|
257
|
+
Toolkit = MakeShareable(new FMyEditorModeToolkit);
|
|
258
|
+
Toolkit->Init(Owner->GetToolkitHost());
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
void FMyEditorMode::Exit()
|
|
263
|
+
{
|
|
264
|
+
if (Toolkit.IsValid())
|
|
265
|
+
FToolkitManager::Get().CloseToolkit(Toolkit.ToSharedRef());
|
|
266
|
+
FEdMode::Exit();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
void FMyEditorMode::Render(const FSceneView* View, FViewport* Viewport,
|
|
270
|
+
FPrimitiveDrawInterface* PDI)
|
|
271
|
+
{
|
|
272
|
+
FEdMode::Render(View, Viewport, PDI);
|
|
273
|
+
DrawWireSphere(PDI, FVector::ZeroVector, FLinearColor::Green, 50.f, 16, SDPG_World);
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Register/unregister in module lifecycle:
|
|
278
|
+
|
|
279
|
+
```cpp
|
|
280
|
+
// StartupModule
|
|
281
|
+
FEditorModeRegistry::Get().RegisterMode<FMyEditorMode>(
|
|
282
|
+
FMyEditorMode::EM_MyMode, FText::FromString("My Mode"), FSlateIcon(), true);
|
|
283
|
+
// ShutdownModule
|
|
284
|
+
FEditorModeRegistry::Get().UnregisterMode(FMyEditorMode::EM_MyMode);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Asset Type Actions
|
|
290
|
+
|
|
291
|
+
`FAssetTypeActions_Base` (from `AssetTypeActions_Base.h`) controls Content Browser appearance and context menu for custom asset types.
|
|
292
|
+
|
|
293
|
+
```cpp
|
|
294
|
+
class FMyAssetTypeActions : public FAssetTypeActions_Base
|
|
295
|
+
{
|
|
296
|
+
public:
|
|
297
|
+
virtual FText GetName() const override { return FText::FromString("My Asset"); }
|
|
298
|
+
virtual FColor GetTypeColor() const override { return FColor(200, 100, 50); }
|
|
299
|
+
virtual UClass* GetSupportedClass() const override { return UMyAsset::StaticClass(); }
|
|
300
|
+
virtual uint32 GetCategories() override { return EAssetTypeCategories::Misc; }
|
|
301
|
+
|
|
302
|
+
virtual void OpenAssetEditor(const TArray<UObject*>& InObjects,
|
|
303
|
+
TSharedPtr<IToolkitHost> EditWithinLevelEditor) override
|
|
304
|
+
{
|
|
305
|
+
for (UObject* Obj : InObjects)
|
|
306
|
+
if (UMyAsset* Asset = Cast<UMyAsset>(Obj))
|
|
307
|
+
MakeShareable(new FMyAssetEditor)->InitMyEditor(
|
|
308
|
+
EToolkitMode::Standalone, EditWithinLevelEditor, Asset);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Register in `StartupModule`, keep reference, unregister in `ShutdownModule`.
|
|
314
|
+
|
|
315
|
+
### FAssetEditorToolkit — Tab-Based Asset Editor Window
|
|
316
|
+
|
|
317
|
+
```cpp
|
|
318
|
+
class FMyAssetEditor : public FAssetEditorToolkit
|
|
319
|
+
{
|
|
320
|
+
public:
|
|
321
|
+
void InitMyEditor(EToolkitMode::Type Mode, TSharedPtr<IToolkitHost> Host, UMyAsset* Asset);
|
|
322
|
+
virtual FName GetToolkitFName() const override { return "MyAssetEditor"; }
|
|
323
|
+
virtual FText GetBaseToolkitName() const override { return INVTEXT("My Asset Editor"); }
|
|
324
|
+
virtual void RegisterTabSpawners(const TSharedRef<FTabManager>& TabMgr) override;
|
|
325
|
+
virtual void UnregisterTabSpawners(const TSharedRef<FTabManager>& TabMgr) override;
|
|
326
|
+
};
|
|
327
|
+
// Call InitAssetEditor() inside InitMyEditor to set up the tab layout via FTabManager::NewLayout
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Query open editors programmatically via `IAssetEditorInstance`:
|
|
331
|
+
|
|
332
|
+
```cpp
|
|
333
|
+
IAssetEditorInstance* Editor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()
|
|
334
|
+
->FindEditorForAsset(MyAsset, /*bFocusIfOpen=*/false);
|
|
335
|
+
if (Editor) Editor->FocusWindow(MyAsset); // CloseWindow(EAssetEditorCloseReason::AssetEditorHostClosed) — no-arg form deprecated 5.3
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Asset Factories
|
|
339
|
+
|
|
340
|
+
`UFactory` subclasses allow the Content Browser's "Add" menu to create new custom assets:
|
|
341
|
+
|
|
342
|
+
```cpp
|
|
343
|
+
UCLASS()
|
|
344
|
+
class UMyDataFactory : public UFactory
|
|
345
|
+
{
|
|
346
|
+
GENERATED_BODY()
|
|
347
|
+
public:
|
|
348
|
+
UMyDataFactory()
|
|
349
|
+
{
|
|
350
|
+
SupportedClass = UMyDataAsset::StaticClass();
|
|
351
|
+
bCreateNew = true; // appears in "Add" menu
|
|
352
|
+
bEditAfterNew = true; // opens editor after creation
|
|
353
|
+
}
|
|
354
|
+
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent,
|
|
355
|
+
FName InName, EObjectFlags Flags, UObject* Context,
|
|
356
|
+
FFeedbackContext* Warn) override
|
|
357
|
+
{
|
|
358
|
+
return NewObject<UMyDataAsset>(InParent, InClass, InName, Flags);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Custom Thumbnails
|
|
364
|
+
|
|
365
|
+
```cpp
|
|
366
|
+
UCLASS()
|
|
367
|
+
class UMyThumbnailRenderer : public UThumbnailRenderer
|
|
368
|
+
{
|
|
369
|
+
GENERATED_BODY()
|
|
370
|
+
virtual void Draw(UObject* Object, int32 X, int32 Y, uint32 Width, uint32 Height,
|
|
371
|
+
FRenderTarget* Target, FCanvas* Canvas, bool bAdditionalViewFamily) override;
|
|
372
|
+
};
|
|
373
|
+
// Register in module startup:
|
|
374
|
+
UThumbnailManager::Get().RegisterCustomRenderer(UMyDataAsset::StaticClass(),
|
|
375
|
+
UMyThumbnailRenderer::StaticClass());
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Editor Subsystems
|
|
381
|
+
|
|
382
|
+
`UEditorSubsystem` (from `EditorSubsystem.h`) — editor-lifetime singletons, auto-discovered (no registration needed). Access via `GEditor->GetEditorSubsystem<T>()`.
|
|
383
|
+
|
|
384
|
+
```cpp
|
|
385
|
+
UCLASS()
|
|
386
|
+
class MYGAMEEDITOR_API UMyEditorSubsystem : public UEditorSubsystem
|
|
387
|
+
{
|
|
388
|
+
GENERATED_BODY()
|
|
389
|
+
public:
|
|
390
|
+
virtual void Initialize(FSubsystemCollectionBase& Collection) override
|
|
391
|
+
{
|
|
392
|
+
Super::Initialize(Collection);
|
|
393
|
+
FEditorDelegates::PostUndoRedo.AddUObject(this, &UMyEditorSubsystem::OnPostUndoRedo);
|
|
394
|
+
}
|
|
395
|
+
virtual void Deinitialize() override
|
|
396
|
+
{
|
|
397
|
+
FEditorDelegates::PostUndoRedo.RemoveAll(this);
|
|
398
|
+
Super::Deinitialize();
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Built-in subsystems:
|
|
404
|
+
|
|
405
|
+
| Subsystem | Purpose |
|
|
406
|
+
|---|---|
|
|
407
|
+
| `UEditorActorSubsystem` | Select, spawn, delete level actors |
|
|
408
|
+
| `UEditorAssetSubsystem` | Load, save, duplicate assets |
|
|
409
|
+
| `ULevelEditorSubsystem` | Open, save, manage levels |
|
|
410
|
+
| `UEditorUtilitySubsystem` | Spawn editor utility widget tabs |
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Menu and Toolbar Extensions (UToolMenus)
|
|
415
|
+
|
|
416
|
+
UToolMenus (UE5+) replaces `FExtender`. Register inside the startup callback so Slate is ready:
|
|
417
|
+
|
|
418
|
+
```cpp
|
|
419
|
+
void FMyGameEditorModule::RegisterMenus()
|
|
420
|
+
{
|
|
421
|
+
FToolMenuOwnerScoped OwnerScoped(this);
|
|
422
|
+
|
|
423
|
+
// Main menu
|
|
424
|
+
UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window");
|
|
425
|
+
Menu->FindOrAddSection("MyGame").AddMenuEntry("OpenMyPanel",
|
|
426
|
+
FText::FromString("My Panel"), FText::GetEmpty(),
|
|
427
|
+
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Settings"),
|
|
428
|
+
FUIAction(FExecuteAction::CreateLambda([]() {
|
|
429
|
+
GEditor->GetEditorSubsystem<UEditorUtilitySubsystem>()
|
|
430
|
+
->SpawnAndRegisterTab(/* WidgetBP */nullptr);
|
|
431
|
+
})));
|
|
432
|
+
|
|
433
|
+
// Toolbar button
|
|
434
|
+
UToolMenu* TB = UToolMenus::Get()->ExtendMenu(
|
|
435
|
+
"LevelEditor.LevelEditorToolBar.PlayToolBar");
|
|
436
|
+
TB->FindOrAddSection("MyTools").AddEntry(
|
|
437
|
+
FToolMenuEntry::InitToolBarButton("MyBtn",
|
|
438
|
+
FUIAction(FExecuteAction::CreateLambda([]() {})),
|
|
439
|
+
FText::FromString("My Tool"), FText::GetEmpty(),
|
|
440
|
+
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Toolbar.Settings")));
|
|
441
|
+
}
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Always unregister: `UToolMenus::UnRegisterStartupCallback(this)` + `TryGet()->UnregisterOwner(this)`.
|
|
445
|
+
|
|
446
|
+
### Command Registration — TCommands Pattern
|
|
447
|
+
|
|
448
|
+
```cpp
|
|
449
|
+
class FMyCommands : public TCommands<FMyCommands>
|
|
450
|
+
{
|
|
451
|
+
public:
|
|
452
|
+
FMyCommands() : TCommands("MyEditor", INVTEXT("My Editor"), NAME_None, FAppStyle::GetAppStyleSetName()) {}
|
|
453
|
+
virtual void RegisterCommands() override;
|
|
454
|
+
TSharedPtr<FUICommandInfo> OpenPanel;
|
|
455
|
+
};
|
|
456
|
+
void FMyCommands::RegisterCommands()
|
|
457
|
+
{
|
|
458
|
+
UI_COMMAND(OpenPanel, "My Panel", "Opens the panel", EUserInterfaceActionType::Button, FInputChord());
|
|
459
|
+
}
|
|
460
|
+
// In StartupModule: FMyCommands::Register(); bind via CommandList->MapAction(...)
|
|
461
|
+
// In ShutdownModule: FMyCommands::Unregister();
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Data Validation
|
|
467
|
+
|
|
468
|
+
```cpp
|
|
469
|
+
// UEditorValidatorBase — auto-discovered by Editor > Tools > Validate Assets
|
|
470
|
+
UCLASS()
|
|
471
|
+
class UMyValidator : public UEditorValidatorBase
|
|
472
|
+
{
|
|
473
|
+
GENERATED_BODY()
|
|
474
|
+
virtual bool CanValidateAsset_Implementation(const FAssetData& InAssetData, UObject* InObject, FDataValidationContext& InContext) const override
|
|
475
|
+
{ return InObject && InObject->IsA<UMyDataAsset>(); }
|
|
476
|
+
virtual EDataValidationResult ValidateLoadedAsset_Implementation(
|
|
477
|
+
const FAssetData& InAssetData, UObject* InAsset, FDataValidationContext& Context) override;
|
|
478
|
+
};
|
|
479
|
+
// Also runnable via commandlet: UnrealEditor-Cmd -run=DataValidation
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Common Mistakes
|
|
485
|
+
|
|
486
|
+
| Mistake | Fix |
|
|
487
|
+
|---|---|
|
|
488
|
+
| Editor headers in Runtime modules | Move to Editor module; use `#if WITH_EDITOR` for runtime-side editor hooks |
|
|
489
|
+
| No `UnregisterCustomClassLayout` in `ShutdownModule` | Always pair register/unregister; crashes Live Coding reload |
|
|
490
|
+
| Raw pointer capture in Slate lambdas | Capture as `TWeakPtr`; pin before use |
|
|
491
|
+
| `LoadingPhase: Default` for editor extension module | Use `PostEngineInit` for modules registering menus or customizations |
|
|
492
|
+
| `UEditorUtilityWidget` referenced from Runtime | Editor-only; will not exist in packaged builds |
|
|
493
|
+
| `ForceRefreshDetails()` on every value change | Use only when layout structure changes; use handles/attributes for values |
|
|
494
|
+
| `RegisterMode` without `UnregisterMode` | Crashes on plugin reload |
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## Related Skills
|
|
499
|
+
|
|
500
|
+
- `/ue-ui-umg-slate` — Slate widget fundamentals (SNew, TAttribute, FReply, SBox, SHorizontalBox)
|
|
501
|
+
- `/ue-module-build-system` — Editor module `.Build.cs`, LoadingPhase, `WITH_EDITOR` guards
|
|
502
|
+
- `/ue-data-assets-tables` — Custom UDataAsset types that need asset editors and type actions
|
|
503
|
+
- `/ue-cpp-foundations` — UPROPERTY, UFUNCTION, UObject reflection system
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Checklist
|
|
508
|
+
|
|
509
|
+
Validate before considering the work done:
|
|
510
|
+
|
|
511
|
+
- [ ] Editor-only code lives in an editor module and is excluded from runtime targets
|
|
512
|
+
- [ ] The tool fails safely on unexpected asset state instead of corrupting data
|
|
513
|
+
- [ ] Bulk operations are undoable or clearly warn before running
|
|
514
|
+
- [ ] Conventions in `docs/unreal/project-context.md` were read and followed
|
|
515
|
+
- [ ] The Common Mistakes section of this skill was reviewed against the change
|
|
516
|
+
|
|
517
|
+
**PASS** — every item holds. **CONCERNS** — the work stands but at least one item is unresolved and worth flagging to the user. **FAIL** — an item is violated in a way that breaks correctness, replication or the frame budget; fix it before handing the work over.
|
|
518
|
+
|
|
519
|
+
## Next Steps
|
|
520
|
+
|
|
521
|
+
- `/ue-module-build-system` — confirm the editor module is wired correctly
|
|
522
|
+
- `/code-review` — architectural and quality review of the change
|
|
523
|
+
- `/commit` — conventional commit once the change is verified
|