Endeavor/Unity Stuff

EditorWindow simple Exemple

hiheybye 2025. 4. 24. 17:48

Chatgpt가 제공해준 코드를 분석한다.

 

Exemple Code

더보기

 ※ 원하는 이름의 프리펩과 머터리얼을 한번에 만드는 방법도 있을까?

using UnityEngine;
using UnityEditor;
using System.IO;

public class AssetAutoCreator : EditorWindow
{
    string assetName = "NewAsset";

    [MenuItem("Tools/Prefab & Material Creator")]
    public static void ShowWindow()
    {
        GetWindow<AssetAutoCreator>("Prefab & Material Creator");
    }

    void OnGUI()
    {
        GUILayout.Label("Create Prefab and Material", EditorStyles.boldLabel);

        assetName = EditorGUILayout.TextField("Asset Name", assetName);

        if (GUILayout.Button("Create Assets"))
        {
            CreateAssets(assetName);
        }
    }

    void CreateAssets(string name)
    {
        // 기본 경로 (원하면 바꿀 수 있음)
        string basePath = "Assets/GeneratedAssets/";
        string prefabPath = Path.Combine(basePath, name + ".prefab");
        string materialPath = Path.Combine(basePath, name + "_Mat.mat");

        // 폴더가 없으면 생성
        if (!AssetDatabase.IsValidFolder(basePath))
        {
            Directory.CreateDirectory(basePath);
        }

        // 머터리얼 생성
        Material newMat = new Material(Shader.Find("Standard"));
        AssetDatabase.CreateAsset(newMat, materialPath);
        Debug.Log("Material created at: " + materialPath);

        // 오브젝트 생성 후 머터리얼 적용
        GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
        obj.name = name;
        obj.GetComponent<Renderer>().sharedMaterial = newMat;

        // 프리팹 생성
        PrefabUtility.SaveAsPrefabAsset(obj, prefabPath);
        Debug.Log("Prefab created at: " + prefabPath);

        // 임시 오브젝트 제거
        DestroyImmediate(obj);

        AssetDatabase.Refresh();
    }
}

※ EditorWindow 에 대해서 알려줘

using UnityEditor;
using UnityEngine;

public class MyWindow : EditorWindow
{
    // 메뉴에서 호출할 이름 설정
    [MenuItem("Tools/My Custom Window")]
    public static void ShowWindow()
    {
        // 윈도우 띄우기
        GetWindow<MyWindow>("My Window");
    }

    // UI 그리기 (에디터 전용 OnGUI)
    void OnGUI()
    {
        GUILayout.Label("Hello, EditorWindow!", EditorStyles.boldLabel);

        if (GUILayout.Button("Click Me"))
        {
            Debug.Log("Button clicked!");
        }
    }
}

 

 

※게임 오브젝트를 생성했다 파괴하지 말고 프리펩에 바로 적용하는 방법은 없어?

using UnityEditor;
using UnityEngine;
using System.IO;

public class PurePrefabCreator
{
    [MenuItem("Tools/Create Prefab Without Spawning")]
    public static void CreatePurePrefab()
    {
        string path = "Assets/GeneratedAssets/";
        string prefabName = "PurePrefab";

        if (!Directory.Exists(path))
            Directory.CreateDirectory(path);

        // 씬에 등장하지 않는 GameObject 생성 (메모리상)
        GameObject tempGO = new GameObject(prefabName);

        // 여기에 컴포넌트나 자식 추가 가능
        tempGO.AddComponent<BoxCollider>();
        tempGO.transform.localScale = new Vector3(2, 1, 2);

        // 프리팹으로 저장
        string fullPath = Path.Combine(path, prefabName + ".prefab");
        PrefabUtility.SaveAsPrefabAsset(tempGO, fullPath);

        // 생성 후 메모리에서 제거 (씬에 등록되지 않았지만 확실하게)
        Object.DestroyImmediate(tempGO);

        AssetDatabase.Refresh();
        Debug.Log($"Created prefab at: {fullPath}");
    }
}

 유니티에서는 게임 오브젝트 기반으로 프리펩을 생성하므로 한번은 만들어야 한다.

 

Destroy 와 DestroyImmediate의 주요 차이는 다음 프레임이 진행될 때 삭제될 것인지 즉시 삭제할 것인지다.

 

※TextField 말고 배열을 사용하는 방법

방법1 배열 편집

using UnityEditor;
using UnityEngine;

public class ArrayData : ScriptableObject
{
    public string[] items;
}
public class ArrayEditorWindow : EditorWindow
{
    SerializedObject serializedObj;
    SerializedProperty itemsProp;

    [MenuItem("Tools/Array Editor")]
    public static void Open()
    {
        GetWindow<ArrayEditorWindow>("Array Editor");
    }

    void OnEnable()
    {
        var asset = ScriptableObject.CreateInstance<ArrayData>();
        serializedObj = new SerializedObject(asset);
        itemsProp = serializedObj.FindProperty("items");
    }

    void OnGUI()
    {
        serializedObj.Update();

        EditorGUILayout.PropertyField(itemsProp, new GUIContent("Items"), true);

        serializedObj.ApplyModifiedProperties();
    }
}

 

방법 2 수동 for문

using UnityEditor;
using UnityEngine;
using System.Collections.Generic;

public class DynamicInputWindow : EditorWindow
{
    private List<string> names = new List<string>() { "" };

    [MenuItem("Tools/Dynamic TextFields")]
    public static void ShowWindow()
    {
        GetWindow<DynamicInputWindow>("Dynamic Inputs");
    }

    void OnGUI()
    {
        GUILayout.Label("Enter names (new field appears as you type)", EditorStyles.boldLabel);

        // 리스트 그리기
        for (int i = 0; i < names.Count; i++)
        {
            names[i] = EditorGUILayout.TextField($"Name {i + 1}", names[i]);
        }

        // 마지막 항목이 비어있지 않으면 새 입력 필드 추가
        if (!string.IsNullOrWhiteSpace(names[names.Count - 1]))
        {
            names.Add("");
        }

        GUILayout.Space(10);

        if (GUILayout.Button("Log Names"))
        {
            foreach (var name in names)
            {
                if (!string.IsNullOrWhiteSpace(name))
                    Debug.Log("Name: " + name);
            }
        }
    }
}

방법 3 ReorderableList

using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using System.Collections.Generic;

public class StringListEditorWindow : EditorWindow
{
    private ReorderableList reorderableList;
    private List<string> stringList = new List<string> { "Apple", "Banana", "Cherry" };

    [MenuItem("Window/ String List Editor")]
    public static void ShowWindow()
    {
        GetWindow<StringListEditorWindow>("String List Editor");
    }

    private void OnEnable()
    {
        reorderableList = new ReorderableList(stringList, typeof(string), true, true, true, true);

        reorderableList.drawHeaderCallback = (Rect rect) =>
        {
            EditorGUI.LabelField(rect, "Fruits List");
        };

        reorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
        {
            stringList[index] = EditorGUI.TextField(
                new Rect(rect.x, rect.y + 2, rect.width, EditorGUIUtility.singleLineHeight),
                $"Item {index + 1}",
                stringList[index]
            );
        };
    }

    private void OnGUI()
    {
        if (reorderableList != null)
            reorderableList.DoLayoutList();
    }
}

기본적으로 에디터가 동작하면서, ShowWindow() 와 OnGUI()를 호출한다.

이때 OnGUI()은 update loop와 비슷하게 호출되고 있다.

 

 [MenuItem("Tools/My Custom Window")]

  • 에디터 상단 메뉴에 항목 추가

void ShowWindow()

  • 내부 동작으로  윈도우를 생성하고 Show()까지 실행한다.
  • GetWindow<WindowClass>("Window name");

void OnEnable()

  • 활성되 될 시 호출, 초기화 담당

void OnGUI()

  • update loop와 비슷하게 호출되고 있다.
    • loop 적으로 호출되고 있음을 유의한다.
    • GUILayout, EditorGUILayout 등의 클래스와 다양한 요소가 많다.
  • 비주얼 관련
    • GUILayout.Label("Create Prefab and Material", EditorStyles.boldLabel);
    • GUILayout.Space(10);
  • 입력 관련 
    • assetNames = EditorGUILayout.TextField("Asset name", assetNames);
    • if (GUILayout.Button("Create Assetes"))
  • 창 내부에 요소를 추가할수있다.

 

ScriptableObject, SerializedObject

  • 아직 완벽하게 이해한건 아니지만, ScriptableObject 파일과 직접적으로 관련된 데이터이고, 이를 한번 랩핑해서 유용한 기능을 제공하는게 SerializedObject이다.
  • SerializedObject를 만들었다면, .FindProperty("변수 이름"); 으로 데이터를 담고있는 속성 오브젝트를 가져올 수 있다.
  • EditorGUILayout.PropertyField(itemsProp, new GUIContent("Assat names"), true);

ReorderableList

  • using UnityEditorInternal에 존재하는 클래스로, List를 넣어주면 에디터에서 순서를 바꿀수 있는 입력칸을 만들어준다.
  • 비주얼 관련해 여러 Callback함수가 제공하는 Rect : 해당하는 공간에 같이 오는 변수를 사용해 구성한다.
        reorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
        {
            assetNames[index] = EditorGUI.TextField(rect,$"Item {index + 1}", assetNames[index]);
            //assetNames[index] = EditorGUILayout.TextField($"Item {index + 1}", assetNames[index]);
        };

주석처리된 코드를 사용하면, 칸에 벗어나서 입력란이 생긴다.

 

 

GUILayout.Label("Create Prefab and Material", EditorStyles.boldLabel);
GUILayout.Space(10);
GUILayout.Label("BasePath : " + basePath + "Prefabs/ || Materials/", EditorStyles.label);
//custom method (assetNames[i] = EditorGUILayout.TextField("Asset name", assetNames[i]);)
DynamicInputWindow();

GUILayout.Space(10);
if (GUILayout.Button("Create Assetes"))
{
    CreateAssets(assetNames);
}

 

 assetNames[i] = EditorGUILayout.TextField("Asset name", assetNames[i]);

  • 텍스트 필드 입력시 사용하며, 동일한 타입의 변수가 필요하다.
    • EditorGUILayout.TextField("Asset name") 입력은 가능하나 적용은 불가

 

Custom Code

더보기
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
using UnityEditor.Search;

public class AssetAutoCreator : EditorWindow
{
    string basePath = "Assets/GeneratedAssets/";
    List<string> assetNames = new List<string>() { "" };

    [MenuItem("Tools/Prefab & Material Creator")]
    public static void ShowWindow()
    {
        GetWindow<AssetAutoCreator>("Prefab & Material Creator");
    }

    private void OnGUI()
    {
        GUILayout.Label("Create Prefab and Material", EditorStyles.boldLabel);

        GUILayout.Space(10);

        GUILayout.Label("BasePath : " + basePath + "Prefabs/ || Materials/", EditorStyles.label);

        DynamicInputWindow();

        GUILayout.Space(10);

        if (GUILayout.Button("Create Assetes"))
        {
            CreateAssets(assetNames.ToArray());
        }
    }

    private void DynamicInputWindow()
    {
        for (int i = 0; i < assetNames.Count; i++)
        {
            assetNames[i] = EditorGUILayout.TextField("Asset name", assetNames[i]);
        }
        if (!string.IsNullOrWhiteSpace(assetNames[assetNames.Count - 1]))
        {
            assetNames.Add("");
        }
    }

    void CreateAssets(string[] names)
    {
        foreach(string name in names)
        {
            if (string.IsNullOrWhiteSpace(name)) continue;


            string prefabPath = Path.Combine(basePath + "Prefabs/", name + ".prefab");
            string materialPath = Path.Combine(basePath + "Materials/", name + "_Mat.mat");


            if (!AssetDatabase.IsValidFolder(basePath + "Prefabs/"))
            {
                Directory.CreateDirectory(basePath + "Prefabs/");
            }
            if (!AssetDatabase.IsValidFolder(basePath + "Materials/"))
            {
                Directory.CreateDirectory(basePath + "Materials/");
            }

            if(File.Exists(prefabPath))
            {
                Debug.Log(name + " File exists already");
                continue;
            }

            if (File.Exists(materialPath))
            {
                Debug.Log(name + " File exists already");
                continue;
            }


            Material newMat = new Material(Shader.Find("Standard"));
            AssetDatabase.CreateAsset(newMat, materialPath);

            GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
            obj.name = name;
            obj.GetComponent<Renderer>().sharedMaterial = newMat;

            PrefabUtility.SaveAsPrefabAsset(obj, prefabPath);

            DestroyImmediate(obj);

            AssetDatabase.Refresh();
            
        }
    }


}

 

using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEditor.PackageManager.UI;

public class AssetAutoCreatorTwo : EditorWindow
{

    public class ArrayData : ScriptableObject
    {
        public string[] items;
    }

    SerializedObject serializedObj;
    SerializedProperty itemsProp;

    string basePath = "Assets/GeneratedAssets/";

    [MenuItem("Tools/Prefab Material Creator2")]
    public static void ShowWindow()
    {
        GetWindow<AssetAutoCreatorTwo>("Prefab & Material Creator2").minSize = new Vector2(500, 300); ;
    }

    void OnEnable()
    {
        var ScriptableObj = ScriptableObject.CreateInstance<ArrayData>();
        serializedObj = new SerializedObject(ScriptableObj);
        itemsProp = serializedObj.FindProperty("items");
    }

    private void OnGUI()
    {
        GUILayout.Label("Create Prefab and Material", EditorStyles.boldLabel);
        GUILayout.Label("Path : " + basePath + "Materials || Prefabs", EditorStyles.label);

        GUILayout.Space(10);

        serializedObj.Update();
        EditorGUILayout.PropertyField(itemsProp, new GUIContent("Assat names"), true);
        serializedObj.ApplyModifiedProperties();

        if (GUILayout.Button("Create Assets"))
        {

            string[] names = new string[itemsProp.arraySize];

            for(int i=0; i < itemsProp.arraySize; i++)
            {
                names[i] = itemsProp.GetArrayElementAtIndex(i).stringValue;
            }
            CreateAssets(names);
        }

    }

    void CreateAssets(string[] names)
    {//동일

    }
}
using NUnit.Framework;
using UnityEditorInternal;
using UnityEngine;
using System.Collections.Generic;
using UnityEditor;
using Unity.VisualScripting;
using System.IO;

public class AssetAutoCreatorThree : EditorWindow
{
    private ReorderableList reorderableList;
    private List<string> assetNames = new List<string>{ };
    string basePath = "Assets/GeneratedAssets/";

    [MenuItem("Tools/Prefab Material Creator3")]
    public static void ShowWindow()
    {
        GetWindow<AssetAutoCreatorThree>("Prefab Material Creator3");
    }

    private void OnEnable()
    {
        reorderableList = new ReorderableList(assetNames, typeof(string) , true, true, true, true);



        reorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
        {
            assetNames[index] = EditorGUI.TextField(rect,$"Item {index + 1}", assetNames[index]);
            //assetNames[index] = EditorGUILayout.TextField($"Item {index + 1}", assetNames[index]);
        };

    }

    private void OnGUI()
    {
        GUILayout.Label("Create Prefab and Material", EditorStyles.boldLabel);
        GUILayout.Label("Path : " + basePath + "Materials || Prefabs", EditorStyles.label);

        GUILayout.Space(10);

        if (reorderableList != null)
            reorderableList.DoLayoutList();

        if (GUILayout.Button("Create Assets"))
        {

            CreateAssets(assetNames.ToArray());
        }

    }

    void CreateAssets(string[] names)
    {//동일
    }
}

 

 

 

 

 

 

'Endeavor > Unity Stuff' 카테고리의 다른 글

Blender remind  (0) 2025.04.29
유니티 각도 관련  (0) 2025.04.26
Unity - FPS microgame code 분석  (0) 2024.05.24
Unity Learn - Creative Core Pathway - Final Submission  (0) 2024.05.16