Files
WhaleAdventure_VR/Assets/02_Scripts/Npcs/FollowObject.cs
2026-06-23 17:03:38 +09:00

103 lines
3.8 KiB
C#

using UnityEngine;
using UnityEngine.AI;
// 대상(보통 플레이어)을 NavMesh 위에서 따라다니는 간단한 동행 스크립트.
// 속도/가속/높이(Base Offset) 등은 NavMeshAgent 컴포넌트에서 설정한다.
// 추적은 _followEnabled로 동적으로 켜고/끌 수 있다.
[RequireComponent(typeof(NavMeshAgent))]
public class FollowObject : MonoBehaviour
{
[Header("Enable")]
[SerializeField] private bool _followEnabled = true; // 추적 on/off (런타임에 동적 변경 가능)
[Header("Target")]
[SerializeField] private Transform _target; // 비워두면 Camera.main 사용
[Header("Follow")]
[SerializeField] private float _followDistance = 3.0f; // 이 거리 안이면 멈춤 (= Agent stoppingDistance)
[SerializeField] private float _repathInterval = 0.2f; // 목적지 갱신 주기(초)
[Header("Rotation")]
[SerializeField] private bool _lookAtTarget = true; // 켜면 이동방향이 아니라 항상 Target을 바라봄
[SerializeField] private float _lookSpeed = 8f; // 바라보기 회전 속도 (0이면 즉시)
private NavMeshAgent _agent;
private float _repathTimer;
public bool FollowEnabled
{
get => _followEnabled;
set => SetFollowEnabled(value);
}
private void Awake()
{
_agent = GetComponent<NavMeshAgent>();
_agent.stoppingDistance = _followDistance;
ApplyAgentStopped();
}
private void Update()
{
if (!_followEnabled) return;
var target = ResolveTarget();
if (target == null || !_agent.isOnNavMesh) return;
// 목적지 갱신 (주기적)
_repathTimer -= Time.deltaTime;
if (_repathTimer <= 0f)
{
_repathTimer = _repathInterval;
_agent.SetDestination(target.position); // NavMesh가 지면/경사/장애물을 알아서 처리
}
// 회전: 켜면 항상 Target을 바라봄(에이전트 자동회전 끔), 끄면 이동방향을 바라봄(기본)
_agent.updateRotation = !_lookAtTarget;
if (_lookAtTarget)
FaceTarget(target);
}
// ── 동적 on/off ──────────────────────────────────────────────
public void SetFollowEnabled(bool on)
{
_followEnabled = on;
if (on) _repathTimer = 0f; // 켜면 즉시 경로 재계산
ApplyAgentStopped();
}
public void EnableFollow() => SetFollowEnabled(true); // UnityEvent 연결용(무인자)
public void DisableFollow() => SetFollowEnabled(false);
// 끄면 즉시 멈추고 남은 경로 제거(잔여 이동 방지), 켜면 이동 재개
private void ApplyAgentStopped()
{
if (_agent == null || !_agent.isActiveAndEnabled || !_agent.isOnNavMesh) return;
_agent.isStopped = !_followEnabled;
if (!_followEnabled)
_agent.ResetPath();
}
// ─────────────────────────────────────────────────────────────
private void FaceTarget(Transform target)
{
Vector3 dir = target.position - transform.position;
dir.y = 0f; // 수평만 — 위아래로 안 기울게
if (dir.sqrMagnitude < 0.0001f) return;
Quaternion look = Quaternion.LookRotation(dir);
transform.rotation = _lookSpeed > 0f
? Quaternion.Slerp(transform.rotation, look, _lookSpeed * Time.deltaTime)
: look;
}
private Transform ResolveTarget()
{
if (_target != null) return _target;
return Camera.main != null ? Camera.main.transform : null;
}
public void SetTarget(Transform target) => _target = target;
}