LambFerret's Blog
건파밍 #3 게임 시스템 (3) : Excel to ScriptableObject 본문
코딩을 할때 몇가지 강박적인 파트가 있는데 그중 하나가 자동화이다. 개발 초기부터, 기획자한테 데이터를 받아서 이를 게임에 적용시키는 단계가 자동으로 이루어지도록 많은 노력과 기획자와의 합의가 (그리고 나자신과도 협상이..) 있었다. 결국 최종적으로 무기데이터에 밖에 쓰이지 않게 되었지만, 그래도 많이 편해졌으므로 한번 소개하고자한다.
우선은 에셋을 써야한다!
https://github.com/greatclock/excel_to_scriptableobject.git
기획자의 데이터를 엑셀로 받아, 이를 ScriptableObject로 관리하기 위해 위의 에셋을 사용하였다. 사용해보니 아주 좋은 에셋이라고 생각하기에 설치부터 소상하게 적어보도록 하겠다.
Window - Package Manager 의 왼쪽위 +를 눌러 git URL로 에셋을 다운로드받는다.
(주소:https://github.com/greatclock/excel_to_scriptableobject.git)
먼저 GreatClock - Excel To ScriptableObjects - Open Window 를 열어서 Excel을 input시키면, 그 엑셀에 대한 SciptableObject객체와 그 스크립트의 경로를 지정해줄 수 있다. 이후 Process Excel을 누르면 자동으로 그 엑셀 포맷에 대한 Script와 내용물에 대한 ScriptableObjects[] 가 생성되어있음을 알 수 있다.
엑셀은 어떤식으로 작성해주면 좋은가 하면, 이 창의 맨 위처럼 Field명의 Row를 지정, Type명의 Row를 지정, Data Row를 지정해주면 되지만 기본으로 둔 후,
이렇게 해둔다.
필드명은 평범히 사용할 필드의 이름을 적으면 되지만, 이 에셋의 강점은 저 타입에 있다. 데이터를 [3, 4, 5] 이렇게 두는 식으로 자동으로 열거형임을 파악하며, Vector3, Color 등도 지정할 수 있다. -> https://github.com/greatclock/excel_to_scriptableobject?tab=readme-ov-file#supported-base-data-types
Output을 보면 충분히 WeaponData[]가 잘 들어가 있는 모습을 볼 수 있지만..
처음부터 끝까지 자동화를 해야한다는 충동을.. 멈출 수 없는 충동이 있기에 데이터를 받고 그 데이터들이 각자의 무기에 적용이 완료되는 것까지 해야 그게 자동화 아닐까? 개발 초기부터 할게 많았을텐데, 그냥 자동화에 미쳐버린 사람이었나보다.
엑셀 데이터는 내가 따로 지정하지 않아도 무기의 데이터로 들어갔으면 한다. 더 욕심스럽게도 개발 편의를 위해 Unity Editor 단계에서 부터 자동 할당이 되었으면 했다. 이부분은 OnValidate() 함수를 사용해서 해결했다. 다음 코드는 내 WeaponBehavior다. 이 클래스에서 무기의 모든 기능을 담당하고, 각 무기의 데이터는 WeaponData로 관리한다.
public class WeaponBehavior : ItemBehavior
{
[Header("WEAPON ID")]
public int id;
[Header("Data")]
[ReadOnly] public Weapon data; //현재 데이터
[Space(10)]
[Header("Gun Info")]
[ReadOnly] public int maxMagazine;
[ReadOnly] public int magazineLeft; //현재 탄창 속 탄약
public SpriteRenderer sr { get; private set; }
#region Editor
#if UNITY_EDITOR
private void OnValidate() => EditorApplication.delayCall += _OnValidate;
private void _OnValidate()
{
// if (data.id == id) return;
if (Application.isPlaying)
{
return; // 플레이 중에는 OnValidate 실행되지 않도록 함
}
SpriteRenderer component;
try
{
component = GetComponentInChildren<SpriteRenderer>();
}
catch (MissingReferenceException)
{
return;
}
WeaponData weaponData = Resources.Load<WeaponData>("ScriptableObjects/WeaponData");
string json = JsonUtility.ToJson(weaponData);
// 새 인스턴스를 생성하고 JSON에서 역직렬화
WeaponData copy = ScriptableObject.CreateInstance<WeaponData>();
JsonUtility.FromJsonOverwrite(json, copy);
data = copy.GetWeapon(id);
if (data == null) return;
name = data.gunName;
try
{
var atlas = Resources.Load<SpriteAtlas>("Sprites/WeaponAtlas");
var prefix = data.type == WeaponData.Type.Melee ? "Mel_" : "Gun_";
// null 체크 잘하는법. 외와두자
var sprite = atlas.GetSprite(prefix + data.gunName + "_0")
?? atlas.GetSprite(prefix + data.gunName + "__1_0")
?? atlas.GetSprite(data.gunName + "_0")
?? atlas.GetSprite(prefix + data.gunName)
?? atlas.GetSprite(data.gunName);
component.sprite = sprite;
}
catch (NullReferenceException)
{
throw new FileNotFoundException($"Sprite not found! Weapon ID : {name}");
}
EditorApplication.delayCall -= _OnValidate;
}
#endif
#endregion
}
이 코드의 효능으로는, 개발자가 Unity Editor Inspector의 id를 변경해주면, 바로 그 id의 data를 로드해온다는 점에 있다.
Inspector의 맨위 id 에 로드하고자 하는 무기의 id로 바꾸는 것만으로도 WeaponBehavior에 그 data를 간단하게 로드할 수 있다. 현재 2009의 소고기가 있지만 이를
바로 다른 Data로 변경된다.
OnValidate()는 Inspector의 어떤값이라도 변경된다면 호출되기에 분명한 퍼포먼스 저하를 가지고오지만, 이정도 편의성을 위해서라면 조금 감수하기로 했다.
주의 사항으로는
코드의 중간을 보면
WeaponData weaponData = Resources.Load<WeaponData>("ScriptableObjects/WeaponData");
string json = JsonUtility.ToJson(weaponData);
// 새 인스턴스를 생성하고 JSON에서 역직렬화
WeaponData copy = ScriptableObject.CreateInstance<WeaponData>();
JsonUtility.FromJsonOverwrite(json, copy);
data = copy.GetWeapon(id);
json을 불러온 후 새 인스턴스에서 작업을 실행하는 Deep copy를 하였는데, 당연히 ScriptableObject는 파일에 기록되는 By reference 하므로 값에 대한 조작을 하면, 이 ScriptableObject에도 반영되는 이슈가 있었기에 이렇게 깊은 복사 과정을 거친다.
"ㄹㅇ 진짜로 찐찐찐 분명한 퍼포먼스 저하" 다. 하지만 이게 편하잖아.
곧 시스템 파트 리뷰를 끝낼 수 있을지도 모른다. 제일 재밌는거였는데 아쉽다.
'게임 개발 > #2 건파밍 (2D 탑뷰슈터)' 카테고리의 다른 글
건파밍 #5 게임 시스템 (5) : Dialogue System (1) | 2025.02.10 |
---|---|
건파밍 #4 게임 시스템 (4) : Localization (0) | 2025.01.27 |
건파밍 #2 게임 시스템 (2) : Data Persistence (0) | 2025.01.18 |
건파밍 #1 게임 시스템 (1) : Singleton, GameManager (0) | 2025.01.18 |
건파밍 #0 개요 (0) | 2025.01.18 |