using DG.Tweening; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.AI; public class MapBlock : MonoBehaviour { public enum Type { None, Monster, Boss, Mini, Hidden, Shop, Wave, Empty } //private void OnEnable() //{ // Door_Init(); // CLEAR = false; //} public EventConnect ecMapBlock = new EventConnect(); public void OnEvent(int type, object value) { switch ((MapEvent)type) { case MapEvent.DoorInit: { Door_Init(); } break; case MapEvent.DoorConnect://맵이 생성 된 직후 호출 되며 인접한 블럭끼리의 문을 연결합니다. { Dictionary blks = (Dictionary)value; var list = blks.Values.ToList().FindAll(a => a.number == number + 1 || a.number == number - 1); for (int i = 0; i < list.Count; i++) Door_Connect(list[i]); //Floor_Setting(); } break; case MapEvent.DoorNearSave: { blkNears = new List(new MapBlock[4]); dirEmpty.Clear(); Dictionary blks = (Dictionary)value; var list = blks.Values.ToList().FindAll(a => (a.idx.x == idx.x + 1 || a.idx.x == idx.x - 1) || (a.idx.y == idx.y + 1 || a.idx.y == idx.y - 1)); for (int i = 0; i < list.Count; i++) NearSave(list[i]); cntNear = blkNears.FindAll(a => a != null).Count; NearEmptyIndex_Save(); } break; } } Vector2 posBlk = new Vector2(22, 15); [HideInInspector] public MapManager manager; public int number; [OnlyRead] public Vector2 idx; public List initOn; /// /// 블럭의 초기화를 진행합니다. /// /// /// /// /// public void OnInitialized(MapManager manager, int number, Vector2 idx, Type blkType) { this.blkType = blkType; this.manager = manager; this.number = number; mobAlives.Clear(); dropItems.Clear(); OnDropChanged = null; for (int i = 0; i < initOn.Count; i++) initOn[i].SetActive(true); //블럭의 위치 this.idx = idx; transform.localPosition = new Vector3(posBlk.x * idx.x, 0, posBlk.y * idx.y); tileDatas = null; CLEAR = false; TintFade(0.8f); } [OnlyRead] public bool CLEAR = false; [OnlyRead] public Type blkType = Type.None; MapBlockShop shopComponent; /// /// 해당 블럭의 타입을 설정 합니다. /// public void TypeSetting() { if (CLEAR) return; idxWave = 0; CLEAR = true; //manager.BossMonsterAppear(null); EventManager.Instance.Invoke("BossAppear", null); switch (blkType) { case Type.None://아무것도 없는 블럭 idxWave = appears.Count; ecMapBlock.Invoke((int)MapEvent.DoorState, true); break; case Type.Monster://몬스터가 존재 하는 블럭 { CLEAR = false; ecMapBlock.Invoke((int)MapEvent.DoorState, false); appears.Clear(); appears.Add(manager.floorData.MonsterAppearGet()); Monster_Create(appears[idxWave++]);//일반 몬스터 블럭 } break; case Type.Wave: { waveTrigger.triggerEvent.OnTrigger.AddListener(WaveChallenge); waveTrigger.gameObject.SetActive(true); manager.floorData.WaveAppearGet(appears); ecMapBlock.Invoke((int)MapEvent.DoorState, true); } break; case Type.Boss://보스가 존재 하는 블럭 { BGMPlay();//보스 CLEAR = false; ecMapBlock.Invoke((int)MapEvent.DoorState, false); appearsBoss = manager.floorData.BossAppearGet(); appears.Clear(); appears.Add(manager.floorData.BossInferiorGet()); Monster_Create(appearsBoss, true, () => { Monster_Create(appears[idxWave++]);//일반 몬스터 블럭 }); } break; case Type.Mini: { BGMPlay();//중보스 CLEAR = false; ecMapBlock.Invoke((int)MapEvent.DoorState, false); appearsBoss = manager.floorData.MiniAppearGet(); appears.Clear(); appears.Add(manager.floorData.MiniInferiorGet()); Monster_Create(appearsBoss, true, () => { Monster_Create(appears[idxWave++]);//일반 몬스터 블럭 }); } break; case Type.Shop: { if (shopComponent == null) shopComponent = GetComponent(); if (shopComponent == null) Debug.LogError($"맵블럭:샵 생성 오류, MapBlockShop 컴포넌트 없음"); else shopComponent.OnInitialized(this, OnGain); ecMapBlock.Invoke((int)MapEvent.DoorState, true); } break; case Type.Hidden: ecMapBlock.Invoke((int)MapEvent.DoorState, true); break; } } int idxWave = 0; /// /// 다음 웨이브가 있는지 확인 하며 있다면 웨이브를 실행 하고 없다면 클리어 처리 /// public void WaveChallenge() { if (idxWave == 0) { if (blkType == Type.Wave) BGMPlay();//웨이브 CLEAR = false; ecMapBlock.Invoke((int)MapEvent.DoorState, CLEAR); } if (idxWave < appears.Count) StartCoroutine(WaveWait()); //Monster_Create(appears[idxWave++]); else Invoke("Clear_Wait", 1f); } [Space(10)] public WaveTrigger waveTrigger; public SoundPlayer spEarthquake; /// /// 다음 웨이브 까지의 n초 동안 대기합니다. /// /// IEnumerator WaveWait() { yield return new WaitForSeconds(1f); manager.tiltCamera.Shaking(0.25f, 2f); SoundPlayer SFX = null; if (spEarthquake != null) SFX = SoundManager.Instance.EffectPlay(spEarthquake.gameObject); yield return new WaitForSeconds(2f); if (SFX != null) SFX.Play_Fade(false); Monster_Create(appears[idxWave++]);//웨이브 생성 } [Space(10)] public SpriteRenderer imgTent; /// /// 캐릭터가 문을 통해 이동시 적용 /// 현재 맵 = 어둡게 /// 이동 맵 = 밝게 /// /// /// public void TintFade(float fade, float duration = 0.2f) { imgTent.DOKill(); imgTent.DOFade(fade = 0, duration); } [Space(10)] int cntNear = 0; public List blkNears = new List(); /// /// 해당 블럭 주변에 있는 블럭을 저장합니다. /// /// void NearSave(MapBlock blk) { Vector2 idxT = idx - blk.idx; MapDirection dir; if (idxT.x != 0 && idxT.y != 0) return; if (idxT.x != 0) dir = 0 < idxT.x ? MapDirection.Left : MapDirection.Right; else if (idxT.y != 0) dir = 0 < idxT.y ? MapDirection.Down : MapDirection.Up; else return; blkNears[(int)dir] = blk; } [HideInInspector] public List dirEmpty; /// /// 해당 블럭 주변에 없는 블럭의 좌표를 저장합니다. /// void NearEmptyIndex_Save() { Vector2 idxT = Vector2.zero; for (int i = 0; i < blkNears.Count; i++) { if (i == (int)MapDirection.Down)//특수블럭 생성시 아래방향 제외 continue; idxT = idx + MapManager.dirs[i]; if (blkNears[i] == null && manager.CheckIndexRange(idxT)) dirEmpty.Add(idx + MapManager.dirs[i]); } } /// /// 연결되어 있지 않은 좌표를 넘깁니다. /// /// public Vector2 NearEmptyIndex_Get() { return dirEmpty[Random.Range(0, dirEmpty.Count)]; } public void Setting(string dungeonName) { gameObject.SetActive(true); Wall_Create(); Floor_Create(dungeonName); Floor_Setting(dungeonName); Roof_Create(); DoorBlocked_Create(); NavMeshBuild(); } [Space(10)] public NavMeshSurface surfacesBoss; public NavMeshSurface surfaces; public void NavMeshBuild() { surfaces.BuildNavMesh(); if (blkType == Type.Boss || blkType == Type.Mini) surfacesBoss.BuildNavMesh(); //for (int i = 0; i < surfaces.Count; i++) // surfaces[i].BuildNavMesh(); } public Transform roof; public GameObject roofPrefab; void Roof_Create() { manager.ObjectCreateLocalPos(roofPrefab, roof, Vector3.one, Vector3.zero); } [Space(10)] public Transform floor; public GameObject floorTile; public Vector2 floorMin = new Vector2(-10, -4); public Vector2 floorMax = new Vector2(10, 4); public Vector3 floorPosNext = new Vector3(2, 0, 2); //[Space(5)] public Dictionary tiles = new Dictionary(); void Floor_Create(string dungeonName) { if (floor == null) return; tiles.Clear(); Vector3 pos = Vector3.zero; Vector2 idx = Vector2.zero; MapTile tile = null; for (int x = (int)floorMin.x; x <= floorMax.x; x++) { for (int y = (int)floorMin.y; y <= floorMax.y; y++) { idx.x = x; idx.y = y; pos.x = (x * floorPosNext.x); pos.z = (y * floorPosNext.y); tile = manager.CreateTile(floor, floorTile, Vector3.one, pos); tiles.Add(idx, tile); tile.OnInitialized(idx, MapTile.Type.None, manager.MaterialGet(dungeonName)[(int)MapTile.Type.None][0]); } } } public List tileDatas = null; void Floor_Setting(string dungeonName) { tileDatas = manager.dataDungeon.TileGet(blkType, doors); if(tileDatas != null && 0 < tileDatas.Count) { TileData data = tileDatas[Random.Range(0, tileDatas.Count)]; Vector2 idx = new Vector2(); for (int i = 0; i < data.tileDatas.Count; i++) { idx.x = data.tileDatas[i].x; idx.y = data.tileDatas[i].y; MapTile tile = tiles[idx]; if (data.tileDatas[i].type != MapTile.Type.None) tile.OnApply(data.tileDatas[i].type, manager.MaterialGet(dungeonName)[data.tileDatas[i].type][0]); if (data.tileDatas[i].obstacle != string.Empty) { tile.obstacle = manager.ObjectCreateLocalPos(manager.ObstacleGet(dungeonName).Find(a => a.name.Equals(data.tileDatas[i].obstacle)), tile.transform, Vector3.one, Vector3.zero); tile.obstacle.transform.localEulerAngles = new Vector3(0, data.tileDatas[i].angle, 0); } } } } List wall = new List(new GameObject[4]); [Space(10)] public List thickness; public GameObject prefabWallUD; public GameObject prefabWallLR; public void Wall_Init() { for(int i = 0; i < wall.Count; i++) { if (wall[i] != null) wall[i].gameObject.SetActive(false); } } void Wall_Create() { Wall_Create(prefabWallLR, (int)MapDirection.Left); Wall_Create(prefabWallLR, (int)MapDirection.Right); Wall_Create(prefabWallUD, (int)MapDirection.Up); Wall_Create(prefabWallUD, (int)MapDirection.Down); } void Wall_Create(GameObject prefab, int dir) { wall[dir] = manager.ObjectCreateLocalPos(prefab, doorsPoint[dir], Vector3.one, Vector3.zero); if (0 < thickness[dir]) wall[dir].SendMessage("Thickness", thickness[dir]); } [Header("문 설정")] //public List doors_Blocked = new List(new MapDoor[4]); public MapDoor prefabWall; [HideInInspector] public List doors_Blocked = new List(new GameObject[4]); void DoorBlocked_Create() { for (int i = 0; i < doors_Blocked.Count; i++) { if (doors[i] == null) doors_Blocked[i] = manager.ObjectCreateLocalPos(prefabDoorWall.gameObject, doorsPoint[i], Vector3.one, Vector3.zero); } } public List doorsPoint = new List(new Transform[4]); [HideInInspector] public List doors = new List(new MapDoor[4]); [Header("생성하게 될 문 종류")] public MapDoor prefabDoorWall; public MapDoor prefabDoorWallBreak; public MapDoor prefabDoorBasic; public MapDoor prefabDoorBasicBreak; public MapDoor prefabDoorLock; public MapDoor prefabDoorBoss; /// /// 도어의 상태를 초기화 합니다. /// public void Door_Init() { ecMapBlock.Clear(); for (int i = 0; i < doors_Blocked.Count; i++) { //doors_Blocked[i] = manager.ObjectCreateLocalPos(prefabWall.gameObject, doorsPoint[i], Vector3.one, Vector3.zero); //doors_Blocked[i].gameObject.SetActive(true); if (doors[i] != null) doors[i].gameObject.SetActive(false); doors[i] = null; } } /// /// 블럭에 문을 생성합니다 /// /// 문 프리팹 /// 생성 방향 /// public MapDoor Door_Create(MapDoor door, MapDirection dir) { MapDoor create = doors[(int)dir]; if (create == null) { create = manager.ObjectCreateLocalPos(door.gameObject, doorsPoint[(int)dir], Vector3.one, Vector3.zero).GetComponent(); doors[(int)dir] = create; } return create; } /// /// 해당 블럭의 문을 연결합니다. /// /// public void Door_Connect(MapBlock blk) { Vector2 idxT = idx - blk.idx; if (idxT.x != 0 && idxT.y != 0) return; MapDirection dir; MapDirection dir2; if (idxT.x != 0) { dir = idxT.x < 0 ? MapDirection.Left : MapDirection.Right; dir2 = 0 < idxT.x ? MapDirection.Left : MapDirection.Right; } else if (idxT.y != 0) { dir = idxT.y < 0 ? MapDirection.Down : MapDirection.Up; dir2 = 0 < idxT.y ? MapDirection.Down : MapDirection.Up; } else return; //if (doors[(int)dir2] != null) // return; List select = null; MapDoor door = null; List select2 = null; MapDoor door2 = null; switch(blkType) { case Type.Boss: door = Door_Create(prefabDoorBoss, dir2); door2 = blk.Door_Create(blk.prefabDoorBoss, dir); break; case Type.Mini: case Type.Hidden: case Type.Shop: door = Door_Create(prefabDoorLock, dir2); door2 = blk.Door_Create(blk.prefabDoorLock, dir); break; default: if (blkType == Type.Boss || blk.blkType == Type.Boss) { door = Door_Create(prefabDoorBoss, dir2); door2 = blk.Door_Create(blk.prefabDoorBoss, dir); } else { door = Door_Create(prefabDoorBasic, dir2); door2 = blk.Door_Create(blk.prefabDoorBasic, dir); } break; } door.Connect(blk, door2); ecMapBlock.Add(door.OnEvent); //doors_Blocked[(int)dir2].gameObject.SetActive(false); if(blkType == Type.Boss) { switch(dir2) { case MapDirection.Left: dir = MapDirection.Right; break; case MapDirection.Right: dir = MapDirection.Left; break; case MapDirection.Down: dir = MapDirection.Up; break; case MapDirection.Up: dir = MapDirection.Down; break; } door = Door_Create(prefabDoorBasic, dir); ecMapBlock.Add(door.OnEvent); //doors_Blocked[(int)dir].gameObject.SetActive(false); door.PortalSetting(manager.effectPortal); } } public void Door_Setting() { if (CLEAR) ecMapBlock.Invoke((int)MapEvent.DoorState, blkType == MapBlock.Type.None); } public GameObject prefabEffectAppear; [Header("몬스터 설정")] public AppearList appearsBoss; public List appears = new List(); public List spwnPoints; [Header("살아있는 몬스터 목록")] public List mobAlives = new List(); /// /// 해당 블럭의 몬스터를 생성합니다. /// public void Monster_Create(AppearList appear, bool BOSS = false, System.Action OnEnd = null) { StartCoroutine(Monster_CreateOrder(appear, BOSS, OnEnd)); } IEnumerator Monster_CreateOrder(AppearList appear, bool BOSS, System.Action OnEnd) { WaitForSeconds wait = new WaitForSeconds(0.1f); for (int i = 0; i < appear.cnt; i++) { SpwnPoint spwn = spwnPoints[Random.Range(0, spwnPoints.Count)]; Vector3 pos = spwn.PointCreate(); int idx = Random.Range(0, appear.mobNames.Count); if (prefabEffectAppear != null) { manager.ObjectCreate(prefabEffectAppear, null, Vector3.one, pos); yield return wait; } GameObject prefab = PrefabManager.Instance.PrefabGet($"Monster/{appear.mobNames[idx]}"); if (prefab == null) continue; Character mob = manager.MonsterCreate(this, prefab.gameObject, Vector3.zero); mob.NO_DAMAGE = true; mob.OnInitialized(0, 0);//몬스터 생성 : 블럭 진입시 mob.skill.OnInitialized(); mob.OnDie += OnMonsterDie; mob.OnWarp(pos); mobAlives.Add(mob); if (BOSS) { //manager.BossMonsterAppear(mobAlives[0]); EventManager.Instance.Invoke("BossAppear", mobAlives[0]); mobAlives[0].OnLocked(true); mobAlives[0].motion.Play(Motion.Type.Appear, true, () => { mobAlives[0].OnLocked(false); if (OnEnd != null) OnEnd(); }); } else { mob.OnLocked(false); } yield return wait; mob.NO_DAMAGE = false; } } /// /// 몬스터 생성과 관련된 스킬에서 사용됨 /// /// 생성 프리팹 /// 생성 위치 /// public Character Monster_Create(Character ai, Vector3 pos) { Character mob = manager.MonsterCreate(this, ai.gameObject, Vector3.zero); mob.OnDie += OnMonsterDie; mobAlives.Add(mob); return mob; } /// /// 몬스터의 부활에 사용 /// /// /// public Character Monster_Resurrection(Character mob) { mob.OnDie += OnMonsterDie; mobAlives.Add(mob); return mob; } /// /// 해당 블럭에서 생성 된 몹이 죽었으며 모두 처치 했다면 클리어 됩니다. /// /// void OnMonsterDie(Character mob, Vector3 dir) { mobAlives.Remove(mob); mob.OnDie -= OnMonsterDie; OnDropItem_Create_MonsterDie(mob); manager.player.character.skill.Active("MobKill", mob, null); //몬스터 전멸 if (mobAlives.Count == 0) WaveChallenge(); else { for (int i = 0; i < mobAlives.Count; i++) { if (mobAlives[i].gameObject.activeSelf) { } } } } [Header("드롭아이템 설정")] public DropItem prefabDropItem; public List dropItems = new List(); /// /// 블럭에 드랍되어 있는 아이템이 있는지 확인합니다 /// /// /// public bool HoldingItemCheck(string idx) { return dropItems.Find(a => a.item.idx.Equals(idx) && a.gameObject.activeSelf) != null; } /// /// 몬스터가 죽었으며 드롭아이템을 생성 합니다. /// /// 죽은 몬스터 public void OnDropItem_Create_MonsterDie(Character mob) { if(Random.Range(0, 1000) < 200) { float rate = Random.Range(0, 1000); if(rate < 700) { manager.DuplicateItemCheck(manager.items_Use, 0, 999, mob.transform); } else { manager.DuplicateItemCheck(manager.items_All, 0, 999, mob.transform); } } //for (int i = 0; i < mob.drops.Count; i++) //{ // int value = mob.drops[i].Get(); // DropItem item = null; // if (0 < value && !manager.OnHoldingItemCheck(mob.drops[i].idx)) // { // if (1 < value) // { // for (int j = 0; j < value; j++) // { // item = manager.DropItemCreate(this, prefabDropItem.gameObject, mob.transform.position); // mob.drops[i].value = 1; // item.Drop_First(mob.drops[i], false); // dropItems.Add(item); // item.OnGain = OnGain; // } // } // else // { // item = manager.DropItemCreate(this, prefabDropItem.gameObject, mob.transform.position); // item.Drop_First(mob.drops[i], false); // dropItems.Add(item); // item.OnGain = OnGain; // } // //manager.HoldinCheck(item.item.idx); // } //} } /// /// 드랍아이템을 생성 합니다. /// /// /// 생성할 아이템의 IDX public void OnDropItem_Create(Transform target, string idx) { DropItem drop = manager.DropItemCreate(this, prefabDropItem.gameObject, target.transform.position); drop.Drop_Again(DataManager.Instance.dataItem.Search(idx)); drop.OnGain = OnGain; dropItems.Add(drop); DropItemChanged(); } //public void OnDropItem_Create(Transform target, DropItemData item) //{ // DropItem drop = manager.DropItemCreate(this, prefabDropItem.gameObject, target.transform.position); // drop.Drop_First(item, true); // drop.OnGain = OnGain; // dropItems.Add(drop); // DropItemChanged(); //} /// /// 드랍아이템을 획득 처리합니다. /// /// /// void OnGain(GameObject owner, DropItem item) { if (!owner.name.Equals("HitCheck Item")) return; manager.player.gameObject.SendMessage("GainItem", item); if (!item.gameObject.activeSelf) { dropItems.Remove(item); item.OnGain -= OnGain; } } public System.Action> OnDropChanged; /// /// 맵블럭에 아이템이 드랍 혹은 습득 되어 상태를 변경 합니다. /// /// public void DropItemChanged(DropItem drop = null) { if (drop != null) { dropItems.Remove(drop); drop.OnGain -= OnGain; } if (OnDropChanged != null) OnDropChanged(dropItems); } [Header("클리어 보상 설정")] public DropItem prefabRewardDropItem; public SoundPlayer spReward; public Vector3 rewardPos = new Vector3(-0.5f, 0, -3f); /// /// 블럭의 몬스터를 모두 제거 했으므로 클리어 처리합니다. /// void Clear_Wait() { if (mobAlives.Count == 0) { Debug.LogWarning($"{name} 블럭 클리어"); BlockClearReward();//맵블럭 클리어 보상 manager.player.character.OnAdded_Mana(1); manager.player.character.skill.Active("BlkClear", null, null); ecMapBlock.Invoke((int)MapEvent.DoorState, CLEAR = true);//문 열기 manager.spBGM.Play(); } } /// /// 클리어 보상을 생성합니다. /// public void BlockClearReward() { ItemData rewardData = manager.DuplicateItemCheck(manager.items_All, 0, 999, null, false); if (rewardData == null) return; if (spReward != null) SoundManager.Instance.EffectPlay(spReward.gameObject); DropItem item = manager.ObjectCreateLocalPos(prefabRewardDropItem.gameObject, transform, Vector3.one, rewardPos, false).GetComponent(); item.item = ItemData.Copy(rewardData, 1); item.OnGain = OnGain; item.OnInitialized(); dropItems.Add(item); StartCoroutine(ClearReward_Wait(item)); } /// /// 플레이어 와 클리어보상이 멀어질때 보상을 활성화합니다. /// /// /// IEnumerator ClearReward_Wait(DropItem item) { while (Mathf.Abs(Vector3.Distance(manager.player.transform.position, item.transform.position)) < 2) yield return null; item.gameObject.SetActive(true); } [Header("BGM")] public SoundPlayer_BGM spBGM; public void BGMPlay() { if (spBGM == null) return; spBGM.Play(); } }