본문 바로가기
유니티

[ Unity ] 유니티 Custom Editor Window 를 사용해 쉽게 데이터를 Json으로 저장하기

by Hexs 2023. 10. 8.
반응형

유니티 메뉴얼

https://docs.unity3d.com/kr/2021.3/Manual/UIE-HowTo-CreateEditorWindow.html

 

커스텀 에디터 창 생성 - Unity 매뉴얼

커스텀 에디터 창을 사용하면 직접 에디터와 워크플로를 만들어 Unity를 확장할 수 있습니다. 이 가이드는 코드를 사용하여 에디터 창을 만들고, 사용자 입력에 응답하고, UI의 크기를 조절할 수

docs.unity3d.com

 

함수 설명
ColorField 색상을 변경하기위한 필드 생성
CurveField AnimationCurve를 변경하기 위한 필드 생성
DoubleField Double 변수 을 입력하기 위한 필드 생성
FloatField Float 변수 을 입력하기 위한 필드 생성
IntField Int 변수 을 입력하기 위한 필드 생성
LongField Long 변수 을 입력하기 위한 필드 생성
TextField String 변수 을 입력하기 위한 필드 생성
GradientField 그라디언트를 수정을 위한 필드 생성
LabelField 레이블 필드를 생성
DrawRect 현재 편집기 창 내에서 지정된 위치와 크기로 채워진 사각형을 생성
Foldout 왼쪽에 접이식 화살표가 있는 레이블을 생성
IntSlider 사용자가 드래그하여 최소값과 최대 사이의 정수 값을 변경할 수 있는 슬라이더를 생성.
HelpBox 사용자에게 메시지가 있는 도움말 상자를 생성
Slider 사용자가 드래그하여 최소값과 최대값 사이의 값을 변경할 수 있는 슬라이더를 생성.
Toggle 토글을 생성

 

디펜스 게임을 제작하면서

스테이지별 데이터를 Json 형식으로 저장하고 불러오고 수정하는 과정에서 불편함이 생겨서 제작하게 되었습니다.



구현 방법은.

커스텀 에디터를 만들고 스테이지 정보를 입력받아서 해당 스테이지가 있으면 값을 변경해 주고 없으면 입력받은 데이터를 가지고 스테이지 정보를 저장합니다.



데이터는 JSON으로 관리하며. 저장은 Resource 폴더를 사용하여 PC에서 데이터를 수정하고

타겟 플랫폼인 모바일에서도 바로 수정된 데이터를 사용할 수 있게 구현함.


1. Custom Editor와 Json을 사용한 스테이지 데이터 관리 코드.

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEngine.UIElements;
using System.IO;

[System.Serializable]
public class EditorStageData // JSON에 저장할 데이터 형식.
{
    public int sdStageIndex;
    public int sdBossCheck;
    public float sdMonsterHp;
    public float sdMonsterSpeed;
    public int sdMonsterCount;
    public int sdSpawnmonster;
    public int sdageWeightI;
    public int sdageWeightII;
    public int sdageWeightIII;
}

public class StageEditor : EditorWindow
{

    int seGetStageIndex;
    int seStageIndex;
    int seBossCheck;
    float seMonsterHp;
    float seMonsterSpeed;
    int seMonsterCount;
    int seSpawnmonster;
    int seageWeightI;
    int seageWeightII;
    int seageWeightIII;
    // JsonUtility 는 객체 안에 다른 객체를 가진 배열처럼 2단 이상 구성되면 클래스로 가져오지 못하기 때문에.
    // 위 데이터 형식을 List로 만들어 주는 class를 하나 더 생성한다.
    public class EditorStageDataList
    {
        public List<EditorStageData> stages = new List<EditorStageData>();
    }
    public EditorStageData editData = new EditorStageData();
    public EditorStageDataList editDataList = new EditorStageDataList();


    // 저장경로, pc에서 수정한 데이터가 타겟 플랫폼인 모바일에 바로 적용되게 하기 위해서.
    // Resources 폴더로 저장되게 해준다.
    string path = Path.Combine(Application.dataPath + "/Resources/", "StageData.json");

    // Menu에 추가해준다 위치는 Tools 아래 StageEditor 라는 이름으로.
    [MenuItem("Tools/StageEditor")]
    public static void ShowMyEditor() // 윈도우 생성
    {
        // This method is called when the user selects the menu item in the Editor
        EditorWindow wnd = GetWindow<StageEditor>();
        wnd.titleContent = new GUIContent("StageEditor");
    }


    void OnGUI() // 윈도우 UI를 그린다.
    {
        // Stage Data Set이라는 라벨 아래로 각각의 스테이지 데이터를 받을 수 있게 만들어 줬다.
        GUILayout.Label("Stage Data Set", EditorStyles.boldLabel);
        GUILayout.Space(10f);
        seStageIndex = EditorGUILayout.IntField("seStageIndex", seStageIndex);
        GUILayout.Space(5f);
        seBossCheck = EditorGUILayout.IntField("seBossCheck", seBossCheck);
        GUILayout.Space(5f);
        seMonsterHp = EditorGUILayout.FloatField("seMonsterHp", seMonsterHp);
        GUILayout.Space(5f);
        seMonsterSpeed = EditorGUILayout.FloatField("seMonsterSpeed", seMonsterSpeed);
        GUILayout.Space(5f);
        seMonsterCount = EditorGUILayout.IntField("seMonsterCount", seMonsterCount);
        GUILayout.Space(5f);
        seSpawnmonster = EditorGUILayout.IntField("seSpawnMonster", seSpawnmonster);
        GUILayout.Space(5f);
        seageWeightI = EditorGUILayout.IntField("seMonsterWeightI", seageWeightI);
        GUILayout.Space(5f);
        seageWeightII = EditorGUILayout.IntField("seMonsterWeightII", seageWeightII);
        GUILayout.Space(5f);
        seageWeightIII = EditorGUILayout.IntField("seMonsterWeightIII", seageWeightIII);
        GUILayout.Space(10f);

        // 버튼을 만들고 if문을 사용해서 버튼을 누를경우 해당 코드가 실행된다.
        if (GUILayout.Button("Make Stage"))
        {
            if (File.Exists(path))
            {
                bool containCheck = false; // INDEX 값이 있는지 체크 하기위한 변수.
                // 기존 파일 불러오기.
                TextAsset loadData = Resources.Load<TextAsset>("StageData");
                // // 기존파일 sd 변수에 저장.
                editDataList = JsonUtility.FromJson<EditorStageDataList>(loadData.ToString());

                // 입력된 인덱스 값이 있는지 체크
                // - 스테이지 데이터가 많지않기 때문에 for문을 사용하여 검사하였다.
                for (int i = 0; i < editDataList.stages.Count; i++)
                {
                    if (editDataList.stages[i].sdStageIndex == seStageIndex)
                    {
                        DataChange(i); // 인덱스 값이 있다면 바뀌는 데이터 저장.
                        containCheck = true;
                        break;
                    }
                }
                if (!containCheck) // 인덱스 값이 없다면 데이터 추가.
                {
                    DataAdd();
                }
                // Stage의 index 기준으로 List를 정렬한다.
                editDataList.stages.Sort((p1, p2) => p1.sdStageIndex.CompareTo(p2.sdStageIndex));
                string data = JsonUtility.ToJson(editDataList, true); // 수정 or 추가한 데이터를 str로 바꿈
                File.WriteAllText(path, data); // str 데이터를 json형식으로 저장
                // 데이터 폴더를 리프래쉬 해주지 않으면 입력된 데이터가 저장이 되지않는다.
                AssetDatabase.Refresh(); 
            }
            else // 초기 데이터가 없는 경우.
            {
                DataAdd();
                string data = JsonUtility.ToJson(editDataList, true);
                File.WriteAllText(path, data);
            }
        }


        if (File.Exists(path))
        {
            GUILayout.Space(30f);
            GUILayout.Label("Stage Data Get", EditorStyles.boldLabel);
            seGetStageIndex = EditorGUILayout.IntField("seGetStageIndex", seGetStageIndex);
            
            // 기존 파일 불러오기.
            TextAsset loadData = Resources.Load<TextAsset>("StageData");
            // // 기존파일 sd 변수에 저장.
            editDataList = JsonUtility.FromJson<EditorStageDataList>(loadData.ToString());
            EditorGUILayout.HelpBox("불러온 스테이지 Index : " + editDataList.stages[seGetStageIndex].sdStageIndex +
        "\n 보스 스테이지 확인 ( 0 or 1 ) : " + editDataList.stages[seGetStageIndex].sdBossCheck +
        "\n 몬스터 체력 : " + editDataList.stages[seGetStageIndex].sdMonsterHp +
        "\n 몬스터 속도 : " + editDataList.stages[seGetStageIndex].sdMonsterSpeed +
        "\n 예상 몬스터 스폰 숫자 : " + editDataList.stages[seGetStageIndex].sdMonsterCount, MessageType.Info);
        }
    }
    /// <summary> 기존 데이터가 없는경우 데이터를 추가한다. </summary>
    void DataAdd()
    {
        editData.sdStageIndex = seStageIndex;
        editData.sdBossCheck = seBossCheck;
        editData.sdMonsterHp = seMonsterHp;
        editData.sdMonsterSpeed = seMonsterSpeed;
        editData.sdMonsterCount = seMonsterCount;
        editData.sdSpawnmonster = seSpawnmonster;
        editDataList.stages.Add(editData);

        Debug.Log("Index : " + editData.sdStageIndex + " 스테이지 데이터가 추가 되었습니다. ");
    }
    /// <summary> 기존 데이터가 있는경우 데이터를 변경한다. </summary>
    /// <param name="idx"> 변경할 index 주소를 입력. </param>
    void DataChange(int idx)
    {
        editDataList.stages[idx].sdBossCheck = seBossCheck;
        editDataList.stages[idx].sdMonsterHp = seMonsterHp;
        editDataList.stages[idx].sdMonsterSpeed = seMonsterSpeed;
        editDataList.stages[idx].sdMonsterCount = seMonsterCount;
        editDataList.stages[idx].sdSpawnmonster = seSpawnmonster;

        Debug.Log("Index : " + editDataList.stages[idx].sdStageIndex + " 스테이지 데이터가 수정 되었습니다. ");
    }
}

 

 

2.  게임 시작전 Json의 데이터를 불러오는 코드.

  • 게임 시작 전 모든 스테이지 데이터를 불러오고.
  • 스테이지 시작 시에 해당 스테이지 index 값을 넣어서 데이터값을 불러감.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

/// <summary> 스테이지 데이터 클래스 </summary>
[System.Serializable]
public class Stage
{
    public int sdStageIndex;
    public int sdBossCheck;
    // 몬스터 Hp
    public float sdMonsterHp;
    public float sdMonsterSpeed;
    public int sdMonsterCount;
    // 어느 몬스터를 스폰할지, 스폰될 몬스터 index를 저장함.
    public int sdSpawnmonster;
    /*
    몬스터 속성시스템. 
    스테이지 별로 가중치 1 2 3 에 따라서
    몬스터마다 최소 0 개 최대 3개의 속성을 부여받음.
    */
    public int sdageWeightI;
    public int sdageWeightII;
    public int sdageWeightIII;
}
public class StageData : MonoBehaviour
{
    public class StageDataInfo
    {
        public List<Stage> stages = new List<Stage>();
    }

    public StageDataInfo st = new StageDataInfo();

    void Start()
    {
        // string loadData = File.ReadAllText(Application.persistentDataPath + "/stagedata");
        //기존파일 sd 변수에 저장.
        // st = JsonUtility.FromJson<StageDataInfo>(loadData);
        TextAsset loadData = Resources.Load<TextAsset>("StageData");
        st = JsonUtility.FromJson<StageDataInfo>(loadData.ToString());


        Debug.Log(loadData);
    }

    // Update is called once per frame
    void Update()
    {

    }

    /// <summary> 스테이지에 대한 index값을 넣으면 해당 스테이지의 정보를 보내주는 함수. </summary>
    /// <param name="index"> 스테이지 index값 입력. </param>
    public (int, float, float, int, int, int, int, int) GetData(int index)
    {
        Debug.Log(st.stages.Count);
        return (
        st.stages[index].sdBossCheck,
        st.stages[index].sdMonsterHp,
        st.stages[index].sdMonsterSpeed,
        st.stages[index].sdMonsterCount,
        st.stages[index].sdSpawnmonster,
        st.stages[index].sdageWeightI,
        st.stages[index].sdageWeightII,
        st.stages[index].sdageWeightIII);
    }

}

 


 

결과물

Custom Editor Window 실행

 

생성된 데이터

 

Log

 


구현 영상

구현 영상

 

※ 안드로이드 빌드시 해당 스크립트는 Editor 폴더 안에 있어야 빌드가 됨.

반응형

'유니티' 카테고리의 다른 글

[ Unity ] Float 형 소수점 버리기  (1) 2024.02.17
이징 그래프  (0) 2023.11.20
[ Unity ] 유니티 Inspector Attribute  (0) 2023.08.03
[ Unity ] Miner : 광부키우기 출시..  (0) 2023.06.20
[ Unity ] TextMeshPro Script 제어 방법  (0) 2023.05.22