Home 컴포넌트 패턴
Post
Cancel

컴포넌트 패턴

1
2
3
4
5
6
7
한 객체가 소지한 여러 기능이 서로 커플링 없이 작동할 수 있게 구현하는 패턴

아래의 상황에 놓여있다면 사용을 고려해볼만한 패턴
- 한 클래스에서 여러 분야를 건드리고 있어서 이들을 서로 디커플링 하고 싶다
- 클래스가 거대해져서 작업하기가 힘들다
- 여러 다른 기능을 공유하는 다양한 객체를 정의하고 싶다. 
   단, 상속으로는 딱 원하는 부분만 골라서 재사용할 수가 없다.

1. 서론

우리가 게임을 만들 때 만드는 객체에는 Animation, Sound, Input System 등 다양한 기능들이 포함되어 있는 경우가 많다.

이 때 하나의 객체 클래스에 기능을 모두 넣는다면 코드가 길어지고 기능들이 서로 묶이게 되어

하나를 수정하려면 코드를 전부 확인해야 하는 문제가 생기게 된다.

이러면 코드를 수정할수록 수정 결과보다 버그가 더 많이 발생할 것임을 알 수 있다.

그리고….. 몇 백 몇 천 줄이 되는 코드를 분석하는 건 정말 지옥과도 같다

이 때 우리가 사용할 수 있는 방법으로 컴포넌트 패턴이 있다.

이 패턴으로 위 문제를 어떻게 해결할 수 있는지 알아보자!

1
2
참고로
게임 엔진인 유니티(UNITY)가 컴포넌트 패턴을 이용하고 있다.

2. 컴포넌트 패턴을 쓰지 않는다면..?

우리는 마리오 게임을 만들 것이다.

마리오 안에 아래와 같은 코드가 있다고 가정하자

아주 극단적인 예시이니 참고만 하자

1
2
3
4
if(IsOnGround() && IsRunAnimation())
{
   PlaySound(ESoundType.Run);
}

여기서 IsOnGround() 를 수정하려면 우리는 IsOnGround() IsRunAnimation() PlaySound(ESoundType.Run) 3개의 내용을 전부 알아야 한다.

그리고 IsOnGround() 를 다른 객체에 사용하려면 마리오 안에 있는 코드를 복사해서 가져와야 하는데 묶인 게 많다 보니 가져오기 쉽지 않을 뿐더러 매번 객체 상황에 맞추어 코드를 바꿔줘야 하니

재사용성도 떨어지게 된다.

3. 컴포넌트 패턴

3.1 컴포넌트 패턴 적용

1
유니티를 기반으로 설명하고 있습니다.

간단한 예시를 통해 확인해보자

왼쪽, 오른쪽으로만 이동하는 객체가 있고 왼쪽으로 갈 때는 파란색으로 오른쪽으로 갈 때는 빨간색으로 변한다.

여기서 큐브가 가지고 있는 기능은 아래 2개로 볼 수 있다.

  • 왼쪽, 오른쪽으로 이동하는 이동 시스템
  • 방향에 따라 컬러가 변하는 렌더링 시스템

컴포넌트 패턴을 쓰지 않는다면 코드는 아래와 같을 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Mario : MonoBehaviour
{
    float _speed = 1;

    Renderer _thisRenderer;
    private void Start()
    {
        _thisRenderer = this.GetComponent<Renderer>();    
    }
    private void Update()
    {
        float input = Input.GetAxis("Horizontal");
        this.transform.position = 
            new Vector3(this.transform.position.x + input * _speed * Time.deltaTime, 0);
				
        // 오른쪽으로 이동
        if(input > 0f)
        {
            _thisRenderer.material.color = Color.red;
        }
        // 왼쪽으로 이동
        else if(input < 0f)
        {
            _thisRenderer.material.color = Color.blue;
        }
        // 멈춘 상태
        else
        {
            _thisRenderer.material.color = Color.white;
        }
    }
}

이동하는 기능과 색이 변하는 렌더링 기능이 같이 있는 것을 볼 수 있다.

간단한 예시로 코드를 구현했음으로 딱히 문제가 될 게 없다고 보일 수 있다. 하지만 코드가 길어지고 기능이 하나 둘 추가 된다면 발생할 문제가 많다는 것을 예상할 수 있을 것이다.

이걸 MovementComponent 와 RenderingComponent 2 개의 컴포넌트로 분리해서 컴포넌트 패턴을 적용해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Mario : MonoBehaviour
{
    MovementComponent _movementComponent;
    RenderingComponent _renderingComponent;

    Renderer _thisRenderer;
    private void Start()
    {
        _movementComponent = new MovementComponent();
        _renderingComponent = new RenderingComponent();
        _thisRenderer = this.GetComponent<Renderer>();    
    }
    private void Update()
    {
        // 공용으로 써야하는 변수
        float input = Input.GetAxis("Horizontal");
        _movementComponent.Update(input, this.transform);
        _renderingComponent.Update(input, _thisRenderer);
    }
}
1
2
3
4
5
6
7
8
9
public class MovementComponent
{
    float _speed = 1f;

    public void Update(float input, Transform player)
    {
        player.position = new Vector3(player.position.x + input * _speed * Time.deltaTime, 0);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RenderingComponent
{
    public void Update(float input, Renderer renderer)
    {
        if (input > 0f)
        {
            renderer.material.color = Color.red;
        }
        else if (input < 0f)
        {
            renderer.material.color = Color.blue;
        }
        else
        {
            renderer.material.color = Color.white;
        }
    }
}

기능 별로 코드를 분리하면서 기존 하나의 클래스에 모든 코드가 있었을 때보다 코드 길이가 짧아졌고 기능 별 클래스가 생기면서 관련 코드를 한 눈에 보기 쉬워졌다.

그 결과,

기능을 수정할 때 그 기능의 컴포넌트 클래스만 보면 되기에 유지 보수가 좋아졌음을 알 수 있고,

다른 객체에서 해당 기능이 필요할 때 객체에 해당 기능의 컴포넌트만 넣어주면 바로 기능을 사용할 수 있기에 재사용성 또한 좋아졌음을 볼 수 있다.

This post is licensed under CC BY 4.0 by the author.