Comment les agents IA accèdent-ils aux informations sur leur environnement?


9

C'est peut-être une question banale, mais j'ai du mal à comprendre cela. J'apprécierais beaucoup votre aide.

Dans le développement de jeux utilisant une conception orientée objet, je veux comprendre comment les agents IA accèdent aux informations dont ils ont besoin du monde du jeu pour effectuer leurs actions.

Comme nous le savons tous, dans les jeux, les agents de l'IA doivent très souvent `` percevoir leur environnement '' et agir en fonction de ce qui se passe autour d'eux. Par exemple, un agent peut être programmé pour chasser le joueur s'il se rapproche suffisamment, éviter les obstacles en se déplaçant (en utilisant le comportement de direction pour éviter les obstacles), etc.

Mon problème est que je ne sais pas comment faire ça. Comment un agent IA peut-il accéder aux informations dont il a besoin sur le monde du jeu?

Une approche possible est que les agents demandent simplement les informations dont ils ont besoin directement au monde du jeu.

Il y a une classe appelée GameWorld. Il gère la logique de jeu importante (boucle de jeu, détection de collision, etc.) et contient également des références à toutes les entités du jeu.

Je pourrais faire de cette classe un Singleton. Lorsqu'un agent a besoin d'informations du monde du jeu, il les obtient simplement directement de l'instance GameWorld.

Par exemple, un agent peut être programmé pour Seekle joueur lorsqu'il est proche. Pour ce faire, l'agent doit obtenir la position du joueur. Ainsi , il peut simplement demander directement: GameWorld.instance().getPlayerPosition().

Un agent peut également simplement obtenir la liste de toutes les entités du jeu et l'analyser en fonction de ses besoins (pour déterminer quelles entités sont à proximité ou autre chose): GameWorld.instance().getEntityList()

C'est l'approche la plus simple: les agents contactent directement la classe GameWorld et obtiennent les informations dont ils ont besoin. Cependant, c'est la seule approche que je connaisse. Y en a t-il un meilleur?

Comment un développeur de jeux expérimenté pourrait-il concevoir cela? Est-ce que l'approche "obtenir une liste de toutes les entités et rechercher tout ce dont vous avez besoin" est naïve? Quelles sont les approches et les mécanismes permettant aux agents de l'IA d'accéder aux informations dont ils ont besoin pour effectuer leurs actions?


Si vous avez accès à un abonnement GDCVault, il y a eu une excellente conférence en 2013 intitulée "Créer l'IA pour le monde vivant et respirant de Hitman Absolution", qui aborde en détail leur modèle de connaissance de l'IA.
DMGregory

Réponses:


5

Ce que vous décrivez est un modèle «pull» classique d'interrogation du monde. La plupart du temps, cela fonctionne plutôt bien, en particulier pour les jeux avec une IA de base (ce qui est le plus). Cependant, il y a quelques points que vous devriez considérer qui pourraient être des inconvénients:

  • Vous voulez probablement doubler le tampon. Voir les modèles de programmation de jeu sur le sujet . En demandant toujours les données directement au monde, vous pouvez obtenir des conditions de course étranges dans lesquelles le résultat dépend de l'ordre dans lequel l'IA est appelée. Si c'est important pour votre jeu, c'est à vous de le déterminer. Un résultat possible est qu'il biaise le jeu pour quiconque passe "premier" ou "dernier", rendant le multijoueur injuste.

  • Il peut souvent être beaucoup plus efficace de traiter des requêtes par lots, en particulier pour certaines structures de données. Ici, vous pouvez faire en sorte que chaque agent d'intelligence artificielle qui souhaite rechercher des obstacles crée un "objet de requête" et l'enregistre auprès d'un singleton d'obstacle central. Ensuite, avant la boucle AI principale, toutes les requêtes sont exécutées sur la structure de données, ce qui maintient la structure de données d'obstacle plus dans le cache. Ensuite, pendant la partie AI, chaque agent traite ses résultats de requête, mais n'est pas autorisé à en faire plus directement. À la fin du cadre, les objets AI mettent à jour les requêtes avec leur nouvel emplacement, ou les ajoutent ou les suppriment. Ceci est similaire à la conception orientée données .

    Notez que cela fait essentiellement un double tampon en stockant le résultat des requêtes dans un tampon. Cela vous oblige également à anticiper si vous devez auparavant interroger le cadre. Il s'agit d'un modèle "push", car les agents déclarent le type de mises à jour qui les intéressent (en créant un objet de requête correspondant), et ces mises à jour leur sont transmises. Notez que vous pouvez également faire en sorte que l'objet de requête contienne un rappel, plutôt que de stocker tous les résultats pour un cadre.

  • Enfin, vous souhaiterez probablement utiliser des interfaces ou des composants pour vos objets consultables plutôt que l'héritage, qui est bien documenté ailleurs. Itérer sur une liste de Entitiescontrôle instanceOfest probablement une recette pour le code assez fragile, la minute où vous voulez à la fois StaticObjectet MovingObjectd'être Healable. (sauf si instanceOffonctionne pour les interfaces dans la langue de votre choix.)


5

L'IA étant coûteuse, les performances sont souvent le facteur moteur de l'architecture.

Pour atténuer vos préoccupations concernant les modèles d'accès aux données, considérons quelques exemples d'IA différents à l'intérieur et à l'extérieur de l'industrie des jeux, en allant de celui qui est le plus éloigné de la navigation humaine à celui qui nous est le plus familier.

(Chaque exemple suppose une mise à jour logique globale unique.)

  • Un * chemin le plus courtChaque IA calcule l'état de la carte à des fins d'orientation. A * nécessite que chaque IA connaisse déjà tout l'environnement (local) dans lequel elle doit trouver son chemin, nous devons donc lui remettre des informations sur les obstacles de la carte et l'espace (souvent un tableau booléen 2D). A * est une forme spécialisée de l'algorithme de Dijkstra, un algorithme de recherche de graphe ouvert à plus court chemin; de telles approches renvoient des listes représentant le chemin, et à chaque étape, l'IA sélectionne simplement le nœud suivant dans cette liste vers lequel aller, jusqu'à ce qu'il atteigne son objectif ou qu'un nouveau calcul soit nécessaire (par exemple en raison d'un changement d'obstacle sur la carte). Sans cette connaissance, aucun chemin le plus court réaliste ne peut être trouvé. A * est la raison pour laquelle les IA dans les jeux RTS savent toujours comment se rendre d'un point A à un point B - s'il existe un chemin. Il recalculera le chemin le plus court pour chaque IA individuelle, car la validité du chemin est basée sur la position des IA qui ont déjà déplacé (et potentiellement bloqué certains chemins). Le processus itératif par lequel A * calcule les valeurs des cellules lors de la recherche de chemin est un processus de convergence mathématique. On pourrait dire que le résultat final ressemble à un odorat mélangé à un sens de la vue, mais dans l'ensemble, il est quelque peu étranger à notre état d'esprit.

  • Diffusion collaborative Également présente dans les jeux, elle ressemble le plus à un odorat basé sur la diffusion de gaz et de particules. CD résout le problème du retraitement coûteux trouvé dans A *: Au lieu de cela, un seul état de la carte est stocké, traité une fois par mise à jour pour toutes les IA, et les résultats sont ensuite accédés par chaque IA tour à tour, pour qu'elle fasse son mouvement respectif . Un chemin unique (liste de cellules) n'est plus renvoyé par un algorithme de recherche; Au lieu de cela, chaque IA inspectera la carte après son traitement et se déplacera vers la cellule adjacente qui a la valeur la plus élevée. C'est ce qu'on appelle l' escalade . Néanmoins, la phase de traitement de la carte doit déjàavoir un accès préalable aux informations de la carte, qui contient également l'emplacement de tous les corps d'IA. Par conséquent, la carte fait référence aux IA, puis les IA font référence à la carte.

  • Vision par ordinateur et diffusion de rayons + chemin le plus court En robotique mobile et drone, cela devient la norme pour déterminer l'étendue des espaces que le robot parcourt. Cela permet aux robots de construire un modèle volumétrique complet de leur environnement, tout comme nous le ferions par la vue ou même le son ou le toucher (pour les aveugles ou les sourds), que le robot peut ensuite réduire à un graphique topographique minimal (un peu comme un graphique de waypoint utilisé dans les jeux avec A *), sur lequel des algorithmes de chemin le plus court peuvent ensuite être appliqués. Dans ce cas, alors que la « vision » peut fournir un indice à l'environnement immédiat, il reste résulteune recherche graphique qui fournit finalement le chemin vers l'objectif. C'est proche de la pensée humaine: pour accéder à la cuisine depuis la chambre, je dois traverser le salon. Le fait que je les ai déjà vus et connaisse leurs espaces et leurs portails, c'est ce qui permet ce déplacement calculé. Il s'agit d'une topologie graphique, à laquelle un algorithme de chemin le plus court est appliqué, bien qu'il soit intégré dans une protéine molle plutôt que dans du silicium dur.

Vous pouvez donc voir que les deux premiers dépendent de la connaissance de l'environnement dans son intégralité. Ceci, pour des raisons de coût d'évaluation d'un environnement à partir de zéro, est courant dans les jeux. De toute évidence, le dernier est le plus puissant. Un robot équipé de cette façon (ou par exemple, une IA de jeu qui lit le tampon de profondeur de chaque image) pourrait naviguer suffisamment dans n'importe quel environnement sans en avoir connaissance auparavant. Comme vous l'avez probablement deviné, c'est également de loin la plus coûteuse des trois approches ci-dessus, et dans les jeux, nous ne pouvons généralement pas nous permettre de le faire sur une base par IA. Bien sûr, il est beaucoup moins coûteux en 2D qu'en 3D.

Points architecturaux

Il devient clair ci-dessus que nous ne pouvons pas supposer qu'un seul modèle d'accès aux données correct pour l'IA; le choix dépend de ce que vous essayez de réaliser. L'accès GameWorlddirect à la classe est absolument standard: il vous fournit simplement des informations sur le monde. Il s'agit essentiellement de votre modèle de données, et c'est à cela que servent les modèles de données. Singleton est très bien pour cela.

"obtenir une liste de toutes les entités et rechercher tout ce dont vous avez besoin"

Rien de naïf à ce sujet. La seule chose qui pourrait être naïve est d'effectuer plus d'itérations de liste que nécessaire. Dans la détection de collision, nous évitons cela en utilisant par exemple des arbres quadruples pour réduire l'espace de recherche. Des mécanismes similaires peuvent s'appliquer à l'IA. Et si vous pouvez partager la même boucle pour faire plusieurs choses, faites-le, car les succursales sont coûteuses.


Merci de répondre. Comme je suis un débutant en développement de jeux, je pense que je vais m'en tenir à l'approche simple "obtenir une liste du monde du jeu" pour l'instant :) Une question: dans ma question, j'ai décrit la GameWorldclasse comme la classe qui contient des références à toutes les entités du jeu, et contient également la plupart de la logique importante du «moteur»: la boucle principale du jeu, la détection des collisions, etc. C'est fondamentalement la «classe principale» du jeu. Ma question est: cette approche est-elle courante dans les jeux? Vous avez une «classe principale»? Ou devrais-je le séparer en classes plus petites et avoir une classe en tant que «base de données d'entités» que les objets peuvent interroger?
Aviv Cohn

@Prog Vous êtes les bienvenus. Encore une fois, il n'y a rien dans les approches d'IA ci-dessus (ou aucune autre, d'ailleurs) qui suggère que votre "obtenir une liste du monde du jeu" est en quelque sorte, forme ou forme architecturalement incorrecte. L' architecture de l' IA doit répondre aux besoins de l'IA; mais cette logique, comme vous le suggérez, devrait être modularisée, encapsulée (dans sa propre classe) loin de votre architecture d'application plus large . Oui, les sous-systèmes doivent toujours être pris en compte dans des modules séparés une fois que de telles questions se posent. Votre principe directeur devrait être SRP .
Ingénieur

2

Fondamentalement, j'aurais 2 façons d'interroger des informations.

  1. lorsque l'AIState change parce que vous avez détecté une collision ou un cache quelconque, une référence à n'importe quel objet est important. De cette façon, vous savez de quelle référence vous avez besoin. Lorsque d'autres systèmes doivent exécuter de grandes recherches à chaque image, je vous recommande de les sauvegarder afin de ne pas avoir à effectuer plusieurs recherches. Ainsi, une «collision» détectée avec la zone qui fait une «alerte» ennemie lui envoie un message / événement qui l'enregistre avec cet objet s'il ne l'est pas déjà et change l'état du jeu en un état lui permettant de faire ses affaires en se basant sur ce gamestate. Vous avez besoin d'un événement d'un certain type qui vous indique de faire des changements, je passerais juste une référence dans le rappel que vous utilisez pour donner cette information. C'est plus extensible que d'avoir simplement à traiter avec le joueur. Peut-être que vous voulez qu'un ennemi poursuive un autre ennemi ou un autre objet. De cette façon, il vous suffit de modifier la balise par laquelle vous l'identifiez.

  2. Avec ces informations, vous effectuerez ensuite une requête vers un système de recherche de chemin qui utilise A * ou un autre algorithme pour vous donner un chemin ou vous pouvez l'utiliser avec un comportement de pilotage. Peut-être une combinaison des deux ou quoi que ce soit. Fondamentalement, avec la transformation des deux, vous devriez pouvoir interroger votre système de nœuds ou navmesh et lui donner un chemin. Votre monde de jeu a probablement beaucoup d'autres choses que la voie à suivre. Je soumettrais votre requête à la recherche de chemin uniquement. Le traitement par lots de ces éléments est probablement préférable si vous avez de nombreuses requêtes, car cela peut devenir assez intensif et le traitement par lots améliorera les performances.

    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;
        }
     }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.