Unity에서 ScriptableObject 제대로 쓰기

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
  • 유니티 에셋 제작 워크플로우에서 디자이너/기획자도 쉽게 사용할 수 있어 협업에 유리합니다.