적이 플레이어를 향해 움직이는 기능 하나에도 수학이 몇 번 들어갑니다. 어느 방향으로 갈지 구하고, 얼마나 움직일지 정하고, 언제 멈출지 비교해야 하죠. 이 세 가지를 따로 보면 벡터가 훨씬 덜 막막합니다.
아래는 Unity 6의 Transform을 사용하는 학습용 예제입니다. 물리 충돌이나 길찾기는 포함하지 않습니다. 벽을 피해야 하는 적이라면 이후에 이동 부분을 NavMesh나 물리 기반 구현으로 바꿔야 합니다.
위치를 빼면 방향과 거리가 함께 나온다
자신이 (1, 0, 2), 목표가 (4, 0, 6)에 있다면 목표에서 자신을 뺀 값은 (3, 0, 4)입니다. 목표까지 오른쪽으로 3, 앞으로 4만큼 가야 한다는 뜻입니다. 길이는 √(3² + 4²) = 5입니다.
이 값을 그대로 초당 이동량으로 사용하면 멀리 있는 목표일수록 빨리 쫓게 됩니다. 속도를 일정하게 하려면 길이를 1로 만든 방향에 원하는 속도를 곱합니다. 다만 마지막 프레임에 목표를 지나치는 문제까지 처리하려면 이동 거리를 제한해야 합니다.
도착하면 멈추는 추적 코드
FollowTarget.cs로 저장해 빈 오브젝트에 붙이고, 다른 오브젝트를 Target에 지정합니다.
using UnityEngine;
public sealed class FollowTarget : MonoBehaviour
{
[SerializeField] private Transform target;
[SerializeField, Min(0f)] private float speed = 3f;
[SerializeField, Min(0f)] private float stopDistance = 0.5f;
private void Update()
{
if (target == null)
return;
Vector3 offset = target.position - transform.position;
float radius = Mathf.Max(0f, stopDistance);
if (offset.sqrMagnitude <= radius * radius)
return;
float distance = offset.magnitude;
float step = Mathf.Min(Mathf.Max(0f, speed) * Time.deltaTime, distance - radius);
transform.position += offset / distance * step;
}
}
거리 비교에는 제곱된 길이를 씁니다. 양쪽이 음수가 아닌 거리라면 제곱해도 대소 관계가 같기 때문입니다. 실제 이동 분기에서는 방향과 남은 거리를 구하므로 제곱근이 필요합니다. 이 코드를 두고 “제곱근을 완전히 없앴다”고 설명하면 틀립니다.
step을 남은 거리로 제한한 이유는 목표 근처의 왕복 현상을 막기 위해서입니다. 매 프레임 무조건 0.3만큼 이동하는데 목표까지 0.1만 남았다면, 목표를 지나쳤다가 다음 프레임에 되돌아올 수 있습니다.
숫자로 확인할 세 장면
| 시작 → 목표 | 설정 | 기대하는 동작 |
|---|---|---|
| (0,0,0) → (3,0,4) | 속도 2, 정지 거리 1 | 약 2초 동안 4만큼 이동 |
| 같은 위치 → 같은 위치 | 정지 거리 0 | 이동하지 않으며 0으로 나누지 않음 |
| 목표 바로 앞 0.1 거리 | 정지 거리 0 | 큰 프레임 간격에서도 목표를 넘지 않음 |
첫 행은 목표가 움직이지 않고 시간 배율이 1인 경우의 계산값입니다. 측정한 실행 시간이 아닙니다. 입력 이동에 응용할 때는 아날로그 입력의 크기까지 없애지 않도록 주의하세요. 대각선 입력만 제한하려면 무조건 정규화하기보다 Vector3.ClampMagnitude(input, 1f)가 적합할 수 있습니다.
다음에는 총알 방향을 회전시키는 예제로 위치와 방향의 차이를 이어서 볼 수 있습니다.
