허브(이하 성소) 에서 던전을 넘나드는 플로우 구현.
이제 각 팀원이 만든 UI와 데이터 들을 연동하여 알파 구현을 앞두고 있다.
각각의 UI 및 구현은 info 를 참조하며 data 를 참조하여 정해진 수치의 데이터들을 불러와 연동할 예정
아래는 해당 플로우를 테스트로 구현한 영상.
(던전 씬의 경우 테스트용 캐릭터로 변경되는데 이는 본 알파때는 성소와 마찬가지로 레이첼 주인공 객체로 대체될 예정이다.)
gif 파일을 재 변환한 관계로 (용량 20메가 제한 ㅠㅠ) 열화가 조금 있다.
이제 App main 이 아래의 씬 메인들과 연동되어 Init 메서드를 매번 씬이 불러올때 마다 불러오게 되면 객체들의 Init 주기를 관리하기 더 편해질 것으로 보인다.
성소의 오브젝트들의 관리와 UI 관리 및 플레이어 객체의 생성을 담당하는 SanturarySceneMain 스크립트
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
using static EventDispatcher;
public class SanturarySceneMain : MonoBehaviour
{
private Camera mainCam;
[SerializeField]
private GameObject player;
private UINPCPopupDirector NPCPopupDirector;
private SanctuaryPlayerSpawnPoint spawnPoint;
private PortalController portal;
private List<KnightsController> KightList;
private void Awake()
{
this.mainCam = Camera.main;
this.NPCPopupDirector = GameObject.FindObjectOfType<UINPCPopupDirector>();
this.NPCPopupDirector.Init();
this.spawnPoint = GameObject.FindObjectOfType<SanctuaryPlayerSpawnPoint>();
this.spawnPoint.Init();
this.KightList = GameObject.FindObjectsOfType<KnightsController>().ToList();
this.KightList.ForEach((x) => x.Init());
this.portal = GameObject.FindObjectOfType<PortalController>();
this.portal.onPlayerGoToThePortal = () => {
var go = SceneManager.LoadSceneAsync("DungeonScene");
};
this.player = GameObject.Instantiate(this.player);
this.player.transform.position = this.spawnPoint.transform.localPosition;
this.mainCam.GetComponent<MainCameraController>().Init();
}
}
던전의 맵 생성기와 플레이어 및 UI를 담당하는 DungeonSceneMain 스크립트
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEngine;
public class DungeonSceneMain : MonoBehaviour
{
[SerializeField]
private UIMiniMap miniMapDirector;
[SerializeField]
private UINPCPopupDirector NPCPopupDirector;
[SerializeField]
private MapGenerator generator;
[SerializeField]
public GameObject player;
[SerializeField]
private MainCameraController mainCam;
public Action<Vector2, string> getPortalInfo;
public Action setPlayer;
private bool isPlayerSpawned = false;
[Tooltip("생성하고 싶은 룸 수")]
[SerializeField]
public int roomCount;
void Awake()
{
this.InitStage();
this.miniMapDirector.Init() ;
this.NPCPopupDirector.Init();
this.mainCam.Init();
EventDispatcher.Instance.AddListener<bool>(EventDispatcher.EventName.DungeonMainToNextStage,
this.InitStage);
EventDispatcher.Instance.AddListener<Vector2,string>(EventDispatcher.EventName.DungeonMainPlayerToNextRoom,
this.MoveToNextArea);
}
private void InitRoom()
{
//this.roomCount = 6; 인포 매니저 참고 하여 할당
while (true)
{
for (int i = 0; i < this.generator.wholeRoomList.Count; i++) { Destroy(this.generator.wholeRoomList[i]);}
this.generator.wholeRoomList.Clear();
this.generator.Init();
//맵 룸 수 조절하는 부분
if (this.generator.wholeRoomList.Count == this.roomCount && this.generator.normalRoomList.Count != 0) break;
}
}
/// <summary>
/// 플레이어 스테이지 이동시 실행
/// </summary>
public bool InitStage()
{
this.player.transform.parent = null;
this.InitRoom();
var startRoomTrans = this.generator.wholeRoomList.Find(x => x.transform.name.Contains("Start")).transform;
if (!this.isPlayerSpawned)
{
this.player = Instantiate(this.player, startRoomTrans);
this.isPlayerSpawned = true;
}
else if (this.isPlayerSpawned)
{
this.player.transform.parent = startRoomTrans;
}
var SpawnPoint = startRoomTrans.GetChild(4).gameObject;
SpawnPoint.GetComponent<SanctuaryPlayerSpawnPoint>().Init();
this.player.transform.localPosition = SpawnPoint.transform.localPosition;
this.miniMapDirector.MiniMapUpdate(startRoomTrans.position);
return true;
}
private void MoveToNextArea(Vector2 originPos , string portalName)
{
this.mainCam.isPlayerTeleporting = true;
this.player.gameObject.SetActive(false);
this.StartCoroutine(this.PlayerTransferToRoom(originPos, portalName));
}
private IEnumerator PlayerTransferToRoom(Vector2 originPos, string portalName)
{
var objIndex = default(int);
if (portalName.Contains("0"))
{
objIndex = this.generator.wholeRoomList.FindIndex(obj => (Vector2)obj.transform.position == new Vector2(originPos.x, originPos.y + this.generator.mapDist));
this.generator.wholeRoomList[objIndex].gameObject.SetActive(true);
var spawnPos = this.generator.wholeRoomList[objIndex].transform.GetChild(2).position;
yield return new WaitForSeconds(1.5f);
this.mainCam.transform.position = new Vector3(spawnPos.x, spawnPos.y, -10);
yield return new WaitForSeconds(0.5f);
this.player.gameObject.SetActive(true);
this.player.transform.parent = this.generator.wholeRoomList[objIndex].transform;
this.player.transform.position = new Vector2(spawnPos.x, spawnPos.y + 2);
}
else if (portalName.Contains("1"))
{
objIndex = this.generator.wholeRoomList.FindIndex(obj => (Vector2)obj.transform.position == new Vector2(originPos.x + this.generator.mapDist, originPos.y));
this.generator.wholeRoomList[objIndex].gameObject.SetActive(true);
var spawnPos = this.generator.wholeRoomList[objIndex].transform.GetChild(3).position;
yield return new WaitForSeconds(1.5f);
this.mainCam.transform.position = new Vector3(spawnPos.x, spawnPos.y, -10);
yield return new WaitForSeconds(0.5f);
this.player.gameObject.SetActive(true);
this.player.transform.parent = this.generator.wholeRoomList[objIndex].transform;
this.player.transform.position = new Vector2(spawnPos.x + 1, spawnPos.y);
}
else if (portalName.Contains("2"))
{
objIndex = this.generator.wholeRoomList.FindIndex(obj => (Vector2)obj.transform.position == new Vector2(originPos.x, originPos.y - this.generator.mapDist));
this.generator.wholeRoomList[objIndex].gameObject.SetActive(true);
var spawnPos = this.generator.wholeRoomList[objIndex].transform.GetChild(0).position;
yield return new WaitForSeconds(1.5f);
this.mainCam.transform.position = new Vector3(spawnPos.x, spawnPos.y, -10);
yield return new WaitForSeconds(0.5f);
this.player.gameObject.SetActive(true);
this.player.transform.parent = this.generator.wholeRoomList[objIndex].transform;
this.player.transform.position = new Vector2(spawnPos.x, spawnPos.y - 1);
}
else if (portalName.Contains("3"))
{
objIndex = this.generator.wholeRoomList.FindIndex(obj => (Vector2)obj.transform.position == new Vector2(originPos.x - this.generator.mapDist, originPos.y));
this.generator.wholeRoomList[objIndex].gameObject.SetActive(true);
var spawnPos = this.generator.wholeRoomList[objIndex].transform.GetChild(1).position;
yield return new WaitForSeconds(1.0f);
this.mainCam.transform.position = new Vector3(spawnPos.x,spawnPos.y,-10);
yield return new WaitForSeconds(0.5f);
this.player.gameObject.SetActive(true);
this.player.transform.parent = this.generator.wholeRoomList[objIndex].transform;
this.player.transform.position = new Vector2(spawnPos.x - 1, spawnPos.y);
}
this.mainCam.player = this.player;
this.mainCam.isPlayerTeleporting = false;
this.miniMapDirector.MiniMapUpdate(this.generator.wholeRoomList[objIndex].transform.position);
}
}
UI들은 큰 부분들로 나누어 각각이 Director 칭호를 달고 스크립트를 보유중이며 하위의 객체들을 관리한다. 이는 다른 객체에게 데이터를 보낼때 아래의 Info 매니저를 수정하거나 참조하며 이벤트 디스패쳐를 통해 이벤트를 발생시키는 것으로 협업후 만들어진 오브젝트들 간의 통신을 보조한다. ( 2번 이상 타고 가는 이벤트 들의 경우 싱글톤 디스패쳐를 통하여 전달 하는 방식 )
특히 이벤트 디스패쳐의 경우 전역적으로 키가 겹치는 것을 고려하여 멀티캐스트를 하지 않고 매번 같은 키로 등록을 하여도 기존 이벤트를 삭제한뒤 새로 이벤트를 등록하도록 약간의 수정을 하였다.
EventDispatcher 싱글톤 클래스
using System;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
public class EventDispatcher
{
/// <summary>
/// 메서드 사용키 (EventDispatcher enum 상수 등록후 사용 * 중복 메서드 등록 방지))
/// </summary>
public enum EventName
{
UINPCPopupUpdate,
UINPCPopupActive,
CoCameraShaking,
CoCameraFlash,
CoCameraShakingAndFlash,
DungeonMainToNextStage,
DungeonMainPlayerToNextRoom,
PlayerExpUp,
UIInventoryAddCell,
UIInventoryAddEquipment,
UICurrentInventoryList,
}
private static EventDispatcher instance;
private Dictionary<EventName, Delegate> listeners = new Dictionary<EventName, Delegate>();
public static EventDispatcher Instance
{
get
{
if (instance == null)
{
instance = new EventDispatcher();
}
return instance;
}
}
/// <summary>
/// 반환값이 없는 메서드 혹은 인스턴스 메서드 등록시
/// </summary>
/// <param name="eventName"></param>
/// <param name="listener"></param>
public void AddListener(EventName eventName, Action listener)
{
if (listeners.ContainsKey(eventName))
{
UnityEngine.Debug.LogWarning("<color=white>이미 해당 키로 메서드가 등록된 상태입니다. 기존 메서드를 덮어 씌웠습니다.</color>");
listeners.Remove(eventName);
listeners.Add(eventName, listener);
}
else
{
listeners.Add(eventName, listener);
}
}
/// <summary>
/// 인자 1개를 받으며 반환값이 없는 메서드 혹은 인스턴스 메서드 등록시
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="eventName"></param>
/// <param name="listener"></param>
public void AddListener<T>(EventName eventName, Action<T> listener)
{
if (listeners.ContainsKey(eventName))
{
UnityEngine.Debug.LogWarning("<color=white>이미 해당 키로 메서드가 등록된 상태입니다. 기존 메서드를 덮어 씌웠습니다.</color>");
listeners.Remove(eventName);
listeners.Add(eventName, listener);
}
else
{
listeners.Add(eventName, listener);
}
}
/// <summary>
/// 인자 2개를 받으며 반환값이 없는 메서드 혹은 인스턴스 메서드 등록시
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <param name="eventName"></param>
/// <param name="listener"></param>
public void AddListener<T1, T2>(EventName eventName, Action<T1, T2> listener)
{
if (listeners.ContainsKey(eventName))
{
UnityEngine.Debug.LogWarning("<color=white>이미 해당 키로 메서드가 등록된 상태입니다. 기존 메서드를 덮어 씌웠습니다.</color>");
listeners.Remove(eventName);
listeners.Add(eventName, listener);
}
else
{
listeners.Add(eventName, listener);
}
}
/// <summary>
/// 반환 값이 있는 메서드 등록시
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="eventName"></param>
/// <param name="listener"></param>
public void AddListener<TResult>(EventName eventName, Func<TResult> listener)
{
if (listeners.ContainsKey(eventName))
{
UnityEngine.Debug.LogWarning("<color=white>이미 해당 키로 메서드가 등록된 상태입니다. 기존 메서드를 덮어 씌웠습니다.</color>");
listeners.Remove(eventName);
listeners.Add(eventName, listener);
}
else
{
listeners.Add(eventName, listener);
}
}
/// <summary>
/// 인자 1개를 받으며 반환 값이 있는 메서드 등록시
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="eventName"></param>
/// <param name="listener"></param>
public void AddListener<T1, TResult>(EventName eventName, Func<T1, TResult> listener)
{
if (listeners.ContainsKey(eventName))
{
UnityEngine.Debug.LogWarning("<color=white>이미 해당 키로 메서드가 등록된 상태입니다. 기존 메서드를 덮어 씌웠습니다.</color>");
listeners.Remove(eventName);
listeners.Add(eventName, listener);
}
else
{
listeners.Add(eventName, listener);
}
}
/// <summary>
/// 인자 2개를 받으며 반환 값이 있는 메서드 등록시
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="eventName"></param>
/// <param name="listener"></param>
public void AddListener<T1, T2, TResult>(EventName eventName, Func<T1, T2, TResult> listener)
{
if (listeners.ContainsKey(eventName))
{
UnityEngine.Debug.LogWarning("<color=white>이미 해당 키로 메서드가 등록된 상태입니다. 기존 메서드를 덮어 씌웠습니다.</color>");
listeners.Remove(eventName);
listeners.Add(eventName, listener);
}
else
{
listeners.Add(eventName, listener);
}
}
/// <summary>
/// 반환값이 없는 메서드 삭제시
/// </summary>
/// <param name="eventName"></param>
/// <param name="listener"></param>
public void RemoveListener(EventName eventName, Action listener)
{
if (listeners.ContainsKey(eventName))
{
listeners[eventName] = Delegate.Remove(listeners[eventName], listener);
if (listeners[eventName] == null)
{
listeners.Remove(eventName);
}
}
}
/// <summary>
/// 인자 1개를 받으며 반환값이 없는 메서드 삭제시
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="eventName"></param>
/// <param name="listener"></param>
public void RemoveListener<T>(EventName eventName, Action<T> listener)
{
if (listeners.ContainsKey(eventName))
{
listeners[eventName] = Delegate.Remove(listeners[eventName], listener);
if (listeners[eventName] == null)
{
listeners.Remove(eventName);
}
}
}
/// <summary>
/// 인자 2개를 받으며 반환값이 없는 메서드 삭제시
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <param name="eventName"></param>
/// <param name="listener"></param>
public void RemoveListener<T1, T2>(EventName eventName, Action<T1, T2> listener)
{
if (listeners.ContainsKey(eventName))
{
listeners[eventName] = Delegate.Remove(listeners[eventName], listener);
if (listeners[eventName] == null)
{
listeners.Remove(eventName);
}
}
}
/// <summary>
/// 반환 값이 있는 메서드 제거시
/// </summary>
/// <typeparam name="TResult"></typeparam>
/// <param name="eventName"></param>
/// <param name="listener"></param>
public void RemoveListener<TResult>(EventName eventName, Func<TResult> listener)
{
if (listeners.ContainsKey(eventName))
{
listeners[eventName] = Delegate.Remove(listeners[eventName], listener);
if (listeners[eventName] == null)
{
listeners.Remove(eventName);
}
}
}
/// <summary>
/// 인자 1개를 받으며 반환 값이 있는 메서드 제거시
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="eventName"></param>
/// <param name="listener"></param>
public void RemoveListener<T1, TResult>(EventName eventName, Func<T1, TResult> listener)
{
if (listeners.ContainsKey(eventName))
{
listeners[eventName] = Delegate.Remove(listeners[eventName], listener);
if (listeners[eventName] == null)
{
listeners.Remove(eventName);
}
}
}
/// <summary>
/// 인자 2개를 받으며 반환 값이 있는 메서드 제거시
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <typeparam name="TResult"></typeparam>
/// <param name="eventName"></param>
/// <param name="listener"></param>
public void RemoveListener<T1, T2, TResult>(EventName eventName, Func<T1, T2, TResult> listener)
{
if (listeners.ContainsKey(eventName))
{
listeners[eventName] = Delegate.Remove(listeners[eventName], listener);
if (listeners[eventName] == null)
{
listeners.Remove(eventName);
}
}
}
/// <summary>
/// 반환값이 없는 메서드 혹은 인스턴스 메서드 사용시
/// </summary>
/// <param name="eventName"></param>
public void Dispatch(EventName eventName)
{
if (listeners.ContainsKey(eventName))
{
((Action)listeners[eventName])();
}
}
/// <summary>
/// 반환값이 없는 메서드 혹은 인스턴스 메서드 사용시
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <param name="eventName"></param>
/// <param name="eventParams"></param>
/// <param name="eventParams2"></param>
public void Dispatch<T>(EventName eventName, T eventParams = default)
{
if (listeners.ContainsKey(eventName))
{
((Action<T>)listeners[eventName])(eventParams);
}
}
/// <summary>
/// 반환값이 없는 메서드 혹은 인스턴스 메서드 사용시
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
/// <param name="eventName"></param>
/// <param name="eventParams"></param>
/// <param name="eventParams2"></param>
public void Dispatch<T1, T2>(EventName eventName, T1 eventParams = default, T2 eventParams2 = default)
{
if (listeners.ContainsKey(eventName))
{
((Action<T1, T2>)listeners[eventName])(eventParams, eventParams2);
}
}
/// <summary>
/// 반환 값이 있는 메서드 혹은 인스턴스 메서드 사용시
/// </summary>
/// <typeparam name="TResult"> 반환 타입</typeparam>
/// <param name="eventName">이벤트 등록 키</param>
/// <param name="result">반환값</param>
/// <exception cref="InvalidCastException"></exception>
public void Dispatch<TResult>(EventName eventName, out TResult result)
{
if (listeners.ContainsKey(eventName))
{
var listener = listeners[eventName];
if (listener is Func<TResult> func)
{
result = func();
}
else
{
throw new InvalidCastException($"Listener for event '{eventName}' is not a Func with the correct signature.");
}
}
else
{
result = default;
}
}
/// <summary>
/// 인자를 1개 받고반환 값이 있는 메서드 혹은 인스턴스 메서드 사용시
/// </summary>
/// <typeparam name="TResult"> 반환 타입</typeparam>
/// <param name="eventName">이벤트 등록 키</param>
/// <param name="eventParams">매개변수</param>
/// <param name="result">반환값</param>
/// <exception cref="InvalidCastException"></exception>
public void Dispatch<T, TResult>(EventName eventName, T eventParams, out TResult result)
{
if (listeners.ContainsKey(eventName))
{
var listener = listeners[eventName];
if (listener is Func<T, TResult> func)
{
result = func(eventParams);
}
else
{
throw new InvalidCastException($"Listener for event '{eventName}' is not a Func with the correct signature.");
}
}
else
{
result = default;
}
}
/// <summary>
/// 인자를 2개 받고반환 값이 있는 메서드 혹은 인스턴스 메서드 사용시
/// </summary>
/// <typeparam name="TResult"> 반환 타입</typeparam>
/// <param name="eventName">이벤트 등록 키</param>
/// <param name="eventParams">첫번째 인자</param>
/// <param name="eventParams2">두번째 인자</param>
/// <param name="result">반환값</param>
/// <exception cref="InvalidCastException"></exception>
public void Dispatch<T1, T2, TResult>(EventName eventName, T1 eventParams, T2 eventParams2, out TResult result)
{
if (listeners.ContainsKey(eventName))
{
var listener = listeners[eventName];
if (listener is Func<T1, T2, TResult> func)
{
result = func(eventParams, eventParams2);
}
else
{
throw new InvalidCastException($"Listener for event '{eventName}' is not a Func with the correct signature.");
}
}
else
{
result = default;
}
}
}
Debug.LogWarning 을 통해 이벤트가 엎어 씌워지는 것을 명시적으로 알리도록 하였고 기존 메서드를 지운뒤 등록되게 바뀌었다.
당장 합쳐보면 좀더 수정 사항등이 보일 듯하다
'프로젝트 > 건즈앤 레이첼스' 카테고리의 다른 글
[유니티 프로젝트] 타일맵 오브젝트 --> PNG 통짜 스프라이트 이미지 변경 코드 (0) | 2023.04.27 |
---|---|
[유니티 프로젝트] 타이틀 씬 to 던전 씬 까지 (0) | 2023.04.26 |
[유니티 프로젝트] 본 프로젝트용 맵 씬 제작 (0) | 2023.04.20 |
[유니티 프로젝트] 카메라 hit shaking 이펙트 & 스크린 flash 이펙트 코루틴 사용 구현 (0) | 2023.04.19 |
[유니티 프로젝트] 로그라이크 허브 마을 (기억의 성소) 제작 .3 구조잡기 - 완료 (0) | 2023.04.18 |