ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [디자인패턴] 전략 패턴(Strategy Pattern) - Unity로 게임 개발하기
    Unity 2024. 12. 15. 20:49

    전략 패턴(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
      • DroneMovementStrategy.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?.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 클래스들을 생성해준다.

    using UnityEngine;
    
    public class ForwardBackwardStrategy : DroneMovementStrategy
    {
       public override void Execute()
       {
           currentTime += Time.deltaTime;
           float forwardOffset = Mathf.Sin(currentTime * moveSpeed) * moveRange;
           transform.position = startPosition + Vector3.forward * forwardOffset;
       }
    }
    
    public class HorizontalFlightStrategy : DroneMovementStrategy
    {
    
        public override void Execute()
        {
            currentTime += Time.deltaTime;
            float horizontalOffset = Mathf.Sin(currentTime * moveSpeed) * moveRange;
            transform.position = startPosition + Vector3.right * horizontalOffset;
        }
    }
    
    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()
        {
            // GUI 스타일 초기화
            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 (i)
            {
                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 컴포넌트를 추가한다.
      Drone Component
    2. 해당 오브젝트를 폴더로 끌어놓고 프리팹화 시킨다. 기존에 있는 오브젝트는 삭제해준다.

    Drone Prefab

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

    1. 빈 게임오브젝트를 만들고 DroneCreator 컴포넌트를 추가한다.


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

     

    결과물

    드론 소환 장면


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

    마무리

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

    전략 패턴 써보니 좋았던 점

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

    앞으로 개선하면 좋을 것들

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

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

     

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

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

Designed by Tistory.