How-To: Display the Skill Tree
Goal: put a skill tree on screen and let the player interact with it, using nothing but this plugin. You build one host canvas widget: it spawns a node widget per node, positions each from the layout the Visual graph baked into the asset, and hands the whole set to the connection layer that paints the lines.
UCrimsonSkillTreeManager on your player and a configured tree (Quick Start), plus a node widget class - either the shipped UCrimsonSkillTreeWidget_Node or your own (How-To: Use Your Own Node Widget Class).1. Point the asset at a node widget class
Open the skill tree asset and set Graph Config -> Default Node Widget Class. Any Widget Blueprint implementing `ICrimsonSkillTreeNodeWidget` is valid - there is no required base class. Subclass UCrimsonSkillTreeWidget_Node for a ready-made implementation (implement On Update Node Appearance / On Node Hovered), or start from your own widget base and implement the interface directly.
| Where you set it | Applies to |
|---|---|
Graph Config -> Default Node Widget Class | Every node in the tree |
Graph Config -> Node Type To Widget Class Map | Every node carrying a given Node Widget Type Tag |
| A Visual node's Preview Widget Class | That one node only (baked as its Widget Class Override) |
ConnectionAnchorInset on the class defaults, not per instance - the Visual Graph reads it off the Class Default Object so editor-side lines attach where the runtime ones will.2. Build the host canvas
Create a Widget Blueprint (any UUserWidget parent). In the Designer, add the two children the host needs. Both names are yours to choose - nothing binds by name here:
| Widget | Name | Purpose |
|---|---|---|
Canvas Panel | NodeCanvasPanel | Holds one child widget per skill node. Nodes are placed by absolute position, so a Canvas Panel is required - not a box or grid. |
UCrimsonSkillTreeWidget_ConnectionLayer | ConnectionLayer | Paints every connection in the tree. Drag it from the palette's Crimson Skill Tree category and place it behind the canvas panel so lines draw under the nodes. |
Then populate it once, after the manager has built its runtime tree instances.
Event Construct -> Get Skill Tree Manager -> Get Skill Tree (Skill Tree Type Tag = your tree's tag) -> Is Valid (Branch, True) -> promote the result to a variable Tree. Get Skill Tree Manager is a pure node under Skill Tree | Utilities and resolves the local player's manager for you.
Then Get Tree -> Node Presentations -> For Each Loop. Inside the loop: Break the presentation struct, feed Node Guid into Get Tree -> Find Node By GUID, and Create Widget (Class = Widget Class Override, or Tree -> Graph Config -> Default Node Widget Class when the override is empty). Finish each node with Add Child to Canvas on NodeCanvasPanel -> Set Auto Size (true) -> Set Position (Game Position multiplied by Graph Config -> Node Position Scalar), then send the interface message Initialize Skill Node to the new widget with the node from Find Node By GUID.
Node Presentations is the Visual graph's output and already carries the position and per-node widget override for every node the designer laid out. Looping All Nodes instead means looking the presentation back up by GUID for each one.3. Place the decorations
The panels, headers and dividers authored in the Visual graph arrive on the asset as Tree -> Decorations. Each one resolves its widget class in one order: its own WidgetClassOverride when the designer set one, otherwise Graph Config -> Decoration Widget Classes for its Kind. So the loop is: resolve the class, spawn it, hand it the decoration, and place it yourself at the decoration's GamePosition and GameSize. A decoration that resolves neither is skipped and never rendered.
Get Tree -> Decorations -> For Each Loop. Inside the loop: Break the decoration struct, then resolve the class - Is Valid Class on Widget Class Override, and when it is not valid fall back to Get Tree -> Graph Config -> Decoration Widget Classes -> Find with Kind (a Select node on the override's validity reads cleanly here). Skip the entry when neither is valid; otherwise Create Widget with the resolved class. Finish with Add Child to Canvas on NodeCanvasPanel -> Set Position (Game Position) -> Set Size (Game Size) -> Set Z Order (-2), then send the interface message Initialize Skill Tree Decoration to the new widget with the same decoration struct.
0, so any negative value puts a decoration under them. The connection layer in step 2 sits behind the whole canvas panel, so lines already draw over decorations placed this way; to put panels behind the lines as well, add a second Canvas Panel behind the connection layer and parent the decorations to that one instead.InitializeSkillTreeDecoration override, so a panel never swallows the presses of the nodes standing on it or the drag the player pans with. UCrimsonSkillTreeWidget_Decoration fills a bound BackgroundBorder and TitleText from the decoration and leaves visibility to you.TintColor is a multiplier on the widget's own authored color, and it defaults to white - so a widget that ignores it entirely still looks right. Honor it the way the shipped base does, by multiplying rather than replacing: UCrimsonSkillTreeWidget_Decoration writes it into a bound BackgroundBorder's brush color, which Slate multiplies over the brush, and a material-driven plate multiplies it onto the parameter's authored value. A decoration can then dim or recolor, never brighten past what the widget carries.Widget Class Override and its Kind has no entry in Decoration Widget Classes.4. Paint the connection lines
One UCrimsonSkillTreeWidget_ConnectionLayer paints every line in the tree, so panning costs nothing, zooming only re-evaluates per-edge level of detail, and a purchase re-resolves styles without spawning or destroying a widget. Call it three times for three different reasons.
| Call | When |
|---|---|
RebuildConnections(Tree, NodeWidgetMap, Policy, ZoomScale, bIsDesignTime) | Once after populating, and again whenever topology or layout changes. Never for pan, zoom, or a state change. |
UpdateConnectionStates() | On OnSkillTreeStateUpdated - re-resolves every line's style and plays the purchase fill or drain. |
UpdateZoomLOD(ZoomScale) | When your canvas render scale changes. Panning needs no call at all. |
RebuildConnections, UpdateConnectionStates and UpdateZoomLOD are C++ entry points - they take a widget map and a policy object that are not Blueprint-exposed, so a Blueprint-only canvas can show and drive nodes but cannot paint the lines between them. Give the canvas a C++ parent class (as in step 2) and Blueprint everything else on top of it. Get Hovered Connection and On Connection Hover Changed are Blueprint-facing, so hover handling stays in Blueprint either way.// At the end of PopulateTree(), once the node widgets exist:ConnectionLayer->RebuildConnections(Tree,NodeWidgetMap,Tree->GraphConfig.LineDrawingPolicy,CurrentZoomScale, // your canvas's uniform render scale; 1.0 to start/*bInIsDesignTime=*/false);// Stay in sync with gameplay. The manager broadcasts on every node state change,// on the server and on clients alike (OnRep drives it client-side).if (UCrimsonSkillTreeManager* Manager = UCrimsonSkillTreeBlueprintLibrary::GetSkillTreeManager(this)){Manager->OnSkillTreeStateUpdated.AddDynamic(this, &UMySkillTreeCanvas::HandleTreeStateUpdated);}// UFUNCTION() void UMySkillTreeCanvas::HandleTreeStateUpdated() { ConnectionLayer->UpdateConnectionStates(); }
5. Navigate with a gamepad or keyboard
Nodes do not sit on a grid, so Slate's built-in navigation cannot answer "which node is up from here". UCrimsonSkillTreeNavigationLibrary builds that answer once per tree: feed it one entry per navigable node with its canvas-space center, then ask the returned map at navigation time. Build the map once - it is O(n^2) in the node count, not per-press work.
Three nodes, all under Crimson Skill Tree | Navigation: you build the entry array, build the map once, then ask it on each direction press.
- Make an array of Crimson Skill Node Nav Entry - for each node widget, set Node to its data node and Center to the widget's canvas-space center (+Y down).
- Call Build Navigation Map (under Crimson Skill Tree | Navigation) with that array and a Crimson Skill Tree Navigation Params struct; promote the result to a variable.
- On a direction press, call Get Navigation Neighbor with the map, the focused node and the direction, then Set Focus on that node's widget.
Tune the feel with FCrimsonSkillTreeNavigationParams - the cone half-angle, the sideways weight, and the discount applied to a candidate that shares an edge with the focused node.
6. Pan and zoom
Pan and zoom are your canvas's own transform, not something the plugin owns: scale NodeCanvasPanel's render transform to zoom, and offset it to pan. Any key works for either - mouse wheel, mouse XY, keyboard or gamepad stick - because the input side is entirely yours. The only plugin call the view needs is `UpdateZoomLOD` with the new scale after a zoom; panning needs none.
7. Show it
Push or add your canvas widget to your UI the way you show any other menu. While it is up, stop the character moving underneath: set Ignore Move Input and Ignore Look Input on the Player Controller, and exclude your gameplay Input Mapping Contexts so ability inputs do not fire behind the menu.
OnSkillTreeStateUpdated drives the refresh.UCrimsonCoreSkillTreeGraph and UCrimsonCoreSkillTreeScreen - this whole page already built, with the navigation map, pan/zoom, gamepad prompts and a selected-node detail view-model wired up; you subclass them in Blueprint and set a few class defaults. It is free to download but compiles only with all Crimson plugins installed, so it is an alternative to this page, never a step in it. Everything above works with CrimsonSkillTree alone.See also
- How-To: Use Your Own Node Widget Class - the node contract, on any base class
- How-To: Line States & Animations - per-state styles, purchase fill, hover
- How-To: Drive Node Actions - what the manager does with a request