2026-01-29 06:58:38 +00:00
|
|
|
|
using System.Collections;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.AI;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
|
|
|
|
|
|
public class NormalMonster : MonsterClass
|
|
|
|
|
|
{
|
|
|
|
|
|
[Header("전투")]
|
|
|
|
|
|
[SerializeField] private float damage = 10f;
|
|
|
|
|
|
[SerializeField] private float attackRange = 2f;
|
|
|
|
|
|
[SerializeField] private float attackDelay = 1.5f;
|
|
|
|
|
|
[SerializeField] private float postAttackDelay = 1.5f;
|
|
|
|
|
|
|
|
|
|
|
|
private float lastAttackTime;
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ MonsterClass에 OnHealthChanged가 있으므로 여기서는 삭제합니다.
|
|
|
|
|
|
|
|
|
|
|
|
[Header("공격 애니메이션")]
|
|
|
|
|
|
[SerializeField] private string[] attackAnimations = { "Monster_Attack_1" };
|
|
|
|
|
|
[Header("이동 애니메이션")]
|
|
|
|
|
|
[SerializeField] private string Monster_Idle = "Monster_Idle";
|
|
|
|
|
|
[SerializeField] private string Monster_Walk = "Monster_Walk";
|
|
|
|
|
|
|
|
|
|
|
|
[Header("AI")]
|
|
|
|
|
|
[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 Transform player;
|
|
|
|
|
|
private int attackIndex;
|
|
|
|
|
|
private bool isAttacking;
|
|
|
|
|
|
private bool isPlayerInZone;
|
|
|
|
|
|
|
|
|
|
|
|
protected override void Start()
|
|
|
|
|
|
{
|
|
|
|
|
|
base.Start();
|
|
|
|
|
|
player = GameObject.FindWithTag("Player")?.transform;
|
|
|
|
|
|
agent.stoppingDistance = attackRange - 0.4f;
|
|
|
|
|
|
animator.applyRootMotion = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-30 03:24:13 +00:00
|
|
|
|
protected override void Update() // 앞 부분에 protected override 추가해줘야 됨
|
2026-01-29 06:58:38 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (isDead || player == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ CS0103 에러 해결: 아래 정의된 Patrol()을 호출
|
|
|
|
|
|
if (isPlayerInZone) HandlePlayerTarget();
|
|
|
|
|
|
else Patrol();
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ CS0103 에러 해결: 아래 정의된 UpdateMovementAnimation()을 호출
|
|
|
|
|
|
UpdateMovementAnimation();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void HandlePlayerTarget()
|
|
|
|
|
|
{
|
|
|
|
|
|
float distance = Vector3.Distance(transform.position, player.position);
|
|
|
|
|
|
|
|
|
|
|
|
if (distance <= agent.stoppingDistance + 0.05f)
|
|
|
|
|
|
{
|
|
|
|
|
|
agent.isStopped = true;
|
|
|
|
|
|
agent.velocity = Vector3.zero;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (!isAttacking)
|
|
|
|
|
|
{
|
|
|
|
|
|
agent.isStopped = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (distance <= attackRange - stopBuffer)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isAttacking) TryAttack();
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (Time.time >= nextRepathTime && !isAttacking)
|
|
|
|
|
|
{
|
|
|
|
|
|
agent.SetDestination(player.position);
|
|
|
|
|
|
nextRepathTime = Time.time + repathInterval;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void TryAttack()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Time.time < lastAttackTime + attackDelay) return;
|
|
|
|
|
|
lastAttackTime = Time.time;
|
|
|
|
|
|
|
|
|
|
|
|
string attackName = attackAnimations[attackIndex];
|
|
|
|
|
|
attackIndex = (attackIndex + 1) % attackAnimations.Length;
|
|
|
|
|
|
|
|
|
|
|
|
isAttacking = true;
|
|
|
|
|
|
agent.isStopped = true;
|
|
|
|
|
|
agent.velocity = Vector3.zero;
|
|
|
|
|
|
animator.Play(attackName, 0, 0f);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ CS0506 해결: 부모의 virtual 메서드를 정상적으로 재정의
|
|
|
|
|
|
public override void TakeDamage(float amount)
|
|
|
|
|
|
{
|
|
|
|
|
|
base.TakeDamage(amount); // 부모의 OnDamaged(UI 신호 포함)를 실행
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OnAttackEnd()
|
|
|
|
|
|
{
|
|
|
|
|
|
StartCoroutine(PostAttackWait());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private IEnumerator PostAttackWait()
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return new WaitForSeconds(postAttackDelay);
|
|
|
|
|
|
isAttacking = false;
|
|
|
|
|
|
if (agent && agent.isOnNavMesh) agent.isStopped = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OnAttackHit()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (player == null) return;
|
|
|
|
|
|
PlayerHealth playerHealth = player.GetComponent<PlayerHealth>();
|
|
|
|
|
|
if (playerHealth != null) playerHealth.TakeDamage(damage);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ 복구된 이동 애니메이션 함수
|
|
|
|
|
|
void UpdateMovementAnimation()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isAttacking || isHit) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (agent.velocity.magnitude < 0.1f)
|
|
|
|
|
|
animator.Play(Monster_Idle);
|
|
|
|
|
|
else
|
|
|
|
|
|
animator.Play(Monster_Walk);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ⭐ 복구된 순찰 함수
|
|
|
|
|
|
void Patrol()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Time.time < nextPatrolTime) return;
|
|
|
|
|
|
|
|
|
|
|
|
Vector3 randomPoint = transform.position +
|
|
|
|
|
|
new Vector3(UnityEngine.Random.Range(-patrolRadius, patrolRadius), 0,
|
|
|
|
|
|
UnityEngine.Random.Range(-patrolRadius, patrolRadius));
|
|
|
|
|
|
|
|
|
|
|
|
if (NavMesh.SamplePosition(randomPoint, out NavMeshHit hit, 3f, NavMesh.AllAreas))
|
|
|
|
|
|
agent.SetDestination(hit.position);
|
|
|
|
|
|
|
|
|
|
|
|
nextPatrolTime = Time.time + patrolInterval;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnTriggerEnter(Collider other)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (other.CompareTag("Player")) isPlayerInZone = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnTriggerExit(Collider other)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (other.CompareTag("Player")) isPlayerInZone = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override void StartHit()
|
|
|
|
|
|
{
|
|
|
|
|
|
isAttacking = false;
|
|
|
|
|
|
if (agent && agent.isOnNavMesh) agent.isStopped = false;
|
|
|
|
|
|
base.StartHit();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|