본문 바로가기
Unity Study/에셋 추천

오딘 인스펙터 - Odin Inspector

by 백삼_zif 2023. 12. 19.
반응형

1. 오늘의 추천 에셋! - Odin Inspector

안녕하세요! 오늘도 Unity 공부는 잘 진행되고 계신가요?
벌써 한해가 거의 다 지나가고 있네요. 크리스마스에도 애인 대신 코드를 공부하고 있을 많은 분들 화이팅입니다! ㅠㅠ
오늘 소개해 드릴 에셋은 바로 Odin Inspector입니다. 해당 에셋은 아쉽게도 무료에셋은 아닙니다. ( 광고 해보고싶다... )
다만, 직접 사용해보고 정말 좋은 에셋이란 것을 느끼고 해당 에셋을 소개해 드리려고 합니다.

2. 오딘 인스펙터란?

게임을 제작하다 보면 여러가지 오브젝트나, 데이터들을 관리해야 하게 되는 경우가 있습니다.
오딘 인스펙터는 이러한 오브젝트들의 인스펙터창을 사용자가 원하는 모양으로 변경시키기 쉽도록
여러가지 어트리뷰트들을 미리 만들어준 에셋으로 이해하시면 됩니다.

더보기

< Unity 용어 > 어트리뷰트가 뭐야?

유니티(Unity)에서 대괄호 []는 속성(Attribute)을 나타냅니다. 이러한 속성(Attribute)은 C# 프로그래밍 언어의 주석과는 다르며, 코드 요소에 메타데이터를 적용하고 특정한 행동이나 설정을 지정하는 데 사용됩니다.
예를 들어, Unity에서는 [SerializeField], [Range], [Header], [ContextMenu] 등과 같은 다양한 속성들이 사용됩니다.
각각의 속성들은 그들이 적용된 필드나 메서드, 클래스 등의 행동을 변경하거나 기능을 추가할 수 있습니다.

다시 본론으로 돌아오면, 오딘 인스펙터란 이러한 속성을 제공하는 것 뿐만 아니라 더 많은 기능들도 있다고 합니다.

다만 아직 제가 다른 부분은 이해를 못해서 소개를 못해드리네요 ㅠㅠ

제가 해당 에셋을 구매하게 된 이유에는 지금 소개시켜 드리는 부분이 제일 중요했기에 해당 부분을 먼저 공부하게 되었습니다.

3. 오딘 인스펙터 사용해보기!

  1. 오딘 인스펙터를 통한 데이터 관리창 만들기!
    아래의 코드와 같이 using 문을 넣어주고 OdinMenuEditorWindow를 상속받는 클래스를 만들어줍니다.
    [MenuItem()] 에 등록된 경로에 저희가 만들어준 데이터관리창을 열 수 있는 메뉴가 생깁니다!
#if UNITY_EDITOR
    using Sirenix.OdinInspector.Editor;
    using Sirenix.Utilities;
    using Sirenix.Utilities.Editor;
    using UnityEditor;

    public class MonsterDataEditorWindow : OdinMenuEditorWindow
    {
        [MenuItem("Tools/MonsterDataEditor")]
        private static void Open()
        {
            var window = GetWindow<MonsterDataEditorWindow>();
            window.position = GUIHelper.GetEditorWindowRect().AlignCenter(950, 500);
        }

        protected override OdinMenuTree BuildMenuTree()
        {
            var tree = new OdinMenuTree(true);
            tree.DefaultMenuStyle.IconSize = 28.00f;
            tree.Config.DrawSearchToolbar = true;

            tree.AddAllAssetsAtPath("Monsters", "Assets/Prefabs/Monsters/MonsterSO", typeof(MonsterSO), true, true);

            tree.EnumerateTree().AddIcons<MonsterSO>(x => x.mosterSprite);
            return tree;
        }
    }

#endif

솔직히 저도 해당 코드의 내용들을 전부 이해하며 사용중이 아닙니다. 걱정은 NoNo! 자주 사용하다보면 언젠가 다 이해할 날이 오겠죠? ㅋㅋ

tree.AddAllAssetsAtPath("원하는 메뉴이름" , "데이터 경로", typeof(데이터 타입), true, true);
을 넣어주시면, 해당 코드를 통해서 저희는 OdinMenuTree라는 클래스를 생성하고. 해당 클래스에 우리가 넣고싶은 MonsterSO 타입의 데이터들을 지정한 경로에서 탐색하여 Tree에 담아주는 것이 가능합니다!

  1. MonsterSO의 화면 꾸며주기!

우선 예시를 보여주고 설명드리는게 좋을것 같아서, 제가 현재 진행중인 프로젝트에 적용시켜서 가져와 보았습니다.

해당 화면과 같이 내가 만들어준 MonsterSO데이터들을 관리할수 있는 에디터 창을 만들수 있습니다.

MonsterSO의 변경 전 코드

using UnityEngine;
using static Enums;

[CreateAssetMenu(fileName ="New Monster", menuName = "Characters/Monsters/Monster",order = 1)]
public class MonsterSO : ScriptableObject
{
    [Header("FosMon Info")]
    public int monsterID;
    public string monsterName;
    public string description;
    public string monsterKoreanName;
    public string koreanDescription;
    public MonsterPlanet planet;
    public MonsterRarity rarity;

    [Header("Resources")]
    public AudioClip monsterSound;
    public Sprite mosterSprite;

    [Header("FosMon Prefers Setting")]
    [Range (1,5)] public int preferTemperature;
    [Range (1,5)] public int preferCleanliness;
    [Range (1,5)] public int preferBrightness;
    public FillersFoodType preferFoodType;
    public FillersFoodType hateFoodType;
    public HarvestType harvestType;
    public PreferPlayerAction preferPlayerAction;

    [Header("Reward")]
    public int DropGold;
    public int ResearchGold;

    [Header("FosMon Behave Patterns")]
    [Header("Friendly <-> Hostile")]
    [Range(0, 2)] public int Hosile;
    [Header("Indifferent <-> Curious")]
    [Range(0, 2)] public int Curious;
    [Header("Indifferent <-> FoodLoving")]
    [Range(0, 2)] public int FoodLoving;
    [Header("Indifferent <-> Fearful")]
    [Range(0, 2)] public int Fearful;


    [Header("FosMon Status Setting")]
    [Range(1, 10)] public float moveSpeed;
    public float moveAcceleration;
    public float moveForce;
    public bool isFlyable;
    [Range(0, 3)] public float jumpPower;
    public int attack;
    public float attackRange;
    public float attackInterval;
    public int maxHP;
    public int maxHunger;
    [Range(5, 30)]
    public int dailyHungerIncrease;

    [Header("FosMon Sensor Setting")]
    [Range(0, 5)] public int HearingRange;
    public bool HearingSensitive;
    [Range(0, 5)] public int SmellRange;
    public bool SmellSensitive;
    [Range(0, 5)] public int SightRange;
    public bool sightSensitive;

}

변경 후 코드

using UnityEngine;
using Sirenix.OdinInspector;
using static Enums;


[CreateAssetMenu(fileName ="New Monster", menuName = "Characters/Monsters/Monster",order = 1)]
public class MonsterSO : ScriptableObject
{
    [BoxGroup("Monster Info")]
    [HorizontalGroup ("Monster Info/Basic Info", 90)]
    [PreviewField(90), HideLabel]
    public Sprite mosterSprite;

    [VerticalGroup("Monster Info/Basic Info/Stats")]
    [LabelWidth(100)]
    public int monsterID;
    [TabGroup("Monster Info/Basic Info/Stats/Language", "English")]
    [LabelWidth(100)]
    public string monsterName;
    [TabGroup("Monster Info/Basic Info/Stats/Language", "English")]
    [LabelWidth(100)]
    public string description;
    [TabGroup("Monster Info/Basic Info/Stats/Language", "Korean")]
    [LabelWidth(150)]
    public string monsterKoreanName;
    [TabGroup("Monster Info/Basic Info/Stats/Language", "Korean")]
    [LabelWidth(150)]
    public string koreanDescription;

    [BoxGroup("Detail Info")]
    [HorizontalGroup("Detail Info/Columns", 300)]
    [VerticalGroup("Detail Info/Columns/Stats")]
    [GUIColor(0.5f,1f,0.5f)]
    public int maxHP;
    [VerticalGroup("Detail Info/Columns/Stats")]
    [GUIColor(1f, 0.5f, 0.5f)]
    public int attack;
    [VerticalGroup("Detail Info/Columns/Stats")]
    [GUIColor(1f, 0.5f, 0.5f)]
    public float attackRange;
    [VerticalGroup("Detail Info/Columns/Stats")]
    [GUIColor(1f, 0.5f, 0.5f)]
    public float attackInterval;
    [VerticalGroup("Detail Info/Columns/Stats")]
    [GUIColor(0.5f, 1f, 1f)]
    [Range(1, 10)] public float moveSpeed;
    [VerticalGroup("Detail Info/Columns/Stats")]
    [GUIColor(0.5f, 1f, 1f)]
    [LabelWidth(150)]
    public float moveAcceleration;
    [VerticalGroup("Detail Info/Columns/Stats")]
    [GUIColor(0.5f, 1f, 1f)]
    public float moveForce;
    [VerticalGroup("Detail Info/Columns/Stats")]
    [GUIColor(0.5f, 1f, 1f)]
    public bool isFlyable;
    [VerticalGroup("Detail Info/Columns/Stats")]
    [GUIColor(0.5f, 1f, 1f)]
    [Range(0, 3)] public float jumpPower;
    [VerticalGroup("Detail Info/Columns/Stats")]
    [GUIColor(0.5f, .5f, .5f)]
    public int maxHunger;
    [VerticalGroup("Detail Info/Columns/Stats")]
    [GUIColor(0.5f, .5f, .5f)]
    [Range(5, 30)]
    [LabelWidth(150)]
    public int dailyHungerIncrease;

    [HorizontalGroup("Detail Info/Columns", 450)]
    [VerticalGroup("Detail Info/Columns/Others"), EnumToggleButtons()]
    public MonsterPlanet planet;
    [VerticalGroup("Detail Info/Columns/Others"), EnumToggleButtons()]
    public MonsterRarity rarity;
    [VerticalGroup("Detail Info/Columns/Others")]
    public int DropGold;
    [VerticalGroup("Detail Info/Columns/Others")]
    public int ResearchGold;
    [VerticalGroup("Detail Info/Columns/Others")]
    [InlineEditor(InlineEditorModes.SmallPreview)]
    public AudioClip monsterSound;


    [FoldoutGroup("Preferences")]
    [Range (1,5)] public int preferTemperature;
    [FoldoutGroup("Preferences")]
    [Range (1,5)] public int preferCleanliness;
    [FoldoutGroup("Preferences")]
    [Range (1,5)] public int preferBrightness;
    [FoldoutGroup("Preferences"), EnumToggleButtons()]
    public FillersFoodType preferFoodType;
    [FoldoutGroup("Preferences"), EnumToggleButtons()]
    public FillersFoodType hateFoodType;
    [FoldoutGroup("Preferences"), EnumToggleButtons()]
    public HarvestType harvestType;
    [FoldoutGroup("Preferences"), EnumToggleButtons()]
    public PreferPlayerAction preferPlayerAction;

    [FoldoutGroup("Behave Patterns")]
    [Range(0, 2)] public int Hosile;
    [FoldoutGroup("Behave Patterns")]
    [Range(0, 2)] public int Curious;
    [FoldoutGroup("Behave Patterns")]
    [Range(0, 2)] public int FoodLoving;
    [FoldoutGroup("Behave Patterns")]
    [Range(0, 2)] public int Fearful;



    [FoldoutGroup("Sensor Setting")]
    [Range(0, 5)] public int HearingRange;
    [FoldoutGroup("Sensor Setting")]
    [Range(0, 5)] public int SmellRange;
    [FoldoutGroup("Sensor Setting")]
    [Range(0, 5)] public int SightRange;

}

코드가 복잡해 보이지만 결국에 제가 추가해준것 폴드아웃그룹, 버티컬그룹, 호리즌탈그룹, 박스그룹 등을 추가해준 것 뿐이라 어렵지 않게 이해하실 수 있답니다. 추가로 Clip 클래스에 [InlineEditor(InlineEditorModes.SmallPreview)] 를 추가해 주시면 해당 클립을 재생해 볼 수 있는 기능이 인스펙터에 추가된답니다! ( 원래 다른 기능들이 너무 많은데.. 아직 실제로 제가 구현을 할 수 있는 게 이정도네요 ㅠㅠ )
[EnumToggleButtons()] 어트리뷰트를 사용하시면 enum값을 토글버튼으로 지정하는 방식으로 인스펙터창에 표시할 수 있답니다.

4. 마무리

Odin Inspector를 통해서 게임 내의 모든 에셋들을 정리해서 관리하는 에디터 창을 미리 구현해두고 이 안에서 데이터들을 관리해준다면 게임 내의 데이터들을 한번에 관리할 수 있게 되어서 정말 좋을 것 같다는 생각이 드네요.
유료 에셋이라 초보자 분들에게 강하게 추천은 해드리기 어렵지만, Unity를 통해서 게임을 진지하게 만들어 보실 분들이라면 충분히 매력적인 에셋이라고 생각됩니다! 해당 에셋의 기능들에 대해서 더 궁금하신 점이 있다면 댓글 달아주시면 찾아서 답변드리도록 하겠습니다!
오늘도 제 티스토리에 방문해주시고, 못난 글을 읽어주신 모든분들께 감사인사 드립니다! ㅎ

( 새롭게 진행하는 프로젝트에는 처음부터 적용시켜서 데이터 관리를 해보려고 합니다. 나중에 프로젝트에 잘 활용한 자료가 만들어지면
추가로 포스팅하는 기회가 생겼으면 좋겠네요! )

반응형