Como os agentes de IA acessam informações sobre seu ambiente?

9

Isso pode ser uma pergunta trivial, mas estou tendo problemas para entender isso. Agradeceria muito sua ajuda.

No desenvolvimento de jogos usando design orientado a objetos, quero entender como os agentes de IA acessam as informações de que precisam no mundo dos jogos para realizar suas ações.

Como todos sabemos, em jogos muitas vezes os agentes de IA precisam 'perceber seu ambiente' e agir de acordo com o que está acontecendo ao seu redor. Por exemplo, um agente pode ser programado para perseguir o jogador se ele chegar perto o suficiente, evitar obstáculos ao se mover (usando o comportamento de direção para evitar obstáculos), etc.

Meu problema é que não tenho certeza de como fazer isso. Como um agente de IA pode acessar as informações necessárias sobre o mundo do jogo?

Uma abordagem possível é que os agentes simplesmente solicitem as informações necessárias diretamente do mundo do jogo.

Há uma classe chamada GameWorld. Ele lida com a lógica do jogo importante (loop do jogo, detecção de colisão, etc.) e também contém referências a todas as entidades do jogo.

Eu poderia fazer desta classe um Singleton. Quando um agente precisa de informações do mundo do jogo, simplesmente as obtém diretamente da instância do GameWorld.

Por exemplo, um agente pode ser programado para Seeko jogador quando ele estiver perto. Para fazer isso, o agente precisa obter a posição do jogador. Então, ele pode simplesmente solicitá-lo diretamente: GameWorld.instance().getPlayerPosition().

Um agente também pode obter a lista de todas as entidades do jogo e analisá-la de acordo com suas necessidades (para descobrir quais entidades estão por perto ou qualquer outra coisa): GameWorld.instance().getEntityList()

Essa é a abordagem mais simples: os agentes entram em contato diretamente com a classe GameWorld e obtêm as informações de que precisam. No entanto, esta é a única abordagem que conheço. existe um melhor?

Como um desenvolvedor de jogos experiente projetaria isso? A abordagem "obtenha uma lista de todas as entidades e procure o que você precisa" é ingênua? Quais abordagens e mecanismos existem para permitir que os agentes de IA acessem as informações necessárias para realizar suas ações?

Aviv Cohn
fonte
Se você tiver acesso a uma assinatura do GDCVault, houve uma excelente palestra em 2013, chamada "Criando a IA para o mundo vivo e respiratório da absolvição de Hitman", que entra em detalhes no modelo de conhecimento da IA.
DMGregory

Respostas:

5

O que você descreve é ​​um modelo "puxado" clássico de consultar o mundo. Na maioria das vezes, isso funciona muito bem, especialmente para jogos com IA básica (o que é mais). No entanto, há alguns pontos que você deve considerar que podem ser negativos:

  • Você provavelmente deseja duplicar o buffer. Veja padrões de programação de jogos sobre o assunto . Sempre solicitando os dados diretamente do mundo, você pode obter condições estranhas de corrida nas quais o resultado depende de qual ordem a IA é chamada. Se isso é importante para o seu jogo, você deve determinar. Um resultado possível é que isso influencia o jogo para quem vai "primeiro" ou "por último", tornando o multiplayer injusto.

  • Muitas vezes, pode ser muito mais eficiente para solicitações em lote, especialmente para determinadas estruturas de dados. Aqui, você pode fazer com que todo agente de IA que deseja procurar obstáculos crie um "objeto de consulta" e registre-o com um obstáculo central singleton. Então, antes do loop principal da AI, todas as consultas são executadas na estrutura de dados, o que mantém a estrutura de dados de obstáculos mais em cache. Então, durante a parte de IA, cada agente processa seus resultados de consulta, mas não tem permissão para fazer mais diretamente. No final do quadro, os objetos do AI atualizam as consultas com o novo local ou as adicionam ou removem. Isso é semelhante ao design orientado a dados .

    Observe que isso basicamente faz o buffer duplo armazenando o resultado das consultas em um buffer. Também exige que você preveja se você precisa fazer uma consulta ao quadro antes. Este é um modelo de "envio", porque os agentes declaram em que tipo de atualizações estão interessados ​​(criando um objeto de consulta correspondente) e essas atualizações são enviadas a eles. Observe que você também pode fazer com que o objeto de consulta contenha um retorno de chamada, em vez de armazenar todos os resultados para um quadro.

  • Finalmente, você provavelmente deseja usar interfaces ou componentes para seus objetos pesquisáveis, em vez de herança, o que está bem documentado em outro lugar. Iteração sobre uma lista de Entitiesverificação instanceOfé provavelmente uma receita para o código muito frágil, o minuto você quer tanto StaticObjecte MovingObjectser Healable. (a menos que instanceOffuncione para interfaces no idioma de sua escolha.)


fonte
5

Como a IA é cara, o desempenho geralmente é o fator determinante na arquitetura.

Para aliviar suas preocupações com os modelos de acesso a dados, vamos considerar alguns exemplos diferentes de IA, dentro e fora da indústria de jogos, trabalhando desde o mais distante da navegação humana até o mais familiar para nós.

(Cada exemplo assume uma única atualização lógica global.)

  • A * caminho mais curtoCada AI calcula o estado do mapa para fins de busca de caminhos. A * exige que cada IA ​​já conheça todo o ambiente (local) no qual deve encontrar o caminho, portanto, devemos fornecer informações sobre obstáculos e espaço no mapa (geralmente um array booleano 2D). A * é uma forma especializada do algoritmo de Dijkstra, um algoritmo de busca de gráfico aberto de caminho mais curto; essas abordagens retornam listas que representam o caminho e, em cada etapa, a IA simplesmente seleciona o próximo nó nesta lista para avançar, até atingir seu objetivo ou é necessário recálculo (por exemplo, devido a uma alteração no obstáculo do mapa). Sem esse conhecimento, nenhum caminho mais curto e realista pode ser encontrado. A * é o motivo pelo qual as IAs nos jogos RTS sempre sabem como ir do ponto A ao ponto B - se existe um caminho. Ele recalculará o caminho mais curto para cada IA ​​individual, porque a validade do caminho é baseada na posição das AIs que foram movidas (e potencialmente bloquearam certos caminhos) anteriormente. O processo iterativo pelo qual A * calcula os valores das células durante a busca de caminhos é de convergência matemática. Pode-se dizer que o resultado final se assemelha a um olfato misturado com um senso de visão, mas, no geral, é algo estranho à nossa mentalidade.

  • Difusão colaborativa Também encontrada nos jogos, ela é mais parecida com o olfato, baseado na difusão de gases e partículas. O CD resolve o problema do reprocessamento caro encontrado em A *: em vez disso, um único estado do mapa é armazenado, processado uma vez por atualização para todos os AIs, e os resultados são acessados ​​por cada AI, por sua vez, para que ele faça seu respectivo movimento . O caminho único (lista de células) não é mais retornado por um algoritmo de pesquisa; em vez disso, cada IA ​​inspecionará o mapa após o processamento e será movida para a célula adjacente que tiver o valor mais alto. Isso é chamado de escalada . No entanto, a fase de processamento do mapa devetenha acesso prévio às informações do mapa, que também contêm os locais de todos os órgãos da IA. Portanto, o mapa faz referência a AIs e, em seguida, AIs fazem referência ao mapa.

  • Visão computacional e radiodifusão + caminho mais curto Na robótica rover & drone, isso está se tornando a norma para determinar a extensão dos espaços pelos quais o robô navega. Isso permite que os robôs construam um modelo volumétrico completo de seu ambiente, exatamente como faríamos pela vista, ou mesmo pelo som ou toque (para cegos ou surdos), que o robô poderá reduzir a um gráfico topográfico mínimo (um pouco como um gráfico de waypoint usado em jogos com A *), sobre os quais algoritmos de caminho mais curto podem ser aplicados. Nesse caso, embora a "visão" possa fornecer uma pista para o ambiente imediato, ela ainda resulta emuma pesquisa gráfica que, em última análise, fornece o caminho para a meta. Isso está próximo do pensamento humano: para alcançar a cozinha a partir do quarto, devo passar pela sala de estar. O fato de eu já os ter visto e conhecer seus espaços e portais, é o que permite esse movimento calculado. Esta é uma topologia gráfica, na qual um algoritmo de caminho mais curto é aplicado, embora incorporado na proteína mole em vez do silício duro.

Assim, você pode ver que os dois primeiros dependem de já conhecer o ambiente em sua totalidade. Isso, por razões do custo de avaliar um ambiente do zero, é comum em jogos. Claramente, o último é mais poderoso. Um robô equipado dessa maneira (ou, por exemplo, uma IA do jogo que leia o buffer de profundidade de cada quadro) pode navegar o suficiente em qualquer ambiente sem o conhecimento prévio. Como você provavelmente adivinhou, também é de longe a mais cara das três abordagens acima, e em jogos normalmente não podemos nos dar ao luxo de fazer isso por IA. Obviamente, é muito menos caro em 2D do que em 3D.

Pontos arquitetônicos

Fica claro acima que não podemos assumir apenas um padrão de acesso a dados correto para a IA; a escolha depende do que você está tentando alcançar. O acesso GameWorlddireto à turma é absolutamente padrão: ele simplesmente fornece informações do mundo. Essencialmente, é o seu modelo de dados, e é para isso que servem os modelos de dados. Singleton é bom para isso.

"obtenha uma lista de todas as entidades e procure o que você precisa"

Nada ingênuo nisso. A única coisa que pode ser ingênua é executar mais iterações de lista do que você precisa. Na detecção de colisões, evitamos isso usando, por exemplo, quadtrees para reduzir o espaço de pesquisa. Mecanismos semelhantes podem ser aplicados à IA. E se você puder compartilhar o mesmo loop para fazer várias coisas, faça-o, porque as filiais são caras.

Engenheiro
fonte
Obrigado por responder. Como sou iniciante no desenvolvimento de jogos, acho que continuarei com a abordagem simples "obtenha uma lista do mundo dos jogos" por enquanto :) Uma pergunta: na minha pergunta, descrevi a GameWorldclasse como a classe que contém referências a todas as entidades do jogo e também contém a maior parte da lógica importante do 'mecanismo': o loop principal do jogo, a detecção de colisões etc. É basicamente a 'classe principal' do jogo. Minha pergunta é: Essa abordagem é comum em jogos? Tem uma 'classe principal'? Ou devo separá-lo em classes menores e ter uma classe para que os objetos de 'banco de dados de entidades' possam pesquisar?
Aviv Cohn
@Prog De nada. Mais uma vez, não há nada nas abordagens de IA acima (ou quaisquer outras, nesse sentido) que sugira que o seu "obtenha uma lista do mundo dos jogos" seja de alguma forma, formato ou formato arquitetonicamente incorreto. A arquitetura da IA deve atender às necessidades da IA; mas essa lógica, como você sugere, deve ser modularizada, encapsulada (em sua própria classe) longe de sua arquitetura de aplicativos mais ampla . Sim, os subsistemas sempre devem ser fatorados em módulos separados quando essas questões surgirem. Seu princípio orientador deve ser SRP .
Engenheiro de
2

Basicamente, eu teria duas maneiras de consultar informações.

  1. quando o AIState muda porque você detectou uma colisão ou qualquer cache, uma referência a qualquer objeto é importante. Dessa forma, você sabe qual referência você precisa. Quando outros sistemas precisam executar pesquisas grandes a cada quadro, eu recomendaria fazer o backup deles para que você não precise realizar várias pesquisas. Portanto, a "colisão" detectada com a zona que alerta o inimigo envia a ele uma mensagem / evento que o registra com esse objeto, se ele ainda não estiver, e muda o estado do jogo para um que o faça negociar com base em estar em que gamestate. Você precisa de um evento de algum tipo que lhe diga para fazer alterações, basta passar uma referência para qualquer retorno de chamada usado para fornecer essas informações. Isso é mais extensível do que apenas ter que lidar com o jogador. Talvez você queira que um inimigo persiga outro inimigo ou outro objeto. Dessa forma, você só precisa alterar qualquer tag que a identifique.

  2. Com essas informações, você realizará uma consulta a um sistema de localização de caminhos que usa A * ou algum outro algoritmo para fornecer um caminho ou você pode usá-lo com algum comportamento de direção. Talvez uma combinação de ambos ou o que quer. Basicamente, com a transformação de ambos, você deve poder consultar o sistema de nós ou a navmesh e permitir que ele ofereça um caminho. Seu mundo do jogo provavelmente tem muitas outras coisas além da busca de caminhos. Eu enviaria sua consulta apenas para o pathfinding. Também é muito melhor fazer o lote dessas coisas se você tiver muitas consultas, pois isso pode ser bastante intenso e o lote ajudará o desempenho.

    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;
        }
     }
user2927848
fonte