2024-12-15 01:39:39 +00:00
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 ;
2025-03-10 04:57:13 +00:00
bool m_LoadComplete ;
2024-12-15 01:39:39 +00:00
2026-09-06 23:24:25 +00:00
// #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 ;
}
2024-12-15 01:39:39 +00:00
IEnumerator Start ( )
{
2025-03-10 04:57:13 +00:00
m_LoadComplete = false ;
2024-12-15 01:39:39 +00:00
text_lv . text = "Lv.???" ;
m_MonsterAppearData = table_monsterappear . Ins . Get_Data_orNull ( SpawnerID ) ;
2024-12-18 02:15:45 +00:00
if ( ! DSUtil . CheckNull ( m_MonsterAppearData ) )
2024-12-15 01:39:39 +00:00
{
text_lv . text = DSUtil . Format ( "Lv.{0}" , m_MonsterAppearData . n_AppearMonsterLv ) ;
2026-09-06 23:24:25 +00:00
// #764 — 맵이 요구하면 스포너 원점이 플레이어 시작 지점에서 도달 가능한지 1회 확인한다.
// NavMesh 연결 성분 안에서 도달성은 대칭·추이적이므로, 원점이 도달 가능하면
// "원점에서 완전 경로가 나오는 표집점" 도 전부 도달 가능하다 → 몹마다 경로를 두 번 계산할 필요가 없다.
var cfg = Get_MapCfg ( ) ;
if ( cfg ! = null & & cfg . spawnRequireReachableFromPlayer )
yield return StartCoroutine ( Co_CheckSpawnerReachable ( ) ) ;
2025-03-05 06:40:37 +00:00
var list_make_mobs = Get_Mobs ( ) ;
2024-12-15 01:39:39 +00:00
for ( int i = 0 ; i < list_make_mobs . Count ; i + + )
2025-02-06 04:56:52 +00:00
{
2026-09-06 23:24:25 +00:00
var rdnpos = Get_SpawnPos ( ) ;
2025-03-05 06:40:37 +00:00
var mobData = list_make_mobs [ i ] ;
yield return StartCoroutine ( Make_Mob ( mobData , i , rdnpos ) ) ;
}
2025-02-06 04:56:52 +00:00
2025-03-05 06:40:37 +00:00
// 생성한 몬스터를 m_MonsterAppearData.n_MaxMonsterCount 만큼 활성화
for ( int i = 0 ; i < m_MonsterAppearData . n_MaxMonsterCount ; i + + )
dic_actors [ 0 ] [ i ] . On_Regen ( ) ;
2025-03-10 04:57:13 +00:00
m_LoadComplete = true ;
2025-03-05 06:40:37 +00:00
}
}
2025-02-06 04:56:52 +00:00
2025-03-10 04:57:13 +00:00
public bool Get_LoadComplete ( ) { return m_LoadComplete ; }
2026-09-06 23:24:25 +00:00
/// <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 ")}) — 스포너 위치 재배치가 필요하다" ) ;
}
2025-03-05 06:40:37 +00:00
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 ;
}
2024-12-15 01:39:39 +00:00
2025-03-05 06:40:37 +00:00
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 ) ;
2024-12-15 01:39:39 +00:00
2025-03-05 06:40:37 +00:00
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 ;
2024-12-15 01:39:39 +00:00
}
2025-03-05 06:40:37 +00:00
mob . Set ( true , null , null , m_MonsterAppearData . n_SpawnerID , false ) ;
mob . Set ( mobData , rdnpos , actDie , i ) ;
mob . Off ( ) ;
mob . MagicID = 0 ;
makeComplete = true ;
} ) ;
2024-12-15 01:39:39 +00:00
2025-03-05 06:40:37 +00:00
while ( ! makeComplete ) yield return null ;
2024-12-15 01:39:39 +00:00
}
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 ( ( ) = >
{
2025-02-06 04:56:52 +00:00
if ( dic_actors [ 0 ] . Count > 0 )
2024-12-15 01:39:39 +00:00
{
2025-02-06 04:56:52 +00:00
orderIndex = orderIndex % dic_actors [ 0 ] . Count ;
while ( true )
2024-12-15 01:39:39 +00:00
{
2025-02-06 04:56:52 +00:00
var mob = dic_actors [ 0 ] [ orderIndex + + ] ;
if ( ! mob . isActiveAndEnabled )
{
mob . On_Regen ( ) ;
break ;
}
orderIndex = orderIndex % dic_actors [ 0 ] . Count ;
2024-12-15 01:39:39 +00:00
}
}
} , 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 ;
}
}
}
}