Overview
Progression Tree Builder uses a modular architecture that separates concerns into specialized sub-controllers and interfaces. This guide covers the advanced customization options available through this architecture.
Prerequisites: This guide assumes you're familiar with the basic setup covered in the Quick Start Guide.
Sub-Controller Architecture
The UI Controller delegates specialized tasks to three sub-controllers, each with a specific responsibility:
Skill Node Renderer
Creates and positions skill node UI elements based on the selected layout mode.
Handles: Node instantiation, position calculation, node caching, custom layouts
Connection Renderer
Draws visual lines between nodes to show skill dependencies.
Handles: LineRenderer creation, connection updates, visual styling
Tree Navigation Controller
Manages the tab UI for switching between multiple unlocked trees.
Handles: Tab creation, tree switching, tab state updates
Skill Node Renderer
The SkillNodeRenderer is the most customizable sub-controller. It supports:
- Multiple Layout Modes: Grid, Graph (Vertical/Horizontal/Radial), List, Latest
- Custom Layout Strategies: Implement
ILayoutStrategyfor custom positioning - Node Caching: Reuses GameObjects for performance
- Event System: Fires
OnSkillNodeClickedfor node selection
// Accessing the sub-controller
public class ProgressionTreeUIController : MonoBehaviour
{
private SkillNodeRenderer skillNodeRenderer;
void Awake()
{
skillNodeRenderer = new SkillNodeRenderer(
uiReferences,
layoutSettings,
skillNodeCache,
debugMode
);
skillNodeRenderer.SetUIController(this);
skillNodeRenderer.OnSkillNodeClicked += SelectSkill;
}
}Connection Renderer
The ConnectionRenderer creates visual links between related skills:
// ConnectionRenderer usage
private ConnectionRenderer connectionRenderer;
void Awake()
{
connectionRenderer = new ConnectionRenderer(
uiReferences,
connectionLines, // LineRenderer prefab pool
debugMode
);
}
void UpdateConnections()
{
connectionRenderer.DrawConnections(
skillNodeCache, // Dictionary<ProgressionSkill, GameObject>
currentTree
);
}Tree Navigation Controller
The TreeNavigationController handles multi-tree UI when players have multiple trees unlocked:
// TreeNavigationController usage
private TreeNavigationController treeNavigationController;
void Awake()
{
treeNavigationController = new TreeNavigationController(
uiReferences,
treeTabButtons,
availableUnlockedTrees,
debugMode
);
treeNavigationController.OnTreeTabClicked += SwitchToTree;
}
void UpdateAvailableTrees(List<IProgressionTreeReference> trees)
{
treeNavigationController.SetupTreeTabs(trees);
}Custom Layout Strategies
Create custom positioning logic by implementing the ILayoutStrategy interface.
The ILayoutStrategy Interface
public interface ILayoutStrategy
{
// Called BEFORE layout components are created
// Modify controller.layoutSettings here
void Prepare(ProgressionTreeUIController controller);
// Calculate positions for Graph mode or custom modes
// Return empty dictionary for Grid/List (LayoutGroup handles it)
Dictionary<ProgressionSkill, Vector2> CalculatePositions(
ProgressionTree tree,
LayoutSettings settings
);
// Display name for debugging/UI
string GetLayoutName();
}Understanding Prepare()
The Prepare() method is called at a critical time in the layout setup:
Execution Order:
- 1. User opens tree →
OpenTree()called - 2.
customLayoutStrategy.Prepare(this)→ Modify layoutSettings - 3.
SetupLayoutComponents()→ Create GridLayoutGroup/etc with modified settings - 4.
BuildSkillTreeDisplay()→ Create nodes
This timing is crucial: modifying layoutSettings after SetupLayoutComponents()has no effect!
Example: Wide Grid Strategy
[CreateAssetMenu(fileName = "WideGridStrategy",
menuName = "My Game/Wide Grid Layout")]
public class WideGridStrategy : ScriptableObject, ILayoutStrategy
{
[Header("Grid Settings")]
[Range(1, 5)]
public int columns = 2; // Fewer columns = wider layout
public Vector2 cellSize = new Vector2(800f, 200f); // Very wide cells
public Vector2 cellSpacing = new Vector2(20f, 20f);
public void Prepare(ProgressionTreeUIController controller)
{
// Apply custom settings
controller.layoutSettings.gridColumns = columns;
controller.layoutSettings.gridCellSize = cellSize;
controller.layoutSettings.gridCellSpacing = cellSpacing;
Debug.Log($"[WideGrid] Applied {columns} column layout");
}
public Dictionary<ProgressionSkill, Vector2> CalculatePositions(
ProgressionTree tree,
LayoutSettings settings)
{
// Grid mode uses LayoutGroup - return empty
return new Dictionary<ProgressionSkill, Vector2>();
}
public string GetLayoutName() => "Wide Grid (2 columns)";
}Tree References System
The tree reference system provides a unified way to work with both ProgressionTree ScriptableObjects and ProgressionTreeGraphAssets.
IProgressionTreeReference Interface
Both tree formats implement this interface:
public interface IProgressionTreeReference
{
string TreeID { get; } // Unique identifier
string TreeName { get; } // Display name
Sprite TreeIcon { get; } // UI icon
ProgressionTree GetProgressionTree(); // Get runtime tree
}This allows the system to handle both formats transparently:
// Works with BOTH ProgressionTree and GraphAsset
public void OpenTree(IProgressionTreeReference treeRef)
{
// Get metadata without loading full tree
Debug.Log($"Opening: {treeRef.TreeName}");
// Load runtime tree when needed
ProgressionTree runtimeTree = treeRef.GetProgressionTree();
// Use the tree
BuildDisplay(runtimeTree);
}ProgressionTreeReference Wrapper
For serialized fields that need to accept any tree type, useProgressionTreeReference:
[Serializable]
public class MyComponent : MonoBehaviour
{
// Accepts ProgressionTree OR GraphAsset
[SerializeField]
private ProgressionTreeReference myTreeRef;
void Start()
{
// Get the runtime tree
ProgressionTree tree = myTreeRef.GetProgressionTree();
// Get metadata
string name = myTreeRef.GetTreeName();
string id = myTreeRef.GetTreeID();
// Check if valid
if (myTreeRef.IsValid())
{
// Use the tree
}
}
}Best Practice: UseIProgressionTreeReference for method parameters and ProgressionTreeReference for serialized fields. This provides maximum flexibility.