Unity에서 ScriptableObject 제대로 쓰기: 개념부터 실전 패턴까지
“ScriptableObject가 좋다는데 도대체 언제 써야 하지?”
“MonoBehaviour랑 뭐가 다르지?”
이런 의문을 가져본 적 있다면, 이 글을 통해 정확히 이해할 수 있을 거예요.
이번 글에서는 ScriptableObject의 기본 개념부터 실제로 유용한 패턴들까지 정리해 보겠습니다.
ScriptableObject란?
ScriptableObject는 Unity에서 씬과 독립적으로 존재하는 데이터 자산입니다.
MonoBehaviour처럼 GameObject에 붙지 않고, Project 뷰에서 에셋으로 저장됩니다.
✔️ 주요 특징
- 씬에 없어도 됨 (런타임이나 에디터 어디서든 접근 가능)
- 데이터 중심 구조 설계 가능
- 여러 오브젝트 간 데이터 공유 가능
- 직렬화되어 저장되므로 인스펙터 편집 가능
기본 사용 예제
[CreateAssetMenu(fileName = "New Item", menuName = "Item")]
public class ItemSO : ScriptableObject {
public string itemName;
public Sprite icon;
public int price;
}
public class Shop : MonoBehaviour {
public ItemSO[] itemList;
void Start() {
foreach (var item in itemList)
Debug.Log($"판매중: {item.itemName} - {item.price} Gold");
}
}
자주 쓰는 유용한 패턴들
📦 패턴 1: 전역 설정값(Global Settings)
용도
- 볼륨, 해상도, 난이도 같은 게임 전체에서 공유되는 설정
예시
[CreateAssetMenu(menuName = "Settings/Game Settings")]
public class GameSettings : ScriptableObject {
public float masterVolume;
public int difficulty;
}
씬 어디서든 참조하면, 전역 설정 관리가 쉬워집니다.
🔁 패턴 2: 런타임 데이터 저장소 (Runtime Data)
용도
- 현재 체력, 점수, 남은 시간 등 씬 간 유지할 일시적 정보
ScriptableObject는 씬을 넘겨도 유지되기 때문에 저장소 역할로 사용 가능합니다.
[CreateAssetMenu(menuName = "Data/Player Runtime Data")]
public class PlayerRuntimeData : ScriptableObject {
public int currentHP;
public int score;
}
※ 단, 에디터에서 저장되지 않으므로 런타임 종료 후 데이터는 초기화됨.
🧠 패턴 3: 이벤트 시스템 (Game Event Pattern)
용도
- GameObject 간 의존성 없이 메시지 전달 가능하게 하는 설계
[CreateAssetMenu(menuName = "Event/Game Event")]
public class GameEvent : ScriptableObject {
private List<System.Action> listeners = new();
public void Raise() {
foreach (var listener in listeners)
listener.Invoke();
}
public void Register(System.Action action) => listeners.Add(action);
public void Unregister(System.Action action) => listeners.Remove(action);
}
버튼 클릭 → GameEvent.Raise() → 여러 구독자가 반응
decoupled architecture에 적합
🧱 패턴 4: 데이터 기반 오브젝트 설정
용도
- 무기, 몬스터, 아이템 등 다양한 종류의 데이터를 관리할 때
예시: 무기 스탯 정보
[CreateAssetMenu(menuName = "Weapon")]
public class WeaponData : ScriptableObject {
public string weaponName;
public int damage;
public float range;
}
캐릭터에게 무기를 할당할 때 스크립트로 생성하지 않고, ScriptableObject로 관리하면 유지보수가 쉬워집니다.
주의사항
- 런타임 중 변경된 값은 저장되지 않음
- 싱글턴처럼 쓰려면 Resources.Load, Addressable 등으로 직접 로드
- 상태 저장이 필요한 경우 별도의 SaveSystem 필요
정리: 언제 써야 할까?
사용 목적 ScriptableObject 적합 여부
데이터 저장 & 공유 | ✅ 매우 적합 |
씬/오브젝트 상태 추적 | ⚠️ 가능하나 휘발성 주의 |
UI 이벤트 연결 | ✅ GameEvent 패턴으로 가능 |
외부 파일처럼 관리 | ✅ 에디터 자산으로 취급 가능 |
지속적 저장(세이브) | ❌ PlayerPrefs나 JSON 등 병행 필요 |
ScriptableObject 생성방법
다음과 같이 class명 뒤에 MonoBehaviour 가 아닌 ScriptableObject를 작성해주면 ScriptableObject가 생성된다
using UnityEngine;
[CreateAssetMenu(fileName = "MyScriptableObject")]
public class MyScriptableObjcet : ScriptableObject
{
public int someData;
}
public class PracticeScriptableObject : MonoBehaviour
{
}
[CreateAssetMenu(fileName = ..., menuName = ...)]에서 menuName은 Unity 에디터의 "Create" 메뉴에 표시되는 경로이자 이름을 뜻합니다.
쉽게 말해서:
menuName = "Scriptable Object" 라고 적으면,
Unity의 Project 창에서 우클릭 > Create > Scriptable Object 메뉴가 생성돼요.
📌 예시 1: 기본 사용법
[CreateAssetMenu(fileName = "New MyScriptable Object", menuName = "MyScriptable Object")]
public class MyScriptableObjcet : ScriptableObject {
public string itemName;
}
📁 예시 2: 폴더/하위 메뉴 구조 만들기
[CreateAssetMenu(fileName = "New Weapon", menuName = "Data/Weapon")]
이 경우:
하위 폴더처럼 구성된 메뉴가 생깁니다.
(실제 폴더 구조와는 관계없고, 메뉴 UI에서만 해당 구조로 표시돼요.)
💬 실전 팁
- 폴더형 메뉴 구분을 적극 활용하면 ScriptableObject 종류가 많아질 때 에디터가 훨씬 깔끔해집니다.
- 예: menuName = "Character/Enemy" → Create > Character > Enemy
- 유니티 에셋 제작 워크플로우에서 디자이너/기획자도 쉽게 사용할 수 있어 협업에 유리합니다.