프로그래머에 의한, 프로그래머를 위한 설계 가이드
Unity 커뮤니티에서 SOLID나 KISS 같은 설계 패턴과 원칙을 개발 작업에 적용하기가 힘들다는 이야기를 자주 듣습니다. 그러한 이유로 유니티에서는 무료 전자책 게임 프로그래밍 패턴으로 코드 작성 스킬 업그레이드하기를 발간하여 잘 알려진 설계 패턴들과 Unity 프로젝트에서 사용할 수 있는 실제 사용 예시를 정리했습니다.
유니티의 내부 및 외부 전문가들이 집필한 이 전자책에는 개발자로서의 역량을 키우고 더 빠르게 프로젝트를 성공적으로 완료하는 데 유용한 정보가 담겨 있습니다. 아래 내용에서 간략하게 살펴 보시기 바랍니다.
■ 설계 패턴으로 게임 개발 문제 해결
설계 패턴을 사용하면 소프트웨어 엔지니어링에서 발생하는 일반적인 문제들을 해결할 수 있습니다. 이는 단순히 복사해서 코드에 붙여 넣는 방식의 솔루션은 아니지만 올바르게 사용할 경우 더 크고, 스케일링이 가능한 애플리케이션을 만들 수 있습니다.
일관성 있게 프로젝트에 패턴을 적용한다면 코드의 가독성도 높이고, 코드 베이스도 깔끔하게 구축할 수 있습니다. 설계 패턴을 적용하면 리팩터링과 테스트 시간을 줄일 수 있을 뿐 아니라 온보딩과 개발 프로세스 전반에 드는 시간을 줄여줍니다.
하지만 모든 설계 패턴은 관리해야 할 구조가 추가되거나 더 많은 초기 설정을 해야 하는 문제 등을 안고 있습니다. 따라서 패턴을 적용함으로써 얻는 이점이 추가 작업을 상쇄할 만큼 바람직한지 파악해야 합니다. 물론 평가의 기준은 프로젝트마다 다릅니다.
■ KISS 원칙으로 코딩 간소화
KISS란 'Keep it simple, stupid의 줄임말로, 시스템에서 불필요하게 복잡한 코드를 줄이는 것이 목적입니다. 단순하게 만들면 사용자가 더 쉽게 이해하고 상호 작용할 수 있습니다.
참고로 '단순함'이 반드시 '쉬움'을 의미하지는 않습니다. 단순하게 만드는 것은 불필요한 것들을 없앤다는 뜻입니다. 패턴을 사용하지 않아도 똑같은 기능을 구현할 수 있고 심지어 더 빠른 구현도 가능하지만, 과정이 쉽고 빠르다 해서 무조건 단순한 결과물이 나오지는 않습니다.
어떤 패턴이 특정한 문제에 적용 가능한지 알 수 없다면, 필요하다는 생각이 들 때까지 미뤄 두어도 됩니다. 새롭거나 흥미롭다는 이유로 패턴을 사용해서는 안 됩니다. 꼭 필요할 때만 사용하세요.
이는 전자책에서 기본적으로 강조하는 내용 중 하나입니다. 이 전차책은 엄격하게 지켜야할 규칙이라기 보다는 새로운 코드 정리 방식을 찾는 지침으로 여기고 활용하시기 바랍니다.
이제 주요 소프트웨어 설계 원칙들을 알아보겠습니다.
■ SOLID 원칙단일 책임 클래스들로 리팩터링된 플레이어
SOLID 원칙은 소프트웨어 설계에서 지켜야 할 다섯 가지 핵심 원칙을 머리글자어로 만든 용어입니다. 유연하고 관리하기 쉬운 객체 지향 설계를 추구하기 위해 명심해야 하는 다섯 가지 원칙이라 생각하면 됩니다.
SOLID 원칙은 로버트 C. 마틴이 자신의 논문인 Design Principles and Design Patterns에서 최초로 도입한 개념입니다. 2000년에 처음으로 발표한 이 원칙은 오늘날에도 여전히 적용 가능하며 Unity의 C# 스크립팅에도 적용됩니다.
● 단일 책임(Single responsibility): 각 모듈과 클래스, 함수는 한 가지 사항만 책임져야 하며 로직에서 오직 해당 부분만 포함해야 합니다.
● 개방-폐쇄(Open-closed): 클래스는 확장에는 개방되어 있되 수정에는 폐쇄되어야 합니다. 즉, 기존 코드를 수정하지 않고도 새로운 행동을 생성할 수 있도록 클래스를 구조화해야 합니다.
● 리스코프 치환(Liskov substitution): 파생된 클래스는 상속 사용 시 기본 클래스를 대체할 수 있어야 합니다.
● 인터페이스 분리(Interface segregation): 클라이언트를 사용하지 않는 메서드에 강제로 의존하도록 하면 안 됩니다. 클라이언트는 오직 필요한 메서드만 구현해야 합니다.
● 종속 관계 반전(Dependency inversion): 상위 수준의 모듈은 하위 수준 모듈로부터 어떤 것도 직접 임포트하면 안 됩니다. 두 모듈 모두 추상화에 종속되어야 합니다.
유니티는 이번 전자책을 통해 Unity에서 각 원칙을 적용하는 상세한 방법과 그에 관한 도식화된 예시를 제공합니다. SOLID 원칙을 따르려면 추가로 사전 작업이 필요할 때도 있습니다. 예를 들어 일부 기능을 추상화나 인터페이스로 리팩터링해야 할 수도 있지만, 그러한 노력이 장기적으로 도움이 되는 경우가 많습니다.
이 다섯 가지 원칙은 스케일링이 필요한 대규모 애플리케이션에 매우 효과적이기 때문에, 기업 수준의 대규모 개발사들은 20년 가까이 이를 소프트웨어 설계의 기본 원칙으로 준수하고 있습니다. 어떻게 활용해야 하는지 확신할 수 없다면 KISS 원칙을 다시 참고해 보세요. 단순함을 유지하되 단지 좋아 보인다는 이유만으로 스크립트에 원칙을 억지로 적용하려 하지 마세요. 필요한 곳에 유기적으로 적용될 수 있도록 해야 합니다.
SOLID 원칙을 더 구체적으로 학습하려면 Productive Edge의 댄 새그밀러가 발표한 SOLID presentation from Unite Austin 2017 영상을 확인하세요.
■ 게임 개발에서의 디자인 설계
설계 원칙과 설계 패턴은 어떻게 다를까요? SOLID 원칙은 객체 지향 코드 작성의 프레임워크 또는 기반으로 간주할 수 있습니다. 설계 패턴은 일반적인 소프트웨어 문제를 방지하는 데 필요한 일종의 솔루션 또는 도구와 같지만, 그렇다고 해서 특정 결과를 도출하기 위해 정해진 절차를 따라야 하는 알고리즘은 아닙니다.
설계 패턴은 일종의 청사진과도 같은 일반적인 계획이며, 실제 작업은 개발자가 직접 해야 합니다. 예를 들어 동일한 패턴을 따르는 두 프로그램의 코드가 전혀 다른 경우도 있습니다.
똑같은 문제에 직면한 개발자들이 있다면 많은 수가 필연적으로 유사한 솔루션을 도출하게 될 것입니다. 그러한 솔루션이 어느 정도 반복되다 보면 누군가 패턴을 '발견'하게 되고 공식적인 이름을 지정하게 됩니다.
■ GoF(Gang of Four)
현재 많은 소프트웨어 설계 패턴이 에릭 감마, 리처드 헬름, 랠프 존슨, 존 블리시디스가 Design Patterns: Elements of Reusable Object-Oriented Software에서 제시한 이론을 기반으로 합니다. 이 책에서는 일상적인 개발 상황에서 식별된 23개의 패턴을 소개합니다.
저자들을 가리키는 'GoF'(Gang of Four)라는 별명을 따라 이 책에서 다루는 패턴들은 GoF 패턴이라 합니다. 책에서는 대부분 C++ 및 Smalltalk 언어로 작성된 예제를 인용하지만 C# 같은 객체 지향 언어에도 같은 개념을 적용할 수 있습니다.
GoF가 Design Patterns를 발간한 시점은 1994년이지만, 개발자들은 그로부터 다양한 분야에서 수십 가지의 더 많은 객체 지향 패턴을 수립했으며 여기에는 게임 개발 분야도 포함됩니다.
■ 설계 패턴 학습하기
팩토리 설계 패턴을 설명하는 도식
싱글턴 설계 패턴을 설명하는 도식
설계 패턴을 모르더라도 게임 프로그래머로서 역할을 수행할 수는 있지만, 패턴을 학습하면 더 능력 있는 개발자가 될 수 있습니다. 설계 패턴을 설계 '패턴'이라 하는 이유는 결국 잘 알려진 문제들에 대한 일반적인 솔루션이기 때문입니다.
소프트웨어 개발자들은 통상적인 개발 과정에서 항상 패턴을 발견합니다. 이미 기존의 패턴 중 일부를 무의식적으로 구현했을 수도 있습니다.
그런 패턴을 식별하는 능력을 키워야 합니다. 그러면 다음과 같은 이점을 누릴 수 있습니다.
● 객체 지향 프로그래밍 학습: 설계 패턴은 StackOverflow 게시물 속에 숨은 난해한 개념이 아닙니다. 개발 과정에서 일상적으로 발생하는 장애물을 극복하기 위한 일반적인 대책입니다. 설계 패턴을 통해 많은 다른 개발자들이 같은 문제에 어떻게 접근했는지 알 수 있습니다. 본인이 사용하지 않더라도 누군가는 분명히 패턴을 사용한다는 점을 기억하세요.
● 다른 개발자와 소통: 패턴을 사용하면 팀에서 보다 원활하게 소통할 수 있습니다. 숙련된 Unity 개발자라면 '명령 패턴' 또는 '객체 풀' 등의 용어를 들으면 무엇을 구현하려 하는지 곧바로 이해할 수 있습니다.
● 새로운 프레임워크 탐색: 에셋 스토어에서 빌트인 패키지 등을 임포트하면 여기에서 설명하는 패턴들을 보게 됩니다. 설계 패턴을 인식할 수 있으면 새로운 프레임워크의 작동 방식과 그러한 프레임워크 구축에 필요한 사고 과정을 더 잘 이해할 수 있습니다.
앞서 언급한 대로 모든 설계 패턴을 각 게임 애플리케이션에 적용할 수 있는 것은 아닙니다. 매슬로우의 망치(Maslow's hammer)라는 비유에서 http://볼 수 있듯이 만병통치약과도 같은 솔루션은 존재하지 않습니다.
다른 도구들과 마찬가지로 설계 패턴도 상황에 따라 유용성이 달라집니다. 각 패턴은 상황에 따라 이점을 제공하기도 하지만 나름대로의 단점도 가지고 있습니다. 소프트웨어 개발에서는 어떤 결정을 내리더라도 타협이 필요합니다.
다수의 게임 오브젝트를 빠르게 제작하는 상황을 가정해 보겠습니다. 성능에 영향을 주나요? 코드를 다시 작성하면 해결할 수 있을까요? 이러한 설계 패턴을 알고 있으면 적절한 시점에 필요한 패턴을 적용하여 문제를 바로 해결할 수 있습니다.
GoF의 Design Patterns와 더불어 로버트 니스트럼의 Game Programming Patterns 역시 독보적인 리소스라 할 수 있으며, 현재 웹 에디션 형태로 무료 사용이 가능합니다. 니스트럼은 이 책에서 다양한 소프트웨어 패턴을 구체적이고 이해하기 쉽게 설명합니다.
유니티의 최신 전자책에서는 팩토리, 객체 풀, 싱글턴, 명령어, 상태, 관찰자 패턴 등 일반적인 설계 패턴 및 Model View Presenter(MVP)와 같은 패턴에 대해 여러 장으로 나누어 상세하게 설명합니다. 각 장에서 패턴에 대한 설명과 장단점, Unity에서 패턴을 구현하는 방법에 대한 예시를 소개하므로 프로젝트에 맞게 최적화해 사용할 수 있습니다.
▶ Download the e-book l 게임 프로그래밍 패턴으로 코드 작성 스킬 업그레이드하기
■ Unity에서 사용되는 패턴
Unity에서는 이미 몇 가지 게임 개발 패턴을 구현해 제공하므로 직접 패턴을 작성해야 하는 수고를 줄일 수 있습니다. 다음과 같은 패턴이 제공됩니다.
● Game loop: 모든 게임의 핵심은 무한 루프이며, 게임 애플리케이션을 가동하는 하드웨어는 다양하므로 무한 루프는 반드시 클럭 속도와 무관하게 작동해야 합니다. 다양한 속도를 가진 여러 컴퓨터에 대응하기 위해, 게임 개발자는 FPS가 설정된 고정 타임스탬프나 이전 프레임에서 경과한 시간을 엔진이 측정하는 가변 타임스탬프를 사용합니다.
Unity에서 이 문제를 해결해 주므로 개발자가 직접 구현할 필요가 없습니다. 개발자는 Update, LateUpdate, FixedUpdate 같은 MonoBehaviour 메서드를 사용해 게임플레이 관리에만 집중하면 됩니다.
● Update: 게임 애플리케이션에서 오브젝트의 행동을 프레임마다 업데이트해야 하는 경우가 많습니다. Unity에서 이를 직접 다시 생성할 수도 있지만, MonoBehaviour 클래스는 이 작업을 자동으로 수행합니다. Update, LateUpdate, or FixedUpdate 등의 메서드를 적절하게 이용하여 게임 클럭의 틱마다 게임 오브젝트와 컴포넌트를 수정할 수 있습니다.
▶ Download the sample project l 게임 프로그래밍 패턴 데모
● Prototype: 때로는 원본에 영향을 주지 않으면서 객체를 복사해야 하는 경우가 있습니다. Prototype 패턴을 사용하면 원본과 유사한 객체를 만들기 위해 복제 및 복사하는 문제를 해결할 수 있습니다. 이 패턴에서는 객체를 생성하는 클래스를 매번 별도로 정의하지 않아도 됩니다.
Unity의 프리팹 시스템은 게임 오브젝트에 일종의 프로토타이핑 양식을 구현합니다. 이렇게 하면 컴포넌트까지 완전하게 갖춘 템플릿 오브젝트를 복제할 수 있습니다. 특정 프로퍼티를 오버라이드해서 프리팹 배리언트를 만들거나 다른 프리팹에 중첩 프리팹을 생성해 계층 구조를 구성할 수 있습니다. 특별한 프리팹 편집 모드를 활용하면 프리팹을 별도로 편집하거나 컨텍스트에 맞게 편집할 수 있습니다.
● Component: Unity에서 작업하는 개발자는 대부분 이 패턴을 알고 있습니다. 다수의 책임을 가지는 대규모 클래스를 생성하는 대신 각각 한 가지 역할만 담당하는 소규모 컴포넌트를 빌드합니다.
원하는 컴포넌트만을 선택하여 복잡한 행동을 수행하도록 구현할 수도 있습니다. 물리에는 Rigidbody 및 Collider 컴포넌트를 추가하고, 3D 지오메트리에는 MeshFilter 및 MeshRenderer 컴포넌트를 추가합니다. 각 게임 오브젝트는 고유의 다양한 기능을 가진 컴포넌트의 조합으로 볼 수 있습니다.
▶ Forums l 새로 발간된 이번 전자책에 대해 유니티 팀과 직접 의견을 나누어 보세요.
전자책 및 설계 패턴에 대한 샘플 프로젝트 모두 현재 무료로 다운로드할 수 있습니다. 예제를 확인해보고 어떤 설계 패턴이 현재 프로젝트에 가장 적합한지 결정하세요. 경험이 쌓임에 따라 개발 과정에서 설계 패턴이 언제, 어떻게 도움이 되는지 더 쉽게 파악할 수 있습니다. 포럼 스레드에 방문해 이번 전자책과 예제에 대해 꼭 의견을 남겨 주시기 바랍니다.