using DG.Tweening; using LitJson; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.AI; using UnityEngine.Events; using UnityEngine.Serialization; public delegate bool ItemUse(string idx, int value); public delegate bool HoldingItemCheck(string idx); [System.Serializable] public class ItemUseRegister { public ItemUse OnItemUse; public System.Action OnTeleport; public System.Action OnLock; } public enum MapEvent { DoorNearSave, DoorConnect, DoorState, DoorInit, Register_OnItemUse } public enum MapDirection { Left, Right, Down, Up } public enum DoorType { Door, DoorBreak, DoorLock, Wall, WallBreak, Boss } public class MapManager : ResourcePool { public Transform targetCamera; [Space(10)] public TiltCamera tiltCamera; [Space(10)] public SoundPlayer_BGM spBGM; [Space(10)] public InputController prefabPlayer; public Vector3 scaleCharacter = new Vector3(1.3f, 1.3f, 1.3f); [HideInInspector] public InputController player; public bool START_ENTER = true; private void Start() { QualitySettings.vSyncCount = 0; //Application.targetFrameRate = 60; items_Passive = DataManager.Instance.dataItem.ListGet().FindAll(a => a.type.Equals("Passive")); items_Active = DataManager.Instance.dataItem.ListGet().FindAll(a => a.type.Equals("Active")); items_Weapon = DataManager.Instance.dataItem.ListGet().FindAll(a => a.type.Equals("Weapon")); items_Armor = DataManager.Instance.dataItem.ListGet().FindAll(a => a.type.Equals("Armor")); items_Accessory = DataManager.Instance.dataItem.ListGet().FindAll(a => a.type.Equals("Accessory")); items_Use = DataManager.Instance.dataItem.ListGet().FindAll(a => a.type.Equals("Use") || a.type.Equals("Pickup")); items_All = new List(); items_All.AddRange(items_Passive); items_All.AddRange(items_Active); items_All.AddRange(items_Weapon); items_All.AddRange(items_Armor); items_All.AddRange(items_Accessory); EventManager.Instance.Add("PrefabCreate", PrefabCreate); EventManager.Instance.Add("PrefabCreateLocal", PrefabCreateLocal); EventManager.Instance.Add("IngameCommand", IngameCommand); if (START_ENTER) DungeonEnter("Proto"); } Dictionary> dicMaterials = new Dictionary>(); void MaterialLoad(string dungeonName) { List materials = Resources.LoadAll($"Prefab/Map/{dungeonName}/Material").ToList(); dicMaterials.Clear(); int max = Util.EnumUtil.Length(); for (int i = 0; i < max; i++) dicMaterials.Add((MapTile.Type)i, materials.FindAll(a => a.name.Contains($"{(MapTile.Type)i}"))); } public Dictionary> MaterialGet(string dungeonName) { if (dicMaterials.Count == 0 || this.dungeonName != dungeonName) MaterialLoad(this.dungeonName = dungeonName); return dicMaterials; } List obstacles = new List(); void ObstacleLoad(string dungeonName) { obstacles = Resources.LoadAll($"Prefab/Map/{dungeonName}/Obstacle").ToList(); } public List ObstacleGet(string dungeonName) { if (obstacles.Count == 0 || this.dungeonName != dungeonName) ObstacleLoad(dungeonName); return obstacles; } private void OnDestroy() { EventManager.Instance.Remove("PrefabCreate", PrefabCreate); EventManager.Instance.Remove("PrefabCreateLocal", PrefabCreateLocal); EventManager.Instance.Remove("IngameCommand", IngameCommand); } void PrefabCreate(object value) { EventManager.ObjectCreate data = (EventManager.ObjectCreate)value; data.create = ObjectCreate(data.prefab, data.target, data.scale, data.pos, true); } void PrefabCreateLocal(object value) { EventManager.ObjectCreate data = (EventManager.ObjectCreate)value; data.create = ObjectCreateLocalPos(data.prefab, data.target, data.scale, data.pos, true); } static public List dirs = new List() { new Vector2(-1, 0), new Vector2(1, 0), new Vector2(0, -1), new Vector2(0, 1) }; List dirsRandom; /// /// 다음에 생성 될 좌표의 목록을 생성합니다. /// void DirectionIndex(bool INIT, bool SHUFFLE) { if (INIT) { dirsRandom = new List(); for (int i = 0; i < 5; i++) dirsRandom.AddRange(dirs); } if (SHUFFLE) dirsRandom = Util.ListShuffle.Shuffle(dirsRandom); } /// /// 좌표 주변에 생성 가능한 좌표가 있는지 확인 /// /// /// /// bool DirectionCheck(Vector2 idx, int cntMax) { //cntMax = cntMax < 4 ? cntMax : 4; int cnt = -1; for(int i = 0; i < dirs.Count; i++) cnt += idxsDic.ContainsKey(idx + dirs[i]) ? 1 : 0; return cnt < cntMax; } [Space(10)] public Vector2 idxMin = new Vector2(-4, -3); public Vector2 idxMax = new Vector2(4, 3); /// /// 맵의 생성 범위 내의 좌표인지를 확인합니다. /// /// /// public bool CheckIndexRange(Vector2 idx) { return (idxMin.x <= idx.x && idx.x <= idxMax.x) && (idxMin.y <= idx.y && idx.y <= idxMax.y); } EventConnect ecMapEvent = new EventConnect(); [Space(10)] public string dungeonName = "Proto"; public DataDungeon dataDungeon; public int floor = 0;//현재 층 public FloorData floorData = new FloorData(); /// /// 던전 진입, 해당 던전의 정보 및 1층의 블럭 생성 /// /// 던전 이름 public void DungeonEnter(string name) { dungeonName = name; floor = 0; dataDungeon = DataManager.Instance.dataDungeon.Load(dungeonName); Generate_Block();//던전 입장 } /// /// 던전을 1층 내려갑니다. /// void FloorGoDown() { Fade.DOFade(1, 0.25f).SetEase(Ease.Linear).OnComplete(() => { Generate_Block();//층 내려감 }); } /// /// 아래층이 존재하는지 확인합니다, /// /// /// bool FloorCheck(bool goDOWN = true) { floor += goDOWN ? 1 : 0; return floor < dataDungeon.floors.Count; } [Space(10)] public GameObject effectPortal; //[Space(10)] //public HPBarBoss barBoss; //public void BossMonsterAppear(Character boss) //{ // barBoss.Setting(boss); //} [System.Serializable] public class MapInitializedEvent : UnityEvent { } [FormerlySerializedAs("onMapInitialized")] [Space(10)] [SerializeField] private MapInitializedEvent OnMapInitialized = new MapInitializedEvent(); public HoldingItemCheck OnHoldingItemCheck; public Dictionary blocks = new Dictionary(); [Space(10)] public CanvasGroup Fade; /// /// 맵의 생성을 시작합니다. /// public void Generate_Block(string thema = "Proto") { floorData = dataDungeon.floors[floor]; floorData.LoadAll($"{thema}"); ecMapEvent.Invoke((int)MapEvent.DoorInit, null); blkCurrent = null; blocks.Clear(); ecMapEvent.Clear(); ObjectAllHide(); DirectionIndex(true, false); Generate_IndexAll(); int cnt = 0; //일반 블럭 생성 List types_Default = floorData.DefaultTypeGet(); for (int i = 0; i < all.Count; i++) BlockCreate(all[i], i * 10, types_Default, ref cnt); //특수 블럭 생성 MapBlock blk = null; List specials = floorData.SpecialTypeGet(); if (0 < specials.Count) { ecMapEvent.Invoke((int)MapEvent.DoorNearSave, blocks); List blocksOrder = blocks.Values.ToList().FindAll(a => a.blkType <= MapBlock.Type.Monster && (0 < a.dirEmpty.Count && a.dirEmpty.Count < 4)); Debug.LogWarning($"생성해야 하는 특수 블럭 {specials.Count} 개\n특수 블럭 생성 가능한 위치 {blocksOrder.Count} 개"); MapBlock blkConnect = null; Vector2 idx = Vector2.zero; for (int i = 0; i < specials.Count; i++) { if (0 == blocksOrder.Count) { Debug.LogWarning($"{i}/{specials} : 특수 블럭의 생성 공간이 부족합니다."); break; } //연결 블럭 blkConnect = blocksOrder[Random.Range(0, blocksOrder.Count)]; idx = blkConnect.NearEmptyIndex_Get();//생성될 좌표 if (blocks.ContainsKey(idx)) { i--; } else { //생성 블럭 blk = ObjectCreateLocalPos(floorData.PrefabGet(specials[i]), transform, Vector3.one, Vector3.zero).GetComponent(); blk.OnInitialized(this, blocks.Count, idx, specials[i]);//특수블럭 초기화 ecMapEvent.Add(blk.OnEvent); //문 연결 blk.Door_Connect(blkConnect); blkConnect.Door_Connect(blk); blocks.Add(idx, blk); blocksOrder.Remove(blkConnect); } } } //맵 생성 완료, 플레이어 와 카메라를 첫 블럭으로 이동 blk = blocks.Values.ToList()[0]; MapDoor.Teleport tel = new MapDoor.Teleport(); blk.gameObject.SetActive(false); tel.blk = blk; tel.door = null; if (player == null) player = ObjectCreateLocalPos(prefabPlayer.gameObject, null, scaleCharacter, new Vector3(-0.5f, 0, -3f)).GetComponent(); OnMapInitialized.Invoke(this); if (floor == 0) player.character.OnInitialized(prefabPlayer.character.HP, prefabPlayer.character.HP_Max);//플레이어 생성 else player.gameObject.SetActive(true); player.Teleport(tel); OnHoldingItemCheck = player.character.skill.HoldingItemCheck; spBGM.Play(); all = null; idxsDic = null; } /// /// 일반 블럭 들을 생성합니다 /// /// 좌표 목록 /// 경로의 넘버링 /// 타입 목록 /// 생성 갯수 void BlockCreate(List idxs, int number, List types_Default, ref int cnt) { //연결통로, 몬스터 방 -> 보스 방 까지의 경로 생성 MapBlock blk = null; for (int i = 0; i < idxs.Count; i++) { //Debug.LogWarning($"{idxs[i]}"); MapBlock.Type type = i == 0 ? MapBlock.Type.None : (idxBoss == idxs[i] ? MapBlock.Type.Boss : types_Default[cnt]); if (blocks.ContainsKey(idxs[i])) { blk = blocks[idxs[i]]; blk.OnInitialized(this, i + number, idxs[i], type);//블럭 초기화 } else { GameObject prefab = null; if (i == 0 && idxs[i] == Vector2.zero) prefab = floorData.PrefabGet(MapBlock.Type.Empty); else prefab = floorData.PrefabGet(type); blk = ObjectCreateLocalPos(prefab, transform, Vector3.one, Vector3.zero).GetComponent(); blocks.Add(idxs[i], blk); blk.OnInitialized(this, i + number, idxs[i], type);//블럭 초기화 cnt++; } ecMapEvent.Add(blk.OnEvent); } ecMapEvent.Invoke((int)MapEvent.DoorConnect, blocks); } Vector2 idxBoss = Vector2.zero; Dictionary idxsDic; List> all = new List>(); /// /// 좌표를 생성합니다. /// void Generate_IndexAll() { idxsDic = new Dictionary(); idxsDic.Add(Vector2.zero, Vector2.zero); all = new List>(); int max = (int)(floorData.cntDefault * floorData.rateMax); int cntRemain = floorData.cntDefault; int cntFind = cntRemain <= max ? cntRemain : (cntRemain - max < 0 ? cntRemain : max); Vector2 idxStart = Vector2.zero; List idxSearch = new List(); idxSearch.Add(idxStart); int cntTry = 1; int cntLength = 0; while (0 < cntRemain && 0 < idxSearch.Count) { List idxs = Generate_Index(idxStart, cntFind, cntTry); if (idxs.Count == 1) { idxSearch.Remove(idxStart); //Debug.LogWarning($"실패, 남은 목록 : {idxSearch.Count}"); if (idxSearch.Count == 0 && cntTry < 3) { cntTry++; for (int i = 0; i < all.Count; i++) { idxSearch.AddRange(all[i]); idxSearch.Remove(all[i][all[i].Count - 1]); } idxSearch = idxSearch.Distinct().ToList(); //Debug.LogWarning($"재탐색, 목록 : {idxSearch.Count}, {cntTry}"); } } else { all.Add(idxs); cntRemain -= idxs.Count - 1; cntFind = cntRemain <= max ? cntRemain : (cntRemain - max < 0 ? cntRemain : max); idxSearch.AddRange(idxs); idxSearch.Remove(idxs[idxs.Count - 1]); idxSearch = idxSearch.Distinct().ToList(); Vector2 idxChk = idxs[idxs.Count - 1]; int tmp = (int)(Mathf.Abs(idxChk.x) + Mathf.Abs(idxChk.y)); if (cntLength < tmp) { //Debug.LogWarning($"{cntLength}({idxBoss}) < {tmp}({idxChk})"); cntLength = tmp; idxBoss = idxChk; } else if (cntLength == tmp) { idxBoss = Random.Range(0, 100) < 50 ? idxBoss : idxChk; } //Debug.LogWarning($"성공, 남은 목록 : {idxSearch.Count}"); } if (0 < idxSearch.Count) idxStart = idxSearch[Random.Range(0, idxSearch.Count)]; } for (int i = 0; i < all.Count; i++) idxSearch.AddRange(all[i]); idxSearch = idxSearch.Distinct().ToList(); if (floorData.cntDefault == idxSearch.Count) { Debug.LogWarning($"생성 완료 결과 : {floorData.cntDefault} == {idxSearch.Count}"); } else { Debug.LogError($"생성 실패 결과 : {floorData.cntDefault} == {idxSearch.Count}"); Generate_IndexAll(); } } /// /// idxStart 를 시작으로 cnt 개의 좌표를 생성합니다. /// /// 시작 좌표 /// 생성 갯수 /// 생성 제한, 주변의 블럭 최소수 /// List Generate_Index(Vector2 idxStart, int cnt, int cntAround = 1) { Vector2 idx = idxStart; List idxs = new List(); idxs.Add(idx); if (!idxsDic.ContainsKey(idx)) idxsDic.Add(idx, idx); Vector2 idxT; bool PASS = true; DirectionIndex(false, true); int j = 0; Vector2 dirBefore = Vector2.zero; for (int i = 1; i < cnt && PASS; i++) { PASS = false; if (j == dirsRandom.Count) { j = 0; dirsRandom = Util.ListShuffle.Shuffle(dirsRandom); } for (; j < dirsRandom.Count; j++) { if (dirBefore == Vector2.zero) { idxT = dirsRandom[j]; idxT = idxT + idx;//생성해야 될 위치 dirBefore = Random.Range(0, 100) < 70 ? dirsRandom[j] : Vector2.zero; } else { idxT = dirBefore; idxT = idxT + idx;//생성해야 될 위치 dirBefore = Random.Range(0, 100) < 70 ? dirBefore : Vector2.zero; } if (!idxsDic.ContainsKey(idxT) && DirectionCheck(idxT, cntAround) && CheckIndexRange(idxT)) { //해당 위치에 생성 가능 idx = idxT; idxsDic.Add(idx, idx); idxs.Add(idx); PASS = true; j++; Generate_Blocked_Init(); break; } else { if (Generate_Blocked(idxT - idx)) { //해당 위치에 생성 불가능 PASS = false; break; } else { PASS = true; } if (j + 1 == dirsRandom.Count) { j = 0; } } } } if (!PASS) { //Debug.LogWarning($"기본 위치 생성 실패, 중복된 경로 존재"); //return Generate_Index(idxStart, cnt); return idxs; } else return idxs; } [System.Serializable] public class MapClearedEvent : UnityEvent { } [FormerlySerializedAs("onMapCleared")] [Space(10)] [SerializeField] private MapClearedEvent OnMapCleared = new MapClearedEvent(); /// /// 층의 클리어를 확인합니다. /// /// 플레이어의 죽음 public void MapCleared(bool DIE = false) { if (FloorCheck() && !DIE) FloorGoDown();//현재 층을 클리어 else OnMapCleared.Invoke(this);//플레이어 죽음 } Dictionary dicCheck = new Dictionary(); /// /// 주변에 생성 가능한 위치가 있었기에 확인을 위한 데이터를 초기화 합니다. /// void Generate_Blocked_Init() { var list = dicCheck.Keys.ToList(); for (int i = 0; i < list.Count; i++) dicCheck[list[i]] = false; } /// /// 주변에 생성 가능한 위치가 있는지 확인 합니다. /// 사방이 막혔다면 True 를 리턴합니다. /// /// /// bool Generate_Blocked(Vector2 dir) { dicCheck[dir] = true; return 4 == dicCheck.Values.Count(a => a == true); } [Space(10)] public MapBlock blkCurrent; [System.Serializable] public class MapChangedEvent : UnityEvent { } [FormerlySerializedAs("onMapChanged")] [Space(10)] [SerializeField] private MapChangedEvent OnMapChanged = new MapChangedEvent(); /// /// 생성 완료 및 플레이어의 블럭 이동시 호출 됩니다. /// /// public void MapBlockChanged(MapBlock blk, System.Action OnEnd = null) { if (blkCurrent != null) blkCurrent.TintFade(0.8f); blk.TintFade(0); MapBlock blkBefore = blkCurrent; blkCurrent = blk; blkCurrent.Setting(dungeonName);//블럭 이동 //카메라를 이동한 블럭으로 이동합니다. targetCamera.DOMove(blkCurrent.transform.position, 0.5f).OnComplete(() => { if (blkBefore != null) blkBefore.gameObject.SetActive(false); if (OnEnd != null) OnEnd(); Fade.DOFade(0, 0.25f); OnMapChanged.Invoke(blkCurrent); }); } Bundle dicTile = new Bundle(); public MapTile CreateTile(Transform target, GameObject prefab, Vector3 scale, Vector3 pos) { GameObject obj = ObjectCreateLocalPos(prefab, target, scale, pos, true); MapTile tile = dicTile.Get(obj.name); if (tile == null) { tile = obj.GetComponent(); dicTile.Add(tile.name, tile); } return tile; } /// /// 플레이어가 있는 위치 이외의 블럭을 하이드 시킨다. /// public void MapBlockHide() { //return; List blks = new List(blocks.Values); for (int i = 0; i < blks.Count; i++) blks[i].gameObject.SetActive(blkCurrent == blks[i]); } [Space(10)] public Character mob; public void MonsterCreate_Designate() { if (mob != null) { blkCurrent.CLEAR = false; blkCurrent.ecMapBlock.Invoke((int)MapEvent.DoorState, false); appear.cnt = 1; appear.mobNames.Clear(); appear.mobNames.Add(mob.name); blkCurrent.Monster_Create(appear); } } public List mobs; Bundle dicMonster = new Bundle(); /// /// 몬스터를 생성합니다. /// /// 생성될 맵 블럭 /// 몬스터 프리팹 /// 생성 위치 /// public Character MonsterCreate(MapBlock blk, GameObject prefabMonset, Vector3 pos) { GameObject obj = ObjectCreate(prefabMonset, blk.transform, scaleCharacter, pos, true); Character Monster = dicMonster.Get(obj.name); if (Monster == null) { Monster = obj.GetComponent(); dicMonster.Add(Monster.name, Monster); } return Monster; } Bundle dicDrop = new Bundle(); /// /// 드롭아이템을 생성합니다. /// /// 생성될 블럭 /// 드롭아이템 프리팹 /// 생성 위치 /// public DropItem DropItemCreate(MapBlock blk, GameObject prefab, Vector3 pos) { GameObject obj = ObjectCreate(prefab, blk.transform, Vector3.one, pos, true); DropItem drop = dicDrop.Get(obj.name); if (drop == null) { drop = obj.GetComponent(); dicDrop.Add(drop.name, drop); } return drop; } [Space(10)] public DropItemData dropItem; public List items_All; public List items_Weapon; public List items_Armor; public List items_Accessory; public List items_Passive; public List items_Active; public List items_Use; List listItems = new List(); //public void HoldinCheckList(List items, string idx) //{ // ItemData item = items.Find(a => a.idx.Equals(idx)); // if(item != null) // items.Remove(item); //} //public void HoldinCheck(string idx) //{ // HoldinCheckList(items_Weapon, idx); // HoldinCheckList(items_Equip, idx); // HoldinCheckList(items_Passive, idx); //} //public void DropItemCreate(List items) //{ // if (items.Count == 0) // return; // ItemData item = items[Random.Range(0, items.Count)]; // blkCurrent.OnDropItem_Create(player.transform, item); // HoldinCheckList(items, item.idx); //} /// /// 현재 맵에 존재하는 모든 블럭에서 해당 아이템이 드랍되었는지 확인합니다. /// /// 확인할 아이템 IDX /// true - 드랍되어 있음 bool DropCheck_All(string idxItem) { foreach(KeyValuePair blk in blocks) { if (blk.Value.HoldingItemCheck(idxItem)) return true; } return false; } /// /// 장착중인 아이템 및 드랍된 아이템 의 중첩드랍을 확인 하며 /// 어느쪽에도 해당하지 않는 아이템을 넘깁니다. /// /// 드랍 리스트 /// 최소 티어 /// 최대 티어 /// 생성 위치 /// 생성 유무 /// public ItemData DuplicateItemCheck(List items, int tierMin = 0, int tierMax = 999, Transform target = null, bool CREATE = true) { listItems.Clear(); for (int i = 0; i < items.Count; i++) { if (tierMin <= items[i].tier && items[i].tier <= tierMax) { if (items[i].type.Equals("Use") || items[i].type.Equals("Pickup")) { listItems.Add(items[i]); continue; } //if (!OnHoldingItemCheck(items[i].idx) && !blkCurrent.HoldingItemCheck(items[i].idx)) if (!OnHoldingItemCheck(items[i].idx) && !DropCheck_All(items[i].idx)) listItems.Add(items[i]); } } if (listItems.Count < 1) return null; ItemData data = listItems[Random.Range(0, listItems.Count)]; if (0 < listItems.Count && CREATE) blkCurrent.OnDropItem_Create(target == null ? player.transform : target, data.idx); return data; } //public void DropItemCreate() //{ // blkCurrent.OnDropItem_Create(player.transform, dropItem); //} AppearList appear = new AppearList(); void Update() { if (Input.GetKeyDown(KeyCode.F8)) { //ecMapEvent.Invoke((int)MapEvent.DoorInit, null); blkCurrent.CLEAR = false; blkCurrent.ecMapBlock.Invoke((int)MapEvent.DoorInit, false); appear.cnt = 1; appear.mobNames.Clear(); appear.mobNames.Add(mobs[Random.Range(0, mobs.Count)].name); blkCurrent.Monster_Create(appear); } if (Input.GetKeyDown(KeyCode.F1)) DuplicateItemCheck(items_Use); if (Input.GetKeyDown(KeyCode.F2)) DuplicateItemCheck(items_Weapon); if (Input.GetKeyDown(KeyCode.F3)) DuplicateItemCheck(items_Accessory); if (Input.GetKeyDown(KeyCode.F4)) DuplicateItemCheck(items_Armor); if (Input.GetKeyDown(KeyCode.F5)) DuplicateItemCheck(items_Passive); if (Input.GetKeyDown(KeyCode.F6)) DuplicateItemCheck(items_Active); //if (Input.GetKeyDown(KeyCode.F12)) // Generate_Block();//치트, 맵 재설정 } public void IngameCommand(object value) { string command = (string)value; string[] split = command.Split(':'); switch(split[0]) { case "Monster": { blkCurrent.CLEAR = false; blkCurrent.ecMapBlock.Invoke((int)MapEvent.DoorInit, false); appear.cnt = 1; appear.mobNames.Clear(); appear.mobNames.Add(split[1]); blkCurrent.Monster_Create(appear); } break; case "Item": { ItemData itemData = DataManager.Instance.dataItem.Search(split[1]); if(itemData != null) blkCurrent.OnDropItem_Create(player.transform, itemData.idx); else Debug.LogWarning($"Can't Find Item IDX : {split[1]}"); } break; default: { Debug.LogWarning($"Wrong input : {command}"); } break; } } }