Aller au contenu

daemondragon

Melinyen
  • Compteur de contenus

    31
  • Inscription

  • Dernière visite

  • Days Won

    2

Tout ce qui a été posté par daemondragon

  1. Totalement partant pour le projet communautaire (je pense qu'un mini-jeux serait plus interressant qu'un logiciel, donc plus de personnes voudrait y participer)
  2. c'est Cinnamon
  3. Le mien est bizzarre mais bon : http://imageshack.com/a/img674/4006/ml2G1J.png
  4. Bienvenue Mini
  5. daemondragon

    PlayOnLinux

    excellent partage ! +1 rep
  6. daemondragon

    Le nouvel iPhone 6

    Sur papier, l'Iphone 6 à l'air bien, mais dès que l'on lit ça : https://plus.google.com/+RonAmadeo/posts/fGooKLxjbqT, ça calme direct !
  7. Moi aussi j'ai cliqué sur le bouton !
  8. daemondragon

    Moteur physique #3

    Et voila la fin de la création d'un moteur physique ! Il ne nous restent plus qu'a détecter les collisions et les corriger, et le moteur est finit. :lol: Détection des collisions : La théorie : Pour que deux rectangles soit en collisions, il faut qu'ils se "rentrent" dedans. Pour cela, la distance (sur un seul axe pour l'instant) entre les deux points doit être inférieur à la somme de la moitié de leur largueur (dans le cas de l'axe x, la hauteur pour l'axe y). C'est pour cela que dans le précédent tuto ( lors de la création de corps, on ne gardait que la moitié de la largueur et de la hauteur. Ont peut ainsi tester chaque axe pour voir s'il y a une collision sur celui-ci. S'il n'y a une collision que sur un seul axe, alors l'objet n'est pas en collision avec l'autre, il est simplement à coté. Une magnifique image qui résume cela : La pratique : En code, on teste chaque axe l'un après l'autre. Si sur le premier axe il n'y a pas de collision, alors on a pas besoin de tester le deuxième. float mlb_abs(float number) { if (number < 0) { number *= -1; } return (number); } float penetration_x(t_body *f_body, t_body *s_body) { return (mlb_abs(f_body->box.x - s_body->box.x) - (f_body->box.w + s_body->box.w)); } float penetration_y(t_body *f_body, t_body *s_body) { return (mlb_abs(f_body->box.y - s_body->box.y) - (f_body->box.h + s_body->box.h)); } char is_collide(t_body *f_body, t_body *s_body) { if (penetration_x(f_body, s_body) <= 0. && penetration_y(f_body, s_body) <= 0.) { return (1); } return (0); } La fonction mlb_abs est strictement équivalente a abs() de math.h, mais à chaque fois que le moteur serait inclus dans un projet, il faudrait linker la biblio, et c'est pas pratique, donc créer la fonction est pour moi plus simple. Vous pouvez évidement la remplacer par celle de math.h si cela vous arrange. On calcule donc les collisions sur chaque axe, et la fonction renvoie 1 en cas de collision. Correction des collisions : Il faut tester tout les corps entre eux pour savoir s'il y a une collision. Je vous rappelle que deux corps statiques ne seront jamais en collisions, donc on ne vas pas les tester entre eux. void check_collision(t_physic_world *world) { t_body *update = world->dynamic_body; t_body *collide_with = world->static_body;; update = world->dynamic_body; while (update != NULL) { /*collide with static body*/ while (collide_with != NULL) { if (is_collide(update, collide_with)) { correct_collision(update, collide_with); } collide_with = collide_with->next; } /*collide with dynamic body*/ collide_with = update->next; while (collide_with != NULL) { if (is_collide(update, collide_with)) { correct_collision(update, collide_with); } collide_with = collide_with->next; } update = update->next; } } On teste d'abord les collisions avec les corps statiques, car cela permet d'éviter que le corps soit repoussé a travers un mur s'il est poussé par un corps dynamique, ce qu'il ne faut surtout pas. Et maintenant, il faut pouvoir corriger ces collisions une fois quelle sont détectées ! Tout d'abord, les corrections ne seront pas les mêmes en cas de collisions dynamique / statique que des collisions dynamique / dynamique. Les première collisions pousseront le corps dynamique hors du corps statique alors que les deuxième collisions, les deux corps se pousseront mutuellement, et celui avec la plus grande force l'emportera. Il faut d'abord dispatcher les corps dans les bonnes collisions : void correct_collision(t_body *f_body, t_body *s_body) { if (f_body->dynamic && !s_body->dynamic) { correct_static_collision(s_body, f_body); } else if (!f_body->dynamic && s_body->dynamic) { correct_static_collision(f_body, s_body); } else if (f_body->dynamic && s_body->dynamic) { correct_dynamic_collision(f_body, s_body); } } Normalement, les collisions entre corps statique et dynamique se font dans cette ordre dans la fonction, mais cela permet de tester les collisions entre un corps qui n'appartient pas a la structure physic_world avec ce que vous voulez, sans vous souciez de l'ordre dans lequel les corps doivent être mis. On va commencer avec les collisions avec des corps statiques : void correct_static_collision(t_body *static_body, t_body *dynamic_body) { if (penetration_x(static_body, dynamic_body) > penetration_y(static_body, dynamic_body)) { /*x axis penetration*/ float direction = dynamic_body->force.total_force.x / mlb_abs(dynamic_body->force.total_force.x); dynamic_body->box.x = static_body->box.x - (dynamic_body->box.w + static_body->box.w) * direction; add_contact_force(&(dynamic_body->force), -dynamic_body->force.total_force.x, static_body->force.contact_force.y); } else { /*y axis penetration*/ float direction = dynamic_body->force.total_force.y / mlb_abs(dynamic_body->force.total_force.y); dynamic_body->box.y = static_body->box.y - ((dynamic_body->box.h + static_body->box.h) * direction); add_contact_force(&(dynamic_body->force), static_body->force.contact_force.x, -dynamic_body->force.total_force.y); } } On corrige la collision en fonction de l'axe ou le corps est le moins en collision, afin de ne pas voir un corps se faire repousser violemment alors que l'on peut faire une chose moins visible. La fonction est simple : 1ere ligne ont calcule la direction de la collision, deuxième ligne, on la corrige, et troisième ligne, on met la force contraire de l'axe ou il y a eut la collisions dans le corps dynamique afin de l’empêcher de passer a travers. Mais pourquoi le faire, si on corrige simplement, il ne passera pas non plus non ? Ce qu'il faut savoir, c'est que le moteur physique ne peut pas être sur qu'il va être appelé suffisamment pour éviter l’effet "tunnel", le corps passe alors a travers alors qu'il aurait du y avoir une collisions. Si l'ont ajoute une force contraire mais de contact, elle ne disparaîtra que lorsque le moteur physique sera remis a jour, donc on pourra alors recorriger la collision. De plus on peut voir que dans l'axe ou il n'y a pas de colliisons, on rajoute un force. Mais qu'est ce que c'est ? C'est le méga truc de la mort qui tue du deuxième tuto ! Grâce a cela, on peut rajouter facilement des tapis roulant et des plate-formes mobiles, alors que sinon, dans les autres moteurs physique, ont doit tricher en rajoutant une force lorsque le personnage se trouve sur celui-ci. Evidemment, la force de contact n'est là que si vous l'ajouter, elle est de base à 0. Et maintenant, la correction la plus difficile : les collisions dynamiques / dynamiques dont voici la fonction : void correct_dynamic_collision(t_body *f_body, t_body *s_body) { t_force common_force; t_force total_force; float weight; if (penetration_x(f_body, s_body) > penetration_y(f_body, s_body)) { /*x axis penetration*/ /*calculate the total force for the total body and divise it*/ calc_total_force(&(f_body->force)); calc_total_force(&(s_body->force)); total_force.x = f_body->force.total_force.x; total_force.y = s_body->force.total_force.x; common_force.x = total_force.x + total_force.y; weight = f_body->weight + s_body->weight; common_force.y = common_force.x / weight * s_body->weight; common_force.x = common_force.x / weight * f_body->weight; add_contact_force(&(f_body->force), common_force.x - total_force.x, 0); add_contact_force(&(s_body->force), common_force.y - total_force.y, 0); /*correct collision*/ float average_x = (f_body->box.y * s_body->weight + s_body->box.y * f_body->weight) / weight; float direction = average_x - f_body->box.x; if (direction < 0) { direction = -1; } else { direction = 1; } f_body->box.x = average_x - (f_body->box.w * direction); s_body->box.x = average_x + (s_body->box.w * direction); } else { /*y axis penetration*/ /*calculate the total force for the total body and divise it*/ calc_total_force(&(f_body->force)); calc_total_force(&(s_body->force)); total_force.x = f_body->force.total_force.y; total_force.y = s_body->force.total_force.y; common_force.y = total_force.x + total_force.y; weight = f_body->weight + s_body->weight; common_force.x = common_force.y / weight * f_body->weight; common_force.y = common_force.y / weight * s_body->weight; add_contact_force(&(f_body->force), 0, common_force.x - total_force.x); add_contact_force(&(s_body->force), 0, common_force.y - total_force.y); /*correct collision*/ float average_y = (f_body->box.y * s_body->weight + s_body->box.y * f_body->weight) / weight; float direction = average_y - f_body->box.y; if (direction < 0) { direction = -1; } else { direction = 1; } f_body->box.y = average_y - (f_body->box.h * direction); s_body->box.y = average_y + (s_body->box.h * direction); } } Les fonctions sont exactement les mêmes sur les deux axes, il n'y a que des ajustement au niveau des axes a faire. La fonction marche comme cela : elle calcule les forces des deux corps, calcule la moyenne de l'endroit de leur collisions en fonctions de la force de l'autre corps (car plus un corps aura de force, moins il devra être bougé car c'est lui le plus fort). On transfère les forces dans les deux corps afin qu'ils aient la même vitesse en sortie, le tout dans des forces de contact, cela permet de faire des caisses que l'ont pourra pousser ! Et ont corrige a la fin la collision, et c'est bon. Malgré que la fonction soit longue, elle reste relativement simple. Et voila, le moteur physique est fini ! J'espère qu'il vous aura plus, et que vous vous en servirez
  9. a voté !
  10. daemondragon

    Moteur physique #2

    je m'en occuperait
  11. daemondragon

    Moteur physique #2

    Maintenant que l'on peut appliquer une force, il faut pouvoir la rattacher à un corps . On peut distinguer, là encore, deux types de corps : les dynamiques et les statiques. Les corps dynamiques seront les seuls qu'il est possible de bouger via des forces, les statiques seront considérés comme des élément du décors, il seront tout le temps immobiles (sauf si vous modifier leur position manuellement bien sur). Ainsi, les personnages, les caisses... seront dynamiques, et les élément du décors seront statiques. Voici donc la structure d'un corps : typedef struct s_body { char dynamic; float weight; t_collision_box box; t_force_container force; struct s_body *next; }t_body; Le poids (weight) sert à savoir à quel point l'objet sera dur a bouger. Ainsi, un objet de 40 kg sera deux fois plus facile à bouger qu'un objet de 80 kg. il faudra donc deux fois moins de force. Et voici donc l'unité des forces : Mètre par secondes par kilogrammes. (40 de force pour bouger un corps de 40 kg à 1m/s) la collision box, servira plus tard pour déterminer les collisions entre les corps, et le tout en liste chainnée, afin de pouvoir les stoker facilement dans une structure world : typedef struct s_physic_world { t_body *static_body; t_body *dynamic_body; }t_physic_world; Il faut faire la différence entre les corps afin d'éviter de faire des tests qui ne servent à rien (par définition, deux corps statiques ne sont jamais en collisions) Tout d'abord, la fonction de création de corps : t_body *create_static_body(float x, float y, float w, float h) { t_body *body = malloc(sizeof(*body)); if (body != NULL) { body->box.x = x; body->box.y = y; body->box.w = w / 2; body->box.h = h / 2; body->dynamic = 0; body->weight = 1; body->force.permanent_force = NULL; set_contact_force(&(body->force), 0, 0); body->force.total_force.x = 0; body->force.total_force.y = 0; body->next = NULL; } return (body); } t_body *create_dynamic_body(float x, float y, float w, float h) { t_body *body = malloc(sizeof(*body)); if (body != NULL) { body->box.x = x; body->box.y = y; body->box.w = w / 2; body->box.h = h / 2; body->dynamic = 1; body->weight = 1; body->force.permanent_force = NULL; set_contact_force(&(body->force), 0, 0); body->force.total_force.x = 0; body->force.total_force.y = 0; body->next = NULL; } return (body); } les deux fonctions sont strictement les mêmes, sauf une initialisation d'une variable. Vous pouvez facilement réunir le tout en une fonction, mais j'ai garder cette présentation car plus claire à utiliser. La fonction retourne un pointeur sur la structure afin de pouvoir la modifier et de l'ajouter à la structure physic_world (la fonction va venir, ne vous inquiétez pas). Il faut initialiser toutes les forces à 0, ont met le poids à 1 par défaut (ne jamais mettre le poids à 0, sinon il va y avoir un problème de division par 0) Il faut aussi savoir que pour déterminer les collisions, on à besoin du centre du corps, pas un de ces coins. On à aussi besoin que de la moitié de la hauteur et de la largeur, mais la conversion est faites dans la fonction, pour plus de simplicité. Et surtout pour ceux qui veulent la structure collision box : typedef struct s_collision_box { float x; float y; float w; float h; }t_collision_box; il faut maintenant aussi, pouvoir détruire une entité : void delete_body(t_body *body) { if (body != NULL) { t_force *remove = body->force.permanent_force; while (remove != NULL) { body->force.permanent_force = remove->next; free(remove); remove = body->force.permanent_force; } free(body); } } On doit enlever toutes les forces permanentes afin d'éviter les fuites de mémoires ! De plus, l'objet doit aussi avoir le poids que vous voulez : void add_weight(t_body *body, float weight) { if (body != NULL) { if (weight <= 0) { weight = 1; } body->weight = weight; } } Mais ce n'est pas finit ! Il faut aussi pouvoir ajouter le corps à la structure world. La fonction pour mettre a jour tout les corps ne va pas attendre qu'on lui passe tout les objets 1 par 1 ! Cela possède plusieurs avantages : On n'envoie qu'une grande structure au lieu de plein de petites, et vous pourrez bénéficier de la détection de collisions sans que l'objet soit présent physiquement : cela permet de faire des capteurs, de savoir si on est dans de l'eau sans autant repousser le personnage etc... Bref suit les fonctions d'initialisation, de suppression du monde physique et d'ajout de corps, rien de bien complexe, presque un copier coller des forces : t_physic_world *create_physic_world() { t_physic_world *world = malloc(sizeof(*world)); if (world != NULL) { world->static_body = NULL; world->dynamic_body = NULL; } return (world); } void delete_physic_world(t_physic_world *world) { if (world != NULL) { t_body *remove = world->static_body; while (remove != NULL) { world->static_body = remove->next; delete_body(remove); remove = world->static_body; } remove = world->dynamic_body; while (remove != NULL) { world->dynamic_body = remove->next; delete_body(remove); remove = world->dynamic_body; } free(world); } } char add_to_physic_world(t_physic_world *world, t_body *body) { if (world != NULL && body != NULL) { if (body->dynamic) { body->next = world->dynamic_body; world->dynamic_body = body; } else { body->next = world->static_body; world->static_body = body; } return (1); } else { return (0); } } Les corps sont encore empilés, car c'est toujours plus simple à faire Et maintenant, on peut mettre a jour touts les corps : void update_physic_world(t_physic_world *world, int millisecond_time) { t_body *update = world->dynamic_body; while (update != NULL) { update_force(update, millisecond_time); update = update->next; } } void update_force(t_body *body, int millisecond_time) { calc_total_force(&(body->force)); body->force.total_force.x *= (millisecond_time / 1000.0) / body->weight; body->force.total_force.y *= (millisecond_time / 1000.0) / body->weight; body->box.x += body->force.total_force.x; body->box.y += body->force.total_force.y; set_contact_force(&(body->force), 0, 0); } Je vous rappelle que seuls les corps dynamiques peuvent être en mouvement, donc il n'y a pas de raison de mettre a jours les corps statiques. La fonction attend un temps en millisecondes car la SDL utilise cette unité, mais vous pouvez changer l'unité de temps, il faudra juste adapter en conséquence. Mais vous avez sûrement remarquer la présence de la structure force_container, même chez les corps statiques, alors pourquoi ne pas l'enlever chez eux ? Tous simplement parce que sinon on doit dupliquer le code en deux : corps statiques et corps dynamiques, que c'est plus complexe à faire, que les risque de bug sont multiplier par deux, et surtout ça empêche le méga truc de la mort qui tue, mais que vous ne saurez pas dans ce tutos. Les forces de contacts sont remisent à 0 a chaque fois car on les recréer aussi à chaque fois. Et voilà, vous pouvez bouger vos personnages comme vous voulez, mais il n'y a toujours pas de collisions !
  12. le topic à l'air sympa ! ( il était bon le muffin ? )
  13. daemondragon

    Moteur physique #1

    Je vais faire dans cette série de tutoriels, une construction suivit d'un moteur physique. Tout d'abord : Qu'est ce qu'un moteur physique ? Un moteur physique, est un programme qui permet de gerer la physique correctement, et qui déplace les personnages, tout en gérant les collisions. (tout les problèmes de collisions d'un jeu viennent du moteur physique). Ce qu'il faut savoir avant de commencer à le faire : Le moteur physique, que je vais vous apprendre à faire, ne gérera que des rectangles, il est donc parfait pour les jeux de plate-formes, de type mario, mais aussi d'autre styles de jeu avec que des blocs (les jeux en tiles quoi) comme zelda. Et oui le moteur ne sera qu'en 2d ! De plus, le principe majeur d'un moteur physique est la réutilisabilité de celui-ci, ce qui veut dire pas de : IF MUR THEN ... L'architecture que nous allons utilisé est celle dite "à posteriori" : -première étape, on génère des forces, comme la gravité, les forces de mouvement... -deuxième étape, on applique ces forces. -troisième étape, on détecte les collisions. -dernière étape, on les corrige. L'autre architecture, aussi appelé "à priori", utilise des matrices de résolution, afin de détecter l'instant exact de la collision. Cette architecture est plus précise, mais elle est aussi beaucoup plus lente, c'est pourquoi AUCUN moteur physique de jeu vidéo ne l'utilise, l'architecture "à posteriori" offrant des résultats satisfaisants. (Ce n'est qu'en cas extrêmes que l'ont remarque des problèmes, mais on ne les vois pas souvent). Et on va donc commencer par créer un générateur de force, et qui va les stoker dans une structure, afin de les garder en mémoire. (On attachera ensuite cette structure à un corps, afin de le déplacer) typedef struct s_force_container { t_force *permanent_force; t_force contact_force; t_force total_force; }t_force_container; Tout d'abord, j'ai distingué deux type de forces : les permanentes (la gravité, les mouvements...), et les forces de contact. celle là seront régénérées à chaque fois que le moteur se mettra à jour, donc on ne va pas créer de listes chaînées pour ça, car le risque qu'un malloc échoue devient très vite important, donc on va uniquement se contenter d'additionner les valeurs entre elles. Les forces permanentes, elles, seront stockées dans la liste chaînées, afin de pouvoir en modifier une sans impacter les autres. La force total_force, elle sert uniquement a connaître le total des forces permanentes et de contact, afin de bouger le corps en conséquences. Les forces sont représentées par la structure suivantes : typedef struct s_force { float x; float y; struct s_force *next; }t_force; Les forces en x représente normalement l'axe horizontal et l'axe x, vertical. Il n'y a pas de sens ou de direction pour les axes, vous pouvez prendre celui que vous voulez, il faudra juste adapter les forces en conséquence. il nous faut maintenant une fonction afin d'ajouter ou d'enlever une force permanente. La fonction d'ajout : t_force *create_permanent_force(t_force_container *container, float x, float y) { t_force *add = malloc(sizeof(*add)); if (add != NULL) { add->x = x; add->y = y; add->next = container->permanent_force; container->permanent_force = add; } return (add); } la fonction retourne un pointeur vers la force, afin de la modifier quand vous voulez. Les forces sont empiler les une à la suite des autres (l'ordre d'empilage n'a absolument aucune importance, c'est juste plus simple à programmer ) Et maintenant la fonction "d'enlevage" : void delete_permanent_force(t_force_container *container, t_force *remove) { if (container != NULL) { if (container->permanent_force != NULL) { t_force *search = container->permanent_force; char finish = 0; if (search == remove) { container->permanent_force = remove->next; free(remove); finish = 1; } while (search->next != NULL && !finish) { if (search->next == remove) { search->next = search->next->next; free(remove); finish = 1; } } } } } Le seul truc dur a faire ici, c'est de penser a enlever la force, tout ne perdant aucun pointeur vers les prochaines forces, tout en ne faisant pas de segfault. On s’occupe maintenant des forces de contact, les plus faciles . pour en ajouter : void add_contact_force(t_force_container *container, float x, float y) { if (container != NULL) { container->contact_force.x += x; container->contact_force.y += y; } } il n'y a pas de fonction pour retirer une force, car pour retirer les forces directement additionner, il suffit juste d'ajouter son contraire. Mais en revanche, il faut une fonction pour remettre à un certain nombre cette force, car je vous rappelle, il faut réinitialiser les forces de contacts a chaque mise à jour (bah oui sinon vous imaginez, il faudrait retenir a cause de quoi cette force de contact a été créer, vérifier si elle a toujours raison d’exister, le tout avec beaucoup de corps en mouvement, et le moteur va ramer très vite. Bref la fonction : void set_contact_force(t_force_container *container, float x, float y) { if (container != NULL) { container->contact_force.x = x; container->contact_force.y = y; } } Toujours aussi simple, normalement pas de difficulté a comprendre . Et enfin, il nous reste une dernière fonction a voir : celle qui calcule la somme de toutes les forces, afin de savoir la force finale pour bouger le corps. Elle parcours la liste chaînées des forces permanentes, les additionnent, puis ajoute les forces de contacts et met le résultat dans total_force : void calc_total_force(t_force_container *container) { if (container != NULL) { float x = 0; float y = 0; t_force *force = container->permanent_force; while (force != NULL) { x += force->x; y += force->y; force = force->next; } x += container->contact_force.x; y += container->contact_force.y; container->total_force.x = x; container->total_force.y = y; } } Et voilà ! on peut maintenant ajouter ou enlever facilement des forces, permanentes où de contact. Évidement, le code comme cela ne sert absolument a rien, il faut ajouter la notion de corps pour comprendre à quoi cela va servir.
  14. De même
  15. Nouveau record sur Smash hit (toujours non premium et sans cheat) : 13236 Et oui, je suis un gros geek !
  16. Je propose de rajouter yarrr! (pirates vs zombies) record : 149 et smash hit : 7750 (désolé mars )
  17. Bonne vacances Soulalex !
  18. Excellent tuto azad ! Il existe aussi un logiciel windows pour faire ça sans passer par des clé : http://ubuntulinuxx.wordpress.com/2013/07/16/how-to-safely-uninstall-linux-mint-from-dual-boot-windows-7/ (l’exécutable est sûr, je l'ai déjà testé)
  19. android pour moi (mais version 2.3 !)
  20. daemondragon

    Centre des tutoriels

    tutoriel : programmation->C
  21. daemondragon

    Vie privée

    personnellement, ce qui m'inquiète le plus sur la vie privée sur internet, c'est que 90% des personnes n'en n'ont absolument rien à faire d'être espionner, qu'on transmette toute leur donnée. Alors que les 10% restant, eu veulent conserver la leur. Et au nom de la lutte anti -terroriste, et aussi celle de la majorité qui s'en foutent absolument, on continu. Le pire, c'est aux infos (TF1 ou autre) : personne n'a entendu parler des loi internet genre ACTA (loi de surveillance) etc.. , alors qu'ils sont tous au courant que dans une X petite ville, il y a un marchand qui fait bien du saucisson par exemple. explication de la loi acta : https://www.youtube.com/watch?v=yTH2TDUSmPQ
  22. daemondragon

    IA #4 - le GOAP

    Bonjour a tous ! Tous d'abord qu'est ce que le GOAP ? C'est l'abréviation de Goal Oriented Action Planning, ce qui pour les non anglophone, veut dire la planification des actions en fonction de tes buts, ou de tes souhaits. C'est un type d'IA qui a pour but de remplacer les FSMs (Finite State Machine), les automates a états finis en gros. (Le mot anglais est plus classe et plus court a écrire donc c'est celui là que je vais utiliser maintenant). En effet, les FSMs sont bêtes et méchantes, bien que facile a réaliser. Mais dès qu'il s'agit d'un comportement où il faut que l'IA ai l'air de réfléchir, les FSMs sont inadaptées. Ainsi les GOAPs sont utilisés principalement dans les FPS, et aussi dans le mondialement connu Starcraft. Cette IA a pour but d'offrir plus de challenge au joueur. Mais rien ne vous empêche de faire une IA bien plus intelligente dans les autres types de jeu comme les RPG, ou les rogues like. Le principe des GOAP maintenant : Un pnj a différents but, comme manger, dormir, travailler etc... chaque but a un niveau d'importance plus ou moins grand. schéma : but : importance dormir : 4 manger : 2 travailler : 1 Ici par exemple le pnj a très envie de dormir. Les niveaux d'importance sont mis a jour au fil du temps et les actions les plus importantes sont exécutées en premier. En code : typedef struct t_state { unsigned char id; unsigned char importance; }t_state; t_state but[nombre_de_but_different]; /* on initialise le tout int i; for( i = 0 ; i < nombre_de_but_different ; i++ ) { but[i].id = i; but[i].importance = 0; } L'id sert a savoir le but ( par exemple dormir c'est 0, manger c'est 1 et travailler c'est 3) parce qu’à chaque fois que l'importance d'un but change, il faut retrier le tableau. Regardez du coté des algorithme de tri, et n'oubliez pas que le tableau est presque trier, seul un élément est a sa mauvaise place. Une fonction de tri en exemple (oui c'est utile de faire cela) void tri(t_state but[], int nb_but) { /* Il s'agit ici d'un tri à bulle qui marche, mais qui est le plus lent possible c'est pour vous forcer à regarder du côté des algo de tri*/ int changement = 1; int i; t_state transition; while(changement) { changement = 0; for(i = 0 ; i < nb_but - 1 ; i++) { if(but[i].importance < but[i+1].importance) { transition.importance = but[i].importance; transition.id = but[i].id; but[i].importance = but[i+1].importance; but[i].id = but[i+1].id; but[i+1].importance = transition.importance; but[i+1].id = transition.id; changement = 1; } } } } On regarde donc le premier élément du tableau, qui est le plus important, et on exécute l'action associé. Et la vous allez me dire : "mais alors c'est la même chose que les FSMs non ?" Et bah non : les état ne sont plus reliés entre eux, donc l’insertion d'un nouveau but ne nécessite pas de tout revoir, mais en contre partie, cela nécessite plus de temps de processeur pour faire cela. Et aussi, une grande nouveauté : pourquoi ce contenter d'UNE action par but ? Si vous avez faim, vous pouvez vous faire vous même à manger, aller chez quelqu'un, ou encore aller dans un fast-food. il y a donc plusieurs solutions possibles. Pour chaque action, il suffit de déterminer son poids : Celui-ci peut varier en fonction de plusieurs facteurs, comme le temps mis à faire l'action, le prix que cela va nous coûter etc... Et il faut aussi déterminer l'importance que vont perdre les but. Par exemple : fastfood : manger - 2 maison : manger - 4 On peut aussi choisir une des deux actions en fonction du poids en utilisant le principe des algorithmes de Dijkstra (parcours de graphe), qui est le même principe que le pathfinding. L'avantage, c'est de pouvoir avoir une plus grande flexibilité. Il faut alors voir les actions comme un enchaînement d'actions, et plusieurs actions peuvent menées a la même suivante. Le poid peut être en fonction de plusieurs choses, comme le temps, et l'importance que cela va perdre. Par exemple, si l'on a très peu de temps et on est juste à coté du fast-food, alors on va y allez, et si on a tous notre temps, et juste à coté de chez soi, autant allez à sa maison. Le poid peut aussi être influencer par l'argent dépensé etc... exemple : /*on calcule le poid de chaque action*/ poid[nb_action_differentes]; int i; for ( i = 0 ; i < nb_action_differente ; i++) { poid[i] = (temps * 0,5) /* plus le temps est court, mieux c'est. 50% d'importance pour le temps*/ + (diminution_de_l_importance * 0,5); } je suis parfaitement conscient que le temps a un poid totalement aléatoire en fonction de l'unité utilisé. C'est à vous de calibrer correctement le calcul du poid. il ne s'agit ici que du principe général. Dans ce cas, plus le poid est grand, plus le temps pris est long. Attention, la variable diminution_de_l'importance est négative (-2, -4 etc...) ! int meilleur_poid; int meilleur_poid = poid[0];/*C'est très important d'initialiser !*/ int meilleure_action; for ( i = 0 ; i < nb_action_differente ; i++) { if(poid[i] < meilleur_poid) { meilleur_poid = poid[i]; meilleure_action = i; } } et on fait l'action : action[meilleure_action](); Je considère ici, que l'on envoie rien a la fonction, mais vous pouvez facilement le faire. Le tableau est un tableau de pointeur de fonctions (voir le tutoriel d'AlexMog à ce sujet). Pensez à updater ce tableau régulièrement tout les X secondes ou millisecondes, car une action peut à un moment être plus utile que celle que vous voulez faire, alors qu'avant, elle ne servait à rien. Vous avez donc maintenant réussi a faire un pnj qui pourra avoir des buts et faire des actions en conséquence. N'oubliez pas pas que plus vous augmenter le nombres de buts et d'actions, plus le pnj sera réaliste, et plus cela prendra de temps pour "réfléchir"(pas beaucoup non plus mais sur des milliers de pnjs, cela se ressent). Par exemple, le FPS F.E.A.R (j’insiste sur le mot FPS là !) possède 67 buts différents (pas un seul où il le but est de tuer l’ennemi ) et 197 actions différentes.
  23. Akimace, ton image est absolument magnifique, mais on à l'impression que le M n'appartient pas à l'image : on dirait qu'il "vole". Le mieux (selon moi) serait de mettre aussi le relief sur le M, comme sur l'étiquette derrière.
  24. Qu'est ce que les mécanismes de groupe ? Ce sont les actions qui se passent lorsque vous êtes avec un nombre important de personnes. Ce tutoriel va mettre en place l'algorithme flack and swarm. Cet algorithme est issue de l'observation des "essaims" de poissons : Lorsqu'un poissons voit un prédateur, il fuit. Le poisson a coté voit celui qui fuit et fuit a son tour, etc... Ce mécanisme est tellement rapide qu'un observateur à l'impression que ceci se fait directement. C'est ça les mécanismes de groupe. Ces mécanismes sont souvent utilisés dans des jeu de type total war, ou un groupe de personnes n'a en fait qu'une vrai personnes qui va "réfléchir", le reste va copier les mouvement de celui qui réfléchit. Mais quels sont les avantages d'utiliser cette technique ? Tout d'abord, cela consomme moins de processeur car une seule personne réfléchit, fait son action, et tout le monde copie son action. Du coup, personnes d'autre ne réfléchit. (vous faites aussi cela en cours, tout le monde copie sur une unique personne, cela prend moins de temps aux autre cerveaux pour réfléchir, donc c'est bien ) Cela permet aussi d'assurer une cohérence dans le groupe vous imaginer une armée où chacun peut faire ce qu'il veut ? limiter le nombre de personnes qui réfléchit permet donc de limiter les incohérences dans le jeu pour le joueur. Donc pour le code, il faut faire cela : "l'entité" leader fait cela : typedef struct t_leader { char etat; void(*p_fonction)(t_info *); }t_leader; Si vous ne savez pas se que signifie etat, aller voir le tutoriel précédent. p_fonction est un pointeur de fonction. Allez voir le tutoriel d'AlexMog qui est très bien fait. Utiliser les pointeur sur fonction permet d'exécuter très rapidement la fonction, au lieu de mettre un ID de fonction et faire un immense switch ou if, else if ... Dès lors, afin que les sbires, où ceux qui suivent le leader fassent les même actions, il suffit de faire : typedef struct t_sbire { void(*p_fonction)(t_info *); }t_sbire; la structure t_info contient toutes les infos utiles pour exécuter les actions Aller encore regarder mon tutoriel précédent pour comprendre se que c'est t_leader leader; t_info info; /* vous mettez le pointeur sur fonction en fonction de l'état du pnj par exemple : */ leader.p_fonction = &attack_near_enemy; t_sbire sbire[16]; /* 16 est un exemple */ int i; for(i = 0 ; i < 16 ; i++) { sbire[i].p_fonction = leader.p_fonction; } /* tout les sbires vont maintenant pouvoir faire la même action que le leader */ et on exécute le tout leader.p_fonction(&info); /* on envoie la structure d'info a la fonction (un pointeur)*/ for(i = 0 ; i < 16 ; i++) { sbire[i].p_fonction(&info); } Et voila, vous pouvez maintenant créer facilement des mécaniques de groupe, afin de créer des immense batailles, ou encore une joli danse (chacun fait la même chose que son voisin) .
  25. Bonjour à tous ! En regardant le titre, vous avez sans doute penser : "kesako ?" Et bah c'est très simple : un automate, c'est une machine qui fait ce qu'on lui dit, et pourquoi a états finis ? C'est parce que cet automate peut avoir plusieurs états différents, mais il ne peut pas prendre les états que l'on n'a pas coder pour lui. Il ne peut aussi prendre qu'un seul état à la fois. Les automate à état fini sont très utilisés dans les jeux vidéos car ils sont très simples à mettre en place et à débuggés. Le majeur inconvénients, c'est qu'il sont prédictible, ce qui peut briser l'illusion qu'un véritable joueur, est devant le joueur. Pour éviter la prédictibilité, une des solutions possibles est d'augmenter le nombre d'états, ce qui permet plus de variations dans le pnj Concrètement, prenons exemple sur un soldat dans un jeu qui protège un village par exemple : On peut lui mettre deux états différents : État 1 : patrouille État 2 : attaque Il faut maintenant dire ce qu'il faut pour changer d'état (c'est mieux non ?) Par exemple, pour aller de l'état 1 à l'état 2, il faut que le soldat ai vu un intrus et de l'état 2 au 1, il faut que le garde ai tuer l'intrus. Pour reprendre l'exemple du soldats, on peut créer d'autre états : État 3 : suivit d'un intrus qui veut s'enfuir. État 4 : fuite car l'intrus est trop fort. État 5 : va dormir (il a le droit de dormir non ?) Bref vous l'avez compris, plus il y a d'états, plus le pnj sera réaliste. Et maintenant le code : /* la structure qui va servir pour votre pnj */ typedef struct t_perso t_perso; struct t_perso { char etat; char type; t_info info; } l'état correspond a l'état actuel du pnj. par exemple, pour le soldat, si l’état est égal a 1, alors le soldat patrouille, etc... le type correspond au type de pnj que vous avez. et la structure t_info, contient tout ce que le pnj a besoin de savoir pour changer d'état. (il a besoin de savoir si un ennemi est proche de lui etc...) void change_etat(t_perso *pnj) { /* Je suppose que toutes les infos utiles sont dans la structure t_perso touts les mot en majuscules sont soit des define, soit des énumérations; faites ce qu'il vous plaît le plus */ if(pnj->etat == PATROUILLE) { if(test_attaque(pnj)) { pnj->etat = ATTAQUE; } else if(test_sommeil(pnj)) { pnj->etat = DODO; } } if(pnj->etat == ATTAQUE) { if(test_intrus_mort(pnj)) { pnj->etat = PATROUILLE; } else if(test_intrus_trop_fort(pnj)) { pnj->etat = FUITE; } } if(pnj->etat == DODO) { if(test_sommeil_fini(pnj)) { pnj->etat = PATROUILLE; } } if(pnj->etat == FUITE) { if(test_intrus_loin(pnj)) { pnj->etat = PATROUILLE; } } } les fonctions test_quelquechose(), sont des fonctions booléenne : si la condition est vrai, alors on retourne 1, et on change d'état associé a la fonction, si la fonction retourne 0, la condition est fausse, alors on ne change pas d'état. ces fonction nécessite une structure t_perso pour tester les conditions, mais rien ne vous oblige à les utiliser. Faites vos propres test avec ce qu'il faut pour changer d’état, il faut juste modifier un peu le code. Comme vous le voyez, ce code est simple, et est très facilement lisible, donc facilement modifiable et débuggable. Chaque type de personnage nécessite sa propre fonction de changement d'état(un fermier qui fabrique ses propres vêtements n'est pas crédible, réservez cela au tailleur). Afin de vous retrouver plus facilement dans la création de changement d'état, n'hésitez pas à faire un graphique des différents état possible : pour info, flee veut dire s'enfuir. Et voila, vous avez maintenant un pnj qui peut réagir en fonction des événement qu'il l'entoure
×
×
  • Créer...