Notice
Recent Posts
Recent Comments
Link
오르막길
[Unity] 옵저버 패턴 (Observer Pattern) 알아보기 본문
✅ 기본 개념
- 옵저버 패턴 (Observer Pattern)은 발행자와 구독자 간의 일대다 관계를 정의하여, 발행자의 상태가 변할 때 이를 구독자에게 자동으로 알리는 패턴
- 이벤트 기반의 비동기 구조에서 자주 사용됨
🗂️ 주요 특징
- 느슨한 결합: 발행자와 구독자가 서로 직접 참조하지 않음
- 확장성: 구독자를 쉽게 추가하거나 제거 가능
- 재사용성: 동일한 이벤트를 여러 구독자가 동시에 사용할 수 있음
- RuleTrigger는 옵저버 패턴으로 설계하는 게 좋음
- EventPublisher가 이미 간단한 옵저버 역할을 하고 있지만, 더 체계적으로 관리할 수 있음
📝 옵저버 패턴 적용 (RuleTrigger)
- EventPublisher - 이벤트 발송자
- RuleManager - 이벤트 수신자
- RuleData - 트리거 상태 관리
📂 구조
1. 발행자 (Publisher) - EventPublisher
using System;
using UnityEngine;
public static class EventPublisher
{
public static event Action<string> OnRuleTriggered;
public static void TriggerRule(string ruleID)
{
// 모든 구독자에게 알림
OnRuleTriggered?.Invoke(ruleID);
Debug.Log($"[Publisher] Rule Triggered: {ruleID}");
}
}
- 역할: 이벤트를 정의하고 발송하는 전역 이벤트 관리자.
- 핵심: OnRuleTriggered 이벤트를 구독한 모든 객체에게 알림을 전달.
2. 구독자 (Observer) - RuleManager
using UnityEngine;
using System.Collections.Generic;
public class RuleManager : MonoBehaviour
{
[SerializeField] private RuleDatabase ruleDatabase;
private Dictionary<string, bool> ruleStatus = new Dictionary<string, bool>();
private void OnEnable()
{
// 이벤트 구독
EventPublisher.OnRuleTriggered += HandleRuleTriggered;
InitializeRules();
}
private void OnDisable()
{
// 구독 해제
EventPublisher.OnRuleTriggered -= HandleRuleTriggered;
}
private void InitializeRules()
{
foreach (RuleData rule in ruleDatabase.rules)
{
if (!rule.isRepeatable && !ruleStatus.ContainsKey(rule.ruleID))
{
ruleStatus[rule.ruleID] = false;
}
}
}
// 규칙 처리
private void HandleRuleTriggered(string ruleID)
{
RuleData rule = ruleDatabase.rules.Find(r => r.ruleID == ruleID);
if (rule != null)
{
// 반복 불가 규칙인지 검사
if (!rule.isRepeatable && ruleStatus.ContainsKey(rule.ruleID) && ruleStatus[rule.ruleID])
{
Debug.Log($"[Observer] Rule '{rule.ruleID}' has already been triggered and is not repeatable.");
return;
}
Debug.Log($"[Observer] Rule Triggered: {rule.description}");
if (!rule.isRepeatable)
{
ruleStatus[rule.ruleID] = true;
}
}
else
{
Debug.LogWarning($"[Observer] No matching rule found for ID: {ruleID}");
}
}
}
- 역할: 특정 이벤트가 발생했을 때 이를 처리하는 구독자.
- 핵심: EventPublisher.OnRuleTriggered를 구독하여 HandleRuleTriggered() 메서드로 이벤트를 처리.
3. 트리거 (Trigger) - RuleTrigger
using UnityEngine;
public class RuleTrigger : MonoBehaviour
{
[SerializeField] private string ruleID;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.CompareTag("Player"))
{
// 트리거 발동 (Publisher에게 알림 전송)
EventPublisher.TriggerRule(ruleID);
}
}
}
- 역할: 물리적인 트리거 이벤트를 감지하고 발행자에게 이벤트를 전달.
- 핵심: OnTriggerEnter2D()에서 EventPublisher.TriggerRule() 호출로 발행.
📂 전체 데이터 흐름
- 플레이어가 트리거 영역에 진입 (RuleTrigger)
- OnTriggerEnter2D()가 호출되어 EventPublisher.TriggerRule(ruleID) 실행.
- 이벤트 발송 (EventPublisher)
- OnRuleTriggered 이벤트가 모든 구독자 (RuleManager)에게 발송됨.
- 이벤트 수신 (RuleManager)
- HandleRuleTriggered(string ruleID)가 호출되어 규칙을 처리.
🛠️ 실제 흐름 예시 (플레이어가 트리거에 닿을 때)
- 플레이어가 RuleTrigger의 콜라이더에 닿음
- [RuleTrigger] Player entered trigger area
- EventPublisher가 이벤트 발송
- [Publisher] Rule Triggered: Rule001
- RuleManager가 이벤트 수신
- [Observer] Rule Triggered: (Rule001에 대한 설명)
- RuleTrigger는 단순히 물리적 충돌을 감지하는 발생 조건일 뿐
- EventPublisher는 이벤트를 발송하는 핵심 발행자
- RuleManager는 이 발행자의 이벤트를 구독하여, 알림이 오면 적절히 반응하는 구독자
📂 그 외 추가 가능한 구독자 예시
1. 🗺️ 미니맵 시스템 (MiniMapManager)
- 목적: 특정 룰이 발동되면 미니맵에 새로운 정보를 표시.
- 예시: 어떤 지역의 문이 열리면 미니맵에 경로 표시.
using UnityEngine;
public class MiniMapManager : MonoBehaviour
{
private void OnEnable()
{
EventPublisher.OnRuleTriggered += HandleRuleTriggered;
}
private void OnDisable()
{
EventPublisher.OnRuleTriggered -= HandleRuleTriggered;
}
private void HandleRuleTriggered(string ruleID)
{
Debug.Log($"[MiniMap] Updating map for rule: {ruleID}");
// 예시: 문이 열리면 미니맵에 새로운 경로 표시
}
}
2. 💡 환경 효과 시스템 (EnvironmentEffectManager)
- 목적: 특정 규칙이 발동되면 게임 환경에 시각적 효과 추가.
- 예시: 특정 트리거에서 불꽃이 일어나거나, 조명이 깜빡임.
using UnityEngine;
public class EnvironmentEffectManager : MonoBehaviour
{
private void OnEnable()
{
EventPublisher.OnRuleTriggered += HandleRuleTriggered;
}
private void OnDisable()
{
EventPublisher.OnRuleTriggered -= HandleRuleTriggered;
}
private void HandleRuleTriggered(string ruleID)
{
Debug.Log($"[EnvironmentEffect] Triggering environmental effect for rule: {ruleID}");
// 예시: 연기가 발생하거나 조명이 깜빡이는 효과 추가
}
}
3. 🖥️ UI 시스템 (UIManager)
- 목적: 규칙이 발동될 때 UI를 업데이트하거나 메시지 표시.
- 예시: 특정 문이 열리면 "새로운 길이 열렸습니다!" 팝업 표시.
using UnityEngine;
public class UIManager : MonoBehaviour
{
private void OnEnable()
{
EventPublisher.OnRuleTriggered += HandleRuleTriggered;
}
private void OnDisable()
{
EventPublisher.OnRuleTriggered -= HandleRuleTriggered;
}
private void HandleRuleTriggered(string ruleID)
{
Debug.Log($"[UI] Displaying message for rule: {ruleID}");
// 예시: 화면에 팝업 메시지 표시
}
}
4. 🎥 카메라 시스템 (CameraManager)
- 목적: 특정 이벤트가 발생하면 카메라를 전환하거나 화면을 흔들리게 함.
- 예시: 적이 등장하면 카메라가 그 방향으로 순간 이동.
using UnityEngine;
public class CameraManager : MonoBehaviour
{
private void OnEnable()
{
EventPublisher.OnRuleTriggered += HandleRuleTriggered;
}
private void OnDisable()
{
EventPublisher.OnRuleTriggered -= HandleRuleTriggered;
}
private void HandleRuleTriggered(string ruleID)
{
Debug.Log($"[Camera] Changing camera perspective for rule: {ruleID}");
// 예시: 특정 상황에서 카메라 이동 또는 줌 효과
}
}
🔗 구독자들의 공통점
- OnEnable: 이벤트 구독 시작
- OnDisable: 이벤트 구독 해제
- HandleRuleTriggered: 규칙 발동 시의 반응 로직 정의
🛠️ 장점
- 유지보수 용이: 발행자와 구독자가 독립적이므로 코드 관리가 쉬움
- 확장성: 새로운 구독자를 쉽게 추가 가능
- 재사용성: 동일한 이벤트를 다양한 구독자가 동시에 처리 가능
⚠️ 주의점
- 구독 해제를 잊으면 메모리 누수가 발생할 수 있음
- 이벤트 호출이 많아지면 성능 문제가 발생할 수 있음
📝 옵저버 패턴 팁
- 메모리 관리
- 구독 해제 (=)를 항상 잊지 말기
- 특히 씬 전환 시 구독 해제를 안 하면 메모리 누수의 주범
- 멀티스레딩
- 이벤트가 많을 때는 비동기 처리와의 호환성을 고려해야 함.
- 특히, Unity의 Main Thread 문제는 항상 염두에 두기
- 구독자 관리
- 너무 많은 구독자가 하나의 이벤트를 구독할 때는 성능 이슈가 생길 수 있음
- 필요하다면 Event Queue나 Event Buffer로 최적화
- 다양한 이벤트 유형
- 단순 Action뿐만 아니라 EventArgs, UnityEvent, C# Delegate 같은 다양한 이벤트 방식을 혼용해보기
- 디버그 편의성
- 이벤트 로그를 잘 관리하면 나중에 디버그가 훨씬 쉬워짐
- 특히, 예기치 않은 이벤트 호출 문제를 쉽게 잡아낼 수 있음
'학습 기록하기 > Unity' 카테고리의 다른 글
[Unity] 상태 패턴 (State Pattern) 알아보기 (0) | 2025.05.26 |
---|---|
[Unity] 쉽게 할 수 있는 최적화 팁들 (+추가 예정) (0) | 2025.04.28 |
[Unity] 유니티 시네머신 훑어보기: 다양한 카메라 연출을 위한 필수 기능 (0) | 2025.03.31 |
[Unity] 유니티 WebGL 빌드 파일을 Nginx 기반 로컬에서 테스트해보자 (0) | 2024.01.09 |
[Unity] 유니티에서 WebGL 템플릿을 직접 커스텀해보자: WebGL Custom Templete (1) | 2023.11.22 |