1

Je développe un jeu. Chaque entité dans le jeu est un GameObject. Chaque GameObject est composé d'un GameObjectController, GameObjectModel et GameObjectView. (Ou héritiers de celui-ci.)Jeu AI: Modèle pour la mise en œuvre des composants Sense-Think-Act?

Pour les PNJ, le GameObjectController est divisé en:

IThinkNPC: lit l'état actuel et rend une décision sur ce qu'il faut faire

IActNPC: mises à jour état basé sur ce qui doit être fait

ISenseNPC: lit l'état actuel pour répondre aux questions du monde (par exemple, « suis-je dans l'ombre? »)

Ma question: est-ce ok pour la ISenseNPC interface?

public interface ISenseNPC 
    { 
     // ... 

     /// <summary> 
     /// True if `dest` is a safe point to which to retreat. 
     /// </summary> 
     /// <param name="dest"></param> 
     /// <param name="angleToThreat"></param> 
     /// <param name="range"></param> 
     /// <returns></returns> 
     bool IsSafeToRetreat(Vector2 dest, float angleToThreat, float range); 

     /// <summary> 
     /// Finds a new location to which to retreat. 
     /// </summary> 
     /// <param name="angleToThreat"></param> 
     /// <returns></returns> 
     Vector2 newRetreatDest(float angleToThreat); 

     /// <summary> 
     /// Returns the closest LightSource that illuminates the NPC. 
     /// Null if the NPC is not illuminated. 
     /// </summary> 
     /// <returns></returns> 
     ILightSource ClosestIlluminatingLight(); 

     /// <summary> 
     /// True if the NPC is sufficiently far away from target. 
     /// Assumes that target is the only entity it could ever run from. 
     /// </summary> 
     /// <returns></returns> 
     bool IsSafeFromTarget(); 
    } 

Aucune des méthodes ne prend de paramètres. Au lieu de cela, l'implémentation doit conserver une référence à la GameObjectController pertinente et lire cela.

Cependant, j'essaie maintenant d'écrire des tests unitaires pour cela. De toute évidence, il est nécessaire d'utiliser le moqueur, car je ne peux pas passer directement les arguments. La façon dont je le fais se sent vraiment fragile - et si une autre implémentation se présente qui utilise les utilitaires de requête du monde d'une manière différente? Vraiment, je ne teste pas l'interface, je suis en train de tester l'implémentation. Pauvre.

La raison pour laquelle je ce modèle en premier lieu était de garder IThinkNPC code d'implémentation propre:

public BehaviorState RetreatTransition(BehaviorState currentBehavior) 
    { 
     if (sense.IsCollidingWithTarget()) 
     { 
      NPCUtils.TraceTransitionIfNeeded(ToString(), BehaviorState.ATTACK.ToString(), "is colliding with target"); 
      return BehaviorState.ATTACK; 
     } 

     if (sense.IsSafeFromTarget() && sense.ClosestIlluminatingLight() == null) 
     { 
      return BehaviorState.WANDER; 
     } 

     if (sense.ClosestIlluminatingLight() != null && sense.SeesTarget()) 
     { 
      NPCUtils.TraceTransitionIfNeeded(ToString(), BehaviorState.ATTACK.ToString(), "collides with target"); 
      return BehaviorState.CHASE; 
     } 
     return currentBehavior; 
    } 

Peut-être que la propreté ne vaut pas, cependant. Donc, si ISenseNPC prend tous les paramètres dont il a besoin à chaque fois, je pourrais le rendre statique. Y a-t-il un problème avec ça?

Répondre

2

NO. Non non Non. Vous créez un nombre ridicule de dépendances cachées (et non cachées) dans votre IA. Tout d'abord, MVC n'est pas vraiment un bon modèle à utiliser ici, car il n'y a vraiment pas de "vue" dont vous devez vous soucier, il n'y a que des actions. De plus, votre "modèle" ici est vraiment l'état du monde tel que connu par l'IA à l'époque, qui est entièrement séparé de l'IA elle-même, bien que cela puisse être considéré comme une "vue" du monde du jeu en termes de un instantané des positions et des attributs de votre objet (je l'ai fait de cette façon, était très efficace). Le problème principal, cependant, est que votre code de retreatTransition est fortement couplé aux actions et à l'état. Que se passerait-il si vous deviez faire un changement? Et si vous aviez besoin de 200 types différents d'IA qui sont tous similaires, comment maintenez-vous cela? La réponse est que vous ne pourriez pas, ce serait un gâchis. Vous créez effectivement une machine d'état ici et les machines d'état ne se développent pas bien. En outre, vous ne pouvez pas ajouter/modifier/supprimer un état de votre machine sans modifier le code.

Ce que je recommanderais est plutôt de penser à passer à une architecture différente. Votre approche TDD est excellente, mais vous devez prendre du recul et examiner différentes architectures d'IA et comprendre les concepts de base avant de faire un choix. Je commencerais par regarder l'excellent article de Jeff Orkin "3 états et un plan" qui traite de l'architecture basée sur les objectifs de F.E.A.R. (http://web.media.mit.edu/~jorkin/goap.html). J'ai déjà implémenté cela auparavant et c'était très efficace et stupide - facile à concevoir et à maintenir.Sa conception de base faciliterait également TDD (en fait BDD est un meilleur choix) assez bien.

Autre chose: Votre ISenseNPC ressemble à son état mondial. Les percepts de votre IA (choses qu'elle peut observer du monde) devraient être complètement séparés, donc cela me dit que vous devriez avoir une classe WorldModel ou quelque chose qui est passée à l'objet ISenseNPC, qui inspecte alors le WorldModel pour l'information via ses percepts (pensez à un percept comme une façon dont l'IA peut percevoir le monde, quelque chose comme un capteur, un rayon de vision, un sonar, etc.) et vous pouvez même créer des perceptions individuelles et les ajouter à votre ISenseNPC l'état du monde, la façon dont une IA perçoit ce monde, et ensuite la compréhension qu'ont les IA du monde lui-même. À partir de là, votre IA peut prendre des décisions sur ce qu'elle devrait faire.

Vous modélisez un agent réflexe simple, qui est juste un ensemble de règles qui répondent à une séquence de perceptions donnée, ce qui est parfait pour une IA simple. Il s'agit essentiellement d'une machine d'état glorifiée, mais vous pouvez ensuite créer une correspondance entre les percepts et les comportements de votre objet Think, qui pourrait être géré séparément et modifier ce mappage ou l'étendre ne nécessiterait pas de changement de code (Principe de responsabilité unique au travail). En outre, vous pouvez créer un éditeur de jeu qui peut énumérer tous les percepts, décisions et actions et les lier ensemble pour n'importe quelle IA donnée, ce qui vous faciliterait la maintenance de vos IA sans avoir à rentrer dans le jeu ou même reconstruire le code. . Je pense que vous trouverez cela beaucoup plus flexible et maintenable que ce que vous essayez de faire ici. Ditch MVC pour cette chose particulière, MVC est très bien adapté aux graphismes et dans une moindre mesure la physique, mais il ne correspond pas très bien à l'IA car AI n'a pas vraiment une "vue". S'il vous plaît laissez-moi savoir si vous avez d'autres questions à ce sujet, j'ai eu une certaine expérience dans la mise en œuvre d'une architecture basée sur les objectifs pour un jeu ainsi que d'autres choses et je serais heureux de vous aider.