현재 프로젝트 B에선 프로토타입을 위한 전투씬 부분의 개발을 진행하고 있는 상태이다.
전투는 다대다로 이루어지며 전투 인원이 정해져 있는 상태가 아니다.
전투를 진행하는 오브젝트들은 필수적으로 초기화를 위한 초기 정보가 필요하며,
해당 정보는 각 오브젝트에 적용하려 했으나 초기화 순서를 정형화하기 위해서이기도 하고
다른 이유들로 인해 한 곳에 모아둔 상태이다.
만약 각 오브젝트에 적용이 되어있다면 오브젝트를 생성한 이후 바로 값을 대입하고
필요없다면 오브젝트 자체를 삭제하면 되지만,
현재는 직렬화되어 있는 정보 리스트에 요소를 추가하거나 삭제를 한 이후
별도로 오브젝트들을 추가해주어야 하는 번거로움이 발생한 상태이다...
그래서 이번 기회에 커스텀 에디터를 통해 정보 리스트를 추가 삭제함에 따라 자동으로 오브젝트가 생성되고 삭제되는
에디터 코드를 작성해보고자 한다.
에디터 관련 코딩은 접해본 적이 없다보니 먼저 웹 검색을 통해 몇 가지를 알아보았다.
우선 Inspector에서 직렬화된 정보 리스트의 편집 여부를 어떻게 판단할 것 인가에 대해서 찾아보았고
SerializedProperty라는 클래스를 통해 직렬화된 정보를 받아올 수 있었다는 것을 알게 되었다.
[CustomEditor(typeof(BattleGameInitializer))]
public class CharacterCreater : Editor
{
private SerializedProperty pInfoList;
private SerializedProperty eInfoList;
private void OnEnable()
{
pInfoList = serializedObject.FindProperty("playerInfos");
eInfoList = serializedObject.FindProperty("enemyInfos");
}
}
[CustomEditor(typeof(...)]의 선언은 특정 스크립트 타입의 Inspector를 커스터마이징 하기 위한 속성이다.
그다음 직렬화된 속성을 저장할 필드 변수를 선언해 주었다.
나는 Player와 Enemy의 정보 리스트를 따로 관리해주고 있기 때문에 두 개의 필드 변수를 선언해 주었다.
이후 OnEnable 메서드에서 스크립트에 정의된 필드 변수 이름으로 직렬화된 속성을 찾아서 저장해 주면 된다.
여기서 OnEnable은 MonoBehaviour에서 정의할 수 있는 OnEnable과 같은 것이다.
해당 메서드는 Inspector뷰에서 보일 때 호출된다.
SerializedProperty에는 다양한 프로퍼티를 통해 정보를 받아올 수 있다.
많은 프로퍼티중 나는 배열 크기만을 이용해 기존 리스트의 크기가 변경되었다는 조건식을 만들고,
리스트의 데이터가 추가되거나 삭제되었을 때 오브젝트의 인스턴스를 생성하고 삭제하는 메서드를 작성해 보자.
[CustomEditor(typeof(BattleGameInitializer))]
public class CharacterCreater : Editor
{
private SerializedProperty pInfoList;
private SerializedProperty eInfoList;
private int prevPSize;
private int prevESize;
private GameObject playerCharPref;
private GameObject enemyCharPref;
private void OnEnable()
{
playerCharPref = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Prefabs/Character/PC.prefab");
enemyCharPref = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Prefabs/Character/Enemy.prefab");
pInfoList = serializedObject.FindProperty("playerInfos");
eInfoList = serializedObject.FindProperty("enemyInfos");
prevPSize = pInfoList.arraySize;
prevESize = eInfoList.arraySize;
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (pInfoList.arraySize != prevPSize)
{
ModifyBattleCharInstance(pInfoList.arraySize > prevPSize);
prevPSize = pInfoList.arraySize;
}
if (eInfoList.arraySize != prevESize)
{
ModifyBattleCharInstance(eInfoList.arraySize > prevESize, false);
prevESize = eInfoList.arraySize;
}
}
private void ModifyBattleCharInstance(bool set, bool isPlayer = true)
{
var _basedName = isPlayer ? "PC" : "Enemy";
var _charParent = GameObject.Find($"{_basedName}List").transform;
// 인스턴스 추가의 경우
if (set)
{
Instantiate(isPlayer ? playerCharPref : enemyCharPref, _charParent).name = $"{_basedName}{_charParent.childCount}";
return;
}
// 삭제의 경우
Destroy(_charParent.GetChild(_charParent.childCount - 1).gameObject);
}
}
우선 이전 배열의 크기를 저장할 수 있는 필드 변수들과
오브젝트 생성을 위해 필요한 프리팹을 저장할 필드 변수들을 선언해주었다.
해당 필드 변수들은 이전과 마찬가지로 OnEnable 메서드에서 대입해 주었다.
이전 배열 크기값은 현재 유지하고 있는 SerializedProperty의 현재 배열 크기를 대입해 주었고,
프리팹은 프리팹이 저장되어 있는 경로를 통해 반환받은 값을 대입해 주었다.
그리고 조건식에 만족했을 때 실행할 메서드를 정의해 주었다.
bool형 매개 변수 "set"은 데이터 추가인지 삭제인지를 나타내주고, "isPlayer"는 Player의 여부를 나타내준다.
인스턴스를 생성할 경우 특정 부모 Transform 하위에 생성되도록 지정해 주었고,
인스턴스를 삭제할 경우 부모 Transform에서 젤 하위에 있는 인스턴스를 삭제해 주도록 코드를 작성했다.
삭제를 할 땐 "Destroy"가 아닌 "DestroyImmediate"를 호출해주어야 한다.
Destroy의 경우 바로 삭제가 아닌 삭제 예약 즉, 한 프레임 이후 삭제를 시키는 메서드이고
DestroyImmediate는 바로 삭제를 해주는 메서드이다.
Editor 편집에서는 삭제를 예약해 주는 것이 아닌 바로 삭제를 해주어야 하기 때문에 DestroyImmediate를 호출해 주는 것이다.
조건식은 OnInspectorGUI 메서드에서 작성해 준다.
OnInspectorGUI는 Unity Editor에서 Insepctor 창이 새로고침 될 때 호출이 되고, 주로 GUI를 그려줄 때 정의하는 메서드이다.
조건식 역시 이전 코드들과 마찬가지로 간단하다.
이전 배열 사이즈와 현재 배열 사이즈를 비교하여 다르다면 정의해 준 메서드를 호출해 주면 의도한 대로 인스턴스가 생성되고 삭제될 것이다.
이렇게 잘 생성되고 삭제되는 것을 확인할 수 있다!
하지만 현재 오브젝트가 프리팹 오브젝트가 아닌 개별 오브젝트로 생성되기 때문에
현재 생성된 오브젝트들은 프리팹 수정에 영향을 받지 않는다.
그렇기 때문에 프리팹 오브젝트로 생성하도록 코드를 수정해 주면 된다!
//Instantiate(isPlayer ? playerCharPref : enemyCharPref, _charParent).name = $"{_basedName}{_charParent.childCount}";
PrefabUtility.InstantiatePrefab(isPlayer ? playerCharPref : enemyCharPref, _charParent).name = $"{_basedName}{_charParent.childCount}";
수정 후 테스트 해보면 위와 같이 프리팹 오브젝트로 잘 생성되고 삭제되는 것을 알 수 있다.
이 글에서 나오는 방식보다 더 좋은 방식이 있다면 댓글로 추천 부탁드립니다 :)
'Unity' 카테고리의 다른 글
유니티 프로젝트 개발 일지 - 8 (Shader를 이용한 물에 잠긴 오브젝트 표현) (1) | 2024.12.25 |
---|---|
유니티 프로젝트 개발 일지 - 7 (Animator 블랜드 트리) (0) | 2024.12.24 |
유니티 프로젝트 개발 일지 - 5 (무기 구현) (0) | 2024.12.22 |
유니티 프로젝트 개발 일지 - 4 (유니티 Line 그리기) (0) | 2024.12.21 |
유니티 프로젝트 개발 일지 - 3 (렌더링) (0) | 2024.12.20 |