유니티

player 순찰 & 추적 / waypoint / NavMeshAgent

nock_ji 2022. 6. 6. 16:43

 

Floor의 Navigation을 Bake 한다 

 

 

순찰 & 추적할 Enemy를 생성하고 NavMeshAgent와 Enmey.cs를 컴포넌트추가한다 

 

 

 

Enemy가 순찰할 포인트를 만든다

빈 오브젝트 PathManager에 자식 WayPoint를 추가한다.

부모에 PathManager.cs도 추가한다

 

 

 

PathManager.cs

스크립트에서 waypoint 순찰 지점 목록을 배열로 가져온다. 

//waypoint목록을 가져오고싶다

public class PathManager : MonoBehaviour
{
	public static PathManager instance;
	public Transform[] wayPoints; //배열 생성
    
    void Awake()
    {
    	instance = this;
    }
    
    void Start()
    {
    	MeshFilter[] temp = GetComponentInChildren<MeshFilter>();
        wayPoints = new Transform[temp.Length];
        for (int i = 0; i < temp.Length; i++)
        {
        	wayPoints[i] = temp[i].transform;
        }
    }
    
}

 

플레이시, waypoints에 목록이 차례대로 할당된다

 

Enemy가 PathManager정보를 가져와야한다

Enemy가 해당 정보를 알고 있는 것도 좋지만, 관리하기 번거롭기 때문에 독립적으로 PathManager을 생성하여 관리하는 것이 좋다

PathManager 스크립트를 싱글톤으로 만들어 Enemy로 가져온다 

 

 


 

📌 Enemy.cs (Enemy 순찰로직) 

 

✏️ NavMeshAgent.destination

NavMeshAgent가 할당된 Enemy가 목표 target으로 이동한다

target은 wayPoints 하나의 위치값이다.

 

Enemy와 목표지점의 거리를 구하여, 거리값이 일정값보다 작으면

wayPoints의 인덱스값을 바꾸어 다음 wayPoints로 다시 이동하는 것을 반복한다. 

public class Enemy : MonoBehaviour
{
	NavMeshAgent agent; 
   	public int index; //wayPoints 목록 
    
	void Start()
    {
    	agent = GetComponent<NavMeshAgent>();

    }
    
    void Update()
    {
    	Vector3 target = PathManager.instance.wayPoints[index].position;
        
    	//Enemy의 위치는 PathManager 위치로 설정
        //PathManager.wayPoints[index] 위치로 이동하고 싶다
    	agent.destination = target;
        
        //만약 도착했다면, 거리가 2 이하라면
        float distance = Vector3.Distance(transform.position, target);
        if (distance < 2)
        {
        	//인덱스의 값을 바꾼다
        	//순방향
        	int len = PathManager.instance.wayPoints.Length; //len은 4
            	index = (index + 1) % len;
            
        }
    }
}

 

 

 

✏️역방향일 경우 로직

//역방향

float distance = Vector3.Distance(transform.position, target);
if (distance < 2)
{
	int len = PathManager.instance.wayPoints.Length;
    	index = (index + len - 1) % len;
}

 

 

 

✏️ Enemy 상태머신 state 생성 : Patrol / Chase

처음 태어났을 때 Patrol로 태어난다

Update()에는 swith문을 사용하여 State상태머신을 작동시킨다

기존의 Update, wayPoints 순서대로 돌던 Enemy 로직은 UpdatePatrol()로 변경한다

NavMeshAgent agent; 
public int index; //wayPoints 목록 
public State state;
   
public enum State
{
	Patrol, //순찰
    	Chase,	//추적
}

void Start()
{
    	agent = GetComponent<NavMeshAgent>();
	state = State.Patrol;
}

void Update()
{
	switch(state)
    {
    	case State.Patrol:
        UpdatePatrol();
        break;
        case State.Chase:
        UpdateChase();
        break;
    }
}

UpdatePatrol()
{
   	Vector3 target = PathManager.instance.wayPoints[index].position;
        
    	//Enemy의 위치는 PathManager 위치로 설정
        //PathManager.wayPoints[index] 위치로 이동하고 싶다
    	agent.destination = target;
        
        //만약 도착했다면, 거리가 2 이하라면
        float distance = Vector3.Distance(transform.position, target);
        if (distance < 2)
        {
        	//인덱스의 값을 바꾼다, 순방향
        	int len = PathManager.instance.wayPoints.Length; //len은 4
            	index = (index + 1) % len;
        }
}

UpdateChase()
{


}

 

 

 


 

📌 Patrol

 

✏️ Enemy - UpdatePatrol()

Patrol 상태에서 Chase상태로 전이하는 부분을 추가

만약 감지범위 안에 Player가 감지되었다면 추적 대상을 Player로 하고 추적상태로 전이하고 싶다 

fpublic float detectedRadius = 5; //감지거리
Transform chaseTarget;

void UpdatePatrol()
{
    	//만약 감지반경 안에 Player가 감지되었다면 
        int layerMask = 1 << LayerMask.NameToLayer("Player"); 
        Collider[] cols = Physics.OverlapSphere(transform.position, detectedRadius, layerMask);
        
        if (cols.Length > 0)
        {
        	//cols[0]은 Player임
        	//추적대상을 Player로 하고 추적상태로 전이하고싶다
		chaseTarget = cols[0].transform;
            	state = State.Chase;
            	return;
        }

   	Vector3 target = PathManager.instance.wayPoints[index].position;
       	//Enemy의 위치는 PathManager 위치로 설정
        //PathManager.wayPoints[index] 위치로 이동하고 싶다
    	agent.destination = target;
        
        //만약 도착했다면, 거리가 2 이하라면
        float distance = Vector3.Distance(transform.position, target);
        if (distance < 2)
        {
        	//인덱스의 값을 바꾼다, 순방향
        	int len = PathManager.instance.wayPoints.Length; //len은 4
            	index = (index + 1) % len;
        }
    
}

🔻 Physics.OverlapSphere()

> OverlapSphere는 Collider배열이다

정의한 Collider 범위 내에 접촉한 Collider에 대한 정보를 반환한다.

특정 Layer의 정보만을 반환 받을 수 있으며, 배열의 형태로 값을 반환받는다

public static Collider[] OverlapSphere(Vector3 position, float radius, int layerMask)  

 

(+) Layer추가  / Player Layer 추가하여 할당

 

 


 

📌 Chase

 

✏️ Enemy - UpdateChase()

agent.destination 목표 타겟이 Player = chaseTarget 이 되어 추적함 

public float chaseDistance = 10; //추적거리

void UpdateChase()
{
	//추적대상을 향해 이동하고싶다
    	agent.destination = chaseTarget.position;
   	//거리를 측정하고
    	float distance = Vector3.Distance(transform.position, chaseTarget.position);
    	//만약 추적대상이 추적거리를 벗어나면 
        if(distance > chaseDistance)
        {
        //순찰상태로 전이하고싶다
        state = State.Patrol;
	}
}

 

 

🔻 추적하다가 대상이 멀어져서 순찰 상태로 전이할 때, Enemy가 가장 가까운 WayPoint로 가는 로직

 

Enmey와 WayPoint 사이의 거리를 모두 잰 후에 가장 가까운 지점으로 향함 

가장 가까운 순찰위치를 목적지로 하고싶다

if (distance > chaseDistance)
{
	//순찰상태로 전이하고싶다
   state = State.Patrol;
    // 나와 wayPoints의 거리를 측정하고
    float patrolDistance = float.MaxValue;
    int tempIndex = -1;
    //가장 가까운 순찰위치를 목적지로 하고싶다
    Transform[] wayList = PathManager.instance.wayPoints;
    for (int i = 0; i < wayList.Length; i++)
    {
    	float temDist = Vector3.Distance(transform.position, wayLIst[i].position);
        if (tempDist < patrolDistance)
        {
        tempIndex = i;
            patrolDistance = tempDist; //가장 작은 값이 patrolDistance에 담긴다 
        }
        
        if(tempIndex != -1) //찾았다면 목적지를 tempIndex로 하고싶다 
        {
        	index = tempIndex;
        }
    }
}