NavMeshAgent 컴포넌트를 사용하는 이동 에이전트의 Area Mask에서 이동 가능/불가능 구역 선택
NavMeshAgent 컴포넌트의 Auto Traverse Off Mesh Link가 체크 해제되어 있으면 Off Mesh Link를 만나면 오브젝트가 멈추게 된다
OffMeshLinkClimb
using System.Collections; using System.Collections.Generic; using UnityEngine; 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) { // IsOnClimb() 함수의 변환 값이 true일 때 까지 반복 호출 yield return new WaitUntil(() => IsOnClimb()); // 올라가거나 내려오는 행동 yield return StartCoroutine(ClimbOrDescend()); } } public bool IsOnClimb() { // 현재 오브젝트의 위치가 OffMeshLink에 있는지 (true / false) if (navMeshAgent.isOnOffMeshLink) { // 현재 위치에 있는 OffMeshLink의 데이터 OffMeshLinkDate linkDate = navMeshAgent.currentOffMeshLinkData; // 설명 : navMeshAgent.currentOffMeshLinkData.offMeshLink가 // true이면 수동으로 생성한 OffMeshLink // false이면 자동으로 생성한 OffMeshLink // 현재 위치에 있는 OffMeshLink가 수동으로 생성한 OffMeshLink이고, 장소 정보가 "Climb"이면 if (linkDate.offMeshLink != null && linkDate.offMeshLink.area == offMeshArea) { return true; } } return false; } private IEnumerator ClimbOrDescend() { // 네비게이션을 이용한 이동을 잠시 중지한다 navMeshAgent.isStopped = true; // 현재 위치에 있는 offMeshLink의 시작 / 종료 위치 OffMeshLinkData linkDate = navMeshAgent.currentOffMeshLinkData; Vector3 start = linkData.startPos; Vector3 end = linckData.endPos; // 오르내리는 시간 설정 float climbTime = Mathf.Abs(end.y - start.y) / climbSpeed; float currentTime = 0.0f; float percent = 0.0f; while (percent < 1) { // 단순히 deltaTime만 더하면 무조건 1초 후에 percent가 1이 되기 때문에 climbTime 변수를 연산해서 시간을 조정한다 currentTime += Time.deltaTime; percent = currentTime / climbTime; // 시간 경과(최대1)에 따라 오브젝트의 위치를 바꿔준다 transform.position = Vector3.Lerp(start, end, percent); yield return null; } // OffMeshLink를 이용한 이동 완료 navMeshAgent.CompleteOffMeshLink(); // OffMeshLink 이동이 완료되었으니 네비게이션을 이용한 이동을 다시 시작한다 navMeshAgent.isStopped = false; } }
올라갈때와 내려올때를 구분해서 작성하고, 애니메이션까지 더해지면 좀 더 그럴싸한 사다리 오르기가 완성될 것이다
using System.Collections; using System.Collections.Generic; using UnityEngine; public class OffMeshLinkJump : MonoBehaviour { [SerializeField] private float jumpSpeed = 10.0f; // 점프 속도 [SerializeField] private float gravity = -9.81f; // 중력 계수 private UnityEngine.AI.NavMeshAgent navMeshAgent; private void Awake() { navMeshAgent = GetComponent<UnityEngine.AI.NavMeshAgent>(); } IEnumerator Start() { while(true) { // IsOnJump() 함수의 반환 값이 true일 때 까지 반복 호출 yield return new WaitUntil(() => IsOnJump()) ; // 점프 행동 yield return StartCoroutine(JumpTo()); } } public bool IsOnJump() { if(navMeshAgent.isOnOffMeshLink) { // 현재 위치에 있는 OffMeshLink의 데이터 UnityEngine.AI.OffMeshLinkData linkData = navMeshAgent.currentOffMeshLinkData; // 설명 OffMeshLinkType은 Manual=0, DropDown=1, JumpAcross=2로 // 자동으로 생성한 OffMeshLink의 속성 구분을 위해 사용(1, 2) // 현재 위치에 있는 OffMeshLink의 OffMeshLinkType이 JumpAcross이면 if(linkData.linkType == UnityEngine.AI.OffMeshLinkType.LinkTypeJumpAcross || linkData.linkType == UnityEngine.AI.OffMeshLinkType.LinkTypeDropDown) { return true; } } return false; } IEnumerator JumpTo() { // 네비게이션을 이용한 이동을 잠시 중지한다 navMeshAgent.isStopped = true; // 현재 위치에 있는 OffmeshLink의 시작/종료 위치 UnityEngine.AI.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; // y 방향의 초기 속도 float v0 = (end - start).y - gravity; while (percent < 1) { // 단순히 deltaTime만 더하면 무조건 1초 후에 percent가 1이 되기 때문에 jumpTime 변수를 연산해서 시간을 조절한다 currentTime += Time.deltaTime; percent = currentTime/jumpTime; // 시간 경과(최대1)에 따라 오브젝트의 우치(x, z)를 바꿔준다 Vector3 position = Vector3.Lerp(start, end, percent); // 시간 경과에 따라 오브젝트의 위치(Y)를 바꿔준다 // 포물선 운동 : 시작위치 + 초기속도 * 시간 + 중력 * 시간제곱 position.y = start.y + (v0 * percent) + (gravity * percent * percent); //위에서 계산한 x, y, z 위치 값을 실제 오브젝트에 대입 transform.position = position; yield return null; } // OffMeshLink를 이용한 이동 완료 navMeshAgent.CompleteOffMeshLink(); // OffMeshLink 이동이 완료되었으니 네비게이션을 이용한 이동을 다시 시작한다 navMeshAgent.isStopped = false; } }
Nav Mesh Obstacle은 장애물의 모양, 중심 점과 크기, 공관데이터를 갱신하는 조건들을 설정할 수 있다
SimplePatrol
using System.Collections; using System.Collections.Generic; 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; } } }
Move Threshold의 값을 낮게 설정할수록 정밀한 위치 계산이 되지만 게임이 무거워질 수 있기 때문에 적당한 거리를 설정해야 한다
Nav Mesh Obstacle -> Carve (선택) -> Carve Only Stationary (체크 해제)
오브젝트가 충돌할 수 있는 범위를 보여준다
Cameracontroller
using System.Collections; using System.Collections.Generic; 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() { // 최초 설정된 target과 카메라의 위치를 바탕으로 distance 값 초기화 distance = Vector3.Distance(transform.position, target.position); // 최초 카메라의 회전 값을 x, y 변수에 저장 Vector3 angles = transform.eulerAngles; x = angles.y; y = angles.x; } private void Update() { if (target == null) return; // target이 존재하지 않으면 실행 하지 않는다 // 오른쪽 마우스를 누르고 있을 때 if (Input.GetMouseButton(1)) { // 마우스를 x, y축 움직임 방향 정보 x += Input.GetAxis("Mouse X") * xMoveSpeed * Time.deltaTime; y -= Input.GetAxis("Mouse Y") * yMoveSpeed * Time.deltaTime; // 오브젝트의 위/아래(x축) 한계 범위 설정 y = ClampAngle(y, yMinLimit, yMaxLimit); // 카메라의 회전(Rotation) 정보 갱신 transform.rotation = Quaternion.Euler(y, x, 0); } // 마우스 휠 스크롤을 이용해 target과 카메라의 거리 값(distance) 조절 distance =+ Input.GetAxis("Mouse ScrollWheel") * wheelSpeed * Time.deltaTime; // 거리는 최소, 최대 거리를 설ㅈ어해서 그 값을 벗어나지 않도록 한다 distance = Mathf.Clamp(distance, minDistance, maxDistance); } private void LateUpdate() { if (target == null) return; // target이 존재하지 않으면 실행 하지 않는다 // 카메라의 위치(Position) 정보 갱싱 // target의 위치를 기준으로 distance만큼 떨어져서 쫒아간다 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); } }