228 lines
11 KiB
C#
228 lines
11 KiB
C#
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
|
|
public class MobControlMgr : MyCoroutine
|
|
{
|
|
public int SpawnerID;
|
|
public float MobGenRange = 10f;
|
|
public TextMeshPro text_lv;
|
|
|
|
MonsterAppearData m_MonsterAppearData;
|
|
Dictionary<int, List<Actor>> dic_actors = new Dictionary<int, List<Actor>>();
|
|
int orderIndex, killmobcount;
|
|
bool m_LoadComplete;
|
|
|
|
// #764 — 이 맵의 스폰 규칙. 프리팹 계층에서 찾고(같은 맵 프리팹 루트), 없으면 현재 로드된 맵의 MapData 를 쓴다.
|
|
MapData m_MapCfg;
|
|
MapData Get_MapCfg()
|
|
{
|
|
if (DSUtil.CheckNull(m_MapCfg)) m_MapCfg = GetComponentInParent<MapData>(true);
|
|
if (DSUtil.CheckNull(m_MapCfg) && MapData.isIns) m_MapCfg = MapData.Ins;
|
|
return DSUtil.CheckNull(m_MapCfg) ? null : m_MapCfg;
|
|
}
|
|
|
|
IEnumerator Start()
|
|
{
|
|
m_LoadComplete = false;
|
|
|
|
text_lv.text = "Lv.???";
|
|
m_MonsterAppearData = table_monsterappear.Ins.Get_Data_orNull(SpawnerID);
|
|
if (!DSUtil.CheckNull(m_MonsterAppearData))
|
|
{
|
|
text_lv.text = DSUtil.Format("Lv.{0}", m_MonsterAppearData.n_AppearMonsterLv);
|
|
|
|
// #764 — 맵이 요구하면 스포너 원점이 플레이어 시작 지점에서 도달 가능한지 1회 확인한다.
|
|
// NavMesh 연결 성분 안에서 도달성은 대칭·추이적이므로, 원점이 도달 가능하면
|
|
// "원점에서 완전 경로가 나오는 표집점" 도 전부 도달 가능하다 → 몹마다 경로를 두 번 계산할 필요가 없다.
|
|
var cfg = Get_MapCfg();
|
|
if (cfg != null && cfg.spawnRequireReachableFromPlayer)
|
|
yield return StartCoroutine(Co_CheckSpawnerReachable());
|
|
|
|
var list_make_mobs = Get_Mobs();
|
|
for (int i = 0; i < list_make_mobs.Count; i++)
|
|
{
|
|
var rdnpos = Get_SpawnPos();
|
|
var mobData = list_make_mobs[i];
|
|
yield return StartCoroutine(Make_Mob(mobData, i, rdnpos));
|
|
}
|
|
|
|
// 생성한 몬스터를 m_MonsterAppearData.n_MaxMonsterCount 만큼 활성화
|
|
for (int i = 0; i < m_MonsterAppearData.n_MaxMonsterCount; i++)
|
|
dic_actors[0][i].On_Regen();
|
|
|
|
m_LoadComplete = true;
|
|
}
|
|
}
|
|
|
|
public bool Get_LoadComplete() { return m_LoadComplete; }
|
|
|
|
/// <summary>
|
|
/// #764 — 몬스터 1마리의 생성 위치. 맵이 요구할 때만 엄격 표집을 쓰고, 아니면 기존 동작 그대로다.
|
|
/// 반경·재시도 수치는 코드 상수가 아니라 MapData 에서 읽는다(C45).
|
|
/// </summary>
|
|
Vector3 Get_SpawnPos()
|
|
{
|
|
var cfg = Get_MapCfg();
|
|
if (cfg == null || !cfg.spawnRequireReachableFromPlayer)
|
|
return DSUtil.Get_RandomPos_onNavMesh(transform.position, 0f, MobGenRange);
|
|
|
|
return DSUtil.Get_RandomPos_onNavMesh_Reachable(
|
|
transform.position, 0f, MobGenRange,
|
|
cfg.spawnSnapRadius,
|
|
cfg.spawnSampleTries > 0 ? cfg.spawnSampleTries : 50);
|
|
}
|
|
|
|
/// <summary>#764 — 스포너 원점이 플레이어 시작 지점(포탈)에서 도달 가능한지 확인하고, 아니면 경고를 남긴다.</summary>
|
|
IEnumerator Co_CheckSpawnerReachable()
|
|
{
|
|
if (!LoadMapMgr.isIns) yield break;
|
|
|
|
bool got = false;
|
|
Vector3 startpos = Vector3.zero;
|
|
LoadMapMgr.Ins.Get_PortalPos(MyValue.MyChoiceMapData, p => { startpos = p; got = true; });
|
|
|
|
// 맵 로딩이 끝날 때까지 기다린다. 안전 상한 — 못 받으면 확인을 건너뛰고 스폰은 정상 진행한다.
|
|
float wait = 0f;
|
|
while (!got && wait < 10f) { wait += Time.deltaTime; yield return null; }
|
|
if (!got) { Debug.LogWarning($"[#764] Spawner {SpawnerID}: 시작 지점을 받지 못해 도달성 확인을 건너뛴다"); yield break; }
|
|
|
|
if (!DSUtil.IsReachable_onNavMesh(startpos, transform.position))
|
|
Debug.LogWarning($"[#764] Spawner {SpawnerID} 원점이 플레이어 시작 지점에서 도달 불가 " +
|
|
$"(spawner {transform.position.ToString("F1")} / start {startpos.ToString("F1")}) — 스포너 위치 재배치가 필요하다");
|
|
}
|
|
|
|
List<MonsterTableData> Get_Mobs()
|
|
{
|
|
var list_make_mobs = new List<MonsterTableData>();
|
|
switch (m_MonsterAppearData.e_SpawnerType)
|
|
{ // 생성할 몹 정보
|
|
case eSpawnerType.Random: // s_AppearMonsterID에 지정된 몬스터가 n_MaxMonsterCount 수량만큼 Spawner 범위 내 랜덤으로 생성되는 방식
|
|
for (int i = 0; i < m_MonsterAppearData.n_MaxMonsterCount * 2; i++)
|
|
list_make_mobs.Add(m_MonsterAppearData.Get_MobData_orNull());
|
|
dic_actors.Add(0, new List<Actor>());
|
|
break;
|
|
case eSpawnerType.Order: // s_AppearMonsterID에 지정 된 몬스터가 n_MaxMonsterCount 수량만큼 Spawner 범위 내 순서대로 생성되는 방식 (예: 1001|1002|1003 → 1001|1002|1003 → 순서를 반복해 생성)
|
|
int index = 0;
|
|
for (int i = 0; i < m_MonsterAppearData.n_MaxMonsterCount * 2; i++)
|
|
{
|
|
list_make_mobs.Add(m_MonsterAppearData.Get_MobData_orNull(index));
|
|
++index;
|
|
if (index >= m_MonsterAppearData.Get_MobTypeCount()) index = 0;
|
|
}
|
|
dic_actors.Add(0, new List<Actor>());
|
|
orderIndex = m_MonsterAppearData.n_MaxMonsterCount - 1;
|
|
break;
|
|
case eSpawnerType.Phase:
|
|
// s_AppearMonsterID에 지정 된 몬스터가 n_MaxMonsterCount 수량만큼 Spawner 범위 한 종류씩 순차적으로 생성되는 방식
|
|
// (예: 1001|1002|1003일 경우 1001번 몬스터가 사망하면 1002번 몬스터가 n_MaxMonsterCount 만큼 f_SpawnerDelay 후 생성)
|
|
case eSpawnerType.PhaseAllKill:
|
|
// s_AppearMonsterID에 지정 된 몬스터가 n_MaxMonsterCount 수량만큼 Spawner 범위 한 종류씩 순차적으로 생성되고, 모든 몬스터가 제거되면 다음 페이즈가 발생되는 방식 (※주로 컨텐츠에서 활용될 예정)
|
|
// (예: 1001|1002|1003일 경우 1001번 몬스터를 전부 제거해야 1002번 몬스터가 n_MaxMonsterCount 만큼 f_SpawnerDelay 후 일괄 생성)
|
|
for (int i = 0; i < m_MonsterAppearData.Get_MobTypeCount(); i++)
|
|
{
|
|
dic_actors.Add(i, new List<Actor>());
|
|
for (int j = 0; j < m_MonsterAppearData.n_MaxMonsterCount; j++)
|
|
list_make_mobs.Add(m_MonsterAppearData.Get_MobData_orNull(i));
|
|
}
|
|
break;
|
|
}
|
|
return list_make_mobs;
|
|
}
|
|
|
|
IEnumerator Make_Mob(MonsterTableData mobData, int i, Vector3 rdnpos)
|
|
{
|
|
bool makeComplete = false;
|
|
AddrResourceMgr.Ins.LoadObject<GameObject>(mobData.s_MonsterPrefab, handle =>
|
|
{
|
|
var mob = DSUtil.Get_Clone<MobActor>(handle.Result, transform);
|
|
AddrResourceMgr.Ins.Set_AddressableReleaseSelf(mob.gameObject);
|
|
ActorInfo.Ins.Add_Actor(mob, eRole.Mob, SpawnerID, mobData.s_MonsterPrefab);
|
|
|
|
switch (m_MonsterAppearData.e_SpawnerType)
|
|
{
|
|
case eSpawnerType.Random:
|
|
case eSpawnerType.Order:
|
|
dic_actors[0].Add(mob);
|
|
break;
|
|
case eSpawnerType.Phase:
|
|
case eSpawnerType.PhaseAllKill:
|
|
var list_ids = m_MonsterAppearData.Get_MobIds();
|
|
int findindex = list_ids.FindIndex(f => f == mobData.n_MonsterID);
|
|
if (findindex >= 0 && findindex < dic_actors.Count) // 인덱스가 유효한지 체크
|
|
dic_actors[findindex].Add(mob);
|
|
else
|
|
Debug.LogError($"Invalid findindex: {findindex}, MonsterID: {mobData.n_MonsterID}");
|
|
break;
|
|
}
|
|
|
|
mob.Set(true, null, null, m_MonsterAppearData.n_SpawnerID, false);
|
|
mob.Set(mobData, rdnpos, actDie, i);
|
|
mob.Off();
|
|
mob.MagicID = 0;
|
|
makeComplete = true;
|
|
});
|
|
|
|
while (!makeComplete) yield return null;
|
|
}
|
|
|
|
private void actDie(int mobid)
|
|
{
|
|
if (m_MonsterAppearData != null)
|
|
{
|
|
++killmobcount;
|
|
switch (m_MonsterAppearData.e_SpawnerType)
|
|
{ // 사망 후 리젠
|
|
case eSpawnerType.Random:
|
|
Set_Coroutine(() =>
|
|
{
|
|
var deadmobs = dic_actors[0].FindAll(f => !f.isActiveAndEnabled);
|
|
deadmobs[Random.Range(0, deadmobs.Count)].On_Regen();
|
|
}, m_MonsterAppearData.f_SpawnerDelay);
|
|
break;
|
|
case eSpawnerType.Order:
|
|
Set_Coroutine(() =>
|
|
{
|
|
if (dic_actors[0].Count > 0)
|
|
{
|
|
orderIndex = orderIndex % dic_actors[0].Count;
|
|
while (true)
|
|
{
|
|
var mob = dic_actors[0][orderIndex++];
|
|
if (!mob.isActiveAndEnabled)
|
|
{
|
|
mob.On_Regen();
|
|
break;
|
|
}
|
|
orderIndex = orderIndex % dic_actors[0].Count;
|
|
}
|
|
}
|
|
}, m_MonsterAppearData.f_SpawnerDelay);
|
|
break;
|
|
case eSpawnerType.Phase:
|
|
Set_Coroutine(() =>
|
|
{
|
|
var ids = m_MonsterAppearData.Get_MobIds();
|
|
var index = ids.FindIndex(f => f == mobid);
|
|
var nextindex = index + 1;
|
|
if (nextindex >= dic_actors.Count) nextindex = 0;
|
|
dic_actors[nextindex].Find(f => !f.isActiveAndEnabled).On_Regen();
|
|
}, m_MonsterAppearData.f_SpawnerDelay);
|
|
break;
|
|
case eSpawnerType.PhaseAllKill:
|
|
if (killmobcount == m_MonsterAppearData.n_MaxMonsterCount)
|
|
{
|
|
Set_Coroutine(() =>
|
|
{
|
|
killmobcount = 0;
|
|
++orderIndex; if (orderIndex >= dic_actors.Count) orderIndex = 0;
|
|
for (int i = 0; i < dic_actors[orderIndex].Count; i++)
|
|
dic_actors[orderIndex][i].On_Regen();
|
|
}, m_MonsterAppearData.f_SpawnerDelay);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} |