스크립터블 오브젝트 ( ScriptableObject )
게임을 만들다 보면 무기 능력치나 적 스탯, 아이템 정보처럼 여러 곳에서 공통으로 사용하는 데이터가 많다.
이런 데이터를 MonoBehaviour 내부에 직접 넣어두면 수정이 번거롭고 같은 값을 여러 오브젝트에 중복해서 넣게 될 가능성이 크다. 클래스에 변수로 선언해서 사용하거나, JSON 파일로 관리하거나, CSV 로 만들어서 파싱하는 방법도 있지만, 유니티 엔진에는 데이터 관리를 위한 강력한 도구가 있다. 바로 ScriptableObject 이다.
ScriptableObject 를 사용하면 데이터를 하나의 에셋으로 분리해두고, 필요한 곳에서 공통으로 참조할 수 있어 관리가 쉬워진다.
ScriptableObject 는 데이터를 저장하고 관리하기 위한 에셋 타입이다.
컴포넌트처럼 게임 오브젝트에 붙이는 것이 아니라, 프로젝트 창에 에셋 파일로 저장된다.
데이터만 담는 컨테이너라고 보면 된다.
MonoBehaviour 는 게임 오브젝트에 붙여서 동작하는 컴포넌트이고, ScriptableObject는 독립적인 데이터 에셋이다.
즉, MonoBehaviour 는 행동하는 객체에 가깝고, ScriptableObject 는 공유해서 사용하는 데이터에 가깝다.
ScriptableObject 의 특징
- 독립적인 에셋 : 씬이나 게임 오브젝트와 무관하게 존재
- 메모리 효율 : 여러 곳에서 같은 데이터를 참조해도 메모리에 한 번만 로드
무기 데이터를 하나 만들어두면, 해당 데이터를 참조하는 무기 프리팹들은 모두 같은 값을 공유 - 에디터 친화적 : 인스펙터에서 직접 수정 가능
인스펙터에서 값을 직접 수정할 수 있어, 코드 수정 없이 밸런스 조정이 가능 - 런타임 안정성 : 에셋 파일이므로 실수로 삭제될 위험이 적다
ScriptableObject 를 사용하기 좋은 경우
ScriptableObject 는 변하지 않는 설정값이나 공통 데이터를 관리할 때 특히 유용하다.
- 무기 데이터
- 아이템 정보
- 적 스탯
- 스킬 수치
- 퀘스트 데이터
- 대사 데이터
- 웨이브 설정값
- 캐릭터 클래스 정보
일반적인 데이터 관리 방식의 한계
Unity 에서 무기, 아이템, 적 능력치 같은 데이터를 관리하려고 한다.
보통 클래스나 MonoBehaviour 에 직접 값을 넣는 방식부터 떠올리게 된다.
작은 규모에서는 이 방법도 가능하지만, 데이터가 많아지고 여러 오브젝트가 같은 값을 공유해야 하는 상황이 생기면 점점 불편해진다.
1. MonoBehaviour 에 직접 데이터 넣기
가장 단순한 방법은 컴포넌트에 데이터를 직접 작성하는 방법이다.
using UnityEngine;
public class Weapon : MonoBehaviour
{
public string weaponName;
public int damage;
public float attackSpeed;
}
이 방식은 구현이 쉽고 바로 테스트할 수 있다는 장점이 있다.
하지만 데이터만 관리하려는 목적에는 비효율적인 부분이 있다.
- 데이터를 사용하려면 반드시 게임 오브젝트가 필요
- 같은 무기 정보를 여러 곳에서 쓰려면 오브젝트나 프리팹 단위로 관리
- 데이터 수정이 필요할 때 관련된 오브젝트를 모두 찾아야 할 수 있다.
데이터 자체보다 오브젝트에 종속된 구조가 된다.
2. 클래스에 데이터 담기
게임 오브젝트에 붙이지 않고, 일반 클래스로 데이터를 정의
using System;
using UnityEngine;
[Serializable]
public class WeaponData
{
public string weaponName;
public int damage;
public float attackSpeed;
}
public class Player : MonoBehaviour
{
public WeaponData currentWeapon;
}
이 방식은 MonoBehaviour 에 직접 값을 넣는 것보다 조금 더 구조적이다.
하지만 여전히 한계가 있다.
- 이 데이터는 보통 특정 컴포넌트 안에 포함된 형태로 사용
- 같은 데이터를 여러 오브젝트가 공통으로 참조하기보다, 각자 별도로 가지게 되는 경우가 많다
- 값이 많아질수록 중복 관리 가능성이 커진다
같은 무기 데이터를 여러 적이나 무기가 사용한다면, 실제로는 같은 의미의 데이터를 여러 인스턴스에 반복해서 들고 있게 될 수 있다.
왜 불편해질까?
문제의 핵심은 데이터를 독립적으로 다루기 어렵다는 점이다.
게임 개발에서는 이런 상황이 자주 나온다.
- 여러 캐릭터가 같은 적 스탯을 사용
- 같은 무기 설정을 여러 프리팹이 공유
- 아아템 정보나 퀘스트 정보를 씬과 무관하게 관리해야 함
이런 데이터는 누가 들고 있느냐보다 하나의 원본 데이터를 여러 곳에서 참조할 수 있느냐가 더 중요하다.
ScriptableObject 는 이런 문제를 해결하기 위해 데이터를 게임 오브젝트와 분리해서 에셋 형태로 관리할 수 있게 해주는 Unity 기능이다.
- MonoBehaviour 처럼 오브젝트에 붙지 않아도 된다
- 일반 클래스처럼 특정 컴포넌트 내부에 종속되지 않는다
- 프로젝트 창에서 독립적인 데이터 파일로 존재한다
ScriptableObject 사용 방법
- 데이터를 담을 ScriptableObject 타입을 만든다
- Unity 에디터에서 해당 타입의 에셋을 생성한다
- 필요한 스크립트에서 참조해서 사용한다
1. 데이터 구조 정의
먼저 어떤 데이터를 저장할지 정하고, 이를 ScriptableObject 클래스로 만든다.
using UnityEngine;
[CreateAssetMenu(fileName = "NewWeaponData", menuName = "Game Data/Weapon")]
public class WeaponData : ScriptableObject
{
public string weaponName;
public int damage;
public float attackInterval;
public float attackRange;
}
[CreateAssetMenu] 애트리뷰트를 사용하면 프로젝트 창에서 마우스 우클릭으로 쉽게 에셋을 생성할 수 있다.
2. 에셋 생성

- 프로젝트 창에서 우클릭
- Create > Game Data > Weapon 선택
- 생성된 에셋 이름 변경
- 인스펙터에서 값 입력
3. 필요한 곳에서 참조
이제 생성한 WeaponData 에셋을 다른 스크립트에서 참조해 사용한다.
using UnityEngine;
public class WeaponUser : MonoBehaviour
{
[SerializeField] private WeaponData weaponData;
private void Start()
{
Debug.Log($"무기 이름: {weaponData.weaponName}");
Debug.Log($"공격력: {weaponData.damage}");
}
public void Attack()
{
int finalDamage = weaponData.damage;
Debug.Log($"{weaponData.weaponName} 공격, 최종 데미지: {finalDamage}");
}
}
이 방식의 장점은 분명하다.
- 같은 데이터를 여러 객체가 함께 사용
- 값 수정이 필요할 때 에셋만 변경
- 코드와 데이터가 분리되어 관리가 쉬움
ScriptableObject 와 디자인 패턴
ScriptableObject 는 디자인 패턴 자체는 아니지만, 실제로 프로젝트에서 사용하다 보면 몇몇 디자인 패턴과 비슷한 역할로 활용되는 경우가 있다. 중요한 점은, ScriptableObject 는 객체 간 관계나 동작 방식을 설계하는 패턴이라기보다 데이터를 독립적으로 분리하고 공유하기 위한 Unity 의 기능이라는 것이다. 특정 패턴을 그대로 구현하는 도구라기보다는 일부 상황에서 패턴과 비슷한 구조를 만드는 데 활용될 수 있다고 보는 편이 더 적절하다.
1. 싱글톤 패턴과의 비교
ScriptableObject 는 종종 싱글톤과 비슷한 용도로 언급된다.
여러 시스템이 하나의 공용 데이터를 함께 참조할 수 있기 때문이다.
하지만 둘은 목적이 다르다
싱글톤 패턴
- 특정 클래스의 인스턴스가 하나만 존재하도록 보장하는 구조
- 전역 접근이 필요한 객체를 관리할 때 사용
- 기능이나 매니저 객체에 많이 사용
ScriptableObject
- 데이터를 에셋 형태로 분리해 여러 곳에서 참조하게 하는 방식
- 전역에서 공통으로 사용하는 설정값, 정적 데이터를 관리할 때 적합
- 인스턴스를 하나만 생성하는 것이 핵심 목적은 아님
2. 팩토리 패턴과의 비교
적, 아이템, 스킬처럼 생성 대상의 설정값을 미리 데이터로 정의해두는 경우 때문에 팩토리 패턴과 함께 언급되기도 한다.
팩토리 패턴
- 객체 생성 과정을 별도로 분리하는 패턴
- 어떤 객체를 만들지, 어떻게 생성할지를 관리하는 데 초첨이 있음
ScriptableObject
- 생성 규칙 자체보다는, 생성에 필요한 데이터 정의에 강점
- 어떤 적을 만들지, 어떤 스킬 수치를 사용할지 같은 설정값을 담는 데 적합
3. 전략 패턴과의 비교
전략 패턴과 함께 자주 언급된다.
행동 규칙을 에셋 단위로 분리해두고, 인스펙터에서 교체하든 사용할 수 있기 때문이다.
전략 패턴
- 알고르짐이나 동작 방식을 캡슐화해서 교체 가능하게 만드는 패턴
- 같은 역할을 하는 여러 행동을 분리해 두고 상황에 따라 바꿔 사용할 수 있음
- 공격 방식, 이동 방식, 스킬 효과처럼 변화 가능한 로직에 자주 사용
ScriptableObject
- 교체할 전략을 에셋으로 관리하게 쉽게 해주는 기능
- 인스펙터에서 다른 전략 에셋을 할당해 동작을 바꾸는 구조로 활용 가능
- 전략 패턴 자체라기보다, 전략을 적용하고 관리하는 데 도움을 주는 방식
ScriptableObject 의 장단점
장점
- 데이터와 로직을 분리하기 쉽다
> 코드에는 동작 로직만 남기기 쉬움
> 데이터 수정 시 스크립트 수정 범위가 줄어듦
> 프로젝트 구조를 정리하기 좋음 - 여러 곳에서 같은 데이터를 참조하기 좋다
> 여러 적이 같은 스탯 데이터 사용
> 여러 무기가 같은 발사 규칙 데이터 참조
> 여러 시스템이 같은 게임 설정값 사용 - 인스펙터에서 바로 수정할 수 있다
> 코드 수정 없이 수치 조정 가능
> 디자이너나 지획자도 접근하기 쉬움
> 밸런스 테스트 반복이 편리함 - 재사용성이 좋다
> 한 번 만든 데이터 구조를 여러 에셋으로 확장해서 사용할 수 있다.
> 데이터 형식은 재사용하고, 실제 값은 에셋별로 분리
단점
- 런타임 중 값 변경에 주의해야 한다
> 플레이어가 사용하는 무기 데이터의 공격력을 직접 변경
> 공용 설정 에셋의 값을 게임 도중 수정
> 여러 객체가 공유하는 데이터를 개별 상태처럼 사용
이 경우 의도하지 않은 부작용이 생길 수 있다. - 개별 런타임 상태 저장에는 적합하지 않다
ScriptableObject 는 주로 공유 설정값이나 정적 데이터에 적합
플레이 중 계속 바뀌는 값은 따로 관리하는 것이 더 자연스럽다. - 공유 데이터라는 점을 항상 의식해야 한다
이 특성을 잊고 일반 변수처럼 다루면 예상과 다른 결과가 나올 수 있다.
예시 코드
여러 개의 적 데이터 에셋을 한 번에 만들기
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using System.IO;
public static class EnemyDataCreator
{
[MenuItem("Tools/Create Sample Enemy Data")]
public static void CreateEnemyData()
{
string folderPath = "Assets/Data/Enemies";
if (!AssetDatabase.IsValidFolder(folderPath))
{
Directory.CreateDirectory(folderPath);
AssetDatabase.Refresh();
}
CreateEnemyAsset("Goblin", 50, 5f, folderPath);
CreateEnemyAsset("Orc", 100, 3f, folderPath);
CreateEnemyAsset("Dragon", 300, 8f, folderPath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("적 데이터 에셋 생성 완료");
}
private static void CreateEnemyAsset(string enemyName, int maxHp, float moveSpeed, string folderPath)
{
EnemyData asset = ScriptableObject.CreateInstance<EnemyData>();
asset.enemyName = enemyName;
asset.maxHp = maxHp;
asset.moveSpeed = moveSpeed;
string assetPath = $"{folderPath}/{enemyName}.asset";
AssetDatabase.CreateAsset(asset, assetPath);
}
}
#endif
using UnityEngine;
[CreateAssetMenu(fileName = "EnemyData", menuName = "Game Data/Enemy")]
public class EnemyData : ScriptableObject
{
public string enemyName;
public int maxHp;
public float moveSpeed;
}
폴더 구조를 미리 정해두기
Assets/
└─ Data/
├─ Enemies/
├─ Weapons/
├─ Items/
└─ Quests/
이름 규칙 통일하기
Enemy_Goblin
Weapon_Pistol
Quest_Main_01
참고 자료
Unity Learn : https://learn.unity.com/tutorial/introduction-to-scriptable-objects
Unity Documentation : https://docs.unity3d.com/kr/2022.3/Manual/class-ScriptableObject.html
'🧊Unity Basic > 데이터 관리' 카테고리의 다른 글
| Data Format (0) | 2025.11.14 |
|---|