Buts:
Nous disposons via la classe ChasingAutomaton d'une version rudimentaire d'« animal » capable de se diriger vers une cible. Il s'agit maintenant d'en faire un Animal et de sophistiquer un peu son comportement.
Concrètement, un Animal va avoir un comportement très voisin d'un ChasingAutomaton, à la différence près :
Vous pouvez donc pour commencer, reprendre dans Animal l'intégralité des méthodes et attributs de la classe ChasingAutomaton en prenant garde de modifier les noms de constructeurs et destructeurs.
Comme pour certaines des caractéristiques du ChasingAutomaton, nous allons faire en sorte que ces caractéristiques soient restituées par des méthodes :
Le constructeur de Animal sera bien sûr mis à jour pour initialiser le rayon de l'animal à ANIMAL_RADIUS.
Vous doterez votre classe animal des méthodes suivantes relatives au vecteur direction :
[Question Q2.7] Pourquoi selon vous est-il préférable de déclarer la méthode setRotation (et par le même raisonnement une éventuelle méthode setPosition de Collider) comme protégée ?
Répondez à cette question dans votre fichier fichier REPONSES et adaptez votre code en conséquence (lorsque vous modifiez une classe déjà codée, n'oubliez pas de relancer les tests la concernant pour vérifier que l'ensemble du code reste fonctionnel).
Pour permettre de vérifier que la mise en oeuvre du déplacement de l'animal est correcte, il sera utile de pouvoir afficher concrètement son champ de vision.
Faites donc en sorte que la méthode draw de Animal, invoque une méthode drawVision affichant le champ de vision.
L'affichage du champs de vision d'un Animal devrait alors ressembler à la partie grisée ci-dessous :
Une méthode buildArc est fournie dans Utility/Utility.[hpp/cpp]. Pour afficher un arc de cercle entre 45° et 135°, en gris transparent, centré en mOrigin, de rayon mRadiusArc, vous écrirez:
sf::Color color = sf::Color::Black; color.a = 16; // light, transparent grey Arc arc(buildArc(45,135, mRadiusArc, mOrigin, color)); targetWindow.draw(arc);
Le fichier de test Tests/GraphicalTests/AnimalTest est fourni pour tester ces derniers développements. Il fonctionne selon des principes analogues à ceux des tests graphiques précédents : AnimalTest hérite de la classe Application .
Par héritage AnimalTest dispose donc d'un attribut de type Environment. La méthode Application::getAppEnv donne accès à cet attribut.
La classe AnimalTest redéfinit la méthode onEvent de sorte à ce que la touche 'T' correponde à appel de la méthode addTarget qui va ajouter une cible à l'environnement à l'endroit où est positionnée la souris. La méthode onSimulationStart de la classe AnimalTest, quant à elle, crée un animal et l'ajoute à la faune du même environnement.
La classe méthode run héritée de Application invoque en boucle le dessin de l'environnement.
[Question Q2.8] Que devez-vous modifier pour que le dessin de l'environnement tienne compte de la présence de l'animal (et donc l'affiche aussi) ?
Répondez à cette question dans votre fichier fichier REPONSES.
Le fichier CMakeLists.txt fourni permet de lancer la compilation du test par le biais de la cible animalTest. N'oubliez pas de décommenter cette cible dans le fichier CMakeLists.txt (lignes 104, 105 et les lignes 146, 147) et d'exécuter Build >Run Cmake lorsque vous êtes prêt à compiler/tester.
Vous devriez voir se produire l'affichage suivant :
Notre Animal a désormais un champ de vision. Il nous faut encore programmer le fait qu'il ne perçoit effectivement que ce qui y est !
Une méthode isTargetInSight prenant en argument la position d'une cible et testant si l'animal la perçoit, compte tenu de son champ de vision, semble donc s'imposer. Cette méthode retournera true si l'Animal «voit» la cible et false sinon.
Soit d⃗ le vecteur x⃗cible - x⃗, où x⃗cible est la position de la cible et x⃗ celle de l'animal, les conditions à remplir pour que ce dernier perçoive la cible sont les suivantes:
Le fichier de test Tests/UnitTests/TargetInSightTest est fourni pour tester ces derniers développements. Il s'agit cette fois d'un test unitaire non graphique (à l'image de ceux utilisés lors de l'étape précédente pour tester la classe Collider).
[Question Q2.9] (avancée) Pour pouvoir tester différentes configurations (cibles dans le champ de vision et hors de celui-ci), il est nécessaire ici de pouvoir faire varier à volonté le vecteur direction de l'animal. Pour se donner cette liberté, le test TargetInSightTest redéfinit une sous-classe de Animal appelée DummyAnimal (juste utilisée pour les besoins du test). Sauriez-vous expliquer pourquoi ? Répondez à cette question, si vous le souhaitez, dans votre fichier REPONSES.
Pour pouvoir lancer ce test, décommenter la cible targetInSightTest dans le fichier CMakeLists.txt (toujours aux deux endroits).
Vous devriez alors obtenir une trace d'exécution ressemblant à ceci :
=============================================================================== All tests passed (9 assertions in 1 test case)
La méthode update, qui met à jour le déplacement de Animal au cours du temps, est pour l'instant identique à celle mise en place pour le ChasingAutomaton :
Le ChasingAutomaton avait connaissance de la cible à atteindre sans que cette dernière ne fasse partie d'un environnement. Les cibles visibles par un Animal lui seront par contre transmises par l'environnement.
Il faut donc adapter la méthode update, dans cet esprit :
Commencez donc par programmer la méthode Environment::getTargetsInSightForAnimal qui prend en argument un pointeur sur un Animal et qui retourne l'ensemble des cibles de l'environnement qu'il voit.
[Question Q2.10] Quel type de retour proposez-vous pour cette méthode ? Répondez à cette question dans votre fichier REPONSES.
Apportez ensuite les modifications nécessaires à la méthode Animal::update.
class Animal;qui prédéclare la classe Animal (indique au compilateur qu'elle sera définie un peu plus tard) et qui résout notre problèmes de dépendances circulaires.
Le fichier de test Tests/GraphicalTests/AnimalTest permet de continuer à tester vos développements.
La classe AnimalTest dispose d'une méthode update héritée de Application. Cette méthode invoque la mise à jour de l'environnement après l'écoulement d'un pas de temps.
[Question Q2.11] Que devez-vous modifier pour que la mise à jour de l'environnement tienne compte de la présence de l'animal (et donc invoque les mises-à-jour nécessaires sur les animaux au bout de l'écoulement d'un pas de temps dt) ? Répondez à cette question dans votre fichier fichier REPONSES.
Vous pourrez relancer le test AnimalTest au moyewn de la cible animalTest.
Positionnez vous avec la souris et utilisez la touche 'T' pour créer des cibles dans l'environnement. Le seul Animal de l'environnement doit y réagir de façon appropriée : si la cible est dans son champ de vision il doit s'y diriger. Sinon il doit rester immobile, tel que montré dans la petite vidéo ci-dessous :
[Video : sensibilité de l'automate au champ de vision] |
Pour l'heure, la méthode update fait en sorte que l'animal se déplace vers une cible de l'environnement. Quand il n'y a aucune cible visible il reste donc immobile.
Il s'agit maintenant d'implémenter la dernière fonctionnalité importante de cette étape: celle permettant à l'Animal de se déplacer au hasard si aucune cible n'est visible.
Pour implémenter cette fonctionnalité nous allons en fait reprendre le même principe que celui de la poursuite d'une cible. La cible sera simplement ici virtuelle : un simple point généré "au hasard" à une certaine distance de l'animal.
Concrètement, l'idée est de générer un point au hasard sur un cercle à une certaine distance de l'animal (la cible est le point bleu dans l'image ci-dessous). Ce point deviendra la nouvelle cible de l'animal (ici simplement représenté par le cercle en rose) :
Au terme de cette étape, l'algorithme Animal::update devra donc être mis à jour comme suit :
Le calcul de la force d'attraction exercée par la cible virtuelle utilise les vecteurs positions exprimées dans le repère global :
Il vous est demandé maintenant de programmer la méthode randomWalk qui met en oeuvre les étapes 4.1 et 4.2 de l'algorithme de Animal::update
Cette méthode utilisera trois nouvelles caractéristiques de l'animal, restituées par des méthodes, pour générer la cible virtuelle :
Soit current_target la cible virtuelle poursuivie par l'animal lors d'un cycle de simulation (qui sera exprimée dans son repère local)
Pour calculer la valeur de current_target à l'étape suivante il faudra :
Soit x⃗cible la conversion de moved_current_target dans le repère global. La force régissant le mouvement est donc simplement x⃗cible - x⃗, où x⃗ est la position de l'animal.
![]() |
![]() |
![]() |
Comme nous l'avons vu précédemment, le calcul de la force d'attraction exercée par la cible virtuelle nécessite de connaître ses coordonnées dans le repère global. Il faut donc procéder à une conversion.
Écrivez une méthode ConvertToGlobalCoord convertissant un Vec2d exprimé dans le repère local d'un Animal, en un Vec2d dans le repère global.
La librairie SFML fournit l'outillage nécessaire au travers de la notion de matrices de transformation. Soit local un Vec2d exprimé dans le repère local d'un Animal. Pour le convertir dans le repère global, il suffit de lui faire subir une translation à la position de l'animal et une rotation de γ, où γ est l'angle du vecteur direction de l'animal . En utilisant les matrices de transformation cela se fait comme suit :
// create a transformation matrix sf::Transform matTransform; // first, translate matTransform.translate(position_animal); // then rotate matTransform.rotate(γ); // now transform the point Vec2d global = matTransform.transformPoint(local);
La translation amène le système de coordonnées au niveau de la position de l'animal, la rotation permet de l'aligner sur la direction de l'animal. On obtient ainsi le repère local de l'animal. La dernière instruction permet de calculer la coordonnée globale du point exprimé dans ce repère local.
Vous pourrez relancer le test AnimalTest comme précédemment:
Votre programme devrait alors avoir un comportement analogue à celui montré par la petite vidéo ci-dessous :
Déplacez la souris et appuyez sur la touche 'T' pour créer une cible dans l'environnement. Le seul Animal de l'environnement doit déambuler au hasard et quand la cible entre dans on champ de vision, il doit s'y diriger.
Pour que le test soit correct, il faut que le déplacement dans un monde torique soit correctement visualisable et qu'appuyer sur la touche 'R' efface les cibles. Notez qu'à ce stade l'animal se dessine en principe toujours chez vous comme un fantôme et la cible virtuelle (bille bleue sur le cercle jaune) n'est pas encore affichée. Quelques indications sont données plus bas pour y remédier. | |
[Video : déplacement aléatoire d'un animal avec champ de vision et cible] |
Pour faciliter la vérification de votre algorithme de déplacement aléatoire, il est utile de faire afficher la cible virtuelle suivie par l'animal en l'absence de cible réelle.
Ajoutez cette facilité à votre programme que devrait alors afficher la cible virtuelle sous le format suivant :
Enfin (facultatif à cette étape), si vous trouvez votre l'aspect de votre "animal" trop .. "vintage video game", vous pouvez lui associer la texture donnée par la constante ANIMAL_TEXTURE.
Afin qu'il soit orienté proprement (c'est à dire que son champs de vision soit plutôt devant lui), vous pouvez utiliser le dernier argument de la fonction buildSprite. Cet argument permet de faire subir à l'image une rotation d'un angle donné.
Votre programme devrait alors avoir le comportement montré par la petite vidéo ci-dessous :