핵심 정리
[CreateAssetMenu(menuName = "Data/Weapon Data")]
public class WeaponData : ScriptableObject
{
public string weaponName;
public int damage;
}구조 이해
ScriptableObject의 역할
ScriptableObject는 씬에 존재하는 컴포넌트가 아니라 프로젝트 asset으로 저장되는 데이터 컨테이너입니다. "행동을 가진 오브젝트"보다 "공유해야 하는 값과 규칙"을 담는 데 더 잘 맞습니다. 가장 큰 장점은 프리팹마다 설정값이 복제돼 퍼지는 문제를 줄여 준다는 점입니다. 무기 데이터, 적 스탯, 아이템 정의, 밸런스 테이블처럼 여러 오브젝트가 공유해야 하는 값은 ScriptableObject로 빼 두면 수정 범위가 훨씬 명확해집니다.
인스펙터에서 직접 다루는 데이터
디자이너나 기획자가 인스펙터에서 직접 수정해야 하는 데이터라면 ScriptableObject가 아주 잘 맞습니다. 프리팹이나 씬 오브젝트를 열지 않고도 자산 단위로 값을 다룰 수 있기 때문입니다. CreateAssetMenu 어트리뷰트를 붙이면 에디터 메뉴에서 인스턴스를 직접 생성할 수 있어 기획 작업 흐름에 자연스럽게 통합됩니다.
public class WeaponView : MonoBehaviour
{
[SerializeField] private WeaponData data;
private void Start()
{
Debug.Log($"{data.weaponName} / {data.damage}");
}
}런타임 상태와 원본 데이터 분리
ScriptableObject를 런타임 상태 저장소처럼 바로 쓰기 시작하면 원본 asset 값과 세션 중 임시 상태가 섞이기 쉽습니다. 그래서 보통은 "원본 데이터", "런타임 복제 상태", "영속 저장 데이터"를 구분하는 편이 좋습니다. ScriptableObject는 만능이 아니라 경계 도구입니다. 씬과 로직을 덜 결합시키고, 데이터를 asset 단위로 빼내고, 공유 구조를 더 선명하게 만들 때 가장 강합니다.
체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 여러 프리팹이 공유하는 설정·스탯 | ScriptableObject asset |
| 씬마다 다른 임시 런타임 상태 | MonoBehaviour 필드 또는 별도 런타임 클래스 |
| 기획자가 인스펙터에서 직접 조정 | CreateAssetMenu + ScriptableObject |
| 세션 간 영속 저장 | JSON / 파일 저장 (PlayerPrefs 또는 별도 저장소) |
| 비교 | 더 잘 맞는 쪽 | 이유 |
|---|---|---|
| 공유 원본 데이터 vs 세션 중 상태 | ScriptableObject / 런타임 클래스 | asset 원본과 플레이 중 변경값을 섞지 않기 쉬움 |
| 씬 참조 묶음 vs 순수 설정 데이터 | MonoBehaviour / ScriptableObject | 씬 수명주기와 프로젝트 자산 수명주기가 다름 |
| 영속 저장 vs 밸런스 데이터 | 저장 파일 / ScriptableObject | 플레이 결과와 원본 정의는 책임이 다름 |
주의할 점
에디터 플레이 모드에서 ScriptableObject 값을 변경하면 에디터 asset이 영구 변경됩니다. 런타임 상태를 asset에 직접 쓰지 마세요.
// ❌ 런타임 상태를 ScriptableObject에 직접 저장
// Play 중 바꾸면 프로젝트 asset이 수정됨 — 빌드와 에디터 값 불일치
[CreateAssetMenu]
public class PlayerStats : ScriptableObject
{
public int currentHp; // ❌ 런타임 상태를 asset에 저장
}
// ✅ 원본 데이터는 ScriptableObject, 런타임 상태는 별도 복사
public class PlayerHealth : MonoBehaviour
{
[SerializeField] private PlayerStats baseStats; // 읽기 전용 원본
private int currentHp; // ✅ 런타임 상태는 MonoBehaviour 필드에
private void Start() => currentHp = baseStats.maxHp;
}
// ❌ 씬 오브젝트 참조를 ScriptableObject에 직접 담아 공유
// → 씬 전환, 프리팹 재사용, 테스트에서 결합도가 급격히 올라감참고 링크
2 sources