
예를 들어, 탄약의 양은 무기가 발사되었거나 새로 탄약을 얻는 두 가지 경우에만 변경된다. 이런 경우에만 프로퍼티가 변경되기 때문에, 이런 지점에서만 프로퍼티의 값을 검증하면 된다.
get, set을 이용하면 유용하다.
using UnityEngine;
using System.Collections;
public class EnemyObject : MonoBehavior, IListener
{
//--------------
//private 변수에 접근하기 위한 C# 접근자
public int Health
{
get{return _healthl;}
set
{
// 체력 값을 0-100 범위에 맞춘다
_health = Mathf.Clamp(value, 0, 100);
//죽었는지 검사한다.
if(_health <= 0)
{
onDead();
return;
}
if(_health <= 20)
{
OnHealthLow();
return;
}
}
}
//--------------------
public int Ammo
{
get{return _ammo;}
set{
// 탄약 값을 0-50 범위에 맞춘다.
_ammo = Mathf.Clamp(value, 0, 50);
//탄약이 떨어졌는지 검사한다.
if(_ammo <= 0)
{
//탄약 소진 이벤트를 호출한다.
OnAmmoExpired();
return;
}
}
}
//-----------------
//체력과 탄약 양을 기록하기 위한 내부 변수
private int _health = 100;
private int _ammo = 50;
//----------------
//Update는 매 프레임마다 한 번씩 호출된다.
void Update()
{
}
//-------------------------------
// 이 이벤트는 체력이 낮을 때 호출된다.
void OnHealthLow()
{
//여기서 이벤트 응답을 처리한다.
}
//이 이벤트는 적이 죽었을 때 호출된다.
void OnDead()
{
//여기에서 이벤트 응답을 처리한다.
}
//----------------------------------
//탄약 소진 이벤트
void OnAmmoExpired()
{
//여기에서 이벤트 응답을 처리한다.
}
//-----------------------
}
Mathf.Clamp : Clamps a value between a minimum float and maximum float value.
EventManager 클래스를 통해 오브젝트가 특정 이벤트를 수신할 수 있도록 하려고 한다.
이벤트 리스너 EventListener : 자신이 발생시킨 이벤트를 포함한 어떤 이벤트가 발생하면 알기를 원하는 모든 오브젝트를 리스너라고 한다. 이벤트 포스터 EventPoster : 오브젝트가 이벤트 발생을 알아차린 경우, 이 오브젝트는 다른 모든 릿스너가 알 수 있게 이벤트에 대해 알려야 한다. 이벤트 매니저 EventManager : 싱글톤 오브젝트로 리스너를 포스터에게 실질적으로 연결하는 역할을 한다. 이벤트 매니저는 포스터가 보낸 알림을 받고 적합한 모든 리스너에게 이벤트 형식으로 즉시 알림을 발생시킨다.C#에서 인터페이스는 껍데기 뿐인 기본 추상 클래스와 같은 것이다. 클래스와 달리 인터페이스는 함수의 이름, 반환 형식, 파라미터와 같은 함수 원형을 선언하는 것만 허용된다. 함수의 본문을 선언하지는 못한다. 파생된 클래스가 가지게 될 모든 전체 함수 세트만 단순하게 선언하는 것이 인터페이스의 역할이기 때문이다.
이런 점이 리스너 오브젝트를 만들 때 인터페이스가 적합한 후보가 될 수 있는 이유다. 파생되는 모든 오브젝트에 리스너 인터페이스를 선언하면, 모든 오브젝트가 이벤트 리스너가 될 수 있는 능력을 가질 수 있다.
using UnityEngine;;
using System.Collections;
//--------------------------------------
// 가능한 게임 이벤트를 모두 열거한다.
// 더 많은 이벤트가 리스트에 추가된다.
public enum EVENT_TYPE { GAME_INIT, GAME_END, AMMO_CHANGE, HEALTH_CHANGE, DEAD};
//--------------------------------------
// 리스너 클래스에서 구현될 리스너 인터페이스
public **interface** IListener
{
// 이벤트가 발생할 때 리스너에서 호출할 함수
void OnEvent(EVENT_TYPE Event_Type, Component Sender, object Param = null);
}
//---------------------------------------
코드 설명 : C# 인터페이스를 선언함. 하나뿐인 이벤트, OnEvent를 지원한다. 이 함수는 모든 파생 클래스에 상속되어 이벤트 매니저에 등록된 리스너에게 이벤트가 발생할 때 이벤트 매니저에 의해 불리게 된다. OnEvent는 단순한 함수의 원형일 뿐 내용을 구현하지 않았다는 점을 주목하자.
이제 어떤 오브젝트든 스스로를 리스너로 선언하면 이벤트를 수신하는 것이 가능해진다.
using UnityEngine;
using System.Collections;
public class MyCustomListener : MonoBehaviour, IListener
{
//여기에서 초기화한다.
void Start () {}
// Update는 매 프레임마다 한번씩 호출된다.
void Update() {}
//--------------------------------------
// 이벤트 수신을 위해 OnEvent 함수를 구현한다.
public void OnEvent(EVENT_TYPE Event_Type, Component Sender, object Param = null)
{
}
//----------------------------------
}
Instance는 EventManager 클래스의 유일한 인스턴스를 제공하는 public static 프로퍼티입니다. 이 프로퍼티를 통해 다른 클래스가 EventManager 인스턴스에 접근할 수 있습니다.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
//--------------------------------------
// 리스너들에게 이벤트를 보내기 위한 이벤트 매니저 싱글톤
// IListener 구현과 함께 작동한다.
public class EventManager : MonoBehaviour
{
#region C# 프로퍼티
//----------------------------------------
// 인스턴스에 접근하기 위한 public 프로퍼티
public static EventManager Instance
{
get { return Instance; }
set {}
}
#endregion
#region 변수들
// 이벤트 매니저 인스턴스에 대한 참조(싱글톤 디자인 패턴)
private static EventManager instance = null;
// 리스너 오브젝트의 배열 (모든 오브젝트가 이벤트 수신을 위해 등록되어 있다)
private Dictionary<EVENT_TYPE, List<IListener>> Listeners = new Dictionary<EVENT_TYPE, List<IListener>> ();
#endregion
//---------------------------------------------
#region 메소드
// 시작 시에 초기화를 위해 호출된다.
void Awake()
{
// 인스턴스가 없는 경우 현재 인스턴스를 할당한다.
if(instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject); // 씬에서 빠져나갈 때 파괴되는 것을 방지한다.
}
else // 인스턴스가 이미 있다면 현재 인스턴스를 파괴한다. 싱글톤 오브젝트가 되어야 한다.
DestroyImmediate(this);
}
//-------------------------------------------------
/// <summary>
/// 리스너 배열에 지정된 리스너 오브젝트를 추가하기 위한 함수
/// </summary>
/// <param name="Event_Type">수신할 이벤트</param>
/// <param name="Listener">이벤트를 수신할 오브젝트</param>
public void AddListener(EVENT_TYPE Event_Type, IListener Listener)
{
// 이 이벤트를 수신할 리스너의 리스트
Listener<IListner> ListenList = null;
// 이벤트 형식 키가 존재하는지 검색한다. 존재하면 이것을 리스트에 추가한다.
if(Listeners.TryGetValue(Event_Type, out ListenList))
{
//리스트가 존재하면 새 항목을 추가한다.
ListenList.Add(Listener);
return;
}
// 아니면 새로운 리스트를 생성한다.
ListenList = new List<IListener> ();
ListenList.Add(Listener);
Listeners.Add(Event_Type, ListenList); // 내부의 리스너 리스트에 추가한다.
}
//-----------------------------------
/// <summary>
/// 이벤트를 리스너에게 전달하기 위한 함수
/// </summary>
/// <param name="Event_Type">불려진 이벤트</param>
/// <param name="Sender">이벤트를 부르는 오브젝트</param>
/// <param name="Param">선택 가능한 파라미터</param>
public void PostNotification (EVENT_TYPE Evemt_Type, Component Sender, object Param = null)
{
// 모든 리스너에게 이벤트에 대해 알린다.
// 이 이벤트를 수신하는 리스너들의 리스트
Listeners<IListener> ListenList = null;
// 이벤트 항목이 없으면, 알릴 리스너가 없으므로 끝낸다.
if(!Listeners.TryGetValue(Event_Type, out ListenList))
return;
// 항목이 존재한다. 이제 적합한 리스너에게 알려준다.
for(int i=0; i<ListenList.Count; i++)
{
// 오브젝트가 null이 아니면 인터페이스를 통해 메세지를 보낸다.
if(!ListenList[i].Equals(null))
ListenList[i].OnEvent(Event_Type, Sender, Param);
}
}
//-----------------------------------------------
// 이벤트 종류와 리스너 항목을 딕셔너리에서 제거한다.
public void RemoveEvent(EVENT_TYPE Event_Type)
{
// 딕셔너리의 항목을 제거한다.
Listeners.Remove(Event_Type);
}
//------------------------------------------------
// 딕셔너리에서 쓸모없는 항목들을 제거한다.
public void RemoveRedundancies()
{
// 새 딕셔너리 생성
Dictionary<EVENT_TYPE, List<IListener>> TmpListeners = new Dictionary<EVENT_TYPE, List<IListener>> ();
// 모든 딕셔너리 항목을 순회한다
foreach(KeyValuePair<EVENT_TYPE, List<IListener>> Item in Listeners)
{
// 리스트의 모든 리스너 오브젝트를 순회하며 null 오브젝트를 제거한다.
for(int i = Item.Value.Count-1; i>=0; i--)
{
// null이면 항목을 지운다.
if(Item.Value[i].Equals(null))
Item.Value.RemoveAt(i);
}
// 알림을 받기 위한 항목들만 리스트에 남으면 이 항목들을 임시 딕셔너리에 담는다.
if(Item.Value.Count > 0)
TmpListeners.Add(Item.Key, Item.Value);
}
// 새로 최적화된 딕셔너리로 교체한다.
Listeners = TmpListeners;
}
//--------------------------
// 씬이 변경될 때 호출된다. 딕셔너리를 청소한다.
void OnLevelWasLoaded ()
{
RemoveRedundancies();
}
//------------------------------------------
#endregion
}
코드 설명 : Dictionary 클래스를 포함해서 추가적인 모노 클래스 접근이 가능한 Systemp.Collections.Generic 네임스페이스를 추가한 점에 주목하자. Dictionary 클래스는 특별한 형식의 2D 배열로서 한 쌍의 키와 값을 기반으로 하는 값들의 데이터베이스를 저장할 수 있도록 한다.
24번 줄 : Dictionary 클래스를 이용해 private 멤버 변수인 Listeners가 선언되었다. 키-값의 쌍들을 가지는 해시 테이블 구조를 이용해 데이터베이스처럼 검색하는 것이 가능하다.
72번 줄 : 이벤트가 감지되면 리스너든 아니든 모든 오브젝트에서 PostNotification 함수를 호출할 수 있다. 호출된 후, EventManager는 딕셔너리의 일치하는 항목들을 순회하며 현재 이벤트에 연결된 모든 리스너를 찾고 IListener 인터페이스의 OnEvent 메소드르르 부름으로써 현재 이벤트를 알려주게 된다.
프로퍼티 : 객체의 데이터에 접근하는 방법을 제공하는 메서드로, get과 set을 이용한다. 이 방식은 객체 지향 프로그래밍에서 데이터 은닉을 유지하면서도 클래스 외부에서 안전하게 값을 가져오거나 설정할 수 있도록 해준다.
인스턴스 : 클래스에서 생성된 개별 객체를 의미한다.
if(Listeners.TryGetValue(Event_Type, out ListenList))
Listeners라는 사전(Dictionary)에서 주어진 Event_Type에 해당하는 키가 있는지 검색합니다.
TryGetValue 메서드는 Event_Type 키가 존재할 경우, 해당 키와 연결된 값을 ListenList 변수에 반환합니다.out 키워드는 해당 메서드 호출 후 결과를 변수 ListenList에 저장하도록 합니다.for문을 사용하지 않고 foreach만으로 처리할 수 있는가?foreach문만으로 처리할 수 없는 이유:
foreach문은 컬렉션을 순회하면서 요소를 수정하거나 제거할 수 없습니다. 만약 foreach 내에서 컬렉션의 요소를 제거하려고 하면 InvalidOperationException이 발생합니다.따라서, 리스트 내부의 null 요소를 제거하기 위해서는 for문을 사용하여 직접 인덱스로 접근해야 합니다.전처리기를 이용해 영역을 표시하고 나면 코드 접어두기 기능이 활성화되어 있을 때 모노디벨롭 코드 에디터에서 코드를 접을 수 있게 된다.
이 글은 "유니티 C# 스크립팅 마스터하기"를 참고하여 작성했습니다. 중상급 책으로 아주 좋음 !! 강추!! 어떻게 해야 EventManager를 잘 활용할 수 있을지 잘 다룸..!!