01 Off Mesh Link 개요
✅ Off Mesh Link란?
- 아래 그림과 같이 사다리와 같이 수직으로 올라가거나 내려오는 길
- 절벽 사이를 뛰어서 넘어가거나 절벽 아래로 떨어지는 길과 같이
- 메시가 끊어져 있는 곳을 이동할 수 있게 설정하는 것
✅ 자동 (Auto) Off Mesh Link 설정 방법
- 장점
- 게임월드에 배치된 많은 오브젝트의 Off Mesh Link를 한꺼번에 설정 가능
- 단점
- 낙하 높이와 점프 거리를 하나만 설정할 수 있기 때문에 다양한 지형을 세세하게 설정하는 것이 불가능
- 위로 올라가는 Off Mesh Link 설정 불가능
✅ 수동 (Manaual) Off Mesh Link 설정 방법
- 장점
- 지형에 따라 세세한 설정 가능
- 사다리/암벽과 같이 위로 올라가는 Off Mesh Link 설정 가능
- 단점
- Off Mesh Link로 연결이 필요한 모든 부분을 직접 설정해야 함
02 자동 (Auto) Off Mesh Link 생성
📌 자동 Off Mesh Link 설정 방법
1. 설정할 게임 오브젝트 선택 (단일 or 복수)
2. Navigation View - Object 탭 - Generate OffMeshLinks 체크
3. 낙하 높이, 점프 거리 설정
4. "Bake" 버튼을 눌러 Navigation Mesh 데이터 생성
다음과 같이 Navigation Mesh 데이터를 생성하고 Scene View를 확인해보면 스프링 모양이 생성된 것을 확인 할 수 있다.
이후 게임을 실행하면, 위와 같이 플레이어 오브젝트가 설정한 부분에 대해 자유롭게 점프도 하고 떨어지는 것도 가능함을 확인 할 수 있다.
03 수동 (Manual) Off Mesh Link 생성
📌 수동 (Manual) Off Mesh Link 설정
1. Off Mesh Link로 연결되는 두 지점으로 사용할 게임 오브젝트 생성
→ 게임 오브젝트의 위치가 Navigation Mesh의 이동 경로 내에 포함되어야 정상적인 이동 가능
2. 두 지점 오브젝트를 가지고 있는 게임 오브젝트에 Off Mesh Link 컴포넌트 추가
3. Off Mesh Link 컴포넌트의 Start와 End 변수에 1에서 생성한 게임 오브젝트 등록
위 이미지와 같이 Start와 End라는 이름으로 구 모양의 오브젝트를 2개 생성해줄 수 있다.
이후 사다리를 의미하는 LadderObject에 Off Mesh Link를 컴포넌트로 추가할 수 있다. 그리고 해당 오브젝트의 Inspector View에 추가한 컴포넌트에 대해 Start와 End 부분에 각각 생성해두었던 오브젝트를 해당하는 부분에 넣어줄 수 있다.
설정 후, 게임을 실행하면 사다리를 타고 이동하는 것을 확인할 수 있다.
04 구역 (Areas) 설정
그림과 같이 A위치에서 B로 이동한다면 이동 경로는 위 이미지와 같겠지만, 구역을 설정할 경우 이동 경로는 달라질 수 있다.
📌 Navigation Areas에 등록된 구역을 게임오브젝트에 설정하는 방법
- 수동 Off Mesh Link 오브젝트 : Off Mesh Link 컴포넌트의 Navigation Area 변수 설정
- 일반 게임 오브젝트 : 게임 오브젝트 선택 후, Navigation View - Object 탭 - Navigation Area 변수 설정
05 Off Mesh Link 액션 설정
📌 OffMeshLink.cs 스크립트 코드
using System.Collections; using UnityEngine; using UnityEngine.AI; public class OffMeshLinkClimb : MonoBehaviour { [SerializeField] private int offMeshArea = 3; // 오프메시의 구역 (Climb) [SerializeField] private float climbSpeed = 1.5f; // 오르내리는 이동 속도 private NavMeshAgent navMeshAgent; private void Awake() { navMeshAgent = GetComponent<NavMeshAgent>(); } IEnumerator Start() { while (true) { yield return new WaitUntil(() => IsOnClimb()); yield return StartCoroutine(ClimbOrDescend()); } } public bool IsOnClimb() { if (navMeshAgent.isOnOffMeshLink) { OffMeshLinkData linkData = navMeshAgent.currentOffMeshLinkData; if (linkData.offMeshLink != null && linkData.offMeshLink.area == offMeshArea) { return true; } } return false; } private IEnumerator ClimbOrDescend() { navMeshAgent.isStopped = true; OffMeshLinkData linkData = navMeshAgent.currentOffMeshLinkData; Vector3 start = linkData.startPos; Vector3 end = linkData.endPos; float climbTime = Mathf.Abs(end.y - start.y) / climbSpeed; float currentTime = 0.0f; float percent = 0.0f; while (percent < 1) { currentTime += Time.deltaTime; percent = currentTime / climbTime; transform.position = Vector3.Lerp(start, end, percent); yield return null; } navMeshAgent.CompleteOffMeshLink(); navMeshAgent.isStopped = false; } }
작성한 코드를 바탕으로 게임을 실행시켜보면 우리가 설정한 속도에 맞추어 조금 천천히 올라가는 것을 확인할 수 있다.이후 유사하게 점프에서 뛰어내릴 때 더 자연스러워보일 수 있도록 아래와 같이 코드를 작성할 수 있다.
📌 OffMeshLinkJump.cs 스크립트 코드
using System.Collections; using UnityEngine; using UnityEngine.AI; public class OffMeshLinkJump : MonoBehaviour { [SerializeField] private float jumpSpeed = 10.0f; // 점프 속도 [SerializeField] private float gravity = -9.81f; // 중력 계수 private NavMeshAgent navMeshAgent; private void Awake() { navMeshAgent = GetComponent<NavMeshAgent>(); } IEnumerator Start() { while (true) { yield return new WaitUntil(() => IsOnJump()); yield return StartCoroutine(JumpTo()); } } public bool IsOnJump() { if (navMeshAgent.isOnOffMeshLink) { OffMeshLinkData linkData = navMeshAgent.currentOffMeshLinkData; if (linkData.linkType == OffMeshLinkType.LinkTypeJumpAcross || linkData.linkType == OffMeshLinkType.LinkTypeDropDown) { return true; } } return false; } IEnumerator JumpTo() { navMeshAgent.isStopped = true; OffMeshLinkData linkData = navMeshAgent.currentOffMeshLinkData; Vector3 start = transform.position; Vector3 end = linkData.endPos; float jumpTime = Mathf.Max(0.3f, Vector3.Distance(start, end) / jumpSpeed); float currentTime = 0.0f; float percent = 0.0f; float v0 = (end - start).y - gravity; while (percent < 1) { currentTime += Time.deltaTime; percent = currentTime / jumpTime; Vector3 position = Vector3.Lerp(start, end, percent); position.y = start.y + (v0 * percent) + (gravity * percent * percent); transform.position = position; yield return null; } navMeshAgent.CompleteOffMeshLink(); navMeshAgent.isStopped = false; } }
이후 게임을 실행하면 아래와 같이 플레이어 오브젝트가 동작하는 모습을 볼 수 있다.
06 이동 가능한 장애물 설정
게임 내에서 이동이 가능한 장애물의 경우 Nav Mesh Obstacle이라는 컴포넌트를 활용하여 실시간으로 네비게이션 메시 데이터를 관리할 수 있다.
해당 설정을 위해 이동하는 장애물 큐브를 하나 만들어 적용시켜보자. 오브젝트를 생성한 이후에 지정된 경로를 자동으로 이동하는 스크립트 코드를 함께 작성해줄 수 있다.
📌 SimplePatrol.cs 스크립트 코드
using UnityEngine; public class SimplePatrol : MonoBehaviour { [SerializeField] private Transform[] paths; private int currentPath = 0; private float moveSpeed = 3.0f; private void Update() { Vector3 direction = (paths[currentPath].position - transform.position).normalized; transform.position += direction * moveSpeed * Time.deltaTime; if ((paths[currentPath].position - transform.position).sqrMagnitude < 0.1f) { if (currentPath < paths.Length - 1) currentPath++; else currentPath = 0; } } }
작성한 코드를 위와 같이 넣어주고 그에 대항하는 변수로 path01과 path02에 각각을 넣어줄 수 있다.
07 3인칭 카메라 제어
카메라의 회전을 제외하는 코드를 아래와 같이 작성해보였다.
📌 CameraController.cs 스크립트 코드
using UnityEngine; public class CameraController : MonoBehaviour { [SerializeField] private Transform target; // 카메라가 추적하는 대상 [SerializeField] private float minDistance = 3; // 카메라와 target의 최소 거리 [SerializeField] private float maxDistance = 30; // 카메라와 target의 최대 거리 [SerializeField] private float wheelSpeed = 500; // 마우스 휠 스크롤 속도 [SerializeField] private float xMoveSpeed = 500; // 카메라의 y축 회전 속도 [SerializeField] private float yMoveSpeed = 250; // 카메라의 x축 회전 속도 private float yMinLimit = 5; // 카메라 x축 회전 제한 최소 값 private float yMaxLimit = 80; // 카메라 x축 회전 제한 최대 값 private float x, y; // 마우스 이동 방향 값 private float distance; // 카메라와 target의 거리 private void Awake() { distance = Vector3.Distance(transform.position, target.position); Vector3 angles = transform.eulerAngles; x = angles.y; y = angles.x; } private void Update() { if (target == null) return; if (Input.GetMouseButton(1)) { x += Input.GetAxis("Mouse X") * xMoveSpeed * Time.deltaTime; y -= Input.GetAxis("Mouse Y") * yMoveSpeed * Time.deltaTime; y = ClampAngle(y, yMinLimit, yMaxLimit); transform.rotation = Quaternion.Euler(y, x, 0); } distance -= Input.GetAxis("Mouse ScrollWheel") * wheelSpeed * Time.deltaTime; distance = Mathf.Clamp(distance, minDistance, maxDistance); } private void LateUpdate() { if (target == null) return; transform.position = transform.rotation * new Vector3(0, 0, -distance) + target.position; } private float ClampAngle(float angle, float min, float max) { if (angle < -360) angle += 360; if (angle > 360) angle -= 360; return Mathf.Clamp(angle, min, max); } }
3인칭 제어를 위해 위와 같이 코드를 작성하고 실행시키면 마우스에 움직임에 따라 카메라가 이동하는 실행 결과모습을 확인할 수 있다.
출처 : 따라하면서 배우는 고박사의 유니티 기초(https://inf.run/sgcy)
'Study > Unity' 카테고리의 다른 글
[Unity 3D Basic] Animation Layer, Blend Tree (0) | 2024.05.22 |
---|---|
[Unity 3D Basic] 3D Model / Animations (0) | 2024.05.15 |
[Unity 3D Basic] Navigation Mesh 기초 (0) | 2024.05.15 |
[Unity 3D Basic] CharacterController 기반의 오브젝트 이동 (1) | 2024.05.08 |
[Unity 2D Basic] 2D Tilemap - Extras (0) | 2024.05.08 |