Progression Tree Builder

v1.2.0

Customizing

Advanced customization guide for sub-controllers, custom layouts, and tree references.

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 ILayoutStrategy for custom positioning
  • Node Caching: Reuses GameObjects for performance
  • Event System: Fires OnSkillNodeClicked for 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. 1. OpenTree()User opens tree
  2. 2. customLayoutStrategy.Prepare(this)Modify layoutSettings
  3. 3. SetupLayoutComponents()Create GridLayoutGroup/etc with modified settings
  4. 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);
}
Benefits: Using 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.