Search code examples
c#unity-game-enginebinary-treenodessimulation

Showcasing a binary tree in unity problom


i made a function in unity that takes a Binode - and making it into a2d tree, but here the problem: my so called idea to display the tree is this:

make a recursive function, and every time the level will go up by 1, and it will also send the function a bool(is the right or is the left of the old node) and then if its true it will get the root of the tree and - the y by level * 55, then it will either x = level * 55, if it right, and if its left the x = level * -55.

now the problome is lets say i have a tree that has 3 levels, then if i will put a right node to the left tree it will just put it were the most right node is and it will overlap, kinda hard to explain in text but i hope you understand.

heres the repository: git

Image

code:

public void GenrateTreeUI(BinNode<int> t, bool right, int Level)
{

    if (Level == 0)
    {
        cir = Instantiate(CirclePrefab, new Vector2(0, 0), Quaternion.identity);
        t.SetGO(cir);
        cir.transform.SetParent(canvas.transform);
        cir.GetComponent<Image>().color = Color.black;
        roottr = cir.GetComponent<RectTransform>();
        Value = cir.GetComponentInChildren<TMP_Text>();


        roottr.anchoredPosition = new Vector2(-11, 213);
        Value.text = t.GetValue().ToString();
        Value.color = Color.white;
    }
    else
    {
        if (!right)
        {
            cir = Instantiate(CirclePrefab, new Vector2(0, 0), Quaternion.identity);
            cir.transform.SetParent(canvas.transform);
            t.SetGO(cir);
            cir.GetComponentInChildren<TMP_Text>().text = t.GetValue().ToString();
            cir.GetComponent<RectTransform>().anchoredPosition = new Vector2(roottr.anchoredPosition.x - Level * 55,
                roottr.anchoredPosition.y - (Level * 55));
        }
        else
        {
            cir = Instantiate(CirclePrefab, new Vector2(0, 0), Quaternion.identity);
            cir.transform.SetParent(canvas.transform);
            t.SetGO(cir);
            cir.GetComponentInChildren<TMP_Text>().text = t.GetValue().ToString();
            cir.GetComponent<RectTransform>().anchoredPosition = new Vector2(roottr.anchoredPosition.x + Level * 55,
                roottr.anchoredPosition.y - Level * 55);
        }
    }


    if (t.HasLeft())
    {

        GenrateTreeUI(t.GetLeft(), false, Level + 1);
        GenrateWire(cir.GetComponent<RectTransform>().anchoredPosition, GetFutreNode(t.GetLeft(), Level, false));

    }
    if (t.HasRight())
    {

        GenrateTreeUI(t.GetRight(), true, Level + 1);
        GenrateWire(cir.GetComponent<RectTransform>().anchoredPosition, GetFutreNode(t.GetRight(), Level, true));

    }

}

thanks for the help!


Solution

  • Before giving an answer here I would once again first suggest some refactors and fixes:

    First of all your CreateRndTree is wrong ;)

    public void CrateRndTree(int levels, BinNode<int> save_node)
    {
        BinNode<int> newNode = null;
        for (int i = 0; i < levels; i++)
        {
            newNode = new BinNode<int>(Random.Range(1, 20));
            save_node.SetLeft(newNode);
            newNode = new BinNode<int>(Random.Range(1, 20));
            save_node.SetRight(newNode);
            save_node = newNode;
        }
    }
    

    you create two new child nodes but only set left and right on the last created one (right) => The left tree would never have any sub children.

    I would do that recursive like e.g.

    private static void CreateRndTree(int levels, BinNode<int> rootNode)
    {
        if (levels <= 0)
        {
            return;
        }
    
        var leftNode = new BinNode<int>(Random.Range(1, 20), rootNode);
        var rightNode = new BinNode<int>(Random.Range(1, 20), rootNode);
    
        rootNode.SetChildren(leftNode, rightNode);
    
        CreateRndTree(levels - 1, leftNode);
        CreateRndTree(levels - 1, rightNode);
    }
    

    Then as the last time I would have a proper class Circle on the circle prefab like e.g.

    public class Circle : MonoBehaviour
    {
        [SerializeField] private Image image;
        [SerializeField] private RectTransform rectTransform;
        [SerializeField] private TMP_Text text;
    
        public Image Image
        {
            get
            {
                if (image) return image;
    
                image = GetComponent<Image>();
    
                return image;
            }
        }
    
        public RectTransform RectTransform
        {
            get
            {
                if (rectTransform) return rectTransform;
    
                rectTransform = GetComponent<RectTransform>();
    
                return rectTransform;
            }
        }
    
        public TMP_Text Text
        {
            get
            {
                if (text) return text;
    
                text = GetComponentInChildren<TMP_Text>();
    
                return text;
            }
        }
    }
    

    and in your BinNode store that instead of GameObject in order to get rid of repeated GetComponent calls.


    And then for the positioning I would rather implement a getter into the nodes themselves for getting their child levels count.

    I would also store the parent node and rather position the new node relative to the parent node instead of always having to calculate from the root

    public class BinNode<T>
    {
        ...
    
        private BinNode<T> parent;
        public BinNode<T> Parent => parent;
    
        // proper class that will be sitting on your circle prefab
        public Circle circle;
        
        public BinNode(T value, BinNode<T> parent = null)
        {
            this.value = value;
            this.parent = parent;
        }
    
        public int GetChildLevels()
        {
            var rightChildLevels = right == null ? 0 : 1 + right.GetChildLevels();
            var leftChildLevels = left == null ? 0 : 1 + left.GetChildLevels();
    
            return Mathf.Max(rightChildLevels, leftChildLevels);
        }
        
        ...
    }
    

    And then when getting a nodes position do

    public float spacing = 55;
    
    public Vector2 GetNodePosition(BinNode<int> node, bool right)
    {
        var subLevels = node.GetChildLevels();
    
        // simply get the position relative to the parent node
        // otherwise you would need to sum them up all the time
        var parentRectTransform = node.Parent.Cirlce.RectTransform;
    
        return parentRectTransform.anchoredPosition + new Vector2(spacing * (Mathf.Pow(2, subLevels)) * (right ? 1 : -1), -spacing);
    }
    

    For Level 3

    enter image description here

    For Level 4

    enter image description here

    For Level 5

    enter image description here


    As the last time I made a pull request for this ;)