내일배움캠프 4주차 3일차 TIL - ScriptableObject

백흰범·2024년 5월 9일
0
post-custom-banner

오늘 한 일

  • 알고리즘 문제 풀기 (LeetCode 3075 문제)
  • 스파르타 강의 진도 나가기 (~1-13)
  • ScriptAbleObject 파헤치기

오늘은 강의 진도를 나아가면서 여러가지를 알게 되었는데 그 중에 Unity의 주요 기능 중 하나인 ScriptableObject에 대해서 자세히 알아보려고 한다.


ScriptableObject

간단 설명

클래스 인스턴스와는 별도로 대량의 데이터를 저장하는 데 사용할 수 있는 데이터 컨테이너입니다. 에디터 세션 동안 데이터를 저장 및 보관하고, 데이터를 프로젝트의 에셋으로 저장하여 런타임 시 사용합니다.


간략한 사용 과정

1. ScriptableObject를 상속받는 스크립트 작성

2. 작성 후 추가된 CreateAssetMenu에서 커스텀 에셋 생성

3. 생성된 ScriptableObject의 field 데이터들을 조정

4. ScriptableObject의 데이터 사용



직접 만들어보자 (사용한 코드 - Unity 메뉴얼)

1. ScriptableObject를 상속받는 스크립트 작성

  • 개체 생성에 관한 정보를 담는 SO 스크립트

using UnityEngine;

[CreateAssetMenu(fileName = "Data", menuName = "ScriptableObjects/SpawnManagerScriptableObject", order = 1)]
public class SpawnManagerScriptableObject : ScriptableObject
{
    // 생성된 애들 이름
    public string prefabName;

    // 얼마의 프리팹을 만들까? 
    public int numberOfPrefabsToCreate;
    // 생성 지점 리스트
    public Vector3[] spawnPoints;
}

그럼 위에 있는 코드들을 하나씩 하나씩 설명해보겠다.


CreateAssetMenu

설명

ScripableObject를 상속 받은 스크립트를 Assets/Create 메뉴에 표시하여 Asset에 쉽게 생성하고 저장하기 위해 사용되는 속성(Attribute)이다.


  • fileName
    • 생성될 시 기본적으로 부여되는 파일 이름

  • menuName
    • Create 하위 메뉴바에서 보여지게 될 이름을 정한다 '/'처리를 할 때마다 새로운 하위 메뉴로 이어간다.

  • order
    • Create 메뉴바에서 보여지게될 우선순위를 정해주는 변수이다.
    • order = 1
      • 맨 상단에 위치
    • order = int.MaxValue
      • 맨 하단에 위치


ScriptableObject

설명

Unity의 주요 기능 중 하나로 게임에서 주로 재사용 가능한 데이터 또는 설정을 저장하는데 사용되는 클래스입니다.
기본 유니티 프로젝트에서 파생되나, MonoBehaviour과 달리 게임 오브젝트에 연결할 수 없으므로 CreateAssetMenu와 함께 사용하여 프로젝트 에셋으로 저장한 후에 활용해야합니다.
Unity 에디터와 통합되어 인스펙터 창에서 직접 수정하고 관리할 수 있습니다.

  • 사실 CreateAssetMenu를 제외한 딱히 사용되는 코드는 없고, 이걸 상속을 받게된다면 Create 메뉴바로 해당 오브젝트를 빠르게 생성하고 해당 스크립트 내 작성한 클래스의 필드들을 마음껏 커스터마이징할 수 있는 기능을 얻게 된다.

! 주의할 점

  • 필드들의 접근 제한자는 최소한 public 범위 정도는 되어야한다.
    (protected 이하로 내려가면 다른 오브젝트에서 해당 필드의 사용이 힘들다.)

+ [Head("Category")]

  • 인스펙터 창에서 데이터를 관리할 때 가독성을 높여주는 역할을 해준다.
    • 사용하는 방법
  [Header("Attack Info")]
    public float size;
    public float delay;
    public float power;
    public float speed;
    public LayerMask target;

    [Header("Knock Back Info")]
    public bool isOnKnockback;
    public float knockbackPower;
    public float knockbackTime;
  • 사진처럼 데이터들을 목록화하여 보기 좋게 관리해줄 수 있다.


2. 작성 후 추가된 CreateAssetMenu에서 커스텀 에셋 생성

1. 파일을 이와 같이 만들어 정리를 쉽게 한다

  • Scripts는 ScriptableObject 스크립트들이 모이는 곳, 뒤에 SO 붙은 게 ScriptableObject 에셋을 생성하는 곳이다.

2. Scriptable Object 생성

  • 우클릭 -> Create -> menuName 지정해준대로 찾아가주면 이와 같이 생성된다.

3. Scriptable Object 이름 지정

  • F2를 눌러 이름을 지어주자

사실 이 항목은 크게 중요한 거 없고 정리를 잘해주자



3. 생성된 ScriptableObject의 field 데이터들을 조정

  • 생성한 오브젝트의 Inspector 보면 밑의 사진처럼 나와있을 것이다.
  • 원하는대로 필드를 마음껏 조종해보자

+ Range(minValue, maxValue)

  • 값의 범위를 지정해 제한 시키는 속성이다.
[Range(1, 10)] public int numberOfPrefabsToCreate;

위와 같이 접근 제한자보다 앞세워서 사용하는 속성(Attribute)인데 이처럼 코드를 짜주면,

  • Inpsector에서 위와 같이 슬라이더가 생기는데

  • 최소값과 최대값의 범위만큼 움직일 수 있다.


4. ScriptableObject의 데이터 사용

  • SO를 참조하는 스크립트
public class Spawner : MonoBehaviour
{
    // 생성에 사용될 게임 오브젝트
    public GameObject entityToSpawn;

    // 정의되었던 SO를 담아줄 변수이다.
    public SpawnManagerScriptableObject spawnManagerValues; // 생성 매니저 SO

    // 개체를 생성할 때마다 이름에 같이 붙여줄 숫자로, 개체가 생성될 때마다 증가한다.
    int instanceNumber = 1;

    void Start()
    {
        SpawnEntities();    // 스폰 메서드
    }

    void SpawnEntities()
    {
        int currentSpawnPointIndex = 0; // 소환 작업에 쓰일 인덱스

        for (int i = 0; i < spawnManagerValues.numberOfPrefabsToCreate; i++) // SO에 있는 Prefabs 소환 횟수
        {
            // SO에서 정해진 스폰 지점에서 프리팹의 인스턴스 생성 
            GameObject currentEntity = Instantiate(entityToSpawn, spawnManagerValues.spawnPoints[currentSpawnPointIndex], Quaternion.identity);

            // SO에 정의된 문자열에 고유한 숫자를 덧붙여줘서 생성된 개체의 이름을 지어준다.  
            currentEntity.name = spawnManagerValues.prefabName + instanceNumber;

            // 다음 소환 지점의 인덱스로 이동한다.. 인덱스의 범위를 넘어간다면, 시작점으로 다시 되돌아간다.
            currentSpawnPointIndex = (currentSpawnPointIndex + 1) % spawnManagerValues.spawnPoints.Length;

            instanceNumber++;
        }
    }
}
  • SO를 담아줄 변수와 그 SO를 사용하는 메서드를 구현하고자 하는 기능대로 짜주면 된다.
    ! 스크립트 파일의 이름이 클래스와 동일해야한다.(Unity의 규칙)

  • Inspector 세팅

  • SpawnManagerSO

  • Spawner Object

  • 실행 결과

    • 위와 같이 지정한 스폰 횟수와 스폰 지점대로 생성이 잘 된다.


장단점

장점

1. 클래스 필드의 다양한 값들을 쉽고 빠르게 만들 수 있다.

  • Create 메뉴바에서 SO를 만든 후 Inspector에서 원하는 대로 건들여주면 된다.

2. 기존에 프리팹을 생성할 때마다 해당 데이터의 자체 사본이 생성되는데, 이러한 방법을 사용하여 중복 데이터를 저장하는 대신 ScriptableObject를 이용하여 데이터를 저장한 후 모든 프리팹의 참조를 통해 동일한 정보를 전달할 수 있다.

  • 기존 방식
    • 같은 값을 계속해서 만들고 있다.
  • SO를 이용한 방식
    • SO를 참조하여 값을 가져오고 있다. (불필요한 중복 데이터로 인한 데이터 낭비 감소)
  • 그래서 많은 양의 동일한 정보를 사용하는 오브젝트를 생성할 때 유리하다.

3. 생성된 스크립터블 오브젝트는 asset파일로 저장되기 때문에 다른 Unity 프로젝트로 복사할 수 있다.

  • 1주차 팀프로젝트 과제로 옮겨보면 잘 옮겨진다.
  • 주의사항
    • SO의 본체인 스크립트를 빼먹으면 안된다.
    • 동일한 파일 경로를 유지해줘야한다. (Unity가 에셋의 상대 경로를 사용한다.)
    • 버전 간 호환성 및 의존되는 스크립트나 플러그인을 잘 봐줘야한다.
    • 파일을 이동할 때는 Unity의 Export Package 기능을 사용하면 다른 프로젝트로 쉽게 이동할 수 있다.

단점

1. 런타임 중에서는 ScriptableObject의 데이터를 수정할 수 없다. 그래서 런타임 중에 매번 변동되어야하는 데이터는 사용을 지양하는 것이 좋다.

주로 만들 수 있는 것

  • 변수 설계 (데이터 컨테이너, 열거형)

  • 이벤트 설계 (델리게이트 및 옵서버 패턴)

  • 런타임 세트

지금 내가 생각하는 사용 시점

1. 게임에서 재사용이 잦은 스텟이 있을 때 (Hp, Attack, Speed와 같은 데이터들)

2. 아이템 사전이나 몬스터 사전과 같이 오브젝트끼리 같은 변수를 사용하지만 서로 간의 값을 달리하려할 때 (아이템 enum, 몬스터 enum 등)

3. 오브젝트의 생성을 담당하는 오브젝트가 존재할 때 (생성 지점, 생성 횟수, 생성 조건 등등)

참고자료




추가로 알게된 점들

- Attribute(속성)

이 녀석들은 []"대괄호"를 주로 사용하고 접근 제한자보다 앞서 사용하는 특징을 가졌다.




작성하면서 느낀점

위 내용을 작성하면서 알게된 게, 내가 처음에 정확한 자료가 별로 없는 줄 알고 Unity 매뉴얼과 봤던 유튜브 영상만으로 내용을 작성하다가 뭔가 부족하다 싶어서 더 조사해본 결과 스크립터블 오브젝트를 전문적으로 다룬 자료를 찾아내게 되었다. 아무래도 TIL을 작성하기 전에 자료 조사 측면에서 좀 더 신경을 쓰고 조사를 해야겠다는 생각이 들었다.
그리고 추후에 시간이 된다면 이벤트 설계와 런타임 세트를 활용한 스크립터블 오브젝트도 한번 활용해볼 생각이다.

profile
게임 개발 꿈나무
post-custom-banner

0개의 댓글