AI 에이전트는 환경에 대한 정보에 어떻게 액세스합니까?


9

이것은 사소한 질문 일 수도 있지만 이것을 이해하는 데 어려움을 겪고 있습니다. 도와 주셔서 감사합니다.

객체 지향 디자인을 사용한 게임 개발에서 AI 에이전트가 액션을 수행하기 위해 게임 세계에서 필요한 정보에 액세스하는 방법을 이해하고 싶습니다.

우리 모두 알다시피, 게임에서 AI 에이전트는 종종 '환경을 인식'하고 주변 환경에 따라 행동해야합니다. 예를 들어, 에이전트는 플레이어가 충분히 가까워지면 플레이어를 쫓아 가거나, 장애물 회피 조향 동작을 사용하여 움직이는 동안 장애물을 피하도록 프로그래밍 될 수 있습니다.

내 문제는 어떻게 해야할지 모르겠습니다. AI 에이전트는 게임 세계에 필요한 정보에 어떻게 액세스 할 수 있습니까?

가능한 접근 방법 중 하나 는 에이전트가 게임 세계에서 직접 필요한 정보를 요청하는 것입니다.

GameWorld라는 클래스가 있습니다. 중요한 게임 로직 (게임 루프, 충돌 감지 등)을 처리하고 게임의 모든 엔티티에 대한 참조도 보유합니다.

이 수업을 싱글 톤으로 만들 수있었습니다. 에이전트가 게임 세계의 정보를 필요로하는 경우 단순히 GameWorld 인스턴스에서 직접 가져옵니다.

예를 들어 상담원은 Seek플레이어가 가까이있을 때 플레이어 에게 프로그래밍 될 수 있습니다 . 이를 위해 에이전트는 플레이어의 위치를 ​​가져와야합니다. 따라서 간단하게 직접 요청할 수 있습니다 GameWorld.instance().getPlayerPosition()..

또한 에이전트는 게임의 모든 엔티티 목록을 가져와 필요에 따라 분석 할 수 있습니다 (가까운 엔티티 또는 기타 항목 파악). GameWorld.instance().getEntityList()

가장 간단한 방법입니다. 에이전트는 GameWorld 클래스에 직접 연락하여 필요한 정보를 얻습니다. 그러나 이것이 내가 아는 유일한 접근 방법입니다. 더 좋은 것이 있습니까?

숙련 된 게임 개발자는 이것을 어떻게 설계합니까? "모든 엔티티의 목록을 가져 와서 필요한 것을 찾으십시오"가 순진합니까? AI 에이전트가 작업을 수행하는 데 필요한 정보에 액세스 할 수 있도록하기 위해 어떤 접근 방식과 메커니즘이 있습니까?


GDCVault 구독에 액세스 할 수 있다면 2013 년에 "Hitman Absolution의 살아있는 숨쉬는 세계를위한 AI 만들기"라는 훌륭한 연설이 있었으며 AI 지식 모델에 대해 자세히 설명했습니다.
DMGregory

답변:


5

당신이 묘사하는 것은 세상을 쿼리하는 고전적인 "풀 (pull)"모델입니다. 대부분의 경우, 이것은 기본 AI (가장 큰)가있는 게임에서 특히 잘 작동합니다. 그러나 단점 있는 몇 가지 사항이 있습니다 .

  • 아마도 더블 버퍼링을 원할 것입니다. 주제에 대한 게임 프로그래밍 패턴을 참조하십시오 . 항상 세계에서 직접 데이터를 요청하면 AI가 호출되는 순서에 따라 결과가 달라지는 이상한 경쟁 조건을 얻을 수 있습니다. 이것이 게임에서 중요한지 여부는 결정하는 것입니다. 가능한 첫 번째 결과는 "첫 번째"또는 "마지막"으로가는 사람에게 게임을 편중시켜 멀티 플레이어를 불공정하게 만드는 것입니다.

  • 요청, 특히 특정 데이터 구조에 대한 요청을 배치하는 것이 훨씬 더 효율적일 수 있습니다. 여기서 장애물을 검색하려는 모든 AI 에이전트가 "쿼리 객체"를 생성하고 중앙 장애물 싱글 톤으로 등록 할 수 있습니다. 그런 다음 기본 AI 루프 전에 모든 쿼리가 데이터 구조에 대해 실행되므로 장애물 데이터 구조가 더 캐시에 유지됩니다. 그런 다음 AI 부분에서 각 에이전트는 쿼리 결과를 처리하지만 더 이상 직접 만들 수는 없습니다. 프레임의 끝에서 AI 객체는 쿼리를 새로운 위치로 업데이트하거나 추가하거나 제거합니다. 이것은 데이터 지향 디자인 과 유사합니다 .

    기본적으로 쿼리 결과를 버퍼에 저장하여 이중 버퍼링을 수행합니다. 또한 프레임 이전에 쿼리를 수행해야하는지 예상해야합니다. 에이전트는 해당 쿼리 오브젝트를 작성하여 관심있는 업데이트 종류를 선언하고 해당 업데이트를 푸시하기 때문에 "푸시"모델입니다. 프레임에 대한 모든 결과를 저장하지 않고 쿼리 객체에 콜백을 포함시킬 수도 있습니다.

  • 마지막으로 상속이 아닌 검색 가능한 객체에 인터페이스 나 구성 요소를 사용하려고합니다. 목록 반복 Entities검사하는 것은 instanceOf아마 꽤 깨지기 쉬운 코드에 대한 조리법, 둘 다 원하는 분입니다 StaticObjectMovingObject될 수를 Healable. ( instanceOf선택한 언어의 인터페이스에서 작동 하지 않는 한 )


5

AI는 비용이 많이 들고 성능은 종종 아키텍처의 원동력입니다.

데이터 액세스 모델에 대한 걱정을 덜기 위해 게임 업계 안팎에서 사람의 탐색에서 가장 친숙한 것까지 가장 다양한 AI 예제를 고려해 보겠습니다.

(각 예제는 단일 글로벌 로직 업데이트를 가정합니다.)

  • A * 최단 경로각 AI는 길 찾기 목적으로지도의 상태를 계산합니다. A *는 각 AI가 경로를 찾아야하는 전체 (로컬) 환경을 이미 알고 있어야하므로지도 장애물과 공간 (종종 2D 부울 배열)에 대한 정보를 전달해야합니다. A *는 최단 경로 오픈 그래프 검색 알고리즘 인 Dijkstra의 알고리즘의 특수 형식입니다. 이러한 접근 방식은 경로를 나타내는 목록을 반환하고 각 단계에서 AI는 목표에 도달하거나 재 계산이 필요할 때까지 (예 :지도 장애물 변경으로 인해)이 목록에서 다음 노드를 선택하기 만하면됩니다. 이 지식이 없으면 현실적인 최단 경로를 찾을 수 없습니다. A *는 RTS 게임의 AI가 경로가 존재하는 경우 항상 A 지점에서 B 지점으로 이동하는 방법을 알고있는 이유입니다. 모든 개별 AI에 대한 최단 경로를 다시 계산합니다. 경로 유효성은 이전에 이동했거나 특정 경로를 잠재적으로 차단 한 AI의 위치를 ​​기반으로하기 때문입니다. A *가 경로 찾기 중에 셀 값을 계산하는 반복 프로세스는 수학 수렴 중 하나입니다. 최종 결과는 시각 감각과 혼합 된 후각과 유사하다고 말할 수 있지만, 결국 그것은 우리의 사고 방식에 다소 이질적입니다.

  • 공동 확산 게임에서도 발견되는 것은 가스와 미립자의 확산에 기초한 후각과 가장 유사합니다. CD는 A *에서 발견되는 값 비싼 재 처리 문제를 해결합니다. 대신 단일 맵 상태가 저장되고 모든 AI에 대해 업데이트마다 한 번씩 처리 된 다음 각 AI가 차례로 결과에 액세스하여 각각의 이동을 수행합니다. . 더 이상 검색 알고리즘에 의해 반환 된 단일 경로 (셀 목록)가 아닙니다. 오히려 각 AI는 맵이 처리 된 후 맵을 검사하여 가장 가까운 값을 가진 셀로 이동합니다. 이것을 언덕 등반 이라고 합니다. 그럼에도 불구하고지도 처리 단계는 이미지도 정보에 미리 액세스 할 수 있으며 여기에는 모든 AI 바디의 위치도 포함됩니다. 따라서지도는 AI를 참조한 다음 AI는지도를 참조합니다.

  • 컴퓨터 비전 및 레이 캐스팅 + 최단 경로 로버 및 드론 로봇 공학에서 로봇이 이동하는 공간의 범위를 결정하는 표준이되고 있습니다. 이를 통해 로봇은 시각이나 청각이나 청각 (맹인 또는 청각 장애인)과 마찬가지로 환경의 전체 체적 모델을 구성 할 수 있습니다. 가장 짧은 경로 알고리즘을 적용 할 수있는 A *) 게임에 사용됩니다. "비전"즉각적인 환경에 대한 단서를 제공 할 수 있지만이 경우, 그것은 여전히 결과궁극적으로 목표 달성 경로를 제공하는 그래프 검색 이것은 인간의 생각에 가깝습니다. 침실에서 부엌에 도달하려면 거실을 통과해야합니다. 내가 이미 그들을 보았고 그들의 공간과 포털을 알고 있다는 사실은이 계산 된 이동을 가능하게하는 것입니다. 이것은 하드 실리콘보다는 연질 단백질에 내장되어 있지만 가장 짧은 경로 알고리즘이 적용되는 그래프 토폴로지입니다.

따라서 처음 두 가지는 환경 전체를 이미 알고 있다는 사실을 알 수 있습니다. 이것은 환경을 처음부터 평가하는 비용 때문에 게임에서 일반적입니다. 분명히 마지막이 가장 강력합니다. 이러한 방식으로 장착 된 로봇 (또는 예를 들어, 각 프레임마다 깊이 버퍼를 읽는 게임 AI)은 사전 지식 없이도 모든 환경에서 충분히 탐색 할 수 있습니다. 아마 당신이 짐작했듯이, 그것은 위의 세 가지 접근법 중 가장 비용이 많이 들며 게임에서는 일반적으로 AI별로이를 수행 할 여유가 없습니다. 물론 3D보다 2D 비용이 훨씬 저렴합니다.

건축 포인트

AI에 대해 하나의 올바른 데이터 액세스 패턴 만 가정 할 수 없다는 점이 분명해졌습니다. 선택은 달성하려는 대상에 따라 다릅니다. GameWorld수업에 직접 액세스하는 것은 절대적으로 표준입니다. 단순히 세계 정보를 제공합니다. 기본적으로 데이터 모델이며 데이터 모델의 용도입니다. 싱글 톤은 이것으로 좋습니다.

"모든 엔터티 목록을 얻고 필요한 것을 찾으십시오"

전혀 순진한 것은 없습니다. 순진한 유일한 것은 필요한 것보다 많은 목록 반복을 수행하는 것입니다. 충돌 감지에서는 예를 들어 쿼드 트리를 사용하여 검색 공간을 줄임으로써이를 피할 수 있습니다. AI에도 비슷한 메커니즘이 적용될 수 있습니다. 동일한 루프를 공유하여 여러 가지 작업을 수행 할 수 있다면 분기가 비용이 많이 들기 때문에 그렇게하십시오.


대답 해줘서 고마워. 나는 게임 개발자의 초보자이기 때문에 지금은 간단한 "게임 세계에서 목록을 얻는다"라는 접근 방식을 고수 할 것이라고 생각 GameWorld한다. 주요 게임 루프, 충돌 감지 등 중요한 '엔진'로직을 포함합니다. 기본적으로 게임의 '메인 클래스'입니다. 내 질문은 :이 접근법은 게임에서 일반적입니까? '메인 클래스'가 있습니까? 아니면 더 작은 클래스로 분리하고 '엔티티 데이터베이스'개체가 폴링 할 수있는 하나의 클래스를 가져야합니까?
Aviv Cohn

@Prog 천만에요. 다시 말하지만, 위의 AI 접근법 (또는 그 문제에 대한 다른 접근법)에는 "게임 세계에서 목록을 얻는 것"이 건축 적으로 부정확 한 방식, 모양 또는 형태 임을 암시하는 것은 없습니다 . AI의 아키텍처는 AI의 요구에 맞게해야합니다 그러나 제안한 바와 같이,이 로직은 모듈화되고, 더 넓은 애플리케이션 아키텍처 로부터 (자체 클래스로) 캡슐화되어야합니다 . 그렇습니다. 서브 시스템은 그러한 질문이 발생하면 항상 별도의 모듈로 분리해야합니다. 당신의지도 원리는 SRP 이어야합니다 .
엔지니어

2

기본적으로 정보를 쿼리하는 두 가지 방법이 있습니다.

  1. 충돌이 감지되었거나 중요한 객체에 대한 참조를 캐시하여 AIState가 변경되는 경우 그렇게하면 어떤 참조가 필요한지 알 수 있습니다. 다른 시스템에서 매 프레임마다 큰 검색을 수행해야 할 경우에는 피기 백 (piggy back)을 권장하므로 여러 검색을 수행 할 필요가 없습니다. 따라서 '충돌'은 적을 '경고'하게 만드는 영역에서 감지되어 해당 개체에 아직 등록하지 않은 경우 그를 등록하는 메시지 / 이벤트를 보내고 게임 상태를 자신의 비즈니스에 기반한 비즈니스 상태로 변경합니다. 그 게임 상태. 변경 사항을 알려주는 일종의 이벤트가 필요합니다. 정보를 제공하는 데 사용하는 콜백에 참조를 전달합니다. 이것은 플레이어를 다루는 것보다 확장 성이 뛰어납니다. 적이 다른 적이나 다른 물건을 추구하기를 원할 수도 있습니다. 이렇게하면 식별 한 태그 만 변경하면됩니다.

  2. 이 정보를 사용하면 A * 또는 다른 알고리즘을 사용하여 경로를 제공하는 경로 검색 시스템에 대한 쿼리를 수행하거나 일부 조향 동작과 함께 사용할 수 있습니다. 어쩌면 둘 다의 조합 또는 어쩌면. 기본적으로 두 가지 변환을 통해 노드 시스템 또는 navmesh를 쿼리하고 경로를 제공 할 수 있어야합니다. 게임 세계에는 길 찾기 이외의 많은 것들이 있습니다. 길 찾기에만 검색어를 제출하겠습니다. 또한 쿼리가 많은 경우 이러한 작업을 일괄 처리하는 것이 가장 좋습니다. 일괄 처리는 성능이 향상 될 수 있기 때문입니다.

    Transform* targetTransform = nullptr;
    EnemyAIState  AIState = EnemyAIState::Idle;
    void OnTriggerEnter(GameObject* go)
    {
       if(go->hasTag(TAG_PLAYER))
       {
       //Cache important information that will be needed during pursuit
       targetTransform = go->getComponent<Transform>();
       AIState = EnemyAIState::Pursue;
       }
    }
    
    void Update()
    {
       switch(AIState)
       {
          case EnemyAIState::Pursue:
           //Find position to move to
           Vector3 nextNode = PathSystem::Seek(
                              transform->position,targetTransform->position);
           /*Update the position towards the target by whatever speed the unit moves
             Depending on how robust your path system is you might want to raycast
             against obstacles it can't take into account or might clip the path.*/
            transform->Move((nextNode - transform->position).unitVector()*speed*Time::deltaTime());
            break;
        }
     }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.