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

185 lines
5.6 KiB
C#
Raw Normal View History

2026-02-02 08:30:23 +00:00
using UnityEngine;
using UnityEngine.AI;
2026-02-06 09:27:08 +00:00
using System.Collections;
using System.Collections.Generic; // ✅ 리스트 사용을 위해 필수 추가
2026-02-02 08:30:23 +00:00
2026-02-04 14:06:25 +00:00
public class MeleeMonster : MonsterClass
2026-02-02 08:30:23 +00:00
{
2026-02-04 14:06:25 +00:00
[Header("=== 근접 공격 설정 ===")]
2026-02-06 09:27:08 +00:00
//[SerializeField] private MonsterWeapon myWeapon; // ✅ 주석 해제됨 (이제 무기 연결 가능!)
2026-02-02 08:30:23 +00:00
[SerializeField] private float attackRange = 2f;
[SerializeField] private float attackDelay = 1.5f;
2026-02-06 09:27:08 +00:00
// 🎁 [수정] 단일 아이템 -> 리스트로 변경
[Header("=== 드랍 아이템 설정 ===")]
[Tooltip("드랍 가능한 아이템들을 여기에 여러 개 등록하세요. (랜덤 1개 드랍)")]
[SerializeField] private List<GameObject> dropItemPrefabs;
[Tooltip("아이템이 나올 확률 (0 ~ 100%)")]
[Range(0, 100)][SerializeField] private float dropChance = 30f; // 기본 30%
2026-02-02 08:30:23 +00:00
private float lastAttackTime;
[Header("공격 / 이동 애니메이션")]
[SerializeField] private string[] attackAnimations = { "Monster_Attack_1" };
[SerializeField] private string Monster_Walk = "Monster_Walk";
2026-02-02 15:02:12 +00:00
[Header("AI 상세 설정")]
2026-02-02 08:30:23 +00:00
[SerializeField] private float stopBuffer = 0.3f;
[SerializeField] private float patrolRadius = 5f;
[SerializeField] private float patrolInterval = 2f;
private float nextPatrolTime;
private float repathInterval = 0.3f;
private float nextRepathTime;
private int attackIndex;
private bool isPlayerInZone;
protected override void Init()
{
if (agent != null) agent.stoppingDistance = attackRange - 0.4f;
if (animator != null) animator.applyRootMotion = false;
}
2026-02-04 14:06:25 +00:00
protected override void OnResetStats()
{
if (myWeapon != null)
{
myWeapon.SetDamage(attackDamage);
}
}
2026-02-02 08:30:23 +00:00
protected override void ExecuteAILogic()
{
if (isHit || isAttacking || isResting) return;
float distance = Vector3.Distance(transform.position, playerTransform.position);
2026-02-04 14:06:25 +00:00
if (isPlayerInZone || distance <= attackRange * 2f)
HandlePlayerTarget();
else
Patrol();
2026-02-02 08:30:23 +00:00
UpdateMovementAnimation();
}
void HandlePlayerTarget()
{
float distance = Vector3.Distance(transform.position, playerTransform.position);
2026-02-04 14:06:25 +00:00
if (distance <= attackRange - stopBuffer)
TryAttack();
2026-02-02 08:30:23 +00:00
else if (Time.time >= nextRepathTime)
{
if (agent.isOnNavMesh) agent.SetDestination(playerTransform.position);
nextRepathTime = Time.time + repathInterval;
}
}
void TryAttack()
{
if (Time.time < lastAttackTime + attackDelay) return;
lastAttackTime = Time.time;
2026-02-03 14:41:49 +00:00
2026-02-02 08:30:23 +00:00
string attackName = attackAnimations[attackIndex];
attackIndex = (attackIndex + 1) % attackAnimations.Length;
2026-02-03 14:41:49 +00:00
isAttacking = true;
if (agent.isOnNavMesh)
2026-02-02 08:30:23 +00:00
{
2026-02-03 14:41:49 +00:00
agent.isStopped = true;
agent.velocity = Vector3.zero;
2026-02-02 08:30:23 +00:00
}
2026-02-03 14:41:49 +00:00
animator.Play(attackName, 0, 0f);
2026-02-02 08:30:23 +00:00
}
void UpdateMovementAnimation()
{
if (isAttacking || isHit || isResting) return;
2026-02-04 14:06:25 +00:00
if (agent.velocity.magnitude < 0.1f)
animator.Play(Monster_Idle);
else
animator.Play(Monster_Walk);
2026-02-02 08:30:23 +00:00
}
void Patrol()
{
if (Time.time < nextPatrolTime) return;
2026-02-04 14:06:25 +00:00
Vector3 randomPoint = transform.position + new Vector3(
Random.Range(-patrolRadius, patrolRadius),
0,
Random.Range(-patrolRadius, patrolRadius)
);
2026-02-02 08:30:23 +00:00
if (NavMesh.SamplePosition(randomPoint, out NavMeshHit hit, 3f, NavMesh.AllAreas))
if (agent.isOnNavMesh) agent.SetDestination(hit.position);
2026-02-04 14:06:25 +00:00
2026-02-02 08:30:23 +00:00
nextPatrolTime = Time.time + patrolInterval;
}
2026-02-05 15:42:48 +00:00
public override void OnAttackStart()
2026-02-04 14:06:25 +00:00
{
isAttacking = true;
isResting = false;
if (myWeapon != null) myWeapon.EnableHitBox();
}
2026-02-05 15:42:48 +00:00
public override void OnAttackEnd()
2026-02-04 14:06:25 +00:00
{
if (myWeapon != null) myWeapon.DisableHitBox();
isAttacking = false;
if (!isDead && !isHit) StartCoroutine(RestAfterAttack());
}
2026-02-05 15:42:48 +00:00
protected override IEnumerator RestAfterAttack()
2026-02-04 14:06:25 +00:00
{
isResting = true;
yield return new WaitForSeconds(attackRestDuration);
isResting = false;
}
protected override void OnStartHit()
{
if (myWeapon != null) myWeapon.DisableHitBox();
}
2026-02-06 09:27:08 +00:00
// 🎲 [핵심 수정] 리스트에서 랜덤 뽑기 + 확률 체크
2026-02-04 14:06:25 +00:00
protected override void OnDie()
{
if (myWeapon != null) myWeapon.DisableHitBox();
2026-02-06 09:27:08 +00:00
// 1. 리스트에 아이템이 하나라도 있는지 확인
if (dropItemPrefabs != null && dropItemPrefabs.Count > 0)
{
// 2. 확률 체크 (0 ~ 100)
float randomValue = Random.Range(0f, 100f);
if (randomValue <= dropChance) // 당첨!
{
// 3. 리스트에서 랜덤하게 하나 뽑기 (0번 ~ 마지막 번호 중 하나)
int randomIndex = Random.Range(0, dropItemPrefabs.Count);
GameObject selectedItem = dropItemPrefabs[randomIndex];
if (selectedItem != null)
{
Instantiate(selectedItem, transform.position + Vector3.up * 0.5f, Quaternion.identity);
// Debug.Log($"🎉 아이템 드랍! ({selectedItem.name})");
}
}
}
}
2026-02-04 14:06:25 +00:00
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player")) isPlayerInZone = true;
}
private void OnTriggerExit(Collider other)
{
if (other.CompareTag("Player")) isPlayerInZone = false;
}
2026-02-02 08:30:23 +00:00
}