본문 바로가기
프로그래밍/디자인패턴

유니티(C#)로 사용해 보는 디자인 패턴_Observer Pattern_관찰자 패턴

by 리뷰하는 (게임)프로그래머_리프TV 2020. 5. 24.

옵저버 패턴,

UI뿐만 아니라 데이터를 일괄로 전달하기 위해 자주 사용하는 패턴

UI가 오픈 되어 있을 때에만 해당 UI에 데이터를 전달하는 예제를 작성

 

실행화면

 

게임 내 UI가 켜져 있을 때에만 작업을 수행 하게 처리 한다

 

 

ObserverPattern.cs

using UnityEngine;

// 옵저버 패턴의 원형
public class KMS_ObserverPattern : MonoBehaviour
{
    public KMS_ObserverPattern nextOb = null;
    public UIType type;

    // 옵저버를 상속받는 클래스들은 다음 함수를 꼭 구현해야 한다
    public virtual void Notify(Event type)
    {
        // 데이터가 전달 되었을 때 수행해야 하는 일들을 진행할 수 있다
    }
}

 

 

해당 클래스를 상속받은 UI들

Equipment.cs

using UnityEngine;

public class KMS_UI_Equipment : KMS_ObserverPattern
{
    private void Start()
    {
        type = UIType.Equip;
    }

    public override void Notify(Event type)
    {
        switch (type)
        {
            // 장비 관련 부분만 업데이트
            case Event.Equipment:
                Debug.Log("장비 UI를 업데이트 합니다.");
                break;
        }
    }
}

Inventory.cs

using UnityEngine;

public class KMS_UI_Inventory : KMS_ObserverPattern
{
    private void Start()
    {
        type = UIType.Inventory;
    }

    public override void Notify(Event type)
    {
        switch (type)
        {
            // 아이템 획득과 장비 변경때
            case Event.Equipment:
            case Event.GetItem:
                Debug.Log("인벤토리 UI를 업데이트 합니다.");
                break;
        }
    }
}

Status.cs

using UnityEngine;

public class KMS_UI_Status : KMS_ObserverPattern
{
    private void Start()
    {
        type = UIType.Status;
    }

    public override void Notify(Event type)
    {
        switch (type)
        {
            // 캐릭터 상태창과 장비 변경때
            case Event.StatusChange:
            case Event.Equipment:
                Debug.Log("스테이터스 UI를 업데이트 합니다.");
                break;
        }
    }
}

 

이런 상황에서 Subject class가 해당 UI들을 컨트롤 한다

// 게임에서 넘어 올 수 있는 이벤트들
public enum Event
{
    StatusChange,
    Equipment,
    GetItem,
}

public class KMS_Subject
{
    KMS_ObserverPattern head = null;

    // 메시지를 받을 UI
    public void AddObserver(KMS_ObserverPattern ob)
    {
        ob.nextOb = head;
        head = ob;
    }

    // 더이상 메시지를 받지 않을 UI
    public void RemoveObserver(KMS_ObserverPattern ob)
    {
        if (head == ob)
        {
            head = ob.nextOb;
            ob.nextOb = null;
            return;
        }

        KMS_ObserverPattern current = head;
        while (current != null)
        {
            if (current.nextOb == ob)
            {
                current.nextOb = ob.nextOb;
                ob.nextOb = null;
                return;
            }
            current = current.nextOb;
        }
    }

    // 메시지 전달
    public void Message(Event type)
    {
        KMS_ObserverPattern ob = head;
        while (ob != null)
        {
            ob.Notify(type);
            ob = ob.nextOb;
        }
    }
}

 

 

UI가 켜지고 꺼질때 마다 Add,Remove 되면서

켜져 있는 UI에게만 메시지를 보내서 서로가 각자 디커플링 된 상태에서 처리 할 수 있도록 진행한다.

 

GameManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 샘플 UI
public enum UIType
{
    Status,
    Inventory,
    Equip,
}

public class KMS_GameManager : MonoBehaviour
{
    // 옵저버 패턴을 상속받은 3개의 UI
    public KMS_ObserverPattern statusUI;
    public KMS_ObserverPattern InventoryUI;
    public KMS_ObserverPattern EquipUI;

    // UI가 생성되거나 삭제 될때 Subject를 통해서 진행
    KMS_Subject subject = new KMS_Subject();

    // UI가 열릴 때 호출
    public void OnClickOpen(int type)
    {
        switch ((UIType)type)
        {
            case UIType.Status: // 상태창
                statusUI.gameObject.SetActive(true);
                subject.AddObserver(statusUI);
                break;
            case UIType.Inventory: // 인벤토리
                InventoryUI.gameObject.SetActive(true);
                subject.AddObserver(InventoryUI);
                break;
            case UIType.Equip: // 장비창
                EquipUI.gameObject.SetActive(true);
                subject.AddObserver(EquipUI);
                break;
        }
    }

    // UI가 닫힐 때 호출
    public void OnClickClose(int type)
    {
        switch ((UIType)type)
        {
            case UIType.Status:
                statusUI.gameObject.SetActive(false);
                subject.RemoveObserver(statusUI);
                break;
            case UIType.Inventory:
                InventoryUI.gameObject.SetActive(false);
                subject.RemoveObserver(InventoryUI);
                break;
            case UIType.Equip:
                EquipUI.gameObject.SetActive(false);
                subject.RemoveObserver(EquipUI);
                break;
        }
    }

    // 게임 내 변화가 있을 때
    private void Update()
    {
        // 키 입력으로 대체
        if (Input.GetKeyUp(KeyCode.A))
        {
            // 캐릭터 채인지
            Debug.Log("캐릭터에 변화가 있습니다.");
            subject.Message(Event.StatusChange);
        }
        else if (Input.GetKeyUp(KeyCode.S))
        {
            // 아이템 획득
            Debug.Log("아이템을 획득 했습니다.");
            subject.Message(Event.GetItem);
        }
        else if (Input.GetKeyUp(KeyCode.D))
        {
            // 장비 장착
            Debug.Log("장비를 장착합니다.");
            subject.Message(Event.Equipment);
        }
    }
}