깃헙 링크 : 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들을 따로 맴버변수로 빼두었다.
여튼 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 |