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.
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:
- 1.
OpenTree()→ User opens tree - 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
The interface defines a common contract for both tree types:
public interface IProgressionTreeReference
{
// Core properties
string TreeID { get; }
string TreeName { get; }
string Description { get; }
Sprite Icon { get; }
// Skills access
List<ProgressionSkill> GetAllSkills();
List<ProgressionSkill> GetRootSkills();
// Tree settings
bool AllowRespec { get; }
LayoutType LayoutType { get; }
List<ChoiceGroup> ChoiceGroups { get; }
// UnityEvents for Game Creator integration
UnityEvent<ProgressionSkill> onSkillUnlocked { get; }
UnityEvent<ProgressionSkill> onSkillUpgraded { get; }
UnityEvent onTreeCompleted { get; }
UnityEvent onTreeRespecced { get; }
}ProgressionTreeReference Wrapper
The ProgressionTreeReference class wraps either tree type and implements the interface:
// Unified reference wrapper
public class ProgressionTreeReference : IProgressionTreeReference
{
private readonly ProgressionTree tree;
private readonly ProgressionTreeGraphAsset graphAsset;
// Constructor accepts either type
public ProgressionTreeReference(ProgressionTree tree)
{
this.tree = tree;
}
public ProgressionTreeReference(ProgressionTreeGraphAsset asset)
{
this.graphAsset = asset;
}
// Properties delegate to whichever is set
public string TreeID => tree?.TreeID ?? graphAsset?.TreeID;
public string TreeName => tree?.TreeName ?? graphAsset?.TreeName;
// ... etc
}Usage Example
Work with trees uniformly regardless of their underlying type:
// In your code - works with either tree type
public void OpenTree(IProgressionTreeReference treeRef)
{
currentTree = treeRef;
// Access properties uniformly
headerText.text = treeRef.TreeName;
descriptionText.text = treeRef.Description;
// Get skills the same way
var skills = treeRef.GetAllSkills();
var roots = treeRef.GetRootSkills();
// Subscribe to events
treeRef.onSkillUnlocked.AddListener(OnSkillUnlocked);
}IProgressionTreeReference allows your code to work with both legacy ProgressionTree assets and the newer ProgressionTreeGraphAssets without modification. This is especially useful for UI code, save systems, and custom extensions.