[멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(48일차) - Nav 실습 및 FPS 게임 (5)
by 독기품은토끼2025. 7. 24.
✅ 오늘의 학습 목표 1. Navigation 실습 2. FPS 게임 (5)
1. 에이전트 동적이동
NavMesh의 영역이 크면 클수록 비효율적인 측면이 있다.
그래서 에이전트 주변만 NavMesh가 만들어지도록 코드를 구현해 보겠다.
using Unity.AI.Navigation;
using UnityEngine;
using UnityEngine.AI;
public class AgentController : MonoBehaviour
{
public Camera camera;
private NavMeshAgent agent;
public NavMeshSurface surface;
void Start()
{
agent = GetComponent<NavMeshAgent>();
surface.transform.position = agent.transform.position;
surface.BuildNavMesh();
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, Mathf.Infinity))
{
agent.SetDestination(hit.point);
}
}
if (Vector3.Distance(transform.position, surface.transform.position) > 20f)
{
surface.transform.position = agent.transform.position;
surface.BuildNavMesh();
}
}
}
마우스 왼쪽 버튼을 누르면 카메라에서 마우스 위치로 Ray를 쏨
에이전트가 현재 NavMeshSurface 중심에서 20m 이상 멀어지면 NavMeshSurface를 에이전트 위치로 이동
그 위치 기준으로 NavMesh를 다시 생성(BuildNavMesh)
2. FPS 게임에 Navigation 적용
Enemy의 이동은 원래 CharacterController 써서 이동 방향 직접 계산했었는데 이번엔 Unity의 NavMeshAgent를 써서 조금 더 자연스럽게 길 찾기 + 이동 처리까지 구현해 보도록 하겠다.
using UnityEngine.AI;
public class EnemyFSM : MonoBehaviour
{
private NavMeshAgent smith;
void Start()
{
smith = GetComponent<NavMeshAgent>();
}
private void Move()
{
else if (Vector3.Distance(transform.position, player.position) > attackDistance)
{
//Vector3 dir = (player.position - transform.position).normalized; // 플레이어 쪽으로 가는 방향 구하기
//cc.Move(dir * moveSpeed * Time.deltaTime);
//transform.forward = dir; // 이동 방향 정면으로 적용
smith.isStopped = true;
smith.ResetPath(); // 경로 재설정
smith.stoppingDistance = attackDistance;
smith.SetDestination(player.position);
}
}
private void Return()
{
if (Vector3.Distance(transform.position, originPos) > 0.1f)
{
//Vector3 dir = (originPos - transform.position).normalized;
//cc.Move(dir * moveSpeed * Time.deltaTime);
//transform.forward = dir;
smith.SetDestination(originPos);
smith.stoppingDistance = 0f;
}
}
public void HitEnemy(int hitPower)
{
smith.isStopped = true;
smith.ResetPath();
}
}
stoppingDistance : 목표지점에 도착하면 그 거리에서 딱 멈추게 하는 용도
ResetPath() : 상태가 바뀌었을 때 예전 경로 따라가지 않도록 경로 리셋
1. Nav Link
내비게이션 메시를 베이크 했을 때
이동 가능한 영역이 높이 등으로 인해 끊어져있는 경우 내비 메시 링크 기능을 이용해서 임의로 연결할 수 있다.
이렇게 적 오브젝트가 Link를 타고 장애물을 넘어서 플레이어에게 다가오는 것을 확인할 수 있다.
지금은 Enemy가 엄청 부자연스러운 움직임으로 장애물을 넘어서 왔는데
이 부분은 아래 다른 실습을 하면서 고치는 방법을 다룰 것이다.
3. Nav 실습 - 3D
🥕 예행 작업 1. Scene 생성 (NavMesh2)
우선 새로운 씬을 생성한 후에 프로빌더로 위와 같이 오브젝트들을 배치해 준 후 Bake 작업을 해주었다.
using UnityEngine;
using UnityEngine.AI;
public class AgentController : MonoBehaviour
{
private NavMeshAgent agent;
public Transform[] points;
private int index;
void Start()
{
agent = GetComponent<NavMeshAgent>();
agent.SetDestination(points[index].position);
}
void Update()
{
if (Vector3.Distance(transform.position, points[index].position) < 3.5f)
{
index++;
if (index >= points.Length)
index = 0;
agent.SetDestination(points[index].position);
}
}
}
플레이어 오브젝트가 points 배열에 있는 위치들을 순서대로 따라가면서 반복 이동하게 구현한 코드이다.
RPG 게임에 NPC들이 아마 이런 원리로 이동하는 거라 생각된다.
이제 플레이어가 제대로 이동하도록
플레이어 오브젝트에 Agent 컴포넌트를 추가해 주고
좌표 포인트들을 모두 넣어주었다.
그리고 NavMesh Modifier 컴포넌트를 통해서 원하는 지형의 비용값을 조절할 수 있다.
분홍색으로 Nav가 표현된 부분의 비용은 3으로 옆에 있는 다리의 값보다 높기 때문에 해당 다리를 건너지 않는다.
Nav Link를 사용해 보면 알겠지만
Nav Link의 저 노란선.. (명칭을 모르겠음) 을 기준으로 오브젝트가 이동하기 때문에
오브젝트가 부자연스럽게 이동하는 것을 볼 수 있을 것이다.
이 부분을 해결해 주기 위해서 AI Navgation 패키지의 Sample을 다운받아주자
그러면 Agent Link Mover라는 스크립트가 생성되는데 이 스크립트를 오브젝트에 추가해 주면 된다.
이동 방식
설명
특징/활용 예시
Parabola
포물선 궤적을 따라 이동
점프 연출, 장애물 넘기, 자연스러운 이동
Teleport
순간적으로 도착 지점으로 이동
포탈, 엘리베이터, 순간이동 연출
Normal Speed
링크 구간을 일반 이동 속도로 통과
걷거나 뛰는 느낌 그대로 연결됨
Curve
커브 애니메이션을 따라 이동
맞춤형 연출 가능 (예: 점프, 슬라이드 등)
마지막으로 Nav Mesh Obstacle을 활용해 보겠다.
Nav Mesh 위에 배치된 장애물 오브젝트를 정의하는 컴포넌트로 Agent가 해당 영역을 우회하거나 멈추도록 만드는 역할을 한다.
Carve를 체크해 주어야 실시간으로 영역을 자를 수 있다
이렇게 경로를 막아버리면 다른 경로를 빠르게 찾아가고 다시 경로를 생기게 해 주면 돌아오는 것을 확인할 수 있다.
🚨 NavMesh 주요 컴포넌트
컴포넌트
주요 역할
적용 대상
작용 방식
NavMeshAgent
이동 주체
플레이어, NPC 등
NavMesh 위 경로 따라 자동 이동
NavMeshLink
연결 지점
NavMesh 영역 사이
NavMesh 사이 이동 가능하게 연결
NavMeshSurface
NavMesh 생성
지형, 구조물, 바닥 등
Bake 기능 제공
NavMeshModifier
NavMesh 특성 변경
특정 오브젝트
포함/제외 및 지역 속성 변경
NavMeshObstacle
장애물 지정
상자, 벽, 트랩 등
Agent가 피하거나 멈춤
4. Nav 실습 - 2D
🥕 예행 작업 1. Scene 생성 (NavMesh Plus 2D) 2. Script 생성 (MovePlayer, FollowPlayer) 3. Package 다운로드 (Nav 2D용)
타일맵으로 배경(흰색)과 장애물(검은색)을 배치해 주고 플레이어 오브젝트(캡슐)와 적(삼각형)을 배치해 주었다.
using UnityEngine;
public class MovePlayer : MonoBehaviour
{
public float moveSpeed = 10f;
void Update()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
var dir = new Vector3(h, v, 0).normalized;
transform.position += dir * moveSpeed * Time.deltaTime;
}
}
플레이어 이동을 위해 플레이어 오브젝트에 해당 스크립트를 추가해 주었고
using UnityEngine;
using UnityEngine.AI;
public class FollowPlayer : MonoBehaviour
{
private Transform player;
private NavMeshAgent agent;
void Start()
{
player = GameObject.Find("Player").transform;
agent = GetComponent<NavMeshAgent>();
}
void Update()
{
agent.SetDestination(player.position);
}
}
적은 플레이어를 따라가도록 Agent를 구현해 주었다.
오브젝트 컴포넌트는 위와 같이 추가해 주었다.
이렇게 하면 2D에서도 Nav 기능을 사용할 수 있다!
5. FPS 게임 - 플레이 모드 추가
지금 프로젝트는 마우스 오른쪽 버튼을 클릭하면 수류탄이 던져졌다.
보통 FPS 게임은 마우스 오른쪽 버튼을 클릭하면 스나이퍼의 경우 줌 인/아웃이 적용되었다.
이 부분을 구현해 주기 위해 if문과 switch문을 활용해 플레이 모드를 나누는 로직을 구현해 주겠다.