[Unity Editor] Simple Cleaner #2 - Editor TreeView

qweasfjbv·2025년 1월 16일

UnityEditor

목록 보기
6/12
post-thumbnail

개요


저번에 구한 Path 들을 에디터에 Treeview로 나타내고, Checkbox를 통해 지울 대상을 선택할 수 있도록 하겠습니다.

Unity Document 에 보면 TreeView라는 클래스를 상속받아서 구현할 수 있습니다.
해당 클래스를 통해 아래와 같은 TreeView를 구현해보겠습니다.

구현


TreeView의 각 아이템은 TreeViewItem 으로 구성되며, 해당 클래스에는 아이디, 부모, 자식, 깊이, 이름, 아이콘 등을 설정할 수 있습니다.

하지만 TreeView의 정적함수 중 SetupDepthsFromParentsAndChildren 이라는 함수를 사용하면 Depth는 계산해주므로, 저희는 Id, parent, children, displayName을 신경써야 합니다.

경로가 들어오면, Split 함수를 통해 '/' 단위로 나누고 루트에서부터 폴더를 만들거나 부모를 설정해주어야합니다.

BuildRoot

        protected override TreeViewItem BuildRoot()
        {
            var root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" };
            int id = 1;

            foreach (var asset in assets)
            {
                var pathParts = asset.Split('/');
                TreeViewItem currentParent = root;

                for (int i = 0; i < pathParts.Length; i++)
                {
                    string pathPart = pathParts[i];
                    TreeViewItem existingChild = currentParent.children?.Find(child => child.displayName == pathPart);

                    if (existingChild == null) 	// 일치하는 이름 없으면 폴더 (혹은 파일) 만들기
                    {
                        var newChild = new TreeViewItem { id = id++, displayName = pathPart, parent = currentParent };
                        if (currentParent.children == null)
                            currentParent.children = new List<TreeViewItem>();
                        currentParent.children.Add(newChild);
                        currentParent = newChild;
                    }
                    else		// 있으면 부모로 설정하고 다음 경로 탐색
                    {
                        currentParent = existingChild;
                    }
                }
            }

            SetupDepthsFromParentsAndChildren(root);	// Depth 자동설정
            return root;
        }

추상함수인 BuildRoot 입니다.
Root로부터 모든 경로의 각 폴더, 파일들을 Item으로 만들어서 Parent-Child 관계를 만들어줍니다.


여기까지의 실행결과입니다.
제 목표는 Checkbox를 통해 지울 에셋들을 선택하는 것입니다.


각 Item의 GUI를 변경하기 위해서는 RowGUI 함수를 사용하면 됩니다. (참고)

Override this method to add custom GUI content for the rows in the TreeView.

각 TreeView의 Row GUI content 를 custom 할때 override 하라고 합니다.

		protected override void RowGUI(RowGUIArgs args)
		{

		}

base.RowGUI(args) 를 호출하면 위의 사진과 같이 일반적인 텍스트 트리뷰가 나오고, 아무것도 적지않으면 아래와같이 나옵니다.

저희가 하고싶은건 저 토글버튼과 TreeViewItem 의 이름 사이에 Checkbox를 넣는 것이기 때문에, base.RowGUI(args) 호출 전에 Checkbox를 그려주면 될 것 같습니다.

		protected override void RowGUI(RowGUIArgs args)
		{
            Rect checkBoxRect = new Rect(args.rowRect.x + GetContentIndent(args.item), args.rowRect.y, 2, args.rowRect.height);
            
			bool newToggled = EditorGUI.Toggle(checkBoxRect, true);

			base.RowGUI(args);
		}

하지만 base.RowGUI 도 Indent 만 고려하여 만들어졌기 때문에 checkbox와 라벨이 겹치게됩니다.
해당 부분은 base.RowGUI 를 호출할때 넘기는 args를 수정하여 고칠 수 있습니다.

RowGUI

		protected override void RowGUI(RowGUIArgs args)
		{
            Rect toggleRect = new Rect(args.rowRect.x, args.rowRect.y, GetContentIndent(args.item), args.rowRect.height);
            base.RowGUI(new RowGUIArgs
            {
                rowRect = args.rowRect,
                item = args.item,
                label = "",		// Label을 아래에서 따로 작성
                selected = args.selected,
                focused = args.focused
            });

            Rect checkBoxRect = new Rect(toggleRect.xMax + 2, args.rowRect.y, 20, args.rowRect.height);
            bool isToggled = toggledItemIds.Contains(args.item.id);
            bool newToggled = EditorGUI.Toggle(checkBoxRect, isToggled);

            Rect nameRect = new Rect(checkBoxRect.xMax + 5, args.rowRect.y, args.rowRect.width - checkBoxRect.xMax - 5, args.rowRect.height);
            EditorGUI.LabelField(nameRect, args.item.displayName);
		}


다음은 편의성을 위한 Checkbox 기능을 추가해야 합니다.
Package manager를 통해 에셋을 임포트 해봤다면 아시겠지만, 부모 아이템의 체크박스를 해제, 선택하면 자식들도 전부 해제/선택 됩니다.
또한, 자식이 하나라도 체크되면 부모도 체크, 아무도 체크되어있지 않으면 부모도 체크해제 되도록 해야합니다.

CheckAllItems

		private void CheckAllItems(TreeViewItem item, bool isChecked)
        {
            if (isChecked)
                toggledItemIds.Add(item.id);
            else
                toggledItemIds.Remove(item.id);

            if (item.children != null)
            {
                foreach (var child in item.children)
                {
                    CheckAllItems(child, isChecked);
                }
            }
        }

체크하면 해당 아이템과 그 자식들 전부 체크/해제 시켜주는 함수입니다.
toggledItemIds 는 토글된 아이템들을 저장하는 HashSet 입니다.
(Paths에는 폴더가 포함되지 않고, TreeViewItem 들은 폴더가 있기 때문에 따로 저장해야합니다.)

자식은 완료되었으니 부모만 신경써주면 됩니다.

UpdateParentItems

        private void UpdateParentItems(TreeViewItem item, bool isChecked)
        {
            if (item.parent == null)
                return;

            if (isChecked)
            {
                bool allChildrenChecked = item.parent.children.
                	TrueForAll(child => toggledItemIds.Contains(child.id));
                    
                if (allChildrenChecked)
                    toggledItemIds.Add(item.parent.id);
            }
            else
            {
                toggledItemIds.Remove(item.parent.id);
            }

            UpdateParentItems(item.parent, isChecked);
        }
  • True 로 체크한 경우 -> 부모의 자식이 전부 True면 부모도 체크
  • False 로 해제한 경우 -> 부모의 자식이 전부 True는 아니므로 체크 해제
  • 재귀 함수로 root까지 반복

마무리


TreeView 를 상속받고 BuildRootRowGUI 함수를 오버라이드 하여 원하는 TreeView Editor를 만들었습니다.
하지만, 모두 선택하면 Assets 폴더까지 선택되는 특성 때문에 폴더는 지우지못하고 있습니다.

따라서 다음에는 빈 폴더를 모두 삭제하는 옵션을 추가하고, Config 를 Editor에서 실시간으로 수정가능하도록 만들어보겠습니다.

참고자료


https://docs.unity3d.com/kr/2018.4/ScriptReference/IMGUI.Controls.TreeView.html

0개의 댓글