2019년 1월 29일 화요일

[C#] UnityEditor를 활용 하여 클래스 표시하기









처음에 어떤 이벤트가 들어있는 클래스들을 넣으면 유니티에 인스펙터 뷰에 있는 프로퍼티
값들이 변경 되었으면 좋는데~ 로 시작해서 제작 하게 되었다.

출처:
https://www.linkedin.com/pulse/unity-hack-dynamic-property-field-inspector-zhen-stephen-gou?trk=portfolio_article-card_title

위의 링크를 참고하여 제작하였습니다.

총 3개의 모듈이 필요하다.

1. 인스펙터에 출력할 클래스
- 기능에 따른 클래스들을 나눔

2. 인스펙터에 정말로 그려줄 에디터
- 실질적인 출력 부분을 담당 할 클래스

3. 실질적으로 오브젝트에 달라붙을 클래스.
- MonoBehaviour 이 녀석을 상속 받아야 오브젝트에 추가 가능

거두절미하고 코드부터 확인하자.


using System;
using UnityEngine;

[Serializable]
public abstract class ObjectEvent
{
    public GameObject m_EventObject;
    public float m_fTime;

    public abstract bool Execute();
}

[Serializable]
public class CreateObj : ObjectEvent
{
    public Transform m_Position;
    public override bool Execute()
    {
        Debug.Log("Create Object!!");
        return true;
    }
}

[Serializable]
public class CreateVecPosObj : ObjectEvent
{
    public Vector3 m_Position;
    public override bool Execute()
    {
        Debug.Log("Create Object!!");
        return true;
    }

}

[Serializable]
public class DestroyObj : ObjectEvent
{
    public override bool Execute()
    {
        Debug.Log("Destroy Object!!");
        return true;
    }
}

[Serializable]
public class AnimPlayObj : ObjectEvent
{
    public override bool Execute()
    {
        Debug.Log("Play Animation!!");
        return true;
    }
}

[Serializable]
public class AudioPlayObj : ObjectEvent
{
    public float m_fValue;
    public override bool Execute()
    {
        Debug.Log("Audio Play!!");
        return true;
    }
}
1. 각 이벤트들에 필요한 변수값들을 담은 클래스들을 정의해 놓는다.
- 위의 코드는 이벤트에서 중복되는 변수를 둔 부모 클래스를 정의하고
- 아래 각 세부 기능들을 담당하는 자식클래스를 두면서 작업한다.
- 위의 장점은 기능들을 세분화 하기에 용의하고, 코드 재활용성이 올라감에 따른 사람눈이 편-안 하다.



using System;

[Serializable]
public class EventFactory
{
    public enum EventType
    {
        CreateEvent,
        CreateVectorObj,
        DestroyEvent,
        AnimPlayEvent,
        AudioPlayEvent,
    }

    public EventType Type = EventType.CreateEvent;
    public CreateObj CreateObj = new CreateObj();
    public CreateVecPosObj CreateVecPosObj = new CreateVecPosObj();
    public DestroyObj DestroyObj = new DestroyObj();
    public AnimPlayObj AnimPlayObj = new AnimPlayObj();
    public AudioPlayObj AudioPlayObj = new AudioPlayObj();


    public ObjectEvent GetEvent()
    {
        return GetEventFromType(Type);
    }

    public System.Type GetClassType(EventType evnetType)
    {
        return GetEventFromType(evnetType).GetType();
    }

    private ObjectEvent GetEventFromType(EventType type)
    {
        switch (type)
        {
            case EventType.CreateEvent:
                return CreateObj;
            case EventType.CreateVectorObj:
                return CreateVecPosObj;
            case EventType.DestroyEvent:
                return DestroyObj;
            case EventType.AnimPlayEvent:
                return AnimPlayObj;
            case EventType.AudioPlayEvent:
                return AudioPlayObj;
            default:
                return CreateObj;
        }
    }

}
1-1. 이벤트들을 조합하는 팩토리 클래스를 하나 둔다.
- 위의 각 자식객체들을 동적 생성 해놓은 후에 선택 되는 값에 따른 리턴 벨류를 두도록 한다.
- 유저는 enum값으로 값을 선택 할 것이기 때문에 간편하고 빠른 스위치 케이스문으로 대체 한다.
- 여기서 클래스 들의 이름은 변수의 명과 같아야 하는데 Editor class 에서 그 이유를 설명 할 것이다.


using UnityEditor;

[CustomEditor(typeof(ActiveEvent), true), CanEditMultipleObjects]
public class ActiveEventEditor : Editor
{

    protected static string EVENT_FACTORY_NAME = "PrimaryEvent";
    protected ActiveEvent ActiveEventobject;
    protected SerializedObject serializedEvent;
    protected SerializedProperty SerializedEventProp;

    void OnEnable()
    {
        ActiveEventobject = (ActiveEvent)target;
        serializedEvent = new SerializedObject(ActiveEventobject);
        SerializedEventProp = serializedEvent.FindProperty(EVENT_FACTORY_NAME);
    }

    public override void OnInspectorGUI()
    {
        serializedEvent.Update();

        DrawPropertiesExcluding(serializedEvent, new string[] { EVENT_FACTORY_NAME });
        DrawPrimaryEvent();
        serializedEvent.ApplyModifiedProperties();
    }

    protected void DrawPrimaryEvent()
    {
        EditorGUILayout.LabelField(EVENT_FACTORY_NAME, EditorStyles.boldLabel);
        EditorGUILayout.PropertyField(SerializedEventProp.FindPropertyRelative("Type"));

        //display relevant ability properties based on ability type
        EventFactory EventFactory = ActiveEventobject.PrimaryEvent;
        System.Type typeOfAbility = EventFactory.GetClassType(EventFactory.Type);

        SerializedProperty specificEvent = (SerializedEventProp.FindPropertyRelative(typeOfAbility.ToString())).Copy();

        string parentPath = specificEvent.propertyPath;
        while (specificEvent.NextVisible(true) && specificEvent.propertyPath.StartsWith(parentPath))
        {
            EditorGUILayout.PropertyField(specificEvent);
        }

    }
}
2. 당연한 소리지만 Editor를 상속받아야 유니티 안에 에디터 기능들을 사용 할 수 있다.
- WindowEditor, Editor 다 비슷한 기능들을 지원하니까 레퍼런스 검색을 통해 입맛에 맞는 기능들을 사용하면 될것 같다.
- OnEnable() 이 함수안에서 초기화를 시작한다.
- OnInspectorGUI() 이 함수에서 그려준다.
- DrawPrimaryEvent() 그려줄 기능들을 분할한 함수이다.

- 왜 1-1 글에서 클래스 이름과 동일한 변수명을 써주어야 하는 이유는 바로 여기에 있다. SerializedProperty specificEvent 변수에 typeOfAblility type변수를 가져와서 ToString()해주기 때문이다. 기본적으로 FindPropertyRelative 인자값으로 클래스 이름을 string값으로 넣어 주어야 하기 때문이다.

https://docs.unity3d.com/Manual/script-Serialization.html
- Serialization 에 대한 설명 글

https://docs.unity3d.com/ScriptReference/SerializedObject.html
- SerializationObject에 대한 설명 글

https://docs.unity3d.com/ScriptReference/EditorGUILayout.html
- EditorGUILayout 클래스 기능 설명 글



* 그래픽디자이너와 같이 작업 하기엔 프로그래머가 편하려면 에디터를 사용해서 기능들을 추가 해주는것이 더 효육적인것 같다.
더 이상 public 을 남발하는 그런 상황은 다메요..
물론, serializable serializefield를 사용 해도 괜찮지만, 상황에 따라서 맞춰 써야할 것 같다.

댓글 없음:

댓글 쓰기

아이디어 및 질문 외에 댓글은 사양합니다.