Projext/Assets/Scripts/Enemy/AI/MonsterClass.cs

382 lines
22 KiB
C#
Raw Normal View History

2026-02-19 18:14:55 +00:00
using UnityEngine; // 유니티 기능을 불러올거에요 -> UnityEngine을
2026-02-13 09:11:54 +00:00
using UnityEngine.AI; // 길찾기 기능을 불러올거에요 -> UnityEngine.AI를
using System.Collections; // 코루틴을 사용할거에요 -> System.Collections를
using System; // 시스템 기능을 사용할거에요 -> System을
2026-02-09 14:49:44 +00:00
2026-02-13 09:11:54 +00:00
public abstract class MonsterClass : MonoBehaviour, IDamageable // 추상 클래스를 선언할거에요 -> MonsterClass를
2026-02-02 08:30:23 +00:00
{
2026-02-13 09:11:54 +00:00
[Header("--- 최적화 ---")] // 인스펙터 제목을 달거에요 -> 최적화 설정을
protected Renderer mobRenderer; // 변수를 선언할거에요 -> 렌더러를
protected Transform playerTransform; // 변수를 선언할거에요 -> 플레이어 위치를
[SerializeField] protected float optimizationDistance = 40f; // 변수를 선언할거에요 -> 최적화 거리를
2026-02-02 08:30:23 +00:00
2026-02-13 09:11:54 +00:00
[Header("스탯")] // 인스펙터 제목을 달거에요 -> 스탯 설정을
[SerializeField] protected float maxHP = 100f; // 변수를 선언할거에요 -> 최대 체력을
[SerializeField] protected float attackDamage = 10f; // 변수를 선언할거에요 -> 공격력을
[SerializeField] protected int expReward = 10; // 변수를 선언할거에요 -> 경험치 보상을
[SerializeField] protected float moveSpeed = 3.5f; // 변수를 선언할거에요 -> 이동 속도를
2026-02-02 15:02:12 +00:00
2026-02-13 09:11:54 +00:00
protected float currentHP; // 변수를 선언할거에요 -> 현재 체력을
public event Action<float, float> OnHealthChanged; // 이벤트를 선언할거에요 -> 체력 변경 알림을
2026-02-02 08:30:23 +00:00
2026-02-13 09:11:54 +00:00
[Header("전투")] // 인스펙터 제목을 달거에요 -> 전투 설정을
[SerializeField] protected MonsterWeapon myWeapon; // 변수를 선언할거에요 -> 무기를
2026-02-12 15:23:25 +00:00
2026-02-19 18:14:55 +00:00
[Header("애니메이션")] // 인스펙터 제목을 달거에요 -> 애니메이션 State 이름을
[SerializeField] protected string Monster_Idle = "Monster_Idle"; // 변수를 선언할거에요 -> 대기 애니 이름을
2026-02-13 09:11:54 +00:00
[SerializeField] protected string Monster_GetDamage = "Monster_GetDamage"; // 변수를 선언할거에요 -> 피격 애니 이름을
2026-02-19 18:14:55 +00:00
[SerializeField] protected string Monster_Die = "Monster_Die"; // 변수를 선언할거에요 -> 사망 애니 이름을
[Tooltip("피격 애니 길이를 못 읽을 때 이 시간 후 자동으로 피격 상태 해제")]
[SerializeField] protected float hitRecoverFallback = 1f; // 변수를 선언할거에요 -> 피격 최대 유지 시간을
2026-02-04 14:06:25 +00:00
2026-02-13 09:11:54 +00:00
protected Animator animator; // 변수를 선언할거에요 -> 애니메이터를
protected NavMeshAgent agent; // 변수를 선언할거에요 -> 에이전트를
protected AudioSource audioSource; // 변수를 선언할거에요 -> 오디오 소스를
protected StatusEffectProcessor statusProcessor; // 변수를 선언할거에요 -> 상태이상 처리기를
2026-02-02 08:30:23 +00:00
2026-02-13 09:11:54 +00:00
protected bool isHit, isDead, isAttacking; // 상태 변수들을 선언할거에요
public bool IsAggroed { get; protected set; } // 프로퍼티를 선언할거에요 -> 어그로 여부를
2026-02-02 08:30:23 +00:00
2026-02-13 09:11:54 +00:00
[Header("AI")] // 인스펙터 제목을 달거에요 -> AI 설정을
[SerializeField] protected float attackRestDuration = 1.5f; // 변수를 선언할거에요 -> 공격 후 휴식 시간을
protected bool isResting; // 변수를 선언할거에요 -> 휴식 여부를
2026-02-03 14:41:49 +00:00
2026-02-19 18:14:55 +00:00
[Header("감지")] // 인스펙터 제목을 달거에요 -> 감지 설정을
[SerializeField] protected float detectionRange = 10f; // 변수를 선언할거에요 -> 플레이어 자동 감지 거리를
[Header("디버그")] // 인스펙터 제목을 달거에요 -> 디버그 설정을
[SerializeField] protected bool showDebugGizmos = false; // 변수를 선언할거에요 -> Scene 뷰 기즈모 on/off를
2026-02-13 09:11:54 +00:00
public static Action<int> OnMonsterKilled; // 이벤트를 선언할거에요 -> 사망 전역 알림을
2026-02-02 08:30:23 +00:00
2026-02-13 09:11:54 +00:00
[Header("사운드/이펙트")] // 인스펙터 제목을 달거에요 -> 효과 설정을
[SerializeField] protected AudioClip hitSound, deathSound; // 변수를 선언할거에요 -> 효과음들을
[SerializeField] protected GameObject deathEffectPrefab; // 변수를 선언할거에요 -> 사망 이펙트를
[SerializeField] protected ParticleSystem hitEffect; // 변수를 선언할거에요 -> 피격 이펙트를
[SerializeField] protected Transform impactSpawnPoint; // 변수를 선언할거에요 -> 이펙트 위치를
2026-02-02 08:30:23 +00:00
2026-02-19 18:14:55 +00:00
// ─── 피격 전용 코루틴 핸들 ───────────────────────────────
private Coroutine _hitCoroutine; // 변수를 선언할거에요 -> 피격 코루틴 핸들을 (개별 취소용)
// ─────────────────────────────────────────────────────────
// 초기화
// ─────────────────────────────────────────────────────────
protected virtual void Awake() // 함수를 실행할거에요 -> 컴포넌트 캐싱을
2026-02-12 15:23:25 +00:00
{
}
2026-02-19 18:14:55 +00:00
protected virtual void OnEnable() // 함수를 실행할거에요 -> 활성화/스폰될 때마다
2026-02-02 08:30:23 +00:00
{
2026-02-13 09:11:54 +00:00
playerTransform = GameObject.FindGameObjectWithTag("Player")?.transform; // 찾을거에요 -> 플레이어를
2026-02-19 18:14:55 +00:00
ResetStats(); // 초기화할거에요 -> 체력/상태를
2026-02-13 09:11:54 +00:00
if (mobRenderer != null) mobRenderer.enabled = true; // 켤거에요 -> 렌더러를
2026-02-19 18:14:55 +00:00
if (MobUpdateManager.Instance != null) MobUpdateManager.Instance.RegisterMob(this); // 등록할거에요 -> 업데이트 매니저에
2026-02-02 08:30:23 +00:00
}
2026-02-20 16:39:14 +00:00
protected virtual void Init() { } // 가상 함수를 선언할거에요 -> 자식 클래스 전용 추가 초기화를
protected virtual void Init() { } // 비워둘거에요 -> MobUpdateManager가 OnManagedUpdate를 담당하니까
2026-02-13 09:11:54 +00:00
protected virtual void OnDisable() // 함수를 실행할거에요 -> 비활성화 시
2026-02-02 08:30:23 +00:00
{
2026-02-13 09:11:54 +00:00
if (MobUpdateManager.Instance != null) MobUpdateManager.Instance.UnregisterMob(this); // 해제할거에요 -> 매니저에서
2026-02-02 08:30:23 +00:00
}
2026-02-19 18:14:55 +00:00
// ─────────────────────────────────────────────────────────
// 리셋
// ─────────────────────────────────────────────────────────
2026-02-04 14:06:25 +00:00
2026-02-19 18:14:55 +00:00
public void ResetStats() // 함수를 선언할거에요 -> 스탯/상태 완전 초기화를
2026-02-02 15:02:12 +00:00
{
2026-02-19 18:14:55 +00:00
isDead = false; // 초기화할거에요 -> 사망 상태를
IsAggroed = false; // 초기화할거에요 -> 어그로 상태를
isHit = false; // 초기화할거에요 -> 피격 상태를
isAttacking = false; // 초기화할거에요 -> 공격 상태를
isResting = false; // 초기화할거에요 -> 휴식 상태를
_hitCoroutine = null; // 초기화할거에요 -> 피격 코루틴 핸들을
currentHP = maxHP; // 채울거에요 -> 체력을 최대로
OnHealthChanged?.Invoke(currentHP, maxHP); // 알릴거에요 -> UI에 체력 변경을
Collider col = GetComponent<Collider>(); // 가져올거에요 -> 콜라이더를
if (col != null) col.enabled = true; // 켤거에요 -> 콜라이더를
if (agent != null)
{
agent.speed = moveSpeed; // 복구할거에요 -> 이동 속도를
agent.isStopped = false; // 켤거에요 -> 이동을
}
StopAllCoroutines(); // 중단할거에요 -> 이전 코루틴들을 (리셋 시에만 전체 중단)
OnResetStats(); // 실행할거에요 -> 자식 클래스 추가 리셋을
2026-02-02 15:02:12 +00:00
}
2026-02-20 16:39:14 +00:00
protected virtual void Update() => OnManagedUpdate(); // 실행할거에요 -> 관리형 업데이트를
2026-02-19 18:14:55 +00:00
public virtual void Reactivate() // 함수를 선언할거에요 -> 재활성화 추가 처리를
2026-02-03 14:41:49 +00:00
{
2026-02-19 18:14:55 +00:00
if (agent != null && agent.isOnNavMesh)
2026-02-03 14:41:49 +00:00
{
2026-02-19 18:14:55 +00:00
agent.isStopped = false; // 재개할거에요 -> 이동을
if (IsAggroed && playerTransform != null)
agent.SetDestination(playerTransform.position); // 이동할거에요 -> 플레이어에게
2026-02-20 16:39:14 +00:00
}
2026-02-03 14:41:49 +00:00
}
2026-02-19 18:14:55 +00:00
protected abstract void ExecuteAILogic(); // 추상 함수를 선언할거에요 -> 자식이 반드시 구현해야 하는 AI를
protected virtual void OnResetStats() { } // 가상 함수를 선언할거에요 -> 자식 클래스 추가 리셋을
// ─────────────────────────────────────────────────────────
// MobUpdateManager 전용 업데이트
// ─────────────────────────────────────────────────────────
2026-02-02 08:30:23 +00:00
2026-02-19 18:14:55 +00:00
public virtual void OnManagedUpdate() // 함수를 선언할거에요 -> 매니저가 매 프레임 호출하는 업데이트를
2026-02-02 08:30:23 +00:00
{
2026-02-19 18:14:55 +00:00
if (isDead || playerTransform == null || !gameObject.activeInHierarchy) return; // 중단할거에요 -> 작동 불가 상태면
float dist = Vector3.Distance(transform.position, playerTransform.position); // 계산할거에요 -> 플레이어와의 거리를
if (!IsAggroed && dist <= detectionRange) // 조건이 맞으면 실행할거에요 -> 감지 범위 안이면
IsAggroed = true; // 설정할거에요 -> 어그로 상태로
2026-02-03 14:41:49 +00:00
2026-02-13 09:11:54 +00:00
if (dist > optimizationDistance && !IsAggroed) // 조건이 맞으면 실행할거에요 -> 최적화 대상이면
2026-02-03 14:41:49 +00:00
{
2026-02-13 09:11:54 +00:00
StopMovement(); // 멈출거에요 -> 이동을
if (mobRenderer != null) mobRenderer.enabled = false; // 끌거에요 -> 렌더러를
2026-02-19 18:14:55 +00:00
return;
2026-02-03 14:41:49 +00:00
}
2026-02-13 09:11:54 +00:00
if (mobRenderer != null) mobRenderer.enabled = true; // 켤거에요 -> 렌더러를
2026-02-19 18:14:55 +00:00
if (!IsAggroed) // 조건이 맞으면 실행할거에요 -> 어그로가 없으면 대기
{
if (agent != null && agent.isOnNavMesh)
{ agent.isStopped = true; agent.velocity = Vector3.zero; }
if (animator != null && animator.isInitialized &&
!animator.GetCurrentAnimatorStateInfo(0).IsName(Monster_Idle))
animator.Play(Monster_Idle, 0, 0f); // 재생할거에요 -> 대기 애니를
return;
}
if (agent != null && agent.isOnNavMesh && agent.isStopped) // 조건이 맞으면 실행할거에요 -> 멈춰있으면
agent.isStopped = false; // 켤거에요 -> 이동을
2026-02-13 09:11:54 +00:00
ExecuteAILogic(); // 실행할거에요 -> AI 로직을
2026-02-02 08:30:23 +00:00
}
2026-02-19 18:14:55 +00:00
// ─────────────────────────────────────────────────────────
// 이동 정지
// ─────────────────────────────────────────────────────────
protected void StopMovement() // 함수를 선언할거에요 -> 이동 정지를
2026-02-03 14:41:49 +00:00
{
2026-02-19 18:14:55 +00:00
if (agent != null && agent.isOnNavMesh)
{ agent.isStopped = true; agent.velocity = Vector3.zero; }
if (animator != null)
{
animator.SetFloat("Speed", 0f); // 설정할거에요 -> 속도 파라미터를 0으로
if (!animator.GetCurrentAnimatorStateInfo(0).IsName(Monster_Idle))
animator.Play(Monster_Idle, 0, 0f); // 재생할거에요 -> 대기 애니를
}
2026-02-02 08:30:23 +00:00
2026-02-19 18:14:55 +00:00
// ─────────────────────────────────────────────────────────
// 피격 처리
//
// ⭐ 핵심 구조:
// StopAllCoroutines() 절대 사용 안 함
// _hitCoroutine 핸들로 피격 코루틴만 개별 관리
// HitRoutine이 클립 길이만큼 기다린 뒤 OnHitEnd() 자동 호출
// → Animation Event 등록 여부와 무관하게 항상 복구됨
// ─────────────────────────────────────────────────────────
2026-02-13 09:11:54 +00:00
public virtual void TakeDamage(float amount) // 함수를 선언할거에요 -> 피격 처리를
2026-02-02 08:30:23 +00:00
{
2026-02-19 18:14:55 +00:00
if (isDead) return; // 중단할거에요 -> 이미 죽었으면
IsAggroed = true; // 설정할거에요 -> 피격 시 즉시 어그로를
currentHP -= amount; // 줄일거에요 -> 체력을
OnHealthChanged?.Invoke(currentHP, maxHP); // 알릴거에요 -> UI에 체력 변경을
if (currentHP <= 0) // 조건이 맞으면 실행할거에요 -> 체력이 바닥났으면
2026-02-20 16:39:14 +00:00
{
2026-02-19 18:14:55 +00:00
Die(); // 실행할거에요 -> 사망 처리를
return;
2026-02-20 16:39:14 +00:00
}
2026-02-19 18:14:55 +00:00
if (!isHit) // 조건이 맞으면 실행할거에요 -> 아직 피격 상태가 아니면
StartHit(); // 실행할거에요 -> 피격 처리를
}
// InvokeHitRecovery — 자식에서 직접 OnHealthChanged 없이 데미지+UI만 처리할 때 사용
protected void ApplyDamageOnly(float amount) // 함수를 선언할거에요 -> 체력 감소+UI만 처리를 (StartHit 없이)
{
IsAggroed = true; // 설정할거에요 -> 어그로를
currentHP -= amount; // 줄일거에요 -> 체력을
2026-02-13 09:11:54 +00:00
OnHealthChanged?.Invoke(currentHP, maxHP); // 알릴거에요 -> UI에
2026-02-02 08:30:23 +00:00
}
2026-02-03 14:41:49 +00:00
2026-02-04 14:06:25 +00:00
{
2026-02-19 18:14:55 +00:00
isHit = true; // 설정할거에요 -> 피격 상태를
isAttacking = false; // 해제할거에요 -> 공격 상태를
isResting = false; // 해제할거에요 -> 휴식 상태를
// 이전 피격 코루틴만 취소 (다른 코루틴은 건드리지 않음)
if (_hitCoroutine != null) // 조건이 맞으면 실행할거에요 -> 이전 피격 코루틴이 있으면
{
StopCoroutine(_hitCoroutine); // 취소할거에요 -> 이전 피격 코루틴만
_hitCoroutine = null; // 초기화할거에요 -> 핸들을
}
OnStartHit(); // 실행할거에요 -> 자식 추가 처리를
if (agent != null && agent.isOnNavMesh)
{ agent.isStopped = true; agent.velocity = Vector3.zero; } // 멈출거에요 -> 이동을
if (hitEffect != null) hitEffect.Play(); // 재생할거에요 -> 이펙트를
if (hitSound != null && audioSource != null) audioSource.PlayOneShot(hitSound); // 재생할거에요 -> 피격 소리를
// 피격 코루틴 시작 — 애니 재생 + 클립 길이 후 자동 OnHitEnd()
_hitCoroutine = StartCoroutine(HitRoutine()); // 시작할거에요 -> 피격 코루틴을
}
private IEnumerator HitRoutine() // 코루틴을 정의할거에요 -> 피격 애니 재생 후 자동 복구를
{
float waitTime = hitRecoverFallback; // 초기화할거에요 -> 대기 시간을 기본값으로
int hash = Animator.StringToHash(Monster_GetDamage); // 계산할거에요 -> 피격 State 해시를
if (animator != null && animator.HasState(0, hash)) // 조건이 맞으면 실행할거에요 -> State가 있으면
{
animator.Play(Monster_GetDamage, 0, 0f); // 재생할거에요 -> 피격 애니를
yield return null; // 대기할거에요 -> 1프레임 (State 전환 대기)
AnimatorClipInfo[] clips = animator.GetCurrentAnimatorClipInfo(0); // 가져올거에요 -> 클립 정보를
if (clips.Length > 0 && clips[0].clip != null) // 조건이 맞으면 실행할거에요 -> 클립이 있으면
waitTime = clips[0].clip.length; // 저장할거에요 -> 실제 클립 길이를
}
else // 조건이 틀리면 실행할거에요 -> State가 없으면
{
Debug.LogWarning($"[MonsterClass] '{Monster_GetDamage}' State 없음! {hitRecoverFallback}초 후 자동 복구"); // 경고를 찍을거에요
}
yield return new WaitForSeconds(waitTime); // 기다릴거에요 -> 클립 길이 or 기본값만큼
if (isHit && !isDead) // 조건이 맞으면 실행할거에요 -> 아직 피격 상태이고 살아있으면
OnHitEnd(); // 실행할거에요 -> 피격 종료를
2026-02-04 14:06:25 +00:00
}
2026-02-19 18:14:55 +00:00
protected virtual void OnStartHit() // 가상 함수를 선언할거에요 -> 자식 추가 피격 처리를
{
if (myWeapon != null) myWeapon.DisableHitBox(); // 끌거에요 -> 무기 판정을
}
2026-02-04 14:06:25 +00:00
{
2026-02-13 09:11:54 +00:00
isHit = false; // 해제할거에요 -> 피격 상태를
2026-02-19 18:14:55 +00:00
_hitCoroutine = null; // 초기화할거에요 -> 코루틴 핸들을
if (agent != null && agent.isOnNavMesh) agent.isStopped = false; // 재개할거에요 -> 이동을
2026-02-04 14:06:25 +00:00
}
2026-02-09 14:49:44 +00:00
2026-02-19 18:14:55 +00:00
// ─────────────────────────────────────────────────────────
// 사망
// ─────────────────────────────────────────────────────────
2026-02-13 09:11:54 +00:00
protected virtual void Die() // 함수를 선언할거에요 -> 사망 처리를
2026-02-09 14:49:44 +00:00
{
2026-02-19 18:14:55 +00:00
isDead = true; // 설정할거에요 -> 사망 상태를
IsAggroed = false; // 해제할거에요 -> 어그로를
// 피격 코루틴만 정리
if (_hitCoroutine != null)
{ StopCoroutine(_hitCoroutine); _hitCoroutine = null; } // 취소할거에요 -> 피격 코루틴을
OnDie(); // 실행할거에요 -> 자식 추가 처리를
2026-02-13 09:11:54 +00:00
OnMonsterKilled?.Invoke(expReward); // 알릴거에요 -> 경험치 지급을
2026-02-19 18:14:55 +00:00
Collider col = GetComponent<Collider>(); // 가져올거에요 -> 콜라이더를
if (col != null) col.enabled = false; // 끌거에요 -> 충돌을
if (agent != null && agent.isOnNavMesh)
{ agent.isStopped = true; agent.velocity = Vector3.zero; } // 멈출거에요 -> 이동을
if (animator != null) animator.Play(Monster_Die, 0, 0f); // 재생할거에요 -> 사망 애니를
if (deathSound != null && audioSource != null) audioSource.PlayOneShot(deathSound); // 재생할거에요 -> 사망 소리를
Invoke(nameof(ReturnToPool), 1.5f); // 예약할거에요 -> 1.5초 후 비활성화를
}
protected virtual void OnDie() // 가상 함수를 선언할거에요 -> 자식 추가 사망 처리를
{
if (myWeapon != null) myWeapon.DisableHitBox(); // 끌거에요 -> 무기 판정을
2026-02-09 14:49:44 +00:00
}
2026-02-13 09:11:54 +00:00
protected void ReturnToPool() => gameObject.SetActive(false); // 함수를 선언할거에요 -> 비활성화를
2026-02-19 18:14:55 +00:00
// ─────────────────────────────────────────────────────────
// 공격
// ─────────────────────────────────────────────────────────
2026-02-13 09:11:54 +00:00
public virtual void OnAttackStart() // 함수를 선언할거에요 -> 공격 시작을
2026-02-09 14:49:44 +00:00
{
2026-02-19 18:14:55 +00:00
isAttacking = true; // 설정할거에요 -> 공격 중으로
isResting = false; // 해제할거에요 -> 휴식 상태를
2026-02-13 09:11:54 +00:00
if (myWeapon != null) myWeapon.EnableHitBox(); // 켤거에요 -> 무기 판정을
2026-02-20 16:39:14 +00:00
}
2026-02-09 14:49:44 +00:00
{
2026-02-13 09:11:54 +00:00
if (myWeapon != null) myWeapon.DisableHitBox(); // 끌거에요 -> 무기 판정을
isAttacking = false; // 해제할거에요 -> 공격 상태를
2026-02-19 18:14:55 +00:00
if (!isDead && !isHit) StartCoroutine(RestAfterAttack()); // 시작할거에요 -> 공격 후 휴식을
2026-02-20 16:39:14 +00:00
}
2026-02-09 14:49:44 +00:00
2026-02-19 18:14:55 +00:00
protected virtual IEnumerator RestAfterAttack() // 코루틴을 선언할거에요 -> 공격 후 휴식 로직을
2026-02-09 14:49:44 +00:00
{
}
2026-02-19 18:14:55 +00:00
// ─────────────────────────────────────────────────────────
// 상태이상
// ─────────────────────────────────────────────────────────
2026-02-13 09:11:54 +00:00
public void ApplyStatusEffect(StatusEffectType type, float dmg, float dur) // 함수를 선언할거에요 -> 상태이상 적용을
2026-02-09 14:49:44 +00:00
{
2026-02-13 09:11:54 +00:00
if (isDead) return; // 중단할거에요 -> 죽었으면
switch (type)
2026-02-19 18:14:55 +00:00
{
2026-02-13 09:11:54 +00:00
case StatusEffectType.Burn: statusProcessor.ApplyBurn(dmg, dur); break; // 적용할거에요 -> 화상을
case StatusEffectType.Slow: statusProcessor.ApplySlow(dmg, dur); break; // 적용할거에요 -> 슬로우를
case StatusEffectType.Poison: statusProcessor.ApplyBurn(dmg, dur); break; // 적용할거에요 -> 독을
case StatusEffectType.Shock: statusProcessor.ApplyShock(dmg, dur); break; // 적용할거에요 -> 충격을
2026-02-09 14:49:44 +00:00
}
}
2026-02-19 18:14:55 +00:00
// ─────────────────────────────────────────────────────────
// 디버그 기즈모
// ─────────────────────────────────────────────────────────
protected virtual void OnDrawGizmosSelected() // 함수를 선언할거에요 -> Scene 뷰 기즈모를
{
if (!showDebugGizmos) return; // 중단할거에요 -> 기즈모가 꺼져 있으면
bool aggroed = Application.isPlaying && IsAggroed; // 판단할거에요 -> 런타임 어그로 여부를
Gizmos.color = aggroed
? new Color(1f, 0.2f, 0.1f, 0.18f)
: new Color(1f, 0.9f, 0f, 0.10f);
Gizmos.DrawSphere(transform.position, detectionRange); // 그릴거에요 -> 감지 범위 채우기를
Gizmos.color = aggroed ? Color.red : Color.yellow; // 설정할거에요 -> 외곽선 색을
Gizmos.DrawWireSphere(transform.position, detectionRange); // 그릴거에요 -> 감지 범위 외곽선을
#if UNITY_EDITOR
if (Application.isPlaying) // 조건이 맞으면 실행할거에요 -> 플레이 중이면
{
string stateLabel = aggroed ? "[ 추격 중 ]" : "[ 대기 ]"; // 결정할거에요 -> 표시할 상태 문자열을
string hpText = $"HP {currentHP:F0} / {maxHP:F0}"; // 만들거에요 -> 체력 문자열을
UnityEditor.Handles.Label(
transform.position + Vector3.up * (detectionRange + 0.4f),
$"{gameObject.name}\n{stateLabel}\n{hpText}"
); // 표시할거에요 -> Scene 뷰 레이블을
}
#endif
}
2026-02-09 14:49:44 +00:00
}