본문 바로가기

C#/디자인 패턴

[옵저버 패턴 활용 1]

C#에서 옵서버 패턴(Observer Pattern)은 객체 간의 일대다(one-to-many) 관계를 정의하는 디자인 패턴이다. 이는 한 객체의 상태가 변경되었을 때, 그 객체에 의존하는 다른 객체들에게 자동으로 알림이 가고 갱신될 수 있도록 하는 것으로 볼 수 있다.

 

C#에서는 이러한 패턴을 Delegate와 Event 키워드를 통해서 구현 가능하다.

+= 를 통해 발행자에게 옵저버를 구독하여, 발행자를 통해 모든 옵서버의 기능을 한 번에 사용할 수 있다는 편리함 때문에 옵서버 패턴이라는 말을 모르는 사람도 알게 모르게 쓰는 방법이다.

 


 

이러한 옵저버 패턴을 나는 이번 카드 덱빌딩 프로젝트에서, 매 턴이 진행될 때 거쳐가는 대기시간, 플레이어턴, 몬스터턴 등에 호출되거나 필요한 실행들을 한 번에 호출하고자 action을 통해서 구현하였다.

 

public class TurnController : MonoBehaviour
{
    public enum TurnState
    {
        GameStart,// 전투의 시작 상태
        StartPlayerTurn,// 플레이어 턴 시작
        PlayerTurn,// 플레이어 턴 진행중
        EndPlayerTurn,// 플레이어 턴 종료
        EnemyTurn,// 적 턴
        GameEnd,// 전투 종료
    }
    public TurnState turnState = TurnState.GameStart; // 현재 턴 상태
    [SerializeField] CardDisplay cardDisplay; // 카드 디스플레이
    public BattleFlowController battleFlow;   //배틀 컨트롤러를 넣기

    public event Action OnStartPlayerTurn;
    public event Action OnPlayerTurn;
    public event Action OnEndPlayerTurn;
    public event Action OnEnemyTurn;
    public event Action OnGameEnd;

private void SetTurnState(TurnState newState)
{
    turnState = newState;

    switch (turnState)
    {
        case TurnState.GameStart:
            break;

        case TurnState.StartPlayerTurn:
            OnStartPlayerTurn?.Invoke();
            StartCoroutine(AtStartPlayerTurn()); // 대기 후 다음 상태로 넘기기
            break;

        case TurnState.PlayerTurn:
            OnPlayerTurn?.Invoke(); // 플레이어가 수동으로 EndTurn 하기 전까지 대기
            break;

        case TurnState.EndPlayerTurn:
            OnEndPlayerTurn?.Invoke();
            StartCoroutine(AtEndPlayerTurn()); // 대기 후 다음 상태로 넘기기 
            break;

        case TurnState.EnemyTurn:
            OnEnemyTurn?.Invoke();
            // 적 행동이 끝났을 때 다시 StartPlayerTurn으로
            break;

        case TurnState.GameEnd:
            OnGameEnd?.Invoke();
            break;
    }
}

 

위와 같은 방식으로 각 턴, 턴의 시작과 끝 등을 enum으로 분리하고, 해당 분기별로 실행해야 하는 동작을 action으로 한 번에 호출하고자 하였다.

 

해당 분기가 되었을때 SetTurnState() 메서드를 호출하여 분기의 시작에서 필요한 행동을 실행하였고, 턴 전후의 대기 분기인지 행동이 끝난 뒤에 다음 분기로 넘어갈지를 StartCoroutine의 사용 유무로 나누었다.

 

 


 

 

 

이러한 옵저버 패턴은, 느슨한 결합으로 서로의 구체적 구현을 몰라도 된다는 것과, 한 번의 사용으로 여러 옵서버에게 동시에 알릴 수 있다는 장점이 있지만, 옵저버를 등록했으면 필요 없어질 때 제거를 해주는 것을 있어서는 안 된다. 제거를 하지 않고 계속 진행하면 메모리 누수가 일어날 수 있기 때문이고, 또한 유니티에서 Event를 통해 사용 시, 등록 순서와 수명 주기를 잘 생각하며 사용 해야한다.

 

만약 등록 순서, 수명 주기를 잘 고려하여 등록하거나 호출하지 않게 되면, https://toacode.tistory.com/40

 

소규모 개인 게임 회고 - Action의 남용에 대한 반성.

https://github.com/Toaaaa/3D-Idle-Advanture GitHub - Toaaaa/3D-Idle-AdvantureContribute to Toaaaa/3D-Idle-Advanture development by creating an account on GitHub.github.com "오늘도 우라라" 레퍼런스 게임을 만들면서,이전에 여러 장점

toacode.tistory.com

앞서 작성한 이러한 일이 일어난다....

잔뜩 등록은 해두고 한 번에 호출은 되는데 버그가 터지거나 잘 작동이 안 되면, 왜 안되는지, 어디서 문제가 발생한 건지 한눈에 안 들어오기에, 옵서버를 등록할 때 주의가 필요하다.