2026-02-02 08:30:23 +00:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 하이브리드 몬스터 스포너 (생성은 스포너 기준, 생존은 몬스터 기준)
|
|
|
|
|
|
/// - Birth: 스포너 기준 거리로 생성
|
|
|
|
|
|
/// - Life & Hibernate: 몬스터 기준 거리로 최적화
|
|
|
|
|
|
/// - Wake Up: 범위 안으로 복귀 시 재활성화
|
|
|
|
|
|
/// </summary>
|
2026-02-02 08:30:23 +00:00
|
|
|
|
public class MonsterSpawner : MonoBehaviour
|
|
|
|
|
|
{
|
2026-02-03 14:41:49 +00:00
|
|
|
|
[Header("=== 생성 설정 (Birth - 스포너 기준) ===")]
|
|
|
|
|
|
[Tooltip("스포너로부터 이 거리 안에 플레이어가 들어오면 몬스터 생성")]
|
|
|
|
|
|
[SerializeField] private float spawnRange = 25f;
|
|
|
|
|
|
|
|
|
|
|
|
[Tooltip("몬스터가 죽은 후 재생성까지 대기 시간")]
|
|
|
|
|
|
[SerializeField] private float respawnCooldown = 3f;
|
|
|
|
|
|
|
|
|
|
|
|
[Tooltip("오브젝트 풀에서 가져올 몬스터 태그")]
|
2026-02-02 15:02:12 +00:00
|
|
|
|
[SerializeField] private string mobTag = "NormalMob";
|
2026-02-02 08:30:23 +00:00
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
[Header("=== 최적화 설정 (Life & Hibernate - 몬스터 기준) ===")]
|
|
|
|
|
|
[Tooltip("몬스터 본체로부터 이 거리를 벗어나면 강제 동면 (전투 중이어도!)")]
|
|
|
|
|
|
[SerializeField] private float optimizationRange = 60f;
|
|
|
|
|
|
|
|
|
|
|
|
// 내부 상태
|
2026-02-02 08:30:23 +00:00
|
|
|
|
private GameObject _myMonster;
|
|
|
|
|
|
private MonsterClass _monsterScript;
|
|
|
|
|
|
private Transform _player;
|
|
|
|
|
|
private float _nextSpawnTime;
|
|
|
|
|
|
|
|
|
|
|
|
private void Start()
|
|
|
|
|
|
{
|
|
|
|
|
|
FindPlayer();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
private void FindPlayer()
|
|
|
|
|
|
{
|
|
|
|
|
|
GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
|
|
|
|
|
|
if (playerObj != null) _player = playerObj.transform;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 08:30:23 +00:00
|
|
|
|
private void Update()
|
|
|
|
|
|
{
|
2026-02-02 15:02:12 +00:00
|
|
|
|
if (_player == null) { FindPlayer(); return; }
|
2026-02-02 08:30:23 +00:00
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// 몬스터가 없거나 죽었으면 -> Birth
|
|
|
|
|
|
if (_myMonster == null || (_monsterScript != null && _monsterScript.IsDead))
|
2026-02-02 08:30:23 +00:00
|
|
|
|
{
|
2026-02-03 14:41:49 +00:00
|
|
|
|
HandleBirth();
|
2026-02-02 08:30:23 +00:00
|
|
|
|
}
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// 몬스터가 살아있으면 -> Life & Hibernate
|
2026-02-02 08:30:23 +00:00
|
|
|
|
else
|
|
|
|
|
|
{
|
2026-02-03 14:41:49 +00:00
|
|
|
|
HandleLifeAndHibernate();
|
2026-02-02 08:30:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
|
|
// 🐣 BIRTH (생성) - 스포너 기준
|
|
|
|
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
2026-02-02 08:30:23 +00:00
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
private void HandleBirth()
|
2026-02-02 08:30:23 +00:00
|
|
|
|
{
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// ⭐ 스포너 <-> 플레이어 거리
|
|
|
|
|
|
float distanceFromSpawner = Vector3.Distance(transform.position, _player.position);
|
|
|
|
|
|
|
|
|
|
|
|
if (distanceFromSpawner <= spawnRange && Time.time >= _nextSpawnTime)
|
2026-02-02 15:02:12 +00:00
|
|
|
|
{
|
2026-02-03 14:41:49 +00:00
|
|
|
|
SpawnMonster();
|
2026-02-02 15:02:12 +00:00
|
|
|
|
}
|
2026-02-03 14:41:49 +00:00
|
|
|
|
}
|
2026-02-02 15:02:12 +00:00
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
private void SpawnMonster()
|
|
|
|
|
|
{
|
2026-02-02 08:30:23 +00:00
|
|
|
|
_myMonster = GenericObjectPool.Instance.SpawnFromPool(mobTag, transform.position, transform.rotation);
|
|
|
|
|
|
|
|
|
|
|
|
if (_myMonster != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_monsterScript = _myMonster.GetComponent<MonsterClass>();
|
2026-02-03 14:41:49 +00:00
|
|
|
|
if (_monsterScript != null) _monsterScript.ResetStats();
|
2026-02-02 08:30:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
|
|
// 💤 LIFE & HIBERNATE (생존/동면) - 몬스터 기준
|
|
|
|
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
|
|
|
|
|
|
|
|
private void HandleLifeAndHibernate()
|
2026-02-02 08:30:23 +00:00
|
|
|
|
{
|
2026-02-03 14:41:49 +00:00
|
|
|
|
if (_myMonster == null) { _monsterScript = null; return; }
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ 몬스터 본체 <-> 플레이어 거리
|
|
|
|
|
|
float distanceFromMonster = Vector3.Distance(_myMonster.transform.position, _player.position);
|
|
|
|
|
|
|
|
|
|
|
|
if (distanceFromMonster > optimizationRange)
|
2026-02-02 08:30:23 +00:00
|
|
|
|
{
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// 😴 HIBERNATE (동면)
|
|
|
|
|
|
HibernateMonster();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 👁 WAKE UP (기상)
|
|
|
|
|
|
WakeUpMonster();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void HibernateMonster()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_myMonster.activeSelf)
|
|
|
|
|
|
{
|
|
|
|
|
|
_myMonster.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void WakeUpMonster()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!_myMonster.activeSelf)
|
|
|
|
|
|
{
|
|
|
|
|
|
_myMonster.SetActive(true);
|
|
|
|
|
|
|
|
|
|
|
|
if (_monsterScript != null)
|
2026-02-02 08:30:23 +00:00
|
|
|
|
{
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// ⭐ AI 복구 (전투 중이었다면 추적 재개)
|
|
|
|
|
|
_monsterScript.Reactivate();
|
2026-02-02 08:30:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
|
|
// 💀 DEATH (사망 처리 및 쿨타임)
|
|
|
|
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 몬스터가 죽었을 때 호출 (쿨타임 적용)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void NotifyMonsterDead()
|
|
|
|
|
|
{
|
|
|
|
|
|
// 쿨타임 계산: 현재 시간 + 대기 시간
|
|
|
|
|
|
_nextSpawnTime = Time.time + respawnCooldown;
|
|
|
|
|
|
|
|
|
|
|
|
// 몬스터 참조를 비워서 Update에서 다시 HandleBirth()로 넘어가게 함
|
|
|
|
|
|
_myMonster = null;
|
|
|
|
|
|
_monsterScript = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
|
|
// Gizmos
|
|
|
|
|
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
|
|
|
|
|
2026-02-02 08:30:23 +00:00
|
|
|
|
private void OnDrawGizmosSelected()
|
|
|
|
|
|
{
|
2026-02-03 14:41:49 +00:00
|
|
|
|
// 스포너 생성 범위 (빨간색)
|
2026-02-02 08:30:23 +00:00
|
|
|
|
Gizmos.color = Color.red;
|
|
|
|
|
|
Gizmos.DrawWireSphere(transform.position, spawnRange);
|
2026-02-03 14:41:49 +00:00
|
|
|
|
|
|
|
|
|
|
// 몬스터 최적화 범위 (파란색)
|
|
|
|
|
|
if (_myMonster != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Gizmos.color = Color.blue;
|
|
|
|
|
|
Gizmos.DrawWireSphere(_myMonster.transform.position, optimizationRange);
|
|
|
|
|
|
}
|
2026-02-02 08:30:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|