ScriptableObject 장점
1. 데이터 재사용 용이
- ScriptableObject는 하나의 에셋 개념으로, 여러 프리팹 또는 스크립트에서 공유 가능
- 예) 동일한 몬스터 데이터 여러 개 스폰 시 재사용 -> 성능 최적화 및 일관성 보장
2. 설정 및 유지보수 용이
- ScriptableObject는 인스펙터에서 직접 수정 가능
- 코드 수정 없이 밸런싱 가능 (예 - 이동속도, 공격력, HP 등)
3. GC 부담 감소
- 일반 클래스는 new로 생성되기 때문에 다수 객체 생성 -> 메모리 할당/해제 시 GC에 부담
- ScriptableObject는 에셋이기 때문에 GC 대상이 아님 -> 런타임 성능 유리
4. 모듈화 및 설계 분리
- 데이터를 구현 코드와 분리할 수 있음 -> 클린 아키텍쳐 적용에 유리
- 전략 패턴, 팩토리 패턴과 잘 맞음
5. 씬 간 공유 가능
- A씬에서 설정한 데이터를 B씬에서 그대로 참조 가능 -> 비종속적
ScriptableObject 주의점
1. 런타임에서 데이터 변경 시 저장되지 않음
- 변경 사항은 플레이모드 종료 시 초기화됨
2. 동시성 문제
- 공유된 ScriptableObject 인스턴스에 런타임 중 값 변경 시 그 값을 참조한 모든 객체에 영향을 줄 수 있음
- 예를 들어 이동 속도, 최대 체력 등 설정 값만 사용하고, 현재 남은 체력 등의 요소는 ScriptableObject에 사용 금지
- ScriptableObject 값을 MonoBehaviour에 복사해 원본 수정이 안되도록 사용하거나 읽기 전용으로 사용
/// <summary>
/// MonsterDataSO 데이터
/// </summary>
[CreateAssetMenu]
public class MonsterDataSO : ScriptableObject
{
public float hp;
}
/// <summary>
/// MonsterDataSO 데이터를 공유할 Monster 클래스
/// </summary>
public class Monster : MonoBehaviour
{
public MonsterDataSO data;
public void TakeDamage(float damage)
{
data.hp -= damage; // 여기서 문제 발생
}
}
// 1. slimePrefab1 → data.hp = 100 (SO 참조)
// 2. slimePrefab2 → data.hp = 100 (같은 SO 참조)
// → slimePrefab1이 공격받아서 hp -= 50 하면...
// → slimePrefab2의 data.hp도 50이 되어버림
/// <summary>
/// MonsterDataSO 데이터의 올바른 사용법
/// </summary>
public class Monster : MonoBehaviour
{
public MonsterDataSO data;
private float currentHp; // 복사본 생성
void Start()
{
currentHp = data.hp; // 복사해서 상태로 사용
}
public void TakeDamage(float dmg)
{
currentHp -= dmg; // 원본 SO는 건드리지 않음
}
}
ScriptableObject는 일반 클래스와 어떻게 다른가??
항목 | ScriptableObject | 일반 C# 클래스 (new로 생성하는 인스턴스) |
메모리 위치 | Unity 엔진이 관리하는 Asset 영역 | 일반적으로 RAM 영역 (GC 대상) |
생성 방법 | CreateAssetMenu로 에셋 파일 생성 | new MyClass()로 런타임에 생성 |
에디터 저장 | 가능 (프로젝트 저장됨) | 불가능 (런타임에만 존재) |
직렬화 | Unity 직렬화 지원 (Inspector에 보임) | Unity 직렬화 안 됨 (보통 별도 처리 필요) |
씬 간 공유 | 가능 (프리팹, 오브젝트 간 공유 쉬움) | 불가능 (매번 복사해서 넘겨야 함) |
어드레서블 연동 | 가능 (데이터도 Addressable 관리 가능) | Addressable 관리 대상 아님 |
성능 측면 | GC 없이 사용 가능 | GC 대상, 필요시마다 생성/해제 필요 |
그럼 ScriptableObject는 어떻게 메모리 측면에서 유리한가??
일반 클래스 방식
// 일반 클래스
public class MonsterData
{
public float hp;
public float speed;
}
// 웨이브 1에서 슬라임 10마리 소환 시
List<Monster> monsters = new List<Monster>();
for (int i = 0; i < 10; i++) {
var data = new MonsterData { hp = 100, speed = 1.0f }; // ← 여기서 매번 인스턴스 생성됨
monsters.Add(new Monster(data));
}
결과
- MonsterData가 메모리에 10개 복사됨
- new 키워드로 생성 시 GC 대상이 되고 양이 많아질 수록 할당/해제 비용이 증가
- 데이터 내용이 같지만 매번 new 로 생성되므로 메모리 낭비 발생
ScriptableObject 방식
// MonsterDataSO 데이터
[CreateAssetMenu]
public class MonsterDataSO : ScriptableObject
{
public float hp;
public float speed;
}
// 웨이브 1에서 슬라임 10마리 소환 시
for (int i = 0; i < 10; i++) {
Instantiate(slimePrefab).Init(MonsterDataSO); // ← 하나의 SO를 공유해서 참조만 전달
}
결과
- 메모리에 존재하는 MonsterDataSO는 1개
- 10마리 몬스터는 모두 같은 ScriptableObject 인스턴스를 참조만 함
- 메모리 절약 및 ScriptableObject는 에셋으로 GC 부하도 없음
항목 일반 클래스 (new) ScriptableObject
항목 | 일반 클래스 (new) | ScriptableObject |
생성 횟수 | N번 복사 | 1번만 로딩 |
메모리 사용 | 데이터당 중복 | 공유 참조 |
GC 영향 | O (할당/해제) | X (에셋으로 존재) |
구조 일관성 | 직접 관리해야 함 | 에셋으로 중앙 관리 |
변경 시 일괄 반영 | X | O (SO 수정 시 모든 참조에 적용) |
그렇다면 GC는 무엇이고 ScriptableObject이 어떤 영향을 주는가??
먼저 GC란?
- C#에서 사용하지 않게 된 메모리를 자동으로 정리해주는 기능
- new로 객체 생성 시 메모리에 올라가서 공간 차지
- 그 객체를 더 이상 참조하지 않는 경우 메모리만 차지하고 쓸모없어짐
- 이 때 GC가 쓸모없는 객체를 찾아서 정리해줌
유니티에서 GC가 발생되는 시점
- new로 객체 생성
- string 문자열 조작 (덧셈연산, Split, Substring, Replace, ToUpper, ToLower..... StringBuilder는 살짝 논외. 객체 생성 후 ToString() 할 때 한 번만 GC 발생)
- List<T>, Dictionary<K,V> 등의 컬렉션 조작
- LINQ 사용
- Instantiate / Destroy 사용
- Boxing / Unboxing (object o = 42; int x = (int)o;)
- 반복적인 이벤트 등록 및 해제
Profiler에서 보는 GC 지표
항목 | 의미 | 언제 증가? | 줄이는 법 | 주의 포인트 |
GC Alloc (Frame) | 현재 프레임에서 새로 힙에 할당된 GC 대상 메모리 크기 | new, string, List<T>.Add, LINQ, Split() 등 | GC 발생 유도 줄이기 (StringBuilder, 풀링 등) | 값이 매 프레임 >0이면 주의! |
Total Reserved |
전체 힙에서 예약된 메모리 크기 (GC가 사용할 메모리 풀) | 유니티가 내부적으로 힙 공간 늘릴 때 | 직접 제어 불가, 하지만 줄일 수 있는 간접 방법 존재 | 계속 증가만 한다면 메모리 누수 가능성 |
GC Count | GC.Collect가 실행된 횟수 (실제 메모리 회수된 시점) |
GC가 조건에 따라 자동 또는 수동 호출 시 | GC Alloc 줄이기 | 너무 자주 발생하면 프레임 드랍 유발 |
GC Time (ms) | GC가 실행되는데 걸린 시간 | 위와 동일 | 위 3가지 줄이면 자연히 줄어듦 | 값이 높으면 GC 중지 시간 동안 FPS 뚝 떨어짐 |
반응형
'개발 > Unity 3D' 카테고리의 다른 글
[Unity] MVC 패턴 (0) | 2025.06.25 |
---|---|
[Unity] 씬 전환 'SceneManagement vs Addressable' (0) | 2025.06.17 |
[Unity] Addressable 'Release vs ReleaseInstance' (1) | 2025.06.17 |
[Unity] Addressable 개요 (1) | 2025.06.17 |
[Unity] Unity 듀얼 모니터 확장 출력 (1) | 2025.06.13 |