Hero image

게임 데이터와 로직을 ScriptableObjects로 분리하세요

이 웹페이지는 이해를 돕기 위해 기계 번역으로 제공됩니다. 기계 번역으로 제공되는 콘텐츠에 대한 정확도나 신뢰도는 보장되지 않습니다. 번역된 콘텐츠의 정확도에 관해 의문이 있는 경우 웹페이지의 공식 영어 원문을 참고해 주시기 바랍니다.

이 페이지는 게임 코드에서 데이터와 로직을 분리하는 데이터 컨테이너로서 ScriptableObjects를 사용하는 방법을 설명합니다.

이것은 Unity 개발자를 돕기 위해 만들어진 여섯 개의 미니 가이드 시리즈 중 두 번째입니다. 이 가이드는 전자책 ScriptableObjects로 Unity에서 모듈형 게임 아키텍처 만들기.에 수반되는 데모를 설명합니다.

이 데모는 고전적인 공과 패들 아케이드 게임 메커니즘에서 영감을 받았으며, ScriptableObjects가 테스트 가능하고 확장 가능하며 디자이너 친화적인 구성 요소를 만드는 데 어떻게 도움이 되는지를 보여줍니다.

전자책, 데모 프로젝트 및 이러한 미니 가이드는 Unity 프로젝트에서 ScriptableObject 클래스를 사용하여 프로그래밍 디자인 패턴을 사용하는 모범 사례를 제공합니다. 이 팁은 코드를 단순화하고, 메모리 사용량을 줄이며, 코드 재사용성을 촉진하는 데 도움이 될 수 있습니다.

이 시리즈에는 다음 기사가 포함됩니다:

시작하기 전에 중요한 참고 사항

ScriptableObject 데모 프로젝트와 이 미니 가이드 시리즈에 들어가기 전에, 디자인 패턴의 핵심은 단지 아이디어라는 것을 기억하세요. 모든 상황에 적용되지 않을 것입니다. 이 기술들은 Unity와 ScriptableObjects를 다루는 새로운 방법을 배우는 데 도움이 될 수 있습니다.

각 패턴은 장단점이 있습니다. 특정 프로젝트에 의미 있게 도움이 되는 것만 선택하세요. 디자이너들이 Unity Editor에 많이 의존하나요? ScriptableObject 기반 패턴은 그들이 개발자와 협력하는 데 좋은 선택이 될 수 있습니다.

궁극적으로, 최고의 코드 아키텍처는 당신의 프로젝트와 팀에 맞는 것입니다.

탭2

데이터 컨테이너

소프트웨어 개발자들은 종종 모듈성에 관심이 있습니다 - 애플리케이션을 더 작고 독립적인 단위로 나누는 것입니다. 각 모듈은 애플리케이션 기능의 특정 측면에 대한 책임을 집니다.

Unity에서 ScriptableObjects는 데이터와 로직의 분리에 도움을 줄 수 있습니다.

ScriptableObjects는 데이터를 저장하는 데 뛰어나며, 특히 정적일 때 그렇습니다. 이것은 게임 통계, 아이템 또는 NPC의 구성 값, 캐릭터 대화 등 다양한 용도로 이상적입니다.

게임 플레이 데이터를 행동 로직과 분리하면 프로젝트의 각 독립적인 부분을 테스트하고 유지 관리하기가 더 쉬워질 수 있습니다. 이 "관심사의 분리"는 필요한 변경을 할 때 의도하지 않은 원치 않는 부작용을 줄일 수 있습니다.

tab3

일반 워크플로우

스크립터블 오브젝트 워크플로우에 대한 복습이 필요하다면, 이 Unity Learn 기사가 도움이 될 수 있습니다. 그렇지 않다면, 간단한 설명은 다음과 같습니다:

스크립터블 오브젝트 정의하기: 하나를 만들기 위해, 저장하려는 데이터에 대한 필드와 속성을 가진 스크립터블 오브젝트 기본 클래스를 상속하는 C# 클래스를 정의합니다. 스크립터블 오브젝트는 모노비헤이비어에 사용할 수 있는 동일한 데이터 유형을 저장할 수 있어, 다재다능한 데이터 컨테이너입니다. 에디터에서 생성 자산 메뉴 속성를 추가하여 프로젝트에서 자산을 쉽게 생성할 수 있습니다.

자산 생성하기: 스크립터블 오브젝트 클래스를 정의한 후, 프로젝트에서 해당 스크립터블 오브젝트의 인스턴스를 생성할 수 있습니다. 이는 디스크에 저장된 자산으로 나타나며, 다양한 게임 오브젝트와 장면에서 재사용할 수 있습니다.

값 설정하기: 자산을 생성한 후, 인스펙터에서 필드와 속성의 값을 설정하여 데이터를 채웁니다.

자산 사용하기: 자산이 데이터를 보유하게 되면, 변수나 필드에서 참조합니다. 스크립터블 오브젝트 자산에 대한 모든 변경 사항은 전체 프로젝트에 반영됩니다.

스크립터블 오브젝트를 게임의 다양한 부분에서 데이터 컨테이너로 재사용할 수 있습니다. 예를 들어, 무기나 캐릭터의 속성을 스크립터블 오브젝트 내에서 정의한 다음, 프로젝트의 어디에서든 해당 자산을 참조할 수 있습니다.

참고: 런타임 중에 인스턴스 생성 메서드를 통해 스크립터블 오브젝트를 생성할 수도 있습니다. 그러나 데이터 저장을 위해, 일반적으로 생성 자산 메뉴 속성를 사용하여 미리 스크립터블 오브젝트 자산을 생성합니다.

탭4

ScriptableObjects 대 MonoBehaviours

ScriptableObjects가 MonoBehaviours보다 데이터 저장에 더 적합한 이유를 더 잘 이해하기 위해 각자의 빈 버전을 비교해 보세요. 자산 직렬화모드로 설정했는지 확인하세요: 프로젝트 설정에서 Force Text를 설정하여 YAML 마크업을 텍스트로 볼 수 있습니다.

다른 모든 것이 비어 있는 MonoBehaviour로 새 GameObject를 만드세요. 그런 다음 빈 ScriptableObject 자산과 비교해 보세요. 나란히 배치하면 위 이미지에 표시된 비교와 같아야 합니다.

ScriptableObjects는 MonoBehaviours에 비해 가벼우며, Transform 컴포넌트와 같은 후속 오버헤드를 지니지 않습니다. 이로 인해 ScriptableObjects는 더 작은 메모리 풋프린트를 가지며 데이터 저장에 더 최적화됩니다.

tab5

패턴 데모

ScriptableObjects는 자산으로 저장되므로 재생 모드 외부에서도 지속되며, 이는 유용할 수 있습니다. 예를 들어, ScriptableObject 데이터는 새로운 장면을 로드하더라도 어디서나 사용할 수 있습니다.

패턴 데모 예제는 직접 테스트할 수 있는 기본 크레딧 화면을 특징으로 합니다. Credits_Data ScriptableObject를 수정한 후 업데이트를 눌러 저장된 텍스트가 나타나는 것을 지켜보세요.

광범위한 대화가 있는 RPG나 미리 작성된 스크립트가 있는 튜토리얼 장면이 있다면, 이는 많은 데이터를 저장하는 일반적인 방법입니다.

ScriptableObject 내의 데이터는 수정 시 즉시 업데이트되지만, 우리의 프로젝트는 화면을 수동으로 새로 고치기 위해 업데이트 버튼이 필요합니다. UI Toolkit 기반 화면은 한 번만 생성되며 데이터가 변경되었을 때 알림을 받아야 합니다.

업데이트를 자동으로 동기화하려면 ScriptableObject 내에 이벤트를 생성하세요. 예를 들어, 이 ExampleSO 스크립트는 OnValueChanged 이벤트를 ExampleValue가 변경될 때마다 호출합니다. 아래 코드 예제를 참조하세요.

그런 다음, 청취 UI 객체가 OnValueChanged에 구독하고 그에 따라 업데이트되도록 하세요.

[CreateAssetMenu(menuName = "ExampleSO")]
public class ExampleSO : ScriptableObject
{
    public Action OnValueChanged;
    
    [SerializeField] private int m_ExampleValue;
    public int ExampleValue
    {
        get => m_ExampleValue;
        set
        {
            m_ExampleValue = value;
            OnValueChanged?.Invoke();
        }
    }
}
tab6

플라이웨이트 패턴으로 게임 데이터 저장하기

스크립터블 오브젝트는 많은 객체가 동일한 데이터를 공유할 때 빛을 발합니다. 예를 들어, 여러 유닛이 동일한 공격 속도와 최대 체력을 가진 전략 게임을 만들고 있다면, 각 게임 오브젝트에 이러한 값을 개별적으로 저장하는 것은 비효율적입니다.

대신, 공유 데이터를 중앙 집중식으로 통합하고 각 객체가 해당 공유 위치를 참조하도록 할 수 있습니다. 소프트웨어 설계에서 이것은 플라이웨이트 패턴으로 알려진 최적화입니다. 이렇게 코드를 재구성하면 많은 값을 복사하는 것을 피하고 메모리 사용량을 줄일 수 있습니다.

패들볼SO에서 GameDataSO 스크립터블 오브젝트는 공유 데이터 저장소 역할을 합니다.

일반 설정(속도, 질량, 물리적 반발력 등)의 별도 복사본을 유지하는 대신, 패들 및 볼 스크립트는 가능한 경우 동일한 GameDataSO 인스턴스를 참조합니다. 각 게임 요소는 위치 및 입력 이벤트와 같은 고유 데이터를 유지하지만, 가능한 경우 공유 데이터로 기본 설정됩니다.

메모리 절약이 두세 개의 객체로는 눈에 띄지 않을 수 있지만, 공유 데이터를 편집하는 것은 각 객체를 수동으로 편집하는 것보다 빠르고 오류가 발생할 가능성이 적습니다.

예를 들어, 패들 속도를 수정해야 하는 경우, 단일 위치에서 조정하면 모든 장면에서 두 개의 패들이 모두 업데이트됩니다. 만약 MonoBehaviours에 고유 필드로 저장했다면, 하나의 잘못된 클릭으로 두 값이 쉽게 동기화되지 않을 수 있습니다.

데이터를 스크립터블 오브젝트로 오프로드하면 버전 관리에 도움이 되고 팀원이 동일한 장면이나 프리팹에서 작업할 때 병합 충돌을 방지할 수 있습니다.

tab7

PaddleBallSO 게임 데이터

GameDataSO는 스크립터블 오브젝트를 데이터 컨테이너로 사용하는 방법을 보여줍니다. 패들볼SO에서, 이는 게임 플레이를 구성하는 다양한 설정을 포함합니다:

  • 패들 데이터: 패들 속도, 드래그 및 질량과 같은 속성은 게임 플레이 중 패들의 움직임과 물리적 특성을 결정합니다.
  • 볼 데이터: 이것은 볼의 현재 속도, 최대 속도 및 반발 배수를 보유하며, 이는 볼이 시뮬레이션과 상호작용할 때의 동작을 제어합니다.
  • 매치 데이터: GameDataSO는 매치 중 포인트 간의 지연에 대한 정보를 포함하여 게임의 빈도를 제어하는 데 도움을 줍니다.
  • 플레이어 ID: PlayerIDSO 스크립터블 오브젝트는 각 플레이어(예: Player1 및 Player2)에 대한 팀 식별 기능을 수행합니다.
  • 플레이어 스프라이트: 이 선택적 스프라이트는 플레이어 아바타 사용자 지정을 가능하게 합니다.
  • 레벨 레이아웃: LevelLayoutSO 객체는 플레이어와 목표 및 벽과 같은 게임 요소의 시작 위치를 정의합니다.

이 설정과 데이터가 중앙 위치에 모두 모여 있으면 GameDataSO는 어떤 객체든 이 공유 데이터에 접근할 수 있게 합니다. 이것은 이러한 객체를 관리하는 방식을 단순화하고 프로젝트 전반에 걸쳐 더 큰 일관성을 촉진합니다. 패들 물리학 변경? 여기에서 한 번의 변경으로 여러 스크립트를 조정하는 대신 변경하세요.

tab10

이중 직렬화 예: 레벨 레이아웃

가끔은, 당신은 케이크를 가지고 먹을 수도 있습니다. 이중 직렬화를 사용하면 ScriptableObject에 데이터를 저장하면서 동시에 다른 형식으로 유지할 수 있습니다.

LevelLayoutSO 스크립트는 이 개념을 보여줍니다. 패들과 공의 시작 위치를 보유하는 것 외에도, 벽과 목표에 대한 변환 데이터를 사용자 정의 구조체에 저장합니다.

이 값들은 ExportToJson 메서드를 통해 디스크에 기록될 수 있습니다. JSON 파일은 사람이 읽을 수 있는 텍스트로, Unity 외부에서 간단한 수정을 가능하게 합니다. 이것은 에디터에서 ScriptableObjects와 작업한 다음 JSON 또는 XML 파일과 같은 다른 위치에 데이터를 저장할 수 있게 합니다.

JSON 및 XML과 같은 파일 형식은 에디터에서 작업하기 어려울 수 있지만, 텍스트 편집기에서 Unity 외부에서 수정하기는 쉽습니다. 이것은 사용자 정의 또는 유저 모드 레벨의 가능성을 열어줍니다.

게임 설정 스크립트는 LevelLayout ScriptableObject 또는 외부 JSON 파일을 사용하여 게임 레벨을 생성할 수 있습니다.

사용자 정의 모드 레벨을 로드하기 위해, 설정 스크립트는 CreateInstance로 런타임에 ScriptableObject를 생성합니다. 그런 다음, JSON 파일에서 텍스트를 읽어 ScriptableObject를 채웁니다.

당신의 사용자 정의 데이터는 ScriptableObject의 내용을 대체하고, 이 외부 모드 레벨을 다른 레벨처럼 사용할 수 있게 해줍니다. 응용 프로그램의 나머지 부분은 정상적으로 작동하며, 변경 사항을 인식하지 못합니다.

tab11

ScriptableObject 데이터 컨테이너의 다른 용도

우리의 패들 볼 미니 게임이 ScriptableObject 데이터 컨테이너의 모든 사용 사례를 보여줄 수는 없지만, 당신의 응용 프로그램을 위해 다음을 고려해 보세요:

  • 게임 구성: 상수, 게임 규칙 또는 게임 플레이 중에 변경할 필요가 없는 기타 설정 매개변수에 대해 생각해 보세요. 다른 구성 요소는 하드 코딩된 값을 사용하지 않고 이 구성 데이터에 참조할 수 있습니다.
  • 캐릭터 및 적 속성: ScriptableObjects를 사용하여 건강, 공격력, 속도 등의 속성을 정의하세요. 이것은 당신의 디자이너가 개발자 없이 게임 플레이 요소를 조정하고 균형을 맞출 수 있게 해줍니다.
  • 인벤토리 및 아이템 시스템: 아이템 정의 및 이름, 설명, 아이콘과 같은 속성은 ScriptableObjects에 적합합니다. 또한 플레이어가 수집, 사용 또는 장착하는 아이템을 추적하는 인벤토리 관리 시스템의 일부로 사용할 수 있습니다.
  • 대화 및 내러티브 시스템: ScriptableObjects는 대화 텍스트, 캐릭터 이름, 분기 대화 경로 및 기타 내러티브 관련 데이터를 저장할 수 있습니다. 이들은 복잡한 대화 시스템의 기초를 마련할 수 있습니다.
  • 레벨 및 진행 데이터: ScriptableObjects를 사용하여 레벨 레이아웃, 적 스폰 포인트, 목표 및 기타 레벨 관련 정보를 정의할 수 있습니다.
  • 오디오 클립: PaddleBallSO 프로젝트에서 볼 수 있듯이, ScriptableObjects는 하나 이상의 오디오 클립을 저장할 수 있습니다. 이들은 게임의 여러 부분에서 오디오 효과나 음악을 정의할 수 있습니다.
  • 애니메이션 클립: ScriptableObjects는 애니메이션 클립을 저장하는 데 사용될 수 있으며, 이는 여러 GameObject 또는 캐릭터에서 공유되는 일반 애니메이션을 정의하는 데 유용합니다.

ScriptableObjects를 더 깊이 탐구하고 자신의 프로젝트에 맞게 조정하면서, 이들을 위한 더 많은 응용 프로그램을 발견하게 될 것입니다. 이들은 데이터를 관리하는 데 특히 유용하며, 다양한 게임 요소 간의 일관성을 유지하는 데 더 쉽게 만들어 줍니다.

스크립터블 아우트로

더 많은 ScriptableObject 리소스

e-book ScriptableObjects로 Unity에서 모듈식 게임 아키텍처 만들기에서 ScriptableObjects와 함께하는 디자인 패턴에 대해 더 읽어보세요. 게임 프로그래밍 패턴으로 코드를 향상시키기에서 일반 Unity 개발 디자인 패턴에 대해 더 알아볼 수 있습니다.