[Editor] 적 생성 Editor Window 코드 부분 제작

2024. 10. 10. 00:18·Editor

깃헙 링크 : https://github.com/ljs1206/TestProject

 

GitHub - ljs1206/TestProject: this Repository organized what I studied

this Repository organized what I studied. Contribute to ljs1206/TestProject development by creating an account on GitHub.

github.com

 

이 글 부터는 에디터를 만들때 썼던 코드들에 대해서 다루어 볼 것이다. 아직 미완성이여서 좀 부족한 부분이 있는점 양해 부탁한다.

 

Window Open

가장 먼저 기본적인 세팅을 해볼거다.

[MenuItem("Editor/Prefab/MakePrefabWindow")] // Show UnityEditor Toolbar
public static void PShowWindow()
{
    MakePrefabWindow wnd = GetWindow<MakePrefabWindow>(); // EditorWindow Load
    wnd.titleContent = new GUIContent("MakePrefabWindow"); // Bring Window
    wnd.minSize = new Vector2(800, 600); // minimum Size
    wnd.maxSize = new Vector2(800, 600); // Maximum Size
}

 

 

실제로 윈도우 보여주는 역할을 하는 함수이다.

[MenuItem("Editor/Prefab/MakePrefabWindow")] // Show UnityEditor Toolbar

 

이런 식으로 선택지가 추가됨

WindowEditor의 Toolbar에 아래 함수를 실행시켜주는 선택지(?)를 만들어주는 명령어이다.

MakePrefabWindow wnd = GetWindow<MakePrefabWindow>(); // EditorWindow Load
wnd.titleContent = new GUIContent("MakePrefabWindow"); // Bring Window

GetWindow을 통해서 (EditorWindow를 상속받은 (Script)Window를 불러옴) MakePrefabWindow라는 Window를 들고온다. 그다음 titleContent(제목 부분)에 새로운 GUIContent를 만들어서 MakePrefabWindow라는 이름을 부여한다.

이 부분이 타이틀임

wnd.minSize = new Vector2(800, 600); // minimum Size
wnd.maxSize = new Vector2(800, 600); // Maximum Size

최소 사이즈와 최대 사이즈를 조절해주는 코드이다. 에디터 UI편집 부분에서 선술했듯 크기는 800 x 600으로 고정 시켰다.

 

실제 구조 (구조 설명)

일단 코드를 설명하기 전에 이 Window의 구조부터 설명하고 코드를 설명하겠다.

크게 보면 다음 그림처럼 PrefabTable이라는 SO에서 Prefab 정보들을 가지고 와서 Window의 정보들을 표현해주고 새로 Prefab을 만들고 Table에 다시 넣어주는 형식이다.

Function : PrefabTableSO

using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "SO/Prefab/Table")]
public class PrefabTableSO : ScriptableObject
{
    public List<GameObject> prefabList;
}

Prefab들을 가지고 있는 SO이다. 특별한 사항은 없이 List하나만 존재한다.

Function : CreateGUI

public void CreateGUI()
{
    _viewLableDictionary = new();
    _root = rootVisualElement;

    VisualElement labelFromUxml = m_VisualTreeAsset.Instantiate();
    labelFromUxml.style.flexGrow = 1;
    _root.Add(labelFromUxml);

    #region GetObject

    Button makeBtn = _root.Q<Button>("MakeBtn");
    Button deleteBtn = _root.Q<Button>("DeleteBtn");
    _prefabView = _root.Q<VisualElement>("PrefabView");
    EnumField viewType = _root.Q<EnumField>("ViewSetting");
    fileNameField = _root.Q<TextField>("FileName");
    selectedLabel = _root.Q<Label>("NameLabel");

    #endregion

    // ViewItem을 Table에 들어있는 Prefab의 갯수만큼 실행
    if (_prefabTable.prefabList.Count > 0)
    {
        foreach(var item in _prefabTable.prefabList){
            ViewItem(item.name);
        }
    }

    // 선택된 Prefab의 file 이름을 변경하는 Event이다.
    fileNameField.RegisterValueChangedCallback(evt =>
    {
        if(_selected == null) return;

        _selected.Q<Label>("label").text = fileNameField.text;

        AssetDatabase.RenameAsset($"{_prefabFilePath}/{_selected.name}.prefab",
            fileNameField.text);
        _selected.name = fileNameField.text;
    });

    // BTN Event
    makeBtn.clicked += HandleMakeBtnCllikEvent;
    deleteBtn.clicked += HandleDeleteBtnClickEvent;
}

Show을 해주면 실행되는 함수이다. 여기서는 Prefab을 가지고 오고 Label, Button 같은 Element들을 가지고 와서 이벤트 구독이나 값들을 변경해주었다. 너무 많아서 하나씩 다루겠다.

_viewLableDictionary = new();

 

갑자기 Dictionary가 나와서 뭔가 할텐데

이 녀석들의 라벨들을 따로 딕셔너리로 가지고 온거다. 이름을 바뀔일이 있어서 딕셔너리로 분류하려고 한다. 나중에 설명할 함수에서 쓰인다.

 

_root = rootVisualElement;

VisualElement labelFromUxml = m_VisualTreeAsset.Instantiate();
labelFromUxml.style.flexGrow = 1;

이 코드는 UIToolkit 할때 다루었다.

Button makeBtn = _root.Q<Button>("MakeBtn");
Button deleteBtn = _root.Q<Button>("DeleteBtn");
_prefabView = _root.Q<VisualElement>("PrefabView");
EnumField viewType = _root.Q<EnumField>("ViewSetting");
fileNameField = _root.Q<TextField>("FileName");
selectedLabel = _root.Q<Label>("NameLabel");

필요한 Element들이다. 다른 함수들에서도 필요한 Element들을 따로 맴버변수로 빼두었다.

    private VisualElement _root;
    private VisualElement _prefabView;
    private Label selectedLabel;
    private TextField fileNameField;

여튼 makeBtn, DeleteBtn은 Toolbar에 있던 버튼들이고

PrefabView는 생성될 Element들의 부모이다.

EnumField는 현재 2D인지 3D인지 분류 하기 위한 용도로 쓰인다. fileNameField랑 selectedLabel은?

요거다. 여튼 선택된 Prefab에 따라서 바뀔 예정이다.

// ViewItem을 Table에 들어있는 Prefab의 갯수만큼 실행
if (_prefabTable.prefabList.Count > 0)
{
    foreach(var item in _prefabTable.prefabList){
        ViewItem(item.name);
    }
}

PrefabTable에 Prefab이 하나라도 있다면 foreach를 돌며 ViewItem 함수를 통해서 Prefab의 Element를 만들어준다.

 

// 선택된 Prefab의 file 이름을 변경하는 Event이다.
fileNameField.RegisterValueChangedCallback(evt =>
{
    if(_selected == null) return;
    
    _selected.Q<Label>("label").text = fileNameField.text;
    
    AssetDatabase.RenameAsset($"{_prefabFilePath}/{_selected.name}.prefab",
        fileNameField.text);
    _selected.name = fileNameField.text;
});

아까 가지고 온 fileNameField (이름 수정하는 부분)의 값이 바뀐다면 아래 람다를 실행시켜주는 코드이다. (참고로 어케든 이름을 바꿀려고 노렸했는데 되긴 되는데 잘 안됨 지금으로써는 로직만 이렇다라고 보고 코드는 사용하지 말자;;)

if(_selected == null) return;

selected가 null (선택이 안됬다면)

_selected.Q<Label>("label").text = fileNameField.text;

_selected(선택된 Element)의 자식를 돌며 label이라는 이름의 TextField를 찾아서 fileNameField의 text를 그 string값을 변경 시켜준다.

AssetDatabase.RenameAsset($"{_prefabFilePath}/{_selected.name}.prefab",
    fileNameField.text);

RenameAsset함수는 경로의 Asset의 이름을 바꾸어주는 함수이다. 경로를 프리팹들이 모여있는 위치 (개개인마다 다르겠죠??) 현재 선택된 객체의 이름.prfab를 통해서 찾고 변경한 fileNameField의text로 변경해준다.

_selected.name = fileNameField.text;

Element의 이름은 아직 바꾸어주었으니 바꿔주면 끝

개인적으로 왜 안되는지 모르겠는 부분이다. RegisterValueChangedCallback의 기능과 내 코드가 맞지 않거나 그냥 내 코들 문제일 수 도 있다. 안되는 거는 참 아쉽다...

// BTN Event
makeBtn.clicked += HandleMakeBtnCllikEvent;
deleteBtn.clicked += HandleDeleteBtnClickEvent;

버튼 이벤트 구독 한거다 함수는 나중에 다루고 아까 얘기했던 ViewItem 부분으로 넘어가겠다.

 

Function : ViewItem

// TableSO에 들어있는 Prefab을 VisualElement으로 표현하는 함수이다.
public void ViewItem(string vName){
    VisualElement element = new VisualElement();
    element.AddToClassList(_prefabVisual);
    element.name = vName;
    
    // Mouse Down Event
    element.RegisterCallback<PointerDownEvent>(ElementPointerDownEvent);

    Label label = new Label();
    label.name = "label";
    label.AddToClassList(_prefabLabel);
    label.text = vName;
    
    _viewLableDictionary.Add(vName, label);
    element.Add(label);
    _prefabView.Add(element);
}

주석을 달아 놓기 했는데 도움이 안되는 것 같다 ㅋㅋ;;

VisualElement element = new VisualElement();
element.AddToClassList(_prefabVisual);
element.name = vName;

일단 새로운 Element를 만들어주고 전에 UIToolkit 하면서 만들었던 _prefabVisual이라는 StyleClass를 추가해준다. 이름은 매개변수로 설정해준다.

// Mouse Down Event
element.RegisterCallback<PointerDownEvent>(ElementPointerDownEvent);

전에 UIToolkit하면서 선택하면 파란색으로 바뀐다고 했는데 그거 해주는 부분이다. (Element위에 마우스를 클랙했을 때 일어나는 이벤트)

Label label = new Label();
label.name = "label";
label.AddToClassList(_prefabLabel);
label.text = vName;

이건 Element아래에 자식으로 추가될 label의 값 설정하는 부분이다.

만들고 이름은 label으로 고정 styleClass 추가하고 이름은 매개변수로 초기화해준다.

_viewLableDictionary.Add(vName, label);
element.Add(label);
_prefabView.Add(element);

아까 말했던 딕셔너리에 추가해주고 만든 element의 자식으로 라벨을 추가해주고 아까 불러왔던 PrefabView의 자식으로 해준다.

 

Function : HandleDeleteBtnCllikEvent

GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>($"{_prefabFilePath}/{_selected.name}.prefab");

VisualElement deleteElement = _prefabView.Q<VisualElement>(_selected.name);
_prefabView.Remove(deleteElement);

_prefabTable.prefabList.Remove(obj);
AssetDatabase.DeleteAsset($"{_prefabFilePath}/{_selected.name}.prefab");
EditorUtility.SetDirty(_prefabTable);
AssetDatabase.SaveAssets();

삭제 버튼 눌렀을 때 실행되는 함수이다.

GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>($"{_prefabFilePath}/{_selected.name}.prefab");

지울 Prefab 들고 온다.

VisualElement deleteElement = _prefabView.Q<VisualElement>(_selected.name);
_prefabView.Remove(deleteElement);

PrefabView아래에서 선택된 Element를 들고 온다.

그리고 PrefabView에서는 지워준다.

_prefabTable.prefabList.Remove(obj);
AssetDatabase.DeleteAsset($"{_prefabFilePath}/{_selected.name}.prefab");
EditorUtility.SetDirty(_prefabTable);
AssetDatabase.SaveAssets();

Table에서 지워주고 실제로도 Prefab을 삭제 해준뒤

SetDirty로 더러우니까 버리라고 해주고 SaveAsset해주면 끝!

Function : ElementPointerDownEvent

private void ElementPointerDownEvent(PointerDownEvent evt)
{
    foreach(var item in _prefabTable.prefabList){
        VisualElement element = _root.Q<VisualElement>(item.name);
        List<string> classNames = element.GetClasses().ToList();
        
        // 모든 SelectEffect가 적용된 VisualElement에 Effect를 제거한다.
        foreach(string str in classNames){
            if(str == _prefabVisualSelect){
                element.AddToClassList(_prefabVisual);
                element.RemoveFromClassList(_prefabVisualSelect);
            }
        }
        
        // 클릭한 위치의 VisualElement와 현재 Element가 같다면? 그 VisualElement에 Effect를 부여하고
        // Toolbar의 Label과 FileNameField(선택된 Prefab의 FileName을 표기하는 TextField이다.)의 Value를 바꾸어 준다.
        if(evt.currentTarget.GetHashCode() == element.GetHashCode()){
            _selected = element;
            element.RemoveFromClassList(_prefabVisual);
            element.AddToClassList(_prefabVisualSelect);
            
            selectedLabel.text = element.name;
            fileNameField.value = element.name;
        }
    }
}

마우스 클릭 시 실행되는 이벤트이다. 

foreach(var item in _prefabTable.prefabList)

Table 전체를 돌며 체크 한다.

VisualElement element = _root.Q<VisualElement>(item.name);
List<string> classNames = element.GetClasses().ToList();

현제 Element와 Element가 가지고 있는 Class들을 가지고 온다.

// 모든 SelectEffect가 적용된 VisualElement에 Effect를 제거한다.
foreach(string str in classNames){
    if(str == _prefabVisualSelect){
        element.AddToClassList(_prefabVisual);
        element.RemoveFromClassList(_prefabVisualSelect);
    }
}

가지고 있는 Class들을 전부 확인하며 Select 됬을 때 생기는 Class와 같은 지 확인한다.

만일 있다면?? 기본 Class를 주고 SelectClass를 지워준다.

// 클릭한 위치의 VisualElement와 현재 Element가 같다면? 그 VisualElement에 Effect를 부여하고
// Toolbar의 Label과 FileNameField(선택된 Prefab의 FileName을 표기하는 TextField이다.)의 Value를 바꾸어 준다.
if(evt.currentTarget.GetHashCode() == element.GetHashCode()){
    _selected = element;
    element.RemoveFromClassList(_prefabVisual);
    element.AddToClassList(_prefabVisualSelect);

    selectedLabel.text = element.name;
    fileNameField.value = element.name;
}

주석만 읽어도 뭐하는 코드인지는 알 수 있다. 

주석 읽어주세요...

 

Function :  OnEnable, OnDisable

외부에서 ViewItem을 불러 올 수 있게 Action을 만들어서 Window열때 구독하고 Window 닫을 떄 구독 취소 해주었다.

private void OnEnable()
{
    _ShowItemEvent += ViewItem;
}

private void OnDisable()
{
    _ShowItemEvent -= ViewItem;
}

Function : HandleMakeBtnCllikEvent

// prefab 생성 함수
private void HandleMakeBtnCllikEvent()
{
    PrefabManager wnd = GetWindow<PrefabManager>();
    wnd.titleContent = new GUIContent("PrefabManager");
    wnd.minSize = new Vector2(600, 800);
}

드디어 나온 PrefabManager(실제 만들어주는 Window) 이름이 지금 보니 참 그렇다 ㅋㅋ

여튼 가지고 와서 열어주고 최소 크기만 정해준다. 맘대로 늘릴 수 있게 하기 위해서 최소 사이즈만 정해둠 PrefabManager 스크립트는 다음 파트에서 다루겠다.... 그리고 참고로 선언한 맴버변수는 아래에 써둠 내가 봐도 코드 진짜 못짜고 안쓰는 게 좀 있어서 고쳐야 될거 같다.

[SerializeField]
private VisualTreeAsset m_VisualTreeAsset = default;
[SerializeField]
private StyleSheet _styleSheet;
[SerializeField]
private PrefabTableSO _prefabTable;

public static Action<string> _ShowItemEvent;

private readonly string _prefabVisual = "prefab-visual";
private readonly string _prefabLabel = "prefab-label";
private readonly string _prefabVisualSelect = "prefab-visual-select";
private readonly string _prefabFilePath = "Assets/Test/CreatePrefabWindow/03_Prefab";

private VisualElement _root;
private VisualElement _prefabView;
private Label selectedLabel;
private TextField fileNameField;

protected Dictionary<string, Label> _viewLableDictionary;

[HideInInspector] public VisualElement _selected;

'Editor' 카테고리의 다른 글

[Unity, Editor] SOManagementWindow 제작 일기 Part.2  (0) 2025.04.25
[Unity, Editor] SOManagementWindow 제작 일기 Part.1  (0) 2025.04.25
[졸업 작품, Unity]Item EditorWindow로 관리하기  (0) 2025.03.18
[Editor] 적 생성 Editor Window 제작 과정 part.2  (0) 2024.10.08
[Editor] 적 생성 Editor Window 제작 과정 part.1  (1) 2024.10.07
'Editor' 카테고리의 다른 글
  • [Unity, Editor] SOManagementWindow 제작 일기 Part.1
  • [졸업 작품, Unity]Item EditorWindow로 관리하기
  • [Editor] 적 생성 Editor Window 제작 과정 part.2
  • [Editor] 적 생성 Editor Window 제작 과정 part.1
HK1206
HK1206
고3 게임 개발자의 개발 일지
  • HK1206
    GGM-LJS
    HK1206
  • 전체
    오늘
    어제
    • 분류 전체보기 (25) N
      • Unity (16)
      • Shader (1)
      • Editor (8) N
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.0
HK1206
[Editor] 적 생성 Editor Window 코드 부분 제작
상단으로

티스토리툴바