들어가기 앞서
구현 및 R&D
턴제시스템 전체 플로우
개발
● 플레이어가 선턴은 고정으로 되어있음
>이전코드
// 필요한 이벤트만 구성 - Good
public enum EventType
{
Boss_Hit, // 피격/경직
Boss_Stun, // 강제 스턴
Boss_Die, // 사망
Boss_Phase, // 페이즈(각성) 진입
}
public class EventManager : Singleton<EventManager>
{
// 이벤트 리스너 리스트를 Dictionary를 통해 관리 EventType은 IN과 OUT으로 두가지 분류의 리스트로 관리
private Dictionary<EventType, List<IEventListener>> listeners = new Dictionary<EventType, List<IEventListener>>();
private void OnEnable()
{
UnityEngine.SceneManagement.SceneManager.sceneLoaded += SceneManagerSceneLoaded;
}
private void OnDisable()
{
UnityEngine.SceneManagement.SceneManager.sceneLoaded -= SceneManagerSceneLoaded;
}
private void SceneManagerSceneLoaded(Scene arg0, LoadSceneMode arg1)
{
//씬이 바뀜에 따라 이벤트 의존성을 제거해준다.
RefreshListeners();
}
public void AddListener(EventType eventType, IEventListener listener) // 이벤트 받는 역할
{
List<IEventListener> ListenList = null;
if (listeners.TryGetValue(eventType, out ListenList))
{
//해당 이벤트 키값이 존재한다면, 이벤트를 추가해준다.
ListenList.Add(listener);
return;
}
ListenList = new List<IEventListener>();
ListenList.Add(listener);
listeners.Add(eventType, ListenList);
}
// object Param 을 구체화 하는 것을 권장
public void PostNotification(EventType eventType, Component Sender, object Param = null) // 이벤트 발생역할
{
List<IEventListener> ListenList = null;
//이벤트 리스너(대기자)가 없으면 그냥 리턴.
if (!listeners.TryGetValue(eventType, out ListenList))
return;
//모든 이벤트 리스너(대기자)에게 이벤트 전송.
for (int i = 0; i < ListenList.Count; i++)
{
if (ListenList[i] != null)
{
ListenList[i].OnEvent(eventType, Sender, Param);
}
}
}
public void RemoveEvent(EventType eventType) // 모든이벤트 삭제
{
listeners.Remove(eventType);
}
public void RemoveListener(EventType evt, IEventListener listener)
{
if (!listeners.TryGetValue(evt, out var set)) return;
set.Remove(listener);
if (set.Count == 0) listeners.Remove(evt);
}
// 리스너가 자신이 등록된 모든 이벤트에서 제거될 때 사용
public void RemoveTarget(IEventListener listener)
{
var keys = new List<EventType>(listeners.Keys);
foreach (var k in keys)
{
var set = listeners[k];
set.Remove(listener);
if (set.Count == 0) listeners.Remove(k);
}
}
private void RefreshListeners() // Scene전환시 모든 이벤트 초기화
{
//임시 Dictionary 생성
Dictionary<EventType, List<IEventListener>> TmpListeners = new Dictionary<EventType, List<IEventListener>>();
//씬이 바뀜에 따라 리스너가 Null이 된 부분을 삭제해준다.
foreach (KeyValuePair<EventType, List<IEventListener>> Item in listeners)
{
for (int i = Item.Value.Count - 1; i >= 0; i--)
{
if (Item.Value[i] == null)
Item.Value.RemoveAt(i);
}
if (Item.Value.Count > 0)
TmpListeners.Add(Item.Key, Item.Value);
}
//살아있는 리스너는 다시 넣어준다.
listeners = TmpListeners;
}
}
>변경코드
public enum EventType
{
// 이벤트 생길시 추가
// Command 파이프라인 이벤트(필요한 것만 최소 추가)
CommandBuffered, // 커맨드가 플래닝 버퍼에 적재됨
CommandCommitted, // 버퍼 → 큐(또는 매크로)로 커밋됨
CommandExecuted, // 큐에서 실제 실행됨(모델 상태 변경 완료)
CommandUndone // 버퍼 취소 또는 실행 후 Undo 수행됨
}
public class EventManager : MonoBehaviour
{
// 이벤트별 델리게이트 저장
private readonly Dictionary<EventType, Delegate> _handlers = new Dictionary<EventType, Delegate>();
// 구독 (메소드에 매개변수 없을때 사용)
public void Subscribe(EventType eventType, Action handler)
{
if (_handlers.TryGetValue(eventType, out var del))
{
_handlers[eventType] = (Action)del + handler;
}
else
{
_handlers[eventType] = handler;
}
}
// 구독 (메소드에 매개변수 있을때 사용)
public void Subscribe<T>(EventType eventType, Action<T> handler)
{
if (_handlers.TryGetValue(eventType, out var del))
{
if (del != null && del.GetType() != typeof(Action<T>))
throw new InvalidOperationException($"Event {eventType} already registered with different payload type.");
_handlers[eventType] = (Action<T>)del + handler;
}
else
{
_handlers[eventType] = handler;
}
}
// 구독 해제 (메소드에 매개변수 없을때 사용)
public void Unsubscribe(EventType eventType, Action handler)
{
if (!_handlers.TryGetValue(eventType, out var del)) return;
del = (Action)del - handler;
if (del == null) _handlers.Remove(eventType);
else _handlers[eventType] = del;
}
// 구독 해제 (메소드에 매개변수있을 경우 사용)
public void Unsubscribe<T>(EventType eventType, Action<T> handler)
{
if (!_handlers.TryGetValue(eventType, out var del)) return;
if (del != null && del.GetType() != typeof(Action<T>)) return;
del = (Action<T>)del - handler;
if (del == null) _handlers.Remove(eventType);
else _handlers[eventType] = del;
}
// 발행 (메소드에 매개변수없을 경우 사용)
public void Publish(EventType eventType)
{
if (_handlers.TryGetValue(eventType, out var del))
{
(del as Action)?.Invoke();
}
}
// 발행 (메소드에 매개변수있을 경우 사용)
public void Publish<T>(EventType eventType, T payload)
{
if (_handlers.TryGetValue(eventType, out var del))
{
(del as Action<T>)?.Invoke(payload);
}
}
}
● 시사점 |
기존의 방식은 구독하는방식에 변수 캐스팅 실수가 발생할수도 있는 상황이있어서 인터페이스에 object Param = null 방식을 제거하고 상속하는 의존성을 제거하는 과정을 걸처서 변경코드에서는 인터페이스를 삭제하여 상속이 강제되는 현상을 제거하고 캐스팅 실수가 일어나지않게 처리하였음 그리고 델리게이트 형식으로 바꾸어 기존의 유니티 내부 이벤트를 사용하는 방식과 거의 유사한 방식으로 동작하게 하여 매니저로 이벤트 관리를 하는 관리역할을 강화 시키면서 코드의 간결함을 주어 사용자의 편의성을 강화함 |
턴제시스템 전체 플로우(수정)
개발
● 이전에 작성한 플로우를 수정함
● 시사점 |
이전 기획에서 이동, 공격, 발차기가 동일 선상에서 출발하고 이후 턴이 바로 소모되었다면 변경된 구조에서는 이동 공격과 발차기는 기존과 동일하지만 이동한후 턴이 바로 종료되지 않고 다시 공격과 발차기중 선택하고 이후 적턴으로 넘어가도록 변경함 |
'유니티개발 TIL' 카테고리의 다른 글
11주차 3일 TIL_유니티 실전 프로젝트(턴제시스템 연결(1)) (0) | 2025.09.16 |
---|---|
11주차 1일 TIL_유니티 실전 프로젝트(CommandManager, 턴제시스템 구상) (0) | 2025.09.09 |
11주차 1일 TIL_유니티 실전 프로젝트(프로젝트 구상) (0) | 2025.09.08 |
9주차 2일 TIL_유니티 입문(3D프로젝트_플레이어AI만들기) (0) | 2025.08.26 |
6주차 4일 TIL_유니티 입문(3D프로젝트_new InputSystem의 고찰) (6) | 2025.08.07 |