디자인 패턴의 정의

유니티 게임 개발에서 디자인 패턴은 반복적으로 발생하는 문제에 대한 일반적인 해결책을 제공합니다. 이는 소프트웨어 엔지니어링의 일반적인 개념으로, 특히 게임 개발의 복잡한 구조와 동작을 관리하는 데 유용합니다. 디자인 패턴을 적절히 활용함으로써, 개발자는 보다 효율적이고 관리하기 쉬운 코드를 작성할 수 있으며, 재사용성과 유지보수성을 높일 수 있습니다. 단, 패턴의 남용은 코드의 복잡성을 증가시킬 수 있으므로, 프로젝트의 요구사항과 팀원의 숙련도를 고려하여 적절한 패턴을 선택하는 것이 중요합니다.

싱글턴 패턴

싱글턴 패턴은 클래스의 인스턴스가 오직 하나만 생성되도록 보장하는 패턴입니다. 유니티에서는 게임 매니저나 사운드 매니저와 같이 애플리케이션 전반에 걸쳐 단 하나만 존재해야 하는 객체를 관리할 때 유용합니다. 싱글턴 패턴의 장점은 전역적으로 접근 가능한 단일 인스턴스를 제공한다는 것이며, 이는 리소스 관리와 데이터 공유에 효과적입니다. 하지만, 과도한 사용은 프로젝트의 결합도를 높이고 테스트하기 어렵게 만들 수 있습니다.

// 유니티에서 싱글턴 패턴 구현 예제
public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

옵저버 패턴

옵저버 패턴은 객체 간의 구독 관계를 통해, 한 객체의 상태 변화를 다른 객체들에게 알리고 자동으로 상태를 갱신하는 패턴입니다. 유니티에서 이벤트 매니저를 통해 구현할 수 있으며, 게임 내의 다양한 이벤트에 대한 반응을 관리하기에 적합합니다.

// 옵저버 패턴 구현 예제
public class EventManager : MonoBehaviour
{
    public delegate void ClickAction();
    public static event ClickAction OnClicked;

    private void Update()
    {
        if (Input.GetButtonDown("Fire1"))
        {
            if (OnClicked != null)
                OnClicked();
        }
    }
}

public class Observer : MonoBehaviour
{
    void OnEnable()
    {
        EventManager.OnClicked += DoAction;
    }

    void OnDisable()
    {
        EventManager.OnClicked -= DoAction;
    }

    void DoAction()
    {
        // 특정 액션 실행
    }
}

커맨드 패턴

커맨드 패턴은 요청 자체를 하나의 객체로 캡슐화하여, 사용자 정의 요청에 따라 저장, 큐잉 또는 로깅을 가능하게 하는 패턴입니다. 유니티에서는 키 입력이나 UI 이벤트에 따른 다양한 게임 액션을 동적으로 변경하고 관리하는 데 유용합니다.

// 커맨드 패턴 구현 예제
public interface ICommand
{
    void Execute();
}

public class JumpCommand : ICommand
{
    public void Execute()
    {
        // 점프 액션 실행
    }
}

public class FireCommand : ICommand
{
    public void Execute()
    {
        // 발사 액션 실행
    }
}

public class InputHandler
{
    private ICommand buttonX = new JumpCommand();
    private ICommand buttonY = new FireCommand();

    public void HandleInput()
    {
        if (Input.GetKeyDown(KeyCode.X))
            buttonX.Execute();
        if (Input.GetKeyDown(KeyCode.Y))
            buttonY.Execute();
    }
}

스테이트 패턴

스테이트 패턴은 객체의 상태에 따라 객체의 행동을 변경할 수 있게 하는 패턴입니다. 유니티에서는 캐릭터의 상태(예: 이동, 공격, 방어) 관리에 사용되며, 각 상태를 클래스로 캡슐화하여 코드의 유지보수를 용이하게 합니다.

// 스테이트 패턴 구현 예제
public class State
{
    public virtual void Handle(Context context) { }
}

public class ConcreteStateA : State
{
    public override void Handle(Context context)
    {
        context.State = new ConcreteStateB();
    }
}

public class ConcreteStateB : State
{
    public override void Handle(Context context)
    {
        context.State = new ConcreteStateA();
    }
}

public class Context
{
    private State _state;

    public Context(State state)
    {
        this.State = state;
    }

    public State State
    {
        get { return _state; }
        set
        {
            _state = value;
            // 상태 변화에 따른 로직 처리
        }
    }
}

컴포지트 패턴

컴포지트 패턴은 개별 객체와 객체의 조합을 동일하게 취급하여, 클라이언트가 단일 객체와 복합 객체를 동일하게 다룰 수 있도록 하는 패턴입니다. 유니티에서는 복잡한 UI 구조나 게임 오브젝트의 계층 구조를 효율적으로 관리하는 데 사용됩니다.

// 컴포지트 패턴 구현 예제
public abstract class Component
{
    public abstract void Operation();
    public abstract void Add(Component component);
    public abstract void Remove(Component component);
    public abstract Component GetChild(int index);
}

public class Leaf : Component
{
    public override void Operation()
    {
        // Leaf 동작 구현
    }

    // Leaf에서는 Add, Remove, GetChild를 구현하지 않습니다.
    public override void Add(Component component) { }
    public override void Remove(Component component) { }
    public override Component GetChild(int index) { return null; }
}

public class Composite : Component
{
    private List<Component> children = new List<Component>();

    public override void Operation()
    {
        foreach (Component component in children)
        {
            component.Operation();
        }
    }

    public override void Add(Component component)
    {
        children.Add(component);
    }

    public override void Remove(Component component)
    {
        children.Remove(component);
    }

    public override Component GetChild(int index)
    {
        return children[index];
    }
}

위 예제 코드들은 각 디자인 패턴의 기본적인 구조와 유니티에서의 활용 방법을 보여줍니다. 프로젝트의 요구사항과 특성에 맞게 패턴을 선택하고 적절히 변형하여 사용하는 것이 중요합니다.

추천 학습 자료