TIL - AI

빵어 ㅣ 2024. 1. 26. 20:50

어김없이 AI 개발중..

 

시도

지금은 AI끼리 서로 길을 막거나 미는 현상을 수정중이다.

AI Object에 Collider와 Rigidbody(isKinematic => true)를 넣은 후

OnTriggerEnter로 일정 범위안에 다른 AI가 들어오면 AI를 멈추도록 구현해봤다. 

private void OnTriggerEnter(Collider other)
{
    if (other.gameObject.CompareTag("AI"))
    {
        if (_agent.destination == _tycoonManager.CustomerCreatePos.position)
        {
            StartCoroutine(StopFewSeconds());
        }
    }
}
...
IEnumerator StopFewSeconds()
{
    _agent.isStopped = true;
    _animator.SetBool("IsIdle", true);

    yield return new WaitForSeconds(2f);
    
    _agent.isStopped = false;
    _animator.SetBool("IsIdle", false);
}

 

_tycoonManger.CustomerCreatePos.position에 도달하면 AI는 Destroy되어 식당을 나간걸로 간주된다.

부딪힌 AI가 둘 다 멈추면 안되고, 한 개의 AI만 멈춰야 하므로 AI 중에 _tycoonManager.CustomerCreatePos.position이 Destination인 AI만 멈추고, 식탁에 앉으러 가는 AI는 원래 가던 길 갈 수 있도록 코드를 짰다.

 

또, 자리에 앉으러 가는 AI의 우선순위가 식당을 나가는 AI보다 낮아서 식당을 나가는 AI를 피해갈 것이다.

 

하지만 해결되지 않았다.

 

Destination position의 y값이 다른 문제 

여전히 서로 밀고 있다.

 

Debug.Log로 _tycoonManager.CustomerCreatePos.position을 찍어봤다.

내가 CustomerCreatePos에 넣어준 값은 (0,0,-9.6) 인데, Destination은 (0, 0.04, -9.6) 이다.

 

이 현상은 NavMesh가 bake될 때 Surface로 지정된 GameObject에 딱 붙어서 bake되지 않고, 좀 떨어져서 bake되는 현상때문인 듯 하다. (추측)

Plane과 Bake된 NavMesh 사이

 

private void OnTriggerEnter(Collider other)
{
    if (other.gameObject.CompareTag("AI"))
    {
        if (_agent.destination.x == _tycoonManager.CustomerCreatePos.position.x
            && _agent.destination.z == _tycoonManager.CustomerCreatePos.position.z)
        {
            StartCoroutine(StopFewSeconds());
        }
    }
}

 

그래서 x, z값만 받아서 비교하도록 수정해줬다.

 

가까이 있으면 잘 멈춘다.

 

두 AI가 서로 멈추는 문제

 

구현하고 나니 내가 간과한 게 있었다.

두 AI 모두 식당을 나가는 AI라면 서로 멈춘다.

 

나가다가 멀뚱멀뚱

 

 

이 부분은 한 AI만 멈출 수 있도록 OnTriggerEnter에서 조건을 더 줘서 해결했다. (otherAgent.isStopped == false)

 

그리고 _agent.destination과 _tycoonManger.CustomerCreatePos.position의 값을 그냥 비교하면 오차로 작동이 이상해질 수 있으니 Approximately를 사용했다.

 

또 Agent의 isStopped을 사용하면 목적지는 있는 상태로 멈추기 때문에,

AI가 걷는 애니메이션을 실행하고 있는 상태로 멈춘다.

그래서 IsIdle 파라미터를 생성해 목적지로 가다 멈췄을 때, Idle 애니메이션으로 전환해줬다.

 private void OnTriggerEnter(Collider other)
 {
     if (other.gameObject.CompareTag("AI"))
     {
         NavMeshAgent otherAgent = other.gameObject.GetComponent<NavMeshAgent>();

         if (Mathf.Approximately(_agent.destination.x, _tycoonManager.CustomerCreatePos.position.x)
             && Mathf.Approximately(_agent.destination.z, _tycoonManager.CustomerCreatePos.position.z)
             && otherAgent.isStopped == false)
         {
             _agent.isStopped = true;
             _animator.SetBool("IsIdle", true);
         }
     }
 }

 

OnTrigger함수들을 사용하게 되면서, 멈춰서 몇 초 동안 기다리던 방식이 필요없어져

원래 사용하던 StopFewSeconds코루틴은 삭제했다. 

 

-> 한 AI만 잘 멈춘다 !

 

 

또 한 개의 AI만 충돌할 거란 보장이 없으므로 현재 충돌중인 AI를 담을 List<GameObject> 변수를 생성했다.

private List<GameObject> collidingAIs = new();

 

OnTriggerEnter로 충돌이 시작되었을 때 List에 충돌된 AI를 넣어준다.

private void OnTriggerEnter(Collider other)
{
    if (other.gameObject.CompareTag("AI"))
    {
        NavMeshAgent otherAgent = other.gameObject.GetComponent<NavMeshAgent>();

        if (Mathf.Approximately(_agent.destination.x, _tycoonManager.CustomerCreatePos.position.x)
            && Mathf.Approximately(_agent.destination.z, _tycoonManager.CustomerCreatePos.position.z)
            && otherAgent.isStopped == false
            && !_collidingAIs.Contains(other.gameObject))
        {
            _collidingAIs.Add(other.gameObject);
            _agent.isStopped = true;
            _animator.SetBool("IsIdle", true);
        }
    }
}

 

그리고 OnTriggerExit에서 List에서 Remove 시켜줬다.

private void OnTriggerExit(Collider other)
{
    if (other.gameObject.CompareTag("AI"))
    {
        RemoveList(other);
    }
}
private void RemoveList(Collider other)
{
    if (_collidingAIs.Contains(other.gameObject))
    {
        _collidingAIs.Remove(other.gameObject);
    }

    if (collidingAIs.Count == 0)
    {
        _agent.isStopped = false;
        _animator.SetBool("IsIdle", false);
    }
}

 

 

AI가 다른 AI가 움직일 때까지 기다리는 문제

이제는 충돌된 상태로 한 AI가 식탁에 앉게되면 나가던 AI가 다른 AI의 식사가 끝날 때까지 기다리는 현상이 생겼다.

- OnTriggerStay와 agent의 isStopped로 해결했다.

 

지금 충돌하고 있는 다른 AI가 만약 식탁에 앉는다면 그 AI의 Agent.isStopped를 true로 바꿔준다.

if (!_isOrderFood)
{
	...
     _agent.isStopped = true;
}

 

_isOrderFood: 식탁에 앉았는지(주문을 했는지) 확인해주는 bool값 변수

 

그리고 OnTriggerStay에서 이 값을 확인해 만약 다른 AI의 isStopped이 true일 경우 충돌하고 있는 AI들을 담고 있던 List인 _collidingAIs에서 그 AI Object를 삭제한다.

private void OnTriggerStay(Collider other)
{
    if (other.gameObject.CompareTag("AI"))
    {
        NavMeshAgent otherAgent = other.gameObject.GetComponent<NavMeshAgent>();
        if (otherAgent.isStopped == true)
        {
            RemoveList(other);
        }
    }
}

 

-> 해결 !

 

해결하고 나니 또 다른 문제가 생겼다.


AI의 transform이 바뀌는 문제

또 똑바로 안앉는다.

 

손님 AI들은 Object Pool로 생성, 제거되는데

풀에서 생성하고 처음 불러와서 사용할 때는 문제가 없지만, 두 번째부터는 Position과 Rotation값이 변경된다..

 

내 추측으로는

AI끼리 충돌되면서 아예 안 밀지는 않으니 살짝 밀릴 때 transform값이 바뀌든가,

움직이고 있는 상태에서 풀로 반환될 때, transform 값이 변환된 상태로 풀로 반환되어서 그런게 아닌가싶다.

 

결국엔 정확한 원인은 찾지 못해 일단 OnEnable에 transform값들을 고정시켜줬다.

 

 transform.rotation = Quaternion.identity;
 _animator.gameObject.transform.localPosition = Vector3.zero;
 _animator.gameObject.transform.localRotation = Quaternion.identity;

 

-> 일단은 해결됐다.

 

 

현재는 잘 작동하지만 어째선지 ObjectPool에서 손님 AI를 두 번째로 불러왔을 때 식당을 나가지 않는 현상이 생겼다..

내일 고치는걸로 !

-> 빠른 테스트를 위해 손님 AI의 음식 기다리는 시간을 0으로 설정해서 일어난 문제.

 

 

더보기

Collider를 사용하지 않고 다른 AI를 체크하려고 Chat GPT의 힘을 빌려 시도해본 것들

//else
//{
//    Collider[] nearbyAgents = Physics.OverlapSphere(transform.position, 0.5f);
//    foreach (Collider agentCollider in nearbyAgents)
//    {
//        if (agentCollider.gameObject != gameObject && agentCollider.gameObject.tag == "AI")
//        {
//            Vector3 avoidanceDirection = transform.position - agentCollider.transform.position;
//            Vector3 newDestination = transform.position + avoidanceDirection.normalized * 0.5f;
//            StartCoroutine(StopFewSeconds(newDestination));
//        }
//    }
//}

//else
//{
//    GameObject[] ais = GameObject.FindGameObjectsWithTag("AI");
//    if (!isStop)
//    {
//        foreach (GameObject aiObject in ais)
//        {
//            if (aiObject == gameObject)
//                continue;
//            if (10f > Vector3.Distance(aiObject.transform.position, transform.position)
//                && _agent.destination == _tycoonManager.CustomerCreatePos.position)
//            {
//                StartCoroutine(StopFewSeconds());
//            }
//        }
//    }
//}

'내일배움캠프(Unity)' 카테고리의 다른 글

실전 프로젝트 중간발표  (0) 2024.02.05
TIL  (0) 2024.02.01
TIL - NavMeshAgent(Base Offset)  (0) 2024.01.25
TIL - 오류 수정  (0) 2024.01.24
TIL - 조리된 음식 프리팹 만들기, 몇 가지 오류  (1) 2024.01.23