184 lines
6.3 KiB
C#
184 lines
6.3 KiB
C#
|
|
using UnityEngine;
|
|||
|
|
using UnityEngine.AI;
|
|||
|
|
using System.Collections;
|
|||
|
|
|
|||
|
|
public class SmartThrowMonster : MonsterClass
|
|||
|
|
{
|
|||
|
|
public enum ThrowType { Unlimited, Reload } // 무제한(활) vs 재장전(돌)
|
|||
|
|
|
|||
|
|
[Header("=== 투척/원거리 설정 ===")]
|
|||
|
|
[SerializeField] private ThrowType attackType = ThrowType.Unlimited;
|
|||
|
|
[SerializeField] private GameObject projectilePrefab; // 날아갈 물체
|
|||
|
|
[SerializeField] private Transform firePoint; // 발사 위치
|
|||
|
|
[SerializeField] private GameObject handModel; // 손에 들고 있는 모델 (던지면 꺼짐)
|
|||
|
|
|
|||
|
|
[Header("수치 설정")]
|
|||
|
|
[SerializeField] private float reloadTime = 2.0f; // 돌 다시 생기는 시간
|
|||
|
|
[SerializeField] private float throwForce = 15f; // 던지는 힘 (곡사)
|
|||
|
|
[SerializeField] private float throwUpward = 5f; // 위로 띄우는 힘
|
|||
|
|
[SerializeField] private float attackRange = 10f;
|
|||
|
|
[SerializeField] private float attackDelay = 2f;
|
|||
|
|
[SerializeField] private float detectRange = 15f; // 플레이어 감지 거리
|
|||
|
|
|
|||
|
|
[Header("애니메이션")]
|
|||
|
|
[SerializeField] private string throwAnim = "Monster_Throw";
|
|||
|
|
[SerializeField] private string walkAnim = "Monster_Walk";
|
|||
|
|
|
|||
|
|
// ⭐ [추가] 순찰 관련 변수
|
|||
|
|
[Header("AI 순찰 설정")]
|
|||
|
|
[SerializeField] private float patrolRadius = 5f;
|
|||
|
|
[SerializeField] private float patrolInterval = 3f;
|
|||
|
|
|
|||
|
|
private float lastAttackTime;
|
|||
|
|
private bool isReloading = false; // 돌 줍는 중인가?
|
|||
|
|
private float nextPatrolTime; // 다음 순찰 시간
|
|||
|
|
private bool isPlayerInZone; // 플레이어가 감지 범위 내에 있는가?
|
|||
|
|
|
|||
|
|
protected override void Init()
|
|||
|
|
{
|
|||
|
|
if (agent != null) agent.stoppingDistance = attackRange * 0.8f;
|
|||
|
|
if (animator != null) animator.applyRootMotion = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected override void ExecuteAILogic()
|
|||
|
|
{
|
|||
|
|
if (isHit || isAttacking || isReloading || isResting) return;
|
|||
|
|
|
|||
|
|
float dist = Vector3.Distance(transform.position, playerTransform.position);
|
|||
|
|
|
|||
|
|
// 1. 플레이어를 감지했거나(Trigger), 사거리 안에 있거나, 감지 거리 안에 있을 때 -> 전투 모드
|
|||
|
|
if (isPlayerInZone || dist <= attackRange || dist <= detectRange)
|
|||
|
|
{
|
|||
|
|
if (dist <= attackRange)
|
|||
|
|
{
|
|||
|
|
TryAttack();
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// 사거리 밖이면 추적
|
|||
|
|
if (agent.isOnNavMesh)
|
|||
|
|
{
|
|||
|
|
agent.isStopped = false;
|
|||
|
|
agent.SetDestination(playerTransform.position);
|
|||
|
|
UpdateMovementAnimation();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 2. 플레이어 없음 -> 순찰 모드
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
Patrol();
|
|||
|
|
UpdateMovementAnimation();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void TryAttack()
|
|||
|
|
{
|
|||
|
|
if (Time.time < lastAttackTime + attackDelay) return;
|
|||
|
|
if (isReloading) return; // 돌 없으면 못 던짐
|
|||
|
|
|
|||
|
|
lastAttackTime = Time.time;
|
|||
|
|
isAttacking = true;
|
|||
|
|
|
|||
|
|
// 조준 (멈추고 바라보기)
|
|||
|
|
if (agent.isOnNavMesh) { agent.isStopped = true; agent.velocity = Vector3.zero; }
|
|||
|
|
transform.LookAt(new Vector3(playerTransform.position.x, transform.position.y, playerTransform.position.z));
|
|||
|
|
|
|||
|
|
animator.Play(throwAnim);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ⭐ [추가] 순찰 로직
|
|||
|
|
void Patrol()
|
|||
|
|
{
|
|||
|
|
if (Time.time < nextPatrolTime) return;
|
|||
|
|
|
|||
|
|
Vector3 randomPoint = transform.position + new Vector3(
|
|||
|
|
Random.Range(-patrolRadius, patrolRadius),
|
|||
|
|
0,
|
|||
|
|
Random.Range(-patrolRadius, patrolRadius)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (NavMesh.SamplePosition(randomPoint, out NavMeshHit hit, 3f, NavMesh.AllAreas))
|
|||
|
|
{
|
|||
|
|
if (agent.isOnNavMesh)
|
|||
|
|
{
|
|||
|
|
agent.SetDestination(hit.position);
|
|||
|
|
agent.isStopped = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
nextPatrolTime = Time.time + patrolInterval;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ⭐ [추가] 움직임 애니메이션 관리
|
|||
|
|
void UpdateMovementAnimation()
|
|||
|
|
{
|
|||
|
|
if (isAttacking || isHit || isResting) return;
|
|||
|
|
|
|||
|
|
if (agent.velocity.magnitude > 0.1f)
|
|||
|
|
animator.Play(walkAnim);
|
|||
|
|
else
|
|||
|
|
animator.Play(Monster_Idle);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ⭐ 애니메이션 이벤트: OnAttackStart
|
|||
|
|
public override void OnAttackStart()
|
|||
|
|
{
|
|||
|
|
if (projectilePrefab && firePoint)
|
|||
|
|
{
|
|||
|
|
// 1. 발사체 생성
|
|||
|
|
GameObject obj = Instantiate(projectilePrefab, firePoint.position, transform.rotation);
|
|||
|
|
|
|||
|
|
// 데미지 전달
|
|||
|
|
if (obj.TryGetComponent<Projectile>(out var proj))
|
|||
|
|
{
|
|||
|
|
// 돌 던지기(Reload) 타입이면 초기 속도 0 (물리력으로 날림)
|
|||
|
|
// 활 쏘기(Unlimited) 타입이면 Projectile 자체 속도 사용
|
|||
|
|
float initSpeed = (attackType == ThrowType.Reload) ? 0f : 20f;
|
|||
|
|
proj.Initialize(transform.forward, initSpeed, attackDamage);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 물리력으로 던지기 (돌 던지기 타입일 때만)
|
|||
|
|
if (attackType == ThrowType.Reload)
|
|||
|
|
{
|
|||
|
|
Rigidbody rb = obj.GetComponent<Rigidbody>();
|
|||
|
|
if (rb)
|
|||
|
|
{
|
|||
|
|
rb.useGravity = true;
|
|||
|
|
Vector3 force = transform.forward * throwForce + Vector3.up * throwUpward;
|
|||
|
|
rb.AddForce(force, ForceMode.Impulse);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 손에 있는 모델 숨기기
|
|||
|
|
if (attackType == ThrowType.Reload && handModel != null)
|
|||
|
|
{
|
|||
|
|
handModel.SetActive(false);
|
|||
|
|
StartCoroutine(ReloadRoutine());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
IEnumerator ReloadRoutine()
|
|||
|
|
{
|
|||
|
|
isReloading = true;
|
|||
|
|
// Debug.Log("🔄 [Mob] 돌 줍는 중..."); // 로그 너무 많이 뜨면 주석 처리
|
|||
|
|
|
|||
|
|
yield return new WaitForSeconds(reloadTime);
|
|||
|
|
|
|||
|
|
if (handModel != null) handModel.SetActive(true);
|
|||
|
|
isReloading = false;
|
|||
|
|
// Debug.Log("✅ [Mob] 장전 완료!");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ⭐ [추가] 감지 영역 설정
|
|||
|
|
private void OnTriggerEnter(Collider other)
|
|||
|
|
{
|
|||
|
|
if (other.CompareTag("Player")) isPlayerInZone = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnTriggerExit(Collider other)
|
|||
|
|
{
|
|||
|
|
if (other.CompareTag("Player")) isPlayerInZone = false;
|
|||
|
|
}
|
|||
|
|
}
|