[유니티] Reflection을 사용해서 List에 값 추가하기
1. 상황
Excel 데이터를 S.O로 변환하기 위해서 ScriptableObjectGenerator 클래스를 만들었다. 해당 클래스는 S.O의 타입을 가지고 있지 않고 Path를 통해서 Script를 읽어온다. 그렇기 때문에 S.O의 타입을 String 문자열 말고는 가질 수 없었고 Generic으로 메서드를 만들기 어려운 상황이었다.
ScriptableObjectGenerator 클래스는 CreateScriptableObject 메서드를 통해서 S.O를 생성하게 되는데, 내가 보유한 정보는 다음과 같았다.
1. S.O 클래스의 위치(string)
2. 데이터가 들어가야 하는 변수의 이름들(List<string>)
3. 데이터로 만들어야 하는 값들(List<string>)
이전에는 List 형태로 데이터를 관리하는 것이 아니라, 데이터 한 묶음 당 하나의 S.O 파일을 통해 만들었기 때문에 FieldInfo의 SetValue 메서드를 통해서 추가할 수 있었지만, List 타입으로 변경함으로써 생성자를 통해서 데이터를 받아서 저장할 수 있도록 수정하기로 결정했다.
시도한 방법들을 보자.
2. 첫 번째 시도 - List<T>의 Add 메서드를 캐스팅하여 값 추가
var addMethod = dataList.GetType().GetMethod("Add");
위 코드와 같이 List의 메서드를 캐스팅할 수 있다. dataList는 List<ExampleClass> 형태로 직접적으로 인스턴스를 보유하고 있다. 만약, 직접적으로 Instance에 접근하여 메서드를 캐스팅한다면 문제없이 돌아가는 코드가 되겠지만, 아쉽게도 내가 dataList를 불러오는 방법은 직접 Instance에 접근하는 것이 아니다.
var obj = ScriptableObject.CreateInstance(fileType);
var listField = obj.GetType().GetField(Variable);
지금같이 ScriptableObject를 생성하고 obj에 접근해서 GetField 메서드를 통해서 가져온다. 이럴 경우, listField의 타입은 FieldInfo 타입이 된다. 이전 코드의 dataList는 List<ExampleClass>로 서로 타입이 다르다.
FieldInfo 타입은 Field의 정보를 담고 있는 타입으로, 필드의 메타데이터에 접근하는 타입이다. 그렇기 때문에 직접적으로 Add 메서드를 가지고 있지 않다.
listField를 통해서 Add 메서드를 캐스팅하면 Null이 나타남으로 해당 방법을 사용할 수 없었다.
3. 두 번째 시도 - List의 인터페이스 IList를 통해서 값 추가
object dataList = obj.GetType().GetField(Variable).GetValue("dataList");
if(dataList is IList ilist)
{
list.add(object type);
}
dataList 명칭을 가지고 있는 object를 가져와서 내부에 IList 값을 포함하고 있는지 검사하고 포함하고 있을 경우, IList 타입의 ilist 변수를 만들어낸다.
이러한 경우, Add 메서드 캐스팅이 성공한다. if문 구현에서 add 메서드를 통해 값을 추가할 수 있다. 해결한 방법도 이와 비슷한 방법으로 해결했다. 글을 작성하면서 생각해 보건대, 가독성을 살리고 싶어서 세 번째 시도와 같은 방법으로 해결했는데, 두번째 시도와 같이 바꾸는 것이 올바를 것 같다.
세번째 삽질을 보자..
4. 세번째 시도 - S.O에서 데이터 타입의 Instance를 만들어서 캐스팅한 Add 메서드를 통해 추가 (해결)
리플렉션은 실행 시점에서 타입이 정해지기 때문에 적지 않은 비용이 발생한다. 조심스럽게 접근해야겠지만, 다행히 지금 클래스는 게임 실행 시점에서 결정되는 것이 아니라, 시작 전에 에디터 모드에서 처리가 되므로, 조금 과감하게 사용하기로 했다.
public void AddData(object obj)
{
var result = (ExampleClass)obj;
dataList.Add(result);
}
위와 같이 S.O 클래스에 AddData 메서드를 추가해 주었다.
var obj = ScriptableObject.CreateInstance(fileType);
var listField = obj.GetType().GetField(Variable);
var addMethod = obj.GetType().GetMethod("AddData");
var fieldType = listField.FieldType.GetGenericArguments()[0];
var typeInstance = Activator.CreateInstance(fieldType);
obj 변수에는 S.O의 인스턴스를 만들어서 가지며, listField 변수에는 dataList 명칭을 가진 변수를 불러온다. addMethod는 S.O 인스턴스에 존재하는 AddData 메서드를 캐스팅한다.
dataList는 List<ExampleClass>다. listField를 통해서 Generic의 첫 타입을 가져와 fieldType 변수에 캐스팅했다.
Activator 클래스의 CreateInstance 메서드를 통해 해당 타입의 인스턴스를 생성했다.
typeInstance.GetType().GetField(names[i]).SetValue(typeInstance, value);
}
addMethod?.Invoke(obj, new object[] { typeInstance });
앞서서 해당 메서드가 가진 정보는 S.O 클래스에 존재하는 변수의 이름들을 알고 있다고 말했다. GetField 메서드를 통해 변수를 불러오고 typeInstance에 값을 추가하고 모든 for문이 종료되면 addMethod를 통해서 인자로 값을 넘겨준다.
그러면 S.O 클래스의 dataList 변수에 값을 추가할 수 있다.
'엔진, 프레임워크 > Unity' 카테고리의 다른 글
[Unity] Custom Editor Window 데이터 Import 시, Save/Load (0) | 2024.04.28 |
---|---|
[WARNING] Unable to find style 'ToolbarSeachTextField' in skin 'DarkSkin' Used (0) | 2024.04.12 |
[인프런] 따라하면서 배우는 고박사의 유니티 하이퍼캐주얼게임 시리즈 01 후기 (1) | 2024.03.22 |
UI에서 Particle System 추가하는 방법 - Raw Image & Render Texture (1) | 2023.11.12 |
Unity - Bezier Curve에 대해서 학습해보자. (0) | 2023.08.31 |