[ Pattern ] Component Pattern(컴포넌트 패턴) in Unity C#

[ Programming Pattern 시리즈 ]
싱글턴 패턴 (Singleton Pattern) Link
컴포넌트 패턴 (Component Pattern) Link

커멘드 패턴 (Command Pattern) Link
관찰자 패턴 (Observer Pattern) Link
상태 패턴 (State Pattern) Link

컴포넌트 패턴은 개발 과정에서 은연 중에 많이 사용하게 되는 패턴이다.
Component Pattern은 말그대로 컴포넌트를 만들어 한 개체가 여러 분야를 서로 커플링 없이 다룰 수 있게 해준다.

가령 AI, 물리, 사운드 처럼 분야가 다른 코드들이 한 클래스 안에 전부 섞여서 들어가 있다고 한다면 기하 급수적으로 복잡도가 올라간다. 이런 코드는 그야말로 코딩이 추가되는 속도보다 버그가 늘어나는 속도가 더 빠를 수 있다.

일전에 다니던 회사에서 한 클래스가 1만 줄이 넘어간 코드가 있었다. 그런데 그런 코드가 아무런 문제 없이 잘 돌아가고 있었고, 심지어 그런 코드가 들어간 Product이 매출을 잘 내고 있는 상황이었다. 그런 이유로 인해 아무도 그 코드가 문제가 있고, 그렇게 만들면 안되고, 그렇게 만들어서는 팀과 회사에 큰 불이익을 줄 수 있다라고 말하지 못했다. 그런데, 문제는 그 코드를 만든 사람이 다른 프로젝트로 넘어가서 다른 사람이 그 코드를 대량으로 수정해야 할 때 본격적으로 발생했다. 그 과정에서 그런 코드를 처음 본 개발자들로 인해 수많은 버그들이 양산되었고, 정말 기능보다 버그가 더 많이 늘어나는 문제가 발생했다. 뿐만 아니라 한 파일 내에 핵심 로직들이 모두 존재하기에 여러 사람이 협업하여 빠르게 코딩 작업을 해 내기도 어려웠다.

로직을 기능별로 컴포넌트화 하는 것, 클래스로 분리하고 Readability를 올리고 Decoupling 시키는 것은 협업하여 개발을 많이 해본 사람들에게는 자연히 녹아 있는 습관이다. 이런 구조적인 개선 작업을 선배 개발자들이 이미 컴포넌트 패턴으로 명명해 두었다.

아래는 큐브와 캡슐, 그리고 카메라 게임 오브젝트가 존재하는 유니티의 Scene이다.
큐브, 캡슐, 카메라는 각각 스플라인 커브를 따라 움직이도록 만들었다.

%e1%84%89%e1%85%b3%e1%84%8f%e1%85%b3%e1%84%85%e1%85%b5%e1%86%ab%e1%84%89%e1%85%a3%e1%86%ba-2016-11-30-%e1%84%8b%e1%85%a9%e1%84%92%e1%85%ae-5-30-03

커브 클래스를 카메라, 큐브, 캡슐에 각각 Attatch 시키고, 카메라에서 모든 것을 제어하는 코드를 만들면 아래와 같이 할 수 있다.

<MainCamera.cs>

거동은 아래와 같이 동작한다.

이 코드를 굳이(?) 컴포넌트 패턴을 적용 시키면 아래와 같이 컴포넌트화 할 수 있을 것이다.
MainCamera를 경량화 시켜서 Update 부분을 컴포넌트 별로 클래스로 분리시켜 간단하게 만든다.

<MainCamera.cs>

 

<CubeActor.cs>

 

<CapsuleActor.cs>

CubeActor와 CapsuleActor를 생성하여, Camear와 Decoupling 시키는 것이 핵심이다.

물론 Unity에서는 MonoBehaviour를 상속 받는 모든 클래스는 Update Method를 자동 호출하므로, 사실 MainCamera의 CubeActor, CapsuleActor 부분을 완전히 제거하여 각각 CubeActor와 CapsusleActor의 Update 메쏘드에 넣어 버릴 수 있다. 그럼에도 불구하고 그냥 놔둔 채로 구현 한 것은 컴포넌트 패턴의 특성을 보여주기 위함이다.
MainCamera 안에 Input 로직, Physics 로직, Rendering 로직이 모두 존재하는 혹은 존재해야 하는 구조라면 각각을 위와 같이 컴포넌트화 시킬 수 있는 것이다.

컴포넌트 패턴에는 반드시 컨테이너 객체, 즉 껍데기 클래스가 존재한다. 이 컨테이너 객체 클래스의 크기를 줄이고, 그 안에 소속되어 있는 객체들을 Decoupling 시키는 것이 컴포넌트 패턴의 의도이다. 그런데 객체를 컴포넌트화 시키면 반드시 서로 통신하거나 데이터를 주고 받아야 하는 경우가 발생한다. 해당 문제는 3가지 방법으로 해결할 수 있다.

1)) 컴포넌트들이 공유하는 정보를 모두 컨테이너 객체에 넣는다.
컴포넌트 들이 공유하는 정보를 모두 컨테이너 객체에 올려둔다. 이른 참조 포인터이든 아니면 직접적인 객체 할당이든 모두 가능하다. 대신 이 경우 컴포넌트들이 보이지 않는 암묵적인 Access를 끊임없이 발생 시키므로 실행 순서에 굉장히 민감한 코드가 될 수 있고, 자칫 컨테이너 객체가 지져분해 질 수 있다.

2))컴포넌트 자체가 서로 참조하도록 한다.
컴포넌트 객체를 초기화 할 때나 런타임 시에 해당 컴포넌트 객체에 필요한 컴포넌트만 참조 형태로 가지고 있는 방식이다. 컨테이너 클래스가 커지지 않고 디커플링을 최소화 시킬 수 있는 한 방법이라고 볼 수 있다. 대신 서로 참조하는 두 컴포넌트가 강하게 결합되는 단점이 발생한다.

3)) 메세지를 전달하는 방식을 사용한다.
컨테이너(껍데기) 객체에서 컴포넌트 객체에 메세지를 보내는 형태이다. 즉, 한 컴포넌트에서 특정 이벤트나 변경 사항이 발생하면 그것을 상위 컨테이너 객체에 알려준다. 그러면 컨테이너 객체는 보유하고 있는 모든 컴포넌트 객체에 대해서 무조건 해당 메세지를 전달하는 형태이다. 하위 컴포넌트의 디커플링이 유지되면서 컨테이너 객체는 메세지 전달만 하면 되므로 간단해 진다. 그러나, 보내려는 메세지 정보 형태 자체가 한계적일 수 있다.

Unity의 GameObject는 Component 패턴이 잘 반영되어 설계된 클래스라고 할 수 있다. (GetComponent Method로 각각의 Decoupling된 하위 컴포넌트들을 불러서 사용할 수 있게 해두었다.)

 

Github Link: Component Pattern In Unity C#