1. ScipratbleObject 2. 생성 방법 3. 사용 방법 4. 유용한 이유, 하나의 메모리에 참조
📝 1. ScriptableObject
: 스크립터블 오브젝트(ScriptableObject )는 대량의 데이터를 저장하는 데 사용할 수 있는 데이터 컨테이너다.
: 값의 사본이 생성되는 것을 방지하여 프로젝트의 메모리 사용을 줄이는데 유용하다.
: MonoBehaviour 스크립트를 프리팹에 넣으면, 해당 프리팹을 인스턴스 화 할 때마다 사본이 생성된다.
: ScriptableObject를 사용하면, 메모리에 데이터 사본을 하나만 저장하여 모든 프리팹이 하나의 메모리에 참조한다.
📝 2. 생성 방법
[CreateAssetMenu(fileName = "MonsterData", menuName = "ScriptableObjects/MonsterData")]
public class Scriptable_Monster : ScriptableObject
{
[SerializeField] private int health;
[SerializeField] private int damage;
[SerializeField] private string monsterName;
// 프로퍼티
public int Health { get => health; set => health = value; }
public int Damage { get => damage; set => damage = value; }
public string MonsterName { get => monsterName; set => monsterName = value; }
}
□ScriptableObject를 상속받아서 구현한다.
□ CreateAssetMenu
■에디터의 메뉴를 통해서 쉽게 에셋을 생성할 수 있게 한다.
■ fileName : 저장할 파일의 이름
■ menuName : create시 눈에 보이는 목록 이름
- 생성방법
상단의 Asset 버튼을 클릭하거나 Asset 폴더 내에서 우클릭 → Create → ScriptableObject → MonsterData 선택
: Asset폴더에 잘 생성되었다.
: 인스펙터창에서 스크립트의 프로퍼티들을 확인할 수 있다.
📝 3. 사용방법
: 각각 다른 필드를 가진 Monster 에셋을 여러 개 만들었다: 예를 들어서, Health는 10, Damage는 10, Name은 Duck으로 입력해 준다.
MonsterTest라는 Monobehavior을 상속받는 클래스를 하나 만들어주고, Slime 프리팹에 추가한다.
public class MonsterClass
{
private int health;
private int damage;
private string monsterName;
}
public class MonsterClassInstanceTest : MonoBehaviour
{
[SerializeField]
MonsterClass monster;
void Start()
{
monster = new MonsterClass();
Debug.Log("클래스 인스턴스화 : " + monster.GetHashCode());
}
}
각각 10번씩 반복해서 Instantiate (인스턴스화) 해보았다.
public class ScriptableSpawn : MonoBehaviour
{
[SerializeField]
private GameObject _objectableSlime; // ScriptableObject를 사용하는 프리팹
[SerializeField]
private GameObject _classSlime; // 클래스를 사용하는 프리팹
private void Start()
{
for (int i = 0; i < 10; i++)
{
Instantiate(_objectableSlime);
Instantiate(_classSlime);
}
}
}
- 출력
□ScriptableObject를 사용하는 오브젝트들은 다 같은 Hashcode를 가진다.
■ 모든 오브젝트가 Asset으로 생성된 동일한 ScriptableObject를 참조하기 때문이다.
□ 클래스를 인스턴스화하는 오브젝트들은 다 다른 Hashcode를 가진다.
■ new 키워드를 사용했기 때문에 각각 독립적인 메모리에 할당되어 있다.
■ 각 인스턴스는 자신만의 데이터 복사본을 가진다.
(+)
🔖문제점
상황에 따라 동일한 ScriptableObject를 참조하는 것이 문제가 될 수 있다.
예를 들어, 던전에 슬라임 10마리가 생성되어서 슬라임 한 마리를 처치했는데, 즉 Health가 0 이하로 떨어졌는데
다 같은 메모리를 참조하기 때문에 던전에 생성된 모든 슬라임의 Health가 0 이하가 되는 대참사가 발생할 수도 있다.
1. 해결방법
: 변경될 수 있는 데이터는 각 인스턴스에서 별도로 관리해야 한다.
public class Monster : MonoBehaviour
{
public MonsterData monster; // ScriptableObject
private int currentHealth; // 변하는 데이터
void Start()
{
// 초기 체력은 ScriptableObject에서 복사
currentHealth = monster.Health;
}
}
private void F_CheckLimitAndFollow()
{
// x와 y 위치를 각각 제한
float clampedX = Mathf.Clamp(_playerTrs.position.x,
_leftDownEdge.position.x + halfWidth,
_rightUpEdge.position.x - halfWidth);
float clampedY = Mathf.Clamp(_playerTrs.position.y,
_leftDownEdge.position.y + halfHeight,
_rightUpEdge.position.y - halfHeight);
// 최종 카메라 위치 설정
_camera.position = new Vector3(clampedX, clampedY, 0);
// 델리게이트
public delegate void Attack(float damage);
public class AttackHandler
{
public Attack OnTtack;
public void AttackExcute(float damage)
{
OnTtack?.Invoke(damage);
}
public void AddToAttack(Attack attck)
{
OnTtack += attck;
}
}
public class Player
{
public float hp;
public float damage;
public Player(float hp,float d)
{
this.hp = hp;
this.damage = d;
}
public void GetAttack(float damage)
{
hp -= damage;
Console.WriteLine($"{damage} 데미지를 얻었습니다.");
}
}
public class SystemAlarm
{
public void UpdateUi(float damage)
{
Console.WriteLine($"player가 {damage} 를 입었습니다.");
}
}
static void Main(string[] args)
{
//
AttackHandler AKHandler = new AttackHandler();
// 인스턴스 생성
Player player = new Player(10f, 3f);
SystemAlarm alarm = new SystemAlarm();
AKHandler.AddToAttack(player.GetAttack);
AKHandler.AddToAttack(alarm.UpdateUi);
// 델리게이트 실행
AKHandler.AttackExcute( 7f );
}