Dynamically drawn skill tree

I’m trying to make a module that will dynamically create a skill tree visually on the client. The issue I’m having is making it so nodes don’t overlap I’ve tried a few things but I am completely stumped.

here is how hierarchy is setup Base is the directory that I want to turn into a tree
image

--LOCAL SCRIPT
local Frame = script.Parent.TestFrame

local NodeDrawlerObj = require(script.Parent.NodeDrawler)
local NodeDrawler = NodeDrawlerObj.new(Frame, script.Parent.Base)
--Main Module
local NodeObj = require(script.CreateNode)

local NodeDrawler = {}
NodeDrawler.__index = NodeDrawler

function NodeDrawler.new(TargetFrame, TargetTree)
	local self = setmetatable({}, NodeDrawler)
	self.TargetTree = TargetTree
	self.TargetFrame = TargetFrame
	
	local MainNode = nil
	local function Recurse(Target, ParentNode)
		local ChildArray = Target:GetChildren()
		for Index, CurrentNodeObj in pairs(ChildArray) do
			if #CurrentNodeObj:GetChildren() > 0 then
				local Node = NodeObj.new(self, Index, CurrentNodeObj, ParentNode)
				if MainNode == nil then
					MainNode = Node
				else
					ParentNode:AddChildNode(Node)
				end
				Recurse(CurrentNodeObj, Node)
			else
				local Node = NodeObj.new(self, Index, CurrentNodeObj, ParentNode)
				ParentNode:AddChildNode(Node)
			end
		end
	end
	Recurse(TargetTree)
	
	local function UpdatePosRecursion(Target)
		for i,v in pairs(Target) do
			v:UpdatePosition()
			UpdatePosRecursion(v.ChildNodes)
		end
	end
	UpdatePosRecursion(MainNode.ChildNodes)
	
	return self
end

function NodeDrawler:ConnectNodes(ParentNode, ChildNode)
	local StartX, StartY = ParentNode.UI.Position.X.Offset, ParentNode.UI.Position.Y.Offset
	local EndX, EndY = ChildNode.UI.Position.X.Offset, ChildNode.UI.Position.Y.Offset
	local Frame = Instance.new("Frame")
	Frame.AnchorPoint = Vector2.new(0.5, 0.5)
	Frame.Size = UDim2.new(0, (math.sqrt((EndX-StartX)^2+(EndY-StartY)^2)), 0, 1)
	Frame.Position = UDim2.new(.5,(StartX+EndX)/2, 0, (StartY+EndY)/2)  
	Frame.Rotation = math.atan2(EndY-StartY, EndX-StartX)*(180/math.pi)
	Frame.Parent = self.TargetFrame
end

return NodeDrawler
--Sub module
local Node = {}
Node.__index = Node

local GuiSize = UDim2.new(0,50,0,50)
local GapSizeX = 50
local GapSizeY = 25

function Node.new(NodeDrawler, Index, CurrentNodeObj, ParentNode)
	local self = setmetatable({}, Node)
	self.NodeDrawler = NodeDrawler
	
	self.Modify = 0
	self.ChildArray = CurrentNodeObj:GetChildren()
	self.ChildNodes = {}
	self.Index = Index
	self.CurrentNodeObj = CurrentNodeObj
	self.ParentNode = ParentNode
	
	
	self:DrawNode()
	return self
end

function Node:UpdatePosition()
	if self.ParentNode ~= nil then
		local Center = self.ParentNode.UI.Position.X.Offset --This will make it aline with the ParentNode
		local AlignLeft = (#self.ParentNode.ChildArray/2)*(GuiSize.X.Offset+GapSizeX) --This will make the guis go to the farthest left node
		local CorrectPosition = ((GuiSize.X.Offset+GapSizeX)*(self.Index-1)) --This will then slide the gui Right until its at the correct spot
		self.UI.Position = UDim2.new(.5,(Center)-AlignLeft+CorrectPosition+(GuiSize.X.Offset+GapSizeX)/2, 0, self.ParentNode.UI.Position.Y.Offset+(GuiSize.Y.Offset+GapSizeY))
	else
		self.UI.Position = UDim2.new(.5, 0, 0, GuiSize.Y.Offset/2)
	end
	self.NodeDrawler:ConnectNodes(self.ParentNode, self)
end

function Node:AddChildNode(Node)
	table.insert(self.ChildNodes, Node)
end

function Node:DrawNode()
	self.UI = Instance.new("TextButton") 
	self.UI.Name = self.CurrentNodeObj.Name
	self.UI.Text = self.Index
	self.UI.Size = GuiSize
	self.UI.AnchorPoint = Vector2.new(0.5, 0.5)
	if self.ParentNode ~= nil then
		local Center = self.ParentNode.UI.Position.X.Offset --This will make it aline with the ParentNode
		local AlignLeft = (#self.ParentNode.ChildArray/2)*(GuiSize.X.Offset+GapSizeX) --This will make the guis go to the farthest left node
		local CorrectPosition = ((GuiSize.X.Offset+GapSizeX)*(self.Index-1)) --This will then slide the gui Right until its at the correct spot
		self.UI.Position = UDim2.new(.5,(Center)-AlignLeft+CorrectPosition+(GuiSize.X.Offset+GapSizeX)/2, 0, self.ParentNode.UI.Position.Y.Offset+(GuiSize.Y.Offset+GapSizeY))
	else
		self.UI.Position = UDim2.new(.5, 0, 0, GuiSize.Y.Offset/2)
	end
	self.UI.Parent = self.NodeDrawler.TargetFrame
	self.UI.ZIndex = 2
end

return Node

heres how it currently looks

1 Like

What do you mean exactly with “overlap”, I don’t see any issue in the image that you attached to the post. If you have a ZIndex fight, you can set ScreenGui ZIndex Behiavour to ‘Global’

example

image This is what I mean