옵저버 패턴은 객체 간 1:N 의존 관계를 정의하여, 한 객체(Subject, 주체)의 상태가 변경될 때 이를 자동으로 여러 객체(Observer, 관찰자)에게 알리고 갱신하는 디자인 패턴이다.
1. 옵저버 패턴의 개념
- 주체(Subject): 상태 변화를 감지하고 옵저버들에게 알리는 역할.
- 옵저버(Observer): 주체의 상태 변화를 감지하고 동작을 수행하는 객체들.
- 등록(Subscribe) / 해제(Unsubscribe): 옵저버는 주체에게 자신을 등록하거나 해제할 수 있음.
📌 동작 과정
- 옵저버가 주체에 등록(Subscribe)
- 주체(Subject)는 옵저버(Observer)를 리스트로 관리.
- 주체의 상태 변화 발생
- 주체가 모든 옵저버에게 변경 사항을 알림
- 각 옵저버가 전달받은 변경 사항을 기반으로 동작 수행
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. 옵저버 패턴 개념
옵저버 패턴에서는 두 가지 주요 개념이 있어요.
- 주체(Subject, Publisher)
- 상태(예: 체력)가 변경되면 이벤트를 발생시키는 역할.
- 예제에서 Player 클래스가 이 역할을 함.
- 구독자(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 등을 구현할 때 사용해봐야겠다.
'공부 > 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 |