[디자인패턴] 전략 패턴(Strategy Pattern) - Unity로 게임 개발하기

Seungmoon Choi·2024년 12월 15일
post-thumbnail

전략 패턴(Strategy Pattern)?

런타임에서 동작을 변경하여 객체에 할당할 수 있도록 하는 디자인 패턴.

날아다니면서 플레이어를 공격하는 드론이 있다고 가정해보자.
해당 드론들은
1. 좌우비행
2. 상하비행
3. 전후비행
등 다양한 행동 패턴을 가지고 있을 수 있다.

우리는 이들이 어떤 행동을 해야 할지 정의를 해줄 수 있다.
어떤 드론은 횡비행을 하며 공격하고, 다른 드론은 종비행을 할 수 있도록 지정할 수 있다.
전략 패턴은 이러한 행동이 쉽게 가능하도록 하는 디자인 패턴의 한 종류이다.

전략 패턴 이해하기

전략이라는 개별 클래스로 나누어 생각해보자.
위에서 언급된 각각의 전략들을 캡슐화 하여 정의하는 것이 가능하다.

  1. 좌우비행 - x축으로 이동
  2. 상하비행 - y축으로 이동
  3. 전후비행 - z축으로 이동

해당 전략들은 Strategy 인터페이스를 상속받아, 드론을 정의하는 클래스에서 변경이 가능하도록 구성되어야 한다.

먼저 전략 패턴을 위한 기본적인 구조를 살펴보자

Client

  • Context의 전환을 통해 실제 Strategy가 적용되는 클래스.

Context

  • 다양하고 구체적인 Strategy 클래스를 사용
  • Strategy 인터페이스로 상호작용

Strategy(interface)

  • Strategy 클래스가 필수로 상속받아야 하는 인터페이스
  • execute() 함수를 담고 있어 Context에서 호출되어야 한다.

Strategies

  • 알고리즘 및 실제 동작을 런타임에서 구체적으로 구현한 것.
  • 실제 동작은 Strategy에서 이뤄진다.

예시를 통한 전략패턴 이해하기

위에서 정의한 구조대로 Strategy 패턴을 정의해보자.

목표 : 전략 패턴을 활용하여 특정 동작으로 행동하는 Drone 만들기

  • Context: Drone.cs
  • Client: DroneCreator.cs
  • Strategy: IStrategy.cs
  • Strategies
    - ForwardBackwardStrategy.cs
    • HorizontalFlightStrategy.cs
    • VerticalFlightStrategy.cs

1. Context에서 전략을 등록할 Drone.cs를 정의한다.

using UnityEngine;

public class Drone : MonoBehaviour
{
    private IStrategy _strategy;

    public void SetStrategy(IStrategy strategy) {
        _strategy = strategy;
    }
    
    private void Update() {
    	//strategy가 등록되었을때만 정상적으로 실행되도록
        _strategy?.Execute();
    }
}

2. IStrategy.cs를 정의한다.

public interface IStrategy
{
    void Execute();
}

3. IStrategy를 상속받으며, 공통속성을 정의할 DroneMovementStrategy.cs를 생성한다.

using UnityEngine;

public abstract class DroneMovementStrategy : MonoBehaviour, IStrategy
{
    
    [SerializeField] protected float moveSpeed = 5f;
    [SerializeField] protected float moveRange = 3f;
    
    protected Vector3 startPosition;
    protected float currentTime = 0f;

    protected virtual void Start()
    {
        startPosition = transform.position;
    }

    public abstract void Execute();
}

4. DroneMovementStrategy.를 상속받는 Strategy 클래스들을 생성해준다.

//ForwardBackwardStrategy.cs
public class ForwardBackwardStrategy : DroneMovementStrategy
{
   public override void Execute()
   {
       currentTime += Time.deltaTime;
       float forwardOffset = Mathf.Sin(currentTime * moveSpeed) * moveRange;
       transform.position = startPosition + Vector3.forward * forwardOffset;
   }
}
//HorizontalFlightStrategy.cs
public class HorizontalFlightStrategy : DroneMovementStrategy
{

    public override void Execute()
    {
        currentTime += Time.deltaTime;
        float horizontalOffset = Mathf.Sin(currentTime * moveSpeed) * moveRange;
        transform.position = startPosition + Vector3.right * horizontalOffset;
    }
}
//VerticalFlightStrategy.cs
public class VerticalFlightStrategy : DroneMovementStrategy
{
    public override void Execute()
    {
        currentTime += Time.deltaTime;
        float verticalOffset = Mathf.Sin(currentTime * moveSpeed) * moveRange;
        transform.position = startPosition + Vector3.up * verticalOffset;
    }
}

5. Drone을 생성하고 Strategy를 넣어주는 DroneCreator.cs를 정의한다.

using UnityEngine;

public class DroneCreator : MonoBehaviour
{
    [SerializeField] private GameObject _dronePrefab;
    [SerializeField] private Vector3 _centerPosition;
    [SerializeField] private Vector3 _areaLength;
    [SerializeField] private Color gizmoColor = new Color(0f, 1f, 0f, 0.2f); // 반투명 초록색
    private GUIStyle guiStyle;

    private void Start()
    {
        guiStyle = new GUIStyle();
        guiStyle.fontSize = 24;
        guiStyle.normal.textColor = Color.white;
        guiStyle.alignment = TextAnchor.UpperCenter;
    }

    private void OnGUI()
    {
        // 화면 상단 중앙에 텍스트 표시
        GUI.Label(new Rect(Screen.width / 2 - 200, 20, 400, 30),
                 "Press SPACE to spawn a drone",
                 guiStyle);
    }
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            int random = Random.Range(0, 3);
            CreateDrone(random);
        }
    }

    public void CreateDrone(int i)
    {
        Vector3 position = new Vector3(
            Random.Range(_centerPosition.x - _areaLength.x / 2, _centerPosition.x + _areaLength.x / 2),
            Random.Range(_centerPosition.y - _areaLength.y / 2, _centerPosition.y + _areaLength.y / 2),
            Random.Range(_centerPosition.z - _areaLength.z / 2, _centerPosition.z + _areaLength.z / 2)
        );
        GameObject drone = Instantiate(_dronePrefab, position, Quaternion.identity);
        IStrategy strategy = null;
        //현재는 전략 패턴 공부를 위해 Switch문으로 구성
        //이후 효율적인 생성을 위해 팩토리 패턴 도입 가능
        switch (i)
        {
        	//Unity Monobehaviour를 상속받기 때문에 생성자 X, AddComponent로 추가 
            case 0:
                strategy = drone.AddComponent<HorizontalFlightStrategy>();
                break;
            case 1:
                strategy = drone.AddComponent<VerticalFlightStrategy>();
                break;
            case 2:
                strategy = drone.AddComponent<ForwardBackwardStrategy>();
                break;
        }
        drone.GetComponent<Drone>().SetStrategy(strategy);
    }
	//소환 범위를 보여주기 위한 기즈모 함수
    private void OnDrawGizmos()
    {
        Color originalColor = Gizmos.color;
        Gizmos.color = gizmoColor;
        Gizmos.DrawCube(_centerPosition, _areaLength);
        Gizmos.color = new Color(gizmoColor.r, gizmoColor.g, gizmoColor.b, 1f);
        Gizmos.DrawWireCube(_centerPosition, _areaLength);
        Gizmos.DrawSphere(_centerPosition, 0.2f);
        Gizmos.color = originalColor;
    }
}

씬 세팅

1. Drone 프리팹을 생성해준다.

  1. 기본 3D 오브젝트를 생성하고, 해당 객체의 Drone 컴포넌트를 추가한다.
  2. 해당 오브젝트를 폴더로 끌어놓고 프리팹화 시킨다. 기존에 있는 오브젝트는 삭제해준다.

2. DroneCreator 오브젝트를 생성한다.

  1. 빈 게임오브젝트를 만들고 DroneCreator 컴포넌트를 추가한다.
  2. DroneCreator에 만들어놓은 Prefab을 추가하고, 영역을 지정해준다.
    영역을 지정함에 따라 씬에 기즈모로 영역이 표시된다.
    **현재 코드에서는 DroneCreator가 CentPosition이 아니고, 인스펙터에서 값을 통해 지정해줘야 한다.
  3. 플레이를 누르고 스페이스바를 두들겨본다.
    생성된 드론에 Strategy 컴포넌트가 부착되어 있는 것을 확인할 수 있다.

결과물

  • Space를 누르면 드론이 소환된다.
  • DroneCreator에 의해 각 Drone은 랜덤으로 Strategy가 추가되며, 개별 동작을 진행한다.

마무리

자, 지금까지 전략 패턴을 써서 드론의 움직임을 만들어봤다.
생각보다 괜찮은 점이 많았는데 한번 정리해보자.

전략 패턴 써보니 좋았던 점

  1. 실행 중에 행동을 자유롭게 바꿀 수 있다
  • 게임 도중에 드론의 움직임을 바꿀 수 있다
  • 새로운 움직임을 추가할 때 기존 코드를 건드리지 않아도 된다
  • 각각의 움직임을 독립적으로 테스트할 수 있다
  1. 코드를 재사용하기 좋다
  • 만든 움직임을 다른 오브젝트에도 쓸 수 있다
  • DroneMovementStrategy에 공통된 것들을 모아둬서 코드 중복도 줄였다
  1. 관리하기 편하다
  • 각각의 움직임이 독립적으로 있어서 관리가 쉽다
  • Unity Inspector에서 값 수정이 가능하다

앞으로 개선하면 좋을 것들

  1. 팩토리 패턴 도입
  • 지금은 switch로 전략을 만들고 있는데, 팩토리 패턴을 쓰면 더 깔끔해질 것 같다
  • 새로운 전략 추가할 때도 DroneCreator 수정 없이 가능해진다
  1. 실행 중 전략 변경
  • 드론이 움직이는 도중에도 전략을 바꿀 수 있게 만들 수 있다
  • 여러개의 Strategy를 드론이 생성되는 부분에서 전부 추가해주고, 특정 키입력 또는 Think 로직을 통한 전략 변경도 가능할 듯 싶다
  1. 더 다양한 움직임 추가
  • 플레이어를 쫓아가는 움직임이나 원형으로 도는 움직임도 추가할 수 있다
  • 각 움직임에 공격 패턴도 추가하면 더 재밌어질 것 같다

이번에 전략 패턴을 Unity에 적용해봤는데, Unity의 컴포넌트 시스템이랑 잘 어울리는 것 같다. 앞으로 이런 패턴들을 더 공부해서 활용해봐야겠다.

모두 관리하기 쉬운 코드를 작성합시다.

😘즐코(즐거운 코딩 되시길)!

profile
게임 하는 것도 만드는 것도 사랑하는 게임 개발자입니다.

0개의 댓글