[Unity] 옵저버 패턴(Observer Pattern)

2025. 4. 1. 15:13·공부/Unity & C#
728x90

옵저버 패턴은 객체 간 1:N 의존 관계를 정의하여, 한 객체(Subject, 주체)의 상태가 변경될 때 이를 자동으로 여러 객체(Observer, 관찰자)에게 알리고 갱신하는 디자인 패턴이다.

 

1. 옵저버 패턴의 개념

  • 주체(Subject): 상태 변화를 감지하고 옵저버들에게 알리는 역할.
  • 옵저버(Observer): 주체의 상태 변화를 감지하고 동작을 수행하는 객체들.
  • 등록(Subscribe) / 해제(Unsubscribe): 옵저버는 주체에게 자신을 등록하거나 해제할 수 있음.

📌 동작 과정

  1. 옵저버가 주체에 등록(Subscribe)
    • 주체(Subject)는 옵저버(Observer)를 리스트로 관리.
  2. 주체의 상태 변화 발생
  3. 주체가 모든 옵저버에게 변경 사항을 알림
  4. 각 옵저버가 전달받은 변경 사항을 기반으로 동작 수행

2. 옵저버 패턴의 장점

✅ 객체 간의 결합도를 낮춤 → 주체(Subject)는 옵저버들이 어떤 동작을 수행하는지 몰라도 됨.
✅ 확장성 증가 → 새로운 옵저버를 쉽게 추가 가능.
✅ 재사용성 증가 → 옵저버는 독립적으로 동작 가능.

3. 옵저버 패턴의 단점

❌ 메모리 관리 필요 → 옵저버를 제거(Unsubscribe)하지 않으면 메모리 누수 발생 가능.
❌ 예측 어려움 → 많은 옵저버가 등록된 경우, 상태 변경 시 어떤 동작이 일어날지 예측 어려움.

 

4. 옵저버 패턴 예제

EventManager.cs

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

public class EventManager : MonoBehaviour
{
    //싱글톤 구현
    private static EventManager instance;

    public static EventManager Instance
    {
        get
        {
            if(instance == null)
            {
                GameObject go = new GameObject("EventManager");
                instance = go.AddComponent<EventManager>();
                DontDestroyOnLoad(go);
            }
            return instance;
        }
    }

    //이벤트와 옵저버를 연결하는 딕셔너리
    //object 타입의 데이터를 옵저버에게 전달할 수 있음
    private Dictionary<string, Action<object>> eventDictionary = new Dictionary<string, Action<object>>();

    //이벤트에 옵저버(listener)를 추가하는 역할
    public void AddListener(string eventName, Action<object> listener)
    {
    	//이미 이벤트가 존재하면 델리게이트를 추가
        if(eventDictionary.TryGetValue(eventName,out Action<object> thisEvent))
        {
            thisEvent += listener;
            eventDictionary[eventName] = thisEvent;
        }
        //존재하지 않으면 새로 추가
        else
        {
            eventDictionary.Add(eventName, listener);
        }
    }

    //이벤트에서 옵저버 제거
    public void RemoveListener(string eventName, Action<object> listener)
    {
        if(eventDictionary.TryGetValue(eventName,out Action <object> thisEvent))
        {
            thisEvent -= listener;
            eventDictionary[eventName] = thisEvent;
        }
    }

    //이벤트가 발생하면 등록된 모든 옵저버들에게 알림을 보냄
    //data를 전달하여 이벤트 핸들러에서 필요한 정보 사용 가능
    public void TriggerEvent(string eventName, object data = null)
    {
        if(eventDictionary.TryGetValue(eventName,out Action<object> thisEvent))
        {
            thisEvent?.Invoke(data);
        }
    }

}

UIHealthDisplay.cs

using UnityEngine;

//이벤트 리스너(옵저버)
public class UIHealthDisplay : MonoBehaviour
{
    void Start()
    {
        //이벤트 구독
        EventManager.Instance.AddListener("PlayerHealthChanged", OnPlayerHealthChanged);
        EventManager.Instance.AddListener("PlayerDied", OnPlayerDied);
    }

	//게임 오브젝트가 살제될 때 자동 호출
    //이벤트 리스너를 제거하여 메모리 누수를 방지
    private void OnDestroy()
    {
        //객체가 삭제될 때 동작하는 함수
        EventManager.Instance.RemoveListener("PlayerHealthChanged", OnPlayerHealthChanged);
        EventManager.Instance.RemoveListener("PlayeerDied", OnPlayerDied);
    }
    
    //PlayerHealthChanged이벤트가 발생하면 실행됨
    private void OnPlayerHealthChanged(object data)
    {
        int health = (int)data;//현재 체력 값을 가져옴
        Debug.Log($"플레이어 체력이 {health}로 변경되었습니다."); 
    }


    private void OnPlayerDied(object data)
    {
        Debug.Log("UI 업데이트: 플레이어가 사망했습니다.");
        //게임 오버 화면 표시 등의 동작 수행
    }
}

player.cs

public class Player : MonoBehaviour
{
    public int health = 100;

    public void TakeDamage(int damage)
    {
        health -= damage;
        EventManager.Instance.TriggerEvent("PlayerHealthChanged", health);

        if (health <= 0)
        {
            EventManager.Instance.TriggerEvent("PlayerDied");
        }
    }
}

 

  • TakeDamage(10); 호출 시, "PlayerHealthChanged" 이벤트 발생 → UIHealthDisplay에서 체력 업데이트됨.
  • 체력이 0 이하로 떨어지면 "PlayerDied" 이벤트 발생 → UI에서 사망 메시지를 출력.

 

예제 요약

✔ EventManager를 활용한 옵저버 패턴 기반 이벤트 리스너
✔ Start()에서 이벤트 등록, OnDestroy()에서 이벤트 해제
✔ OnPlayerHealthChanged() → 체력 변경 이벤트 처리
✔ OnPlayerDied() → 플레이어 사망 이벤트 처리
✔ Player 클래스에서 TriggerEvent로 이벤트 발생 가능

✔ EventManager를 통해 옵저버(UIHealthDisplay)에 체력 변화를 자동으로 전달

5. 옵저버 패턴이 자주 쓰이는 곳

🔹 게임 개발 (유니티)

  • UI 업데이트 (예: 체력바, 경험치바)
  • 적 AI 반응 (예: 플레이어 사망 시 적들이 반응)
  • 퀘스트 시스템 (예: 특정 목표 달성 시 보상 지급)

🔹 일반 소프트웨어

  • MVC 패턴에서 View 업데이트 (모델 데이터 변경 시 UI 자동 업데이트)
  • 이벤트 시스템 (버튼 클릭 시 여러 시스템 반응)

옵저버 패턴에 대한 개념이 잘 잡히지 않아서 챗지피티한테 물어봤다..

1. 옵저버 패턴이 필요한 이유

게임에서 플레이어 체력(Health)이 변경될 때마다 UI가 자동으로 업데이트되도록 만들고 싶다고 가정해볼게요.

만약 옵저버 패턴이 없다면?

  • Player 스크립트에서 UI를 직접 찾아서 업데이트해야 함.
  • 체력뿐만 아니라 경험치, 스코어, 보스 체력 등 추가할 요소가 많아질수록 코드가 복잡해짐.
  • 여러 곳에서 체력 정보를 수정할 경우, 수정해야 할 코드가 많아짐 → 유지보수가 어려워짐.

그래서 "플레이어 체력이 변경되면 알아서 UI가 반응하도록 만들자!"
이게 바로 옵저버 패턴의 핵심 원리예요.

2. 옵저버 패턴 개념

옵저버 패턴에서는 두 가지 주요 개념이 있어요.

  1. 주체(Subject, Publisher)
    • 상태(예: 체력)가 변경되면 이벤트를 발생시키는 역할.
    • 예제에서 Player 클래스가 이 역할을 함.
  2. 구독자(Observer, Listener, Subscriber)
    • 주체가 이벤트를 발생시키면 자동으로 반응하는 역할.
    • 예제에서 UIHealthDisplay 클래스가 이 역할을 함.

📌 즉, "주체가 변화하면 구독자들이 자동으로 반응하도록 연결하는 패턴"

3. 옵저버 패턴 정리

📌 옵저버 패턴을 사용하면?
✔ Player(주체) 가 UI(옵저버)를 직접 수정할 필요 없이 "체력이 바뀌었다"는 신호(이벤트)만 보냄
✔ UI는 이 신호를 받아서 자동으로 업데이트됨
✔ UI뿐만 아니라, 사운드, 이펙트, AI 반응 등 여러 요소를 추가로 연결 가능

 

4. 옵저버 패턴 적용 전후 비교

❌ 옵저버 패턴이 없는 코드 (직접 접근 방식)

public class Player : MonoBehaviour
{
    public UIHealthDisplay uiHealthDisplay; // 직접 UI를 참조

    private int health = 100;

    public int Health
    {
        get => health;
        set
        {
            health = value;
            uiHealthDisplay.UpdateHealth(health); // 직접 UI를 변경
        }
    }
}

🚫 Player가 UIHealthDisplay를 직접 수정해야 함 → UI가 변경되면 Player도 수정해야 함.
🚫 유지보수 어렵고, 다른 기능을 추가하기 어려움

✅ 옵저버 패턴 적용 후

public int Health
{
    get => health;
    set
    {
        health = value;
        EventManager.Instance.TriggerEvent("PlayerHealthChanged", health); // UI가 자동으로 반응
    }
}

✅ Player는 UI가 뭘 하는지 신경 쓸 필요 없음
✅ UI, 사운드, 이펙트, AI 등 여러 요소를 독립적으로 추가 가능
✅ 유지보수가 쉬워지고, 코드 확장성이 좋아짐

 

5. 결론

✔ 옵저버 패턴 = "어떤 객체의 상태가 변하면, 그 변화를 자동으로 여러 객체에게 알리는 패턴"
✔ Player는 "PlayerHealthChanged" 이벤트를 발생시키고, UIHealthDisplay는 그 이벤트를 구독해서 자동으로 UI 업데이트
✔ 이벤트 기반 프로그래밍이라서 코드를 분리하고, 유지보수하기 쉬워짐

 


지금까지 이해된 걸로는 옵저버 패턴을 사용하면 UI 캔버스 오브젝트를 직접 참조하지 않고도

이벤트를 발생시켜서 플레이어의 체력, 경험치 등의 속성을 전달할 수 있다는 것 같다.

 

앞으로 UI 등을 구현할 때 사용해봐야겠다.

728x90

'공부 > Unity & C#' 카테고리의 다른 글

[Unity6] Multiplay 멀티플레이  (0) 2025.04.18
[Visual Studio] 클래스 다이어그램 보기  (0) 2025.04.07
[Unity 2D]시네머신 카메라로 카메라 범위 제한하기  (0) 2025.03.26
[멋쟁이사자처럼부트캠프 Unity 게임개발 4기] 시네머신 카메라로 카메라 범위 조정하기  (0) 2025.03.18
[Unity] 디자인패턴 : Singleton 싱글톤 패턴  (0) 2025.01.10
'공부/Unity & C#' 카테고리의 다른 글
  • [Unity6] Multiplay 멀티플레이
  • [Visual Studio] 클래스 다이어그램 보기
  • [Unity 2D]시네머신 카메라로 카메라 범위 제한하기
  • [멋쟁이사자처럼부트캠프 Unity 게임개발 4기] 시네머신 카메라로 카메라 범위 조정하기
knhoo
knhoo
  • knhoo
    &*
    knhoo
  • 전체
    오늘
    어제
    • 전체 (145)
      • Unity 개발일지 (20)
        • [Unity2D]졸업프로젝트 (17)
        • [Unity3D]VR프로젝트 (2)
      • 공부 (120)
        • 게임 수학 (1)
        • 부트캠프 (13)
        • C++ (39)
        • Unity & C# (8)
        • 데이터베이스 (2)
        • 컴퓨터비전 (0)
        • 컴퓨터구조 (0)
        • python (7)
        • BAEKJOON (39)
        • 개발 (2)
        • 자료구조 (9)
      • 일상 (2)
  • 블로그 메뉴

    • Github
    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • 📖README
  • 인기 글

  • 태그

    캐시워크
    unity
    til
    비트버니
    자료구조
    앱테크
    unity2d
    C++
    머니워크
    오블완
    c#
    백준 #python
    야핏무브
    Python
    백준
    티스토리챌린지
    구간합
    멋쟁이사자처럼후기
    Cpp
    패널파워
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
knhoo
[Unity] 옵저버 패턴(Observer Pattern)
상단으로

티스토리툴바