반응형
스크립터블오브젝트란?
유니티에서 제공하는 데이터 컨테이너 클래스로, 게임 내의 데이터를 관리하고 저장하는데 매우 유용하다
주요 특징
- 프리팹처럼 에셋으로 저장됩니다. Project 창에서 직접 생성하고 관리할 수 있다
- MonoBehaviour와 달리 GameObject에 부착할 필요가 없다
- 메모리 관리가 효율적입니다. 같은 ScriptableObject를 여러 곳에서 참조해도 메모리에 한 번만 로드된다
- Unity 인스펙터에서 직접 데이터를 수정할 수 있다
스크립터블 오브젝트 만드는 법
C# 스크립트 생성
// ItemData.cs
using UnityEngine;
[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Item")]
public class ItemData : ScriptableObject
{
public string itemName;
public Sprite itemIcon;
public int itemPrice;
public string description;
}
사용 방법
1. Project 창에서 우클릭
2. Create > Inventory > Item 선택
3. 생성된 아이템 데이터를 인스펙터에서 수정
4. 스크립트에서 참조하여 사용
주의사항
1. 런타임 데이터 변경 주의
// 권장되는 방법: 런타임용 복사본 생성
public class GameManager : MonoBehaviour
{
public GameData originalData;
private GameData runtimeData;
void Awake()
{
// 실행 중 사용할 복사본 생성
runtimeData = Instantiate(originalData);
}
}
2. 순환 참조 방지
// 순환 참조가 발생할 수 있는 예시
public class ItemData : ScriptableObject
{
public WeaponData weaponData; // WeaponData를 참조
}
public class WeaponData : ScriptableObject
{
public ItemData itemData; // ItemData를 참조
// 이런 순환 참조는 직렬화 문제와 메모리 누수를 일으킬 수 있음
}
3. 리소스 관리
- Resources 폴더에 너무 많은 ScriptableObject를 넣으면 초기 로딩 시간이 길어짐
- 큰 데이터셋은 적절히 분리하여 관리
4. 직렬화 제한 이해
public class DataContainer : ScriptableObject
{
// Dictionary 등 직렬화 불가능한 자료구조 사용 불가
public Dictionary<string, int> data; // 작동하지 않음
// 직렬화 가능한 구조로 변경
[System.Serializable]
public class SerializableKeyValuePair
{
public string key;
public int value;
}
public List<SerializableKeyValuePair> serializableData;
}
데이터 변경
런타임 중 값을 변경하면 원본값이 변하게 된다
ScriptableObject의 기본 원리
ScriptableObject는 참조 타입(Reference Type)으로, 에셋으로 저장되는 데이터 컨테이너이다.
게임 오브젝트에서 ScriptableObject를 참조할 때, 실제로는 에셋 파일의 동일한 인스턴스를 참조하게 된다
예시
[CreateAssetMenu(fileName = "PlayerData", menuName = "Game/PlayerData")]
public class PlayerData : ScriptableObject
{
public int health = 100;
}
// 여러 곳에서 사용
public class Player1 : MonoBehaviour
{
public PlayerData playerData; // 에셋 참조
void DamagePlayer()
{
playerData.health -= 10; // 원본 데이터 직접 수정
}
}
public class Player2 : MonoBehaviour
{
public PlayerData playerData; // 같은 에셋 참조
void CheckHealth()
{
// Player1이 수정한 값이 여기서도 반영됨
Debug.Log(playerData.health);
}
}
이유
메모리 효율성을 위해 Unity는 ScriptableObject를 싱글톤처럼 관리
프로젝트의 모든 참조가 동일한 메모리 주소를 가리킴
한 곳에서 수정하면 모든 참조에 영향을 미침
이는 의도적인 디자인이다
그렇지만 런타임 중 데이터 변경이 필요한 경우에는
복사본을 만들어 사용하는 것이 안전하다
값 변경 관련 주의사항
1. 참조 타입 데이터 처리
[CreateAssetMenu(fileName = "GameData", menuName = "Game/GameData")]
public class GameData : ScriptableObject
{
public List<int> scores = new List<int>(); // 참조 타입
public int currentLevel; // 값 타입
}
2. 런타임 데이터 초기화 방법
public class GameDataManager : MonoBehaviour
{
public GameData gameData;
private GameData runtimeData;
void Awake()
{
// 런타임용 복사본 생성
runtimeData = Instantiate(gameData);
}
}
3. 안전한 데이터 접근을 위한 래퍼 클래스 사용
[CreateAssetMenu(fileName = "ItemDatabase", menuName = "Game/ItemDatabase")]
public class ItemDatabase : ScriptableObject
{
[SerializeField]
private List<ItemData> items = new List<ItemData>();
// 읽기 전용 프로퍼티로 제공
public IReadOnlyList<ItemData> Items => items;
}
권장되는 사용 패턴
1. 읽기 전용 데이터로 사용
[CreateAssetMenu(fileName = "Constants", menuName = "Game/Constants")]
public class GameConstants : ScriptableObject
{
[SerializeField] private int maxHealth;
public int MaxHealth => maxHealth; // 읽기 전용 프로퍼티
}
2. 런타임 데이터 리셋 구현
public class RuntimeDataResetter : MonoBehaviour
{
[SerializeField] private GameData gameData;
private GameData originalData;
void Awake()
{
// 원본 데이터 백업
originalData = Instantiate(gameData);
}
void OnDisable()
{
// 게임 종료시 원본 데이터로 복원
gameData.ResetToDefault(originalData);
}
}
3. 이벤트 기반 업데이트 사용
[CreateAssetMenu(fileName = "PlayerStats", menuName = "Game/PlayerStats")]
public class PlayerStats : ScriptableObject
{
public event System.Action<int> OnHealthChanged;
private int health;
public void UpdateHealth(int newHealth)
{
health = newHealth;
OnHealthChanged?.Invoke(health);
}
}
결론
장점
1. 메모리 효율성
여러 곳에서 참조해도 메모리에 한번만 로드됨
프리팹 인스턴스마다 데이터 복제가 필요없음
2. 데이터 관리 용이성
중앙집중식 데이터 관리 가능
인스펙터에서 쉽게 수정 가능
씬 간에 데이터 공유가 쉬움
단점
1. 런타임 데이터 관리의 위험성
직접 참조하면 원본 데이터가 수정될 수 있음
의도치 않은 데이터 변경이 영구적으로 저장될 수 있음
2. 직렬화 제한
Dictionary 등 일부 자료구조 직렬화 불가
복잡한 데이터 구조 저장의 제한
스크립터블오브젝트는 적절한 상황에서 사용하면 매우 강력하지만,
데이터 변경 시 주의가 필요한 도구이다.
그렇기에, 런타임 데이터는 반드시 복사본을 사용하는 것이 안전하다
반응형
'유니티 > 문법' 카테고리의 다른 글
[Unity] 유니티 C#) Garbage Collector ( 가비지 컬렉터 ) (0) | 2025.01.06 |
---|---|
[Unity] 유니티 C#) ObjectPool ( 오브젝트풀링 ) (2) | 2025.01.03 |
[Unity] 유니티 C#) 코루틴(Coroutine) (3) | 2024.12.27 |
[Unity] 유니티 C#) 네임스페이스 ( namespace ) (0) | 2024.12.26 |
[Unity] 유니티 C#) 제네릭 (0) | 2024.11.29 |