[Unity] 옵저버 패턴

jaehyeonLee·2024년 9월 8일
0

옵저버 패턴은 한 객체가 주체 역할을 하고 다른 객체가 관찰자 역할을 맡는 객체간의 일대다 관계를 설정하는 것이 핵심 목적으로 객체가 내부에서 변경되었다면 관찰자에게 알리는 책임을한다.

옵저버 패턴의 장단점은 무엇이있을까 ?
장점으로는 주체에 필요한 만큼 객체를 관찰자로 추가할 수있으며 런타임에서 동적으로 제거할수 있는장점이 있지만, 단점으로는 관찰자에 대한 강한 참조를 가져 메모리 누수를 일으킬 수있는 단점이있기에 조심하여야한다.

옵저버 패턴을 어떻게 이용할 수있을까 생각을 해보았는데 던전에 남아있는몬스터 수를 카운트하는데에 사용할 수 있을것으로 보였고 나중에 만들 퀘스트(몬스터 몇마리 잡아오기 등 ) 등에 도 사용될수있을것같아 만들어보기로 하였다.

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

public interface ISubject
{
    void RegisterObserver(IObserver observer);
    void RemoveObserver(IObserver observer);

    void NotifyObserver();
}

public interface IObserver
{
    void UpdateObserver();
}

이후 사용하기 위한 Monster 스크립트에 ISubject를 상속시켜주었다.

public abstract class Monster : MonoBehaviour,ISubject
{
    public MonsterData monsterdata;
    private MonsterHpManager monsterhp;
    private MonsterController monsterController;
    
    private readonly List<IObserver> observers=new List<IObserver>();
    
    public abstract void InitSetting();
    
    protected virtual void Start()
    { 
        InitSetting();
        monsterController=GetComponent<MonsterController>();
        monsterhp=GetComponent<MonsterHpManager>();
      
        if(monsterhp != null)
        {
          monsterhp.SetHP(monsterdata.HP);
          monsterhp.InitialHpBar(); 
        }
       
    }
    public virtual void GiveExp()
    {
        PlayerManager.Instance.GetEXP(monsterdata.Exp);
    }

    public virtual void Die()
    {
        var DestroyCollider = this.GetComponent<Collider2D>();
        if(monsterController!=null)
        {
            monsterController.Die();
        }
        Destroy(DestroyCollider);
        GiveExp();
        NotifyObserver();
        Destroy(gameObject, 1.0f);
    }
  
    public virtual void Hurt(float damage)
    { 
        monsterhp.Damaged(damage);
    }


    private  void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.CompareTag("Player"))
        {
            collision.gameObject.GetComponent<HPManager>().Damaged(monsterdata.attackDamage);
        }
    }
    
    public void RegisterObserver(IObserver observer) // 몬스터가 observer를 등록함
    {
        this.observers.Add(observer);
    }
    public void RemoveObserver(IObserver observer) 
    {
        this.observers.Remove(observer);
    }
    public void NotifyObserver() // 몬스터의 상태에 변화가 생기면 알림을 보냄 
    {
        foreach(var observer in observers)
        {
            observer.UpdateObserver();
        }
    }

}

몬스터의 상태에 변화가 생긴다, 즉 몬스터가 죽게되면 알림을 보내게 된다.
이후 MonsterCount 스크리브에서 IObserver를 상속시켜주었다.


public class MonsterCount : MonoBehaviour,IObserver
{
    
    [SerializeField] int RemainMonster;
    [SerializeField] int MaxMonster;
    [SerializeField] TMP_Text CountText;
    [SerializeField] GameObject bossSpawner;
    
    public void UpdateObserver() // 몬스터가 죽었으므로 남은 몬스터 갱신
    {
        RemainMonster--;
        CountText.text = RemainMonster + "/" + MaxMonster;
       
        if(RemainMonster==0) // 남은 몬스터 없으면 보스 소환
        {
            bossSpawner.SetActive(true);
        }
    }
    public void AddMonster(Monster monster) // 몬스터  추가 
    {
        RemainMonster++;
        MaxMonster++;
        monster.RegisterObserver(this);
     
    }
    private void Start()
    {
      
        RemainMonster = 0; MaxMonster = 0;
        Monster[]monsters=FindObjectsOfType<Monster>();//Scene에 Monster들을 찾음
        foreach(var monster in monsters)
        {
            AddMonster(monster);
        }
        CountText.text = RemainMonster + "/" + MaxMonster;
        bossSpawner.SetActive(false);
    }

}

처음 Start에서는 Scene에 있는 monster 들을 찾은 다음 Addmonster를 하여주었다.
그다음 UpdateObserver는
NotifyObserver를 통해 몬스터가 죽었다는것을 알림을 받았으므로, RemainMonster의 값을 감소시켜준다.

이렇게 Observer 패턴을 구현을 해보았다.
Observer 패턴을 나중에는 어떻게 이용해볼수있을까도 생각을 해보았는데 덩치가 큰 몬스터의 부위 들을 나누어 부위 하나가 파괴되었을때 Observer를 통해 알린후 그다음 행동으로 넘어갈수있지 않을까 라는 생각을 해보았다.

profile
이재현의 필기노트

0개의 댓글