무엇을 찾고 계신가요?

협업과 코딩 측면에서 유용한 스크립터블 오브젝트 활용법 6가지

THOMAS KROGH-JACOBSEN / UNITY TECHNOLOGIESProduct Marketing Core Tech
Apr 20, 2023|17 Min
Hero image

Unity에서 스크립터블 오브젝트로 모듈식 게임 아키텍처 만들기 기술 전자책이 새로 발간되었다는 기쁜 소식을 전합니다. 이 전자책에서는 프로덕션 단계에서 스크립터블 오브젝트를 배포하는 방법에 대한 전문 개발자들의 베스트 프랙티스를 소개합니다.

고전적인 벽돌깨기 아케이드 게임 메카닉을 바탕으로 제작된 데모 프로젝트를 GitHub에서 이 전자책과 함께 다운로드할 수 있습니다. 이 데모를 통해 스크립터블 오브젝트로 디자이너 친화적인 방식을 유지하면서 테스트 및 확장/축소가 용이한 컴포넌트를 만드는 방법을 살펴볼 수 있습니다. 실제로는 이런 유형의 게임은 훨씬 더 적은 양의 코드로 빌드할 수 있지만 이 데모는 스크립터블 오브젝트의 사용 사례를 제시하는 데 중점을 둡니다.

A ball and paddle demo accompanies the e-book.
스크립터블 오브젝트 데모 프로젝트는 해당 전자책과 함께 Github에서 받을 수 있습니다.

이 게시물은 스크립터블 오브젝트가 주는 이점에 대해 설명하며 기초적인 내용이나 Unity 코딩의 일반론은 다루지 않습니다. Unity 프로그래밍이 처음이라면 유용한 입문자용 튜토리얼이 제공되는 Unity Learn을 이용하세요. 이 전자책의 첫 번째 챕터에서도 기본적인 개념을 살펴볼 수 있습니다.

이제 프로젝트에 스크립터블 오브젝트를 사용해 얻을 수 있는 6가지 이점을 알아보겠습니다. 더 자세히 알고 싶으신가요? 이 예시는 모두 해당 전자책과 데모 프로젝트에서 더 자세히 살펴볼 수 있습니다.

1. 효율적인 팀 협업

여기서 소개한 대부분의 기법은 C# 클래스를 사용해 구현할 수 있지만 스크립터블 오브젝트는 특히 아티스트와 디자이너가 쉽게 접근할 수 있다는 장점을 가집니다. 코드를 편집할 필요 없이 스크립터블 오브젝트를 사용해 프로젝트의 게임 로직을 설정하고 적용할 수 있기 때문입니다.

에디터를 통해 편리하게 스크립터블 오브젝트를 확인하고 편집할 수 있으므로 디자이너는 개발자 팀의 대규모 지원 없이도 게임플레이 데이터를 설정할 수 있습니다. 이러한 장점은 스크립터블 오브젝트를 추가하여 NPC에 동작을 적용하는 등 게임 로직의 측면에서도 빛을 발합니다(아래 패턴에서 설명).

두 사람이 동일한 프리팹이나 씬의 서로 다른 부분을 변경할 때 단일 MonoBehaviour에 데이터와 로직을 저장하면 병합 충돌이 발생하여 많은 시간이 소요될 수 있습니다. 스크립터블 오브젝트로 공유 데이터를 더 작은 파일과 에셋으로 분할하면 디자이너는 테스트 전에 개발자가 코드로 게임플레이를 설정하는 작업을 마칠 때까지 기다리지 않고도 개발자와 동시에 게임플레이를 빌드할 수 있습니다.

서로 다른 역할을 가진 동료들이 게임 코드와 에셋에 동시에 액세스하면 문제가 발생할 수 있습니다. 스크립터블 오브젝트를 사용하면 프로젝트의 어떤 부분을 에디터에서 편집할 수 있는지 프로그래머가 제어할 수 있습니다. 또한 스크립터블 오브젝트로 코드를 구성하면 더 모듈화되고 효율적인 테스트가 가능한 코드베이스가 자연스럽게 생성됩니다.

게임 디자이너가 스크립터블 오브젝트를 사용하는 방법 살펴보기

크리스토 놉스는 시스템 게임 디자인 및 Unity(C#)를 전문으로 하는 시니어 테크니컬 게임 디자이너로 Unity 게임 디자이너 플레이북 제작에 도움을 주었으며, Unity에서 게임 시스템을 디자인하는 방법에 대한 블로그 게시물 시리즈의 주요 저자로 활동하고 있습니다. 크리스토 놉스가 게시한 ‘생태계를 만드는 시스템: 새로운 게임 디자인’ 및 ‘예측할 수 없는 즐거움: 무작위화가 게임 설계에 주는 이점’은 디자이너가 스크립터블 오브젝트를 사용하는 방법에 대한 흥미로운 예시를 제공합니다.

2. 데이터 컨테이너

모듈성은 스크립터블 오브젝트를 사용하지 않아도 C#으로 구현할 수 있는 일반적인 소프트웨어 원칙입니다. 그러나 위에서 언급한 대로 스크립터블 오브젝트를 사용하면 로직에서 데이터를 분리하여 클린 코딩 프랙티스를 촉진하는 데 도움이 됩니다. 이런 방식은 모듈식 게임 코딩의 첫 번째 단계이기도 합니다. 로직에서 데이터를 분리하면 예상치 못한 부작용 없이 변경 사항을 쉽게 적용할 수 있으며 테스트 가능성도 향상됩니다.

스크립터블 오브젝트는 정적 데이터 저장에 특화되어 있으므로 아이템 또는 NPC 능력치, 캐릭터 대화 등 정적 게임플레이 값을 설정하는 데 유용합니다. 스크립터블 오브젝트는 에셋으로 저장되어 게임 모드에서 벗어나도 유지되기 때문에 런타임에 동적으로 변경되는 정적 설정으로 로드하는 데 사용할 수 있습니다.

스크립터블 오브젝트 데이터의 변경 사항이 에디터에서 유지되기는 하지만 게임 데이터를 저장하기 위해 스크립터블 오브젝트가 만들어진 것은 아닙니다. 그러한 용도에는 JSON, XML, 바이너리 솔루션(성능이 중요한 경우) 같은 직렬화 시스템을 사용하는 것이 좋습니다.

MonoBehaviour는 호스트 역할을 할 게임 오브젝트와 트랜스폼(기본값)이 필요하기 때문에 추가 오버헤드가 발생합니다. 따라서 단일 값을 저장하기 전에 다량의 미사용 데이터를 생성해야 합니다. 스크립터블 오브젝트는 이러한 메모리 사용량을 줄이고 게임 오브젝트와 트랜스폼의 수를 줄입니다. 프로젝트 수준에서 데이터를 저장하므로 여러 씬에서 동일한 데이터에 액세스해야 하는 경우에도 유용합니다.

일반적으로 많은 게임 오브젝트가 런타임 시 변경할 필요가 없는 중복 데이터에 의존합니다. 각 게임 오브젝트에 이러한 중복 로컬 데이터를 사용하는 대신 스크립터블 오브젝트에 연동할 수 있습니다. 각 오브젝트는 데이터 자체를 복사하는 대신 공유 데이터 에셋에 대한 레퍼런스를 저장합니다. 이렇게 하면 오브젝트 수가 많은 프로젝트에서 성능을 대폭 개선할 수 있습니다.

Many objects with duplicate local data leads to performance inefficiencies.
중복 로컬 데이터를 사용하는 오브젝트가 많으면 성능 효율성이 떨어집니다.
Many objects sharing data (rather than duplicating it) via a ScriptableObject
데이터를 복제하는 대신 스크립터블 오브젝트를 통해 공유하는 다수의 오브젝트

소프트웨어 디자인에서는 이러한 최적화 방식을 플라이웨이트(flyweight) 패턴이라고 합니다. 이렇게 스크립터블 오브젝트로 코드를 재구성하면 값을 복사하지 않고 메모리 사용량을 줄일 수 있습니다. Unity에서 디자인 패턴을 사용하는 방법에 대한 자세한 내용은 게임 프로그래밍 패턴으로 코드 작성 스킬 업그레이드하기 전자책에서 확인하세요.

3. 열거형

비교 작업의 열거형으로 스크립터블 오브젝트를 사용하는 것은 스크립터블 오브젝트를 통한 코드 간소화의 좋은 예시입니다. 스크립터블 오브젝트로 특수 대미지 효과(예: 냉기, 열기, 전기, 마법) 같은 카테고리나 아이템 유형을 나타낼 수 있습니다.

애플리케이션에 게임플레이 아이템 장착을 위한 인벤토리 시스템이 필요한 경우 스크립터블 오브젝트로 아이템 유형이나 무기 슬롯을 나타낼 수 있습니다. 그러면 인스펙터(Inspector)의 해당 필드가 스크립터블 오브젝트 설정을 위한 드래그 앤 드롭 인터페이스로 작동합니다.

Drag-and-drop ScriptableObject-based categories
드래그 앤 드롭 스크립터블 오브젝트 기반 카테고리

스크립터블 오브젝트를 열거형으로 사용하면 스크립터블 오브젝트를 확장하고 데이터를 추가하는 작업이 더 흥미로워집니다. 보통의 열거형과 달리 스크립터블 오브젝트는 추가 필드와 메서드를 가질 수 있습니다. 별도의 룩업 테이블을 사용하거나 새로운 데이터 배열과 연결할 필요가 없습니다.

기존의 열거형에는 고정된 값 집합이 있지만, 스크립터블 오브젝트 열거형은 런타임 시에도 생성하고 수정할 수 있으므로 필요에 따라 값을 추가하거나 제거할 수 있습니다.

명시적으로 번호가 지정되지 않은 긴 열거형 값 목록은 열거형을 삽입하거나 제거하면 순서가 바뀔 수 있습니다. 이렇게 순서가 변경되면 미세한 버그나 의도치 않은 동작이 발생할 수 있습니다. 스크립터블 오브젝트 기반 열거형은 이러한 문제를 일으키지 않습니다. 매번 코드를 변경할 필요 없이 프로젝트에 열거형을 추가하거나 삭제할 수 있습니다.

RPG에서 장착 가능한 아이템을 만든다고 가정해 보겠습니다. 추가 부울 필드를 스크립터블 오브젝트에 추가해 해당 아이템을 구현할 수 있습니다. 특정 아이템을 소지할 수 없는 특정 캐릭터가 있나요? 일부 아이템에 마법이나 특수 능력이 있나요? 스크립터블 오브젝트 기반 열거형으로 전부 구현할 수 있습니다.

4. 델리게이트 오브젝트

스크립터블 오브젝트에 메서드를 만들 수 있으므로 스크립터블 오브젝트는 데이터를 보관하는 용도로 로직이나 동작을 그대로 저장하는 데 유용하게 활용할 수 있습니다. MonoBehaviour에서 스크립터블 오브젝트로 로직을 옮기면 스크립터블 오브젝트를 델리게이트 오브젝트로 사용하여 동작을 더 모듈화할 수 있습니다.

특정 작업을 수행해야 하는 경우 해당 알고리즘을 전용 오브젝트로 캡슐화할 수 있습니다. 이 방식을 고안한 4인방은 이 일반적인 디자인을 전략 패턴이라 칭합니다. EnemyAI 구현을 목적으로 추상 클래스를 사용해 전략 패턴을 더 유용하게 만드는 방법을 아래 예시에서 확인할 수 있습니다. 결과적으로 동작이 서로 다른 여러 스크립터블 오브젝트가 파생되어 각 에셋을 서로 바꿔 사용할 수 있으므로 동작을 연결할 수 있습니다. 선택한 스크립터블 오브젝트를 MonoBehaviour로 드래그 앤 드롭하면 됩니다.

Pluggable behaviors can change at runtime or in the Editor.
연결 가능한 동작을 런타임이나 에디터에서 변경할 수 있습니다.

스크립터블 오브젝트로 동작을 파생시키는 방법에 대한 자세한 예시가 필요하면 스크립터블 오브젝트를 사용한 연결 가능 AI 동영상 시리즈를 참조하세요. 이 세션에서는 상태, 동작, 상태 간 전환과 관련하여 스크립터블 오브젝트를 사용해 설정할 수 있는 유한 상태 머신 기반 AI 시스템에 대해 설명합니다.

5. 이벤트 채널

보통 대규모 프로젝트는 여러 게임 오브젝트에서 데이터나 상태를 공유하되 해당 오브젝트 간의 직접적인 참조는 방지해야 할 때 문제가 발생합니다. 이러한 종속 관계를 대규모로 관리하는 데는 막대한 노력이 필요하고 버그의 원인이 되는 경우도 많습니다. 많은 개발자들이 씬을 로드할 때 유지되는 클래스의 전역 인스턴스 중 하나인 싱글톤을 사용합니다. 안타깝게도 싱글톤을 사용하면 전역 상태를 다뤄야 하고 단위 테스트가 어려워집니다. 싱글톤을 참조하는 프리팹을 사용하는 경우 격리된 함수를 테스트하기 위해 모든 종속 관계를 임포트해야 합니다. 그러면 코드의 모듈화 수준과 디버깅 효율이 떨어집니다.

한 가지 해결책은 게임 오브젝트 통신에 도움이 되는 스크립터블 오브젝트 기반 이벤트를 사용하는 것입니다. 이 경우에는 스크립터블 오브젝트로 관찰자 디자인 패턴의 형태를 구현합니다. 이 패턴에서 주체는 느슨하게 분리된 하나 이상의 관찰자(observer)에게 메시지를 브로드캐스트합니다. 관찰하는 각 오브젝트는 주체로부터 독립적으로 반응할 수 있지만 다른 관찰자를 인식하지는 못합니다. 이러한 주체를 ‘퍼블리셔’ 또는 ‘브로드캐스터’라 부르고 관찰자를 ‘구독자’ 또는 ‘리스너’라 부르기도 합니다.

MonoBehaviour 또는 C# 오브젝트로 관찰자 패턴을 구현할 수 있습니다. Unity 개발에서는 이미 이러한 방식이 일반적이지만, 스크립트만 사용하는 접근 방식에서는 디자이너가 게임플레이 중에 필요한 모든 이벤트를 프로그래밍 팀에 의존해 작업해야 합니다.

The ScriptableObject-based event channel
스크립터블 오브젝트 기반 이벤트 채널

언뜻 보기엔 관찰자 패턴에 오버헤드 레이어를 추가한 것처럼 보여도 이 구조는 몇 가지 이점을 제공합니다. 스크립터블 오브젝트는 에셋이므로 계층 구조에 있는 모든 오브젝트에 액세스할 수 있으며 씬을 로드해도 사라지지 않습니다.

많은 개발자들은 특정 리소스에 쉽고 지속적으로 액세스할 수 있다는 이유로 싱글톤을 사용합니다. 스크립터블 오브젝트는 불필요한 종속 관계를 형성하지 않고도 동일한 이점을 제공하는 경우가 많습니다.

스크립터블 오브젝트 기반 이벤트에서는 모든 오브젝트가 이벤트를 브로드캐스트하는 퍼블리셔 역할과 이벤트를 수신하는 구독자 역할을 수행할 수 있습니다. 스크립터블 오브젝트는 중간에서 신호를 릴레이하는 데 도움을 주며 중앙에서 둘 사이를 중개하는 역할을 맡습니다.

이러한 방식을 ‘이벤트 채널’로 여길 수 있습니다. 스크립터블 오브젝트를 신호를 수신하는 일정 수의 오브젝트가 있는 라디오 타워라고 생각해 보세요. 관련 MonoBehaviour가 해당 이벤트 채널을 구독하고 어떤 일이 발생했을 때 응답을 보낼 수 있습니다.

UI, 사운드, 점수 기록을 위해 게임 이벤트를 설정하는 데 관찰자 패턴이 어떻게 도움이 되는지 데모를 통해 확인해 보세요.

6. 런타임 세트

런타임 시 씬의 게임 오브젝트나 컴포넌트 목록을 추적해야 하는 경우가 자주 발생합니다. 예를 들어 적 목록은 자주 액세스해야 하는 목록이면서도 더 많은 적이 나타나거나 패배함에 따라 변경되는 동적 목록이기도 합니다. 싱글톤은 간편한 전역 액세스를 제공하지만 몇 가지 단점을 가집니다. 싱글톤을 사용하는 대신 데이터를 스크립터블 오브젝트에 ‘런타임 세트’로 저장해 보세요. 스크립터블 오브젝트 인스턴스는 프로젝트 수준에서 나타납니다. 즉 모든 씬의 모든 오브젝트가 사용 가능한 데이터를 저장할 수 있으므로 비슷한 전역 액세스를 제공합니다. 데이터가 에셋에 있으므로 공용 아이템 목록에 언제든지 액세스할 수 있습니다.

이 사용 사례에서 나타난 특수한 데이터 컨테이너는 요소의 공용 컬렉션을 유지할 뿐만 아니라 컬렉션에서 추가하거나 제거할 수 있는 기본 메서드도 제공합니다. 그러면 싱글톤의 필요성이 줄어들고 테스트 용이성과 모듈성이 향상됩니다.

A Runtime Set provides global access to a collection of data.
런타임 세트는 데이터 컬렉션에 대한 전역 액세스를 제공합니다.

Object.FindObjectOfType 또는 GameObject.FindWithTag 같은 찾기 연산으로 씬 계층 구조를 검색하는 것보다 스크립터블 오브젝트에서 직접 데이터를 읽는 편이 최적화 측면에서 더 바람직합니다. 사용 사례와 계층 구조 크기에 따라 프레임별 업데이트 효율성이 떨어져 비교적 많은 비용이 소요될 가능성이 있습니다.

여기에서 소개한 6가지 시나리오보다 많은 사용 사례를 제공하는 몇 가지 스크립터블 오브젝트 프레임워크가 존재합니다. 스크립터블 오브젝트를 폭넓게 사용하는 팀이 있는 반면, 정적 데이터로 로드하고 데이터에서 로직을 분리하는 용도로 사용을 제한하는 팀도 있습니다. 결국 프로젝트의 필요에 따라 사용 방법이 결정됩니다.

Unity 프로그래머를 위한 고급 시리즈

Unity에서 스크립터블 오브젝트로 모듈식 게임 아키텍처 만들기는 중급 및 고급 Unity 프로그래머 대상 시리즈의 세 번째 가이드입니다. 각 가이드는 숙련된 프로그래머에 의해 작성되었으며 중요한 개발 관련 주제에 대한 베스트 프랙티스를 제공합니다.

C# 코드 스타일 가이드 만들기를 참고하면 더욱 일관된 코드베이스 제작을 위해 접근 방식을 통합하는 데 도움이 되는 스타일 가이드를 개발할 수 있습니다.

게임 프로그래밍 패턴으로 코드 작성 스킬 업그레이드하기에서는 SOLID 원칙과 일반 프로그래밍 패턴을 사용하여 Unity 프로젝트에서 확장성이 뛰어난 게임 코드 아키텍처를 만드는 방법에 대한 베스트 프랙티스를 중점적으로 다룹니다.

이 시리즈는 경험이 풍부한 크리에이터에게 실용적인 팁과 영감을 제공할 목적으로 제작되었을 뿐 반드시 따라야 하는 규칙은 아닙니다. Unity 프로젝트는 다양한 방법으로 구성할 수 있으며 어떤 애플리케이션에 자연스럽게 어울리는 요소라 해도 다른 애플리케이션에는 그렇지 않을 수 있습니다. 각 권장 사항과 팁, 패턴을 배포하기 전에 동료들과 함께 그 장단점을 먼저 평가해 보세요.

Unity 베스트 프랙티스 허브에서 더 자세한 내용이 담긴 가이드와 게시물을 찾아보세요.

E-book cover for “Create modular game architecture in Unity with ScriptableObjects”