Ce programme utilise le principe des machines à état (FSM : Finit State Machine) pour imposer un séquencement (ordre de succession d'opérations) au programme.
Ici, l'appuie sur une touche lancera une succession d'animations imaginée par l'utilisateur.
Par exemple, on voudrait , en fonction d'un événement clavier, lancer un "combo" (combinaison d'animations), le séquencement suivra alors l'ordre des animations imposée par l'utilisateur.
J'ai fais ce programme pour montrer le principe de base. On peut imaginer que par la suite nous aurons plusieurs machines à état.
Chacune figée dans un état d'attente.
Si on appui sur une touche, on active la combinaison d'animation 1, l'animation est "busy", l'appui sur d'autres touches devient inopérant jusqu'à ce que la machine à état fsm_combo1 ait terminée son séquencement.
Le code fait en sorte que vous ayez juste à changer les valeurs des start / end / speed des frames au début du fichier (#define).
On peut ajouter des états ou en enlever ( enum => ST_ANIM_X).
Voici le code :
Voila, j’espère que cela vous sera utile. J'ai appliqué cela à des animations mais on peut imaginer pleins d'autres cas d'utilisation (exemple séquencement d'un déplacement d'un objet, ...).
Cette machine a état est linéaire, mais on peut imaginer que à chaque état nous ayons plusieurs états futurs possible.
Dernière modification par jonath313 (05-03-2017 22:37:18)
Hors ligne
Sympas, merci à toi
pour aller plus loin, je pense que tu pourrais coder cela a coup de template
et de foncteur pour généré une matrice qui correspond au action et au suite d'animation
en théori il "faudrais" faire une couche d'abstraction et tout ça, en pratique les template vont supprimer les mécasime de table virtuel
ou les éventuel dereferencement nécéssaire à l'utilisation d'un objet -> par reference du coup, bref c'est plus rapide
a voir, je ne pensse pas avoir le temps cette semaine, mais j'esserais de te faire un code d'exemple pour te montrer
le but étant de te d'étacher des contraintes d'implémentation qui ajoute des sections de code a tester quand ton code "grossit"
puisque finalement tu risque de te retrouver avec plusieurs sections qui peuvent devenir complexe à maintenir à meusure que tu rajoute des actions
et apres tu peux avoir du code en double ... :p, bon je ne sais pas si je suis très claire ...
mais ça peux être utile pour certain
Hors ligne
Merci de tas réponse Magun. En fait ce code à rien avoir avec ce que je voulais faire à la base. Mais je trouvais le principe sympas pour créer une séquence de succession d'animations.
Je me rend compte que je suis toujours dépendant des tests "if" et comme le sujet "animation binding" j'aurais préféré utiliser ce system (fsm) pour manager les animations d'un personnage.
Etre dans un état "Idle" et de là passer à des animations toutes prédéfinies. C'est un peut le foutoir, je voudrais limiter le nombre de sous-modules implémenté dans le code car çà devient vite une usine à gaz.
Je suis curieux d'en savoir plus sur ton modèle de template.
Hors ligne
Salut
voila un petit code, dit moi ce que tu en pensse ?
tu comprendras que les fonctions:
template <> void AnimationSwitch::enter<ST_ANIM_0>()
template <> void AnimationSwitch::enter<ST_ANIM_1>()
template <> void AnimationSwitch::enter<ST_ANIM_2>()
template <> void AnimationSwitch::enter<ST_ANIM_3>()
permettent de définir les frames des animations
et que les fonctions:
template <> int AnimationSwitch::event<ST_ANIM_0>() { cout << "event 0" << endl; return ST_ANIM_0; };
template <> int AnimationSwitch::event<ST_ANIM_1>() { cout << "event 1" << endl; return ST_ANIM_2; };
template <> int AnimationSwitch::event<ST_ANIM_2>() { cout << "event 2" << endl; return ST_ANIM_3; };
template <> int AnimationSwitch::event<ST_ANIM_3>() { cout << "event 3" << endl; return ST_ANIM_0; };
donne la fsm, donc l'enchainement des évènement.
et donc grâce à irr::scene::IAnimationEndCallBack:
dans l'état 0, quand l'animation est fini, on la rejoue (loop -> idle)
dans les autres états, quand l'animation est fini, on passe à la suivant
dans l'état 3, on retourne à l'état 0 qui reboucle, jusqu'a un event
et donc tu peux faire un ton "graphe" d'animation tranqu'il et juste chager les fonctions d'events
voila, désolé du temp de réponsse, j'avais un éxamain a préparer
Hors ligne
Oui merci j'ai vue çà, je viens d'adapter ma fsm pour manager les animations de personnage. çà marche plutôt bien. En ce qui concerne ton code la façon de coder en template apporte coi ? Gain en ressource ? Je t'avoue que je n'ai jamais utilisé cette façon de faire...
Autre question : Est-ce qu'on peut utiliser IAnimationEndCallBack avec un model en format .b3d ?
Finalement, et sur tout autre chose :
J'ai utilisé ma fsm pour faire "attraper un objet" et "relacher un objet" (avec du addchild), çà marche bien mais par contre il faut que ce soit un node bien précis. J'aimerais pouvoirs attraper tous les objets d'un type (exemple : type caisse en bois).
C'est possible ?
Merci pour ton aide.
Hors ligne
Salut, alors déjà oui concernant les performances, mon code est bien mieux, notament grâce a IAnimationEndCallBack
il est aussi beaucoup plus clair et facile a maintenir (pas de if magique et de comptage de frame, ... etc)
tu met la fsm dans un header, la state machine dans un autre et comme tu peut le voir il suffi d'instancier AnimationSwitch c; et AnimationStateMachine sm(c);
ici le but est vraiment de ce focaliser sur les trasitions de ton graphe, et tu dit explicitement quand tu lance une série d'animation
pas de soucis avec le format b3d
a partir du moment ou tu ne fait pas du "binding" d'aimation il n'y a pas de soucis
(et encore puisque tu peut émettre l'evenement dans ton code)
mais il faut donc géré une fsm a plusieurs états simultané
pour l'histoire d'attraper un objet dans ta scene tu as plusieurs possibilitées:
ISceneNode propose une fonction setID(s32) et getID(), tu t'arrange pour donné un flags d'ID correspondant a ton objet (bit [0 à 4] matérial {bois, fer, ...}, bit [5 à 8] type {caisse, consomable, statique, ...})
sinon tu peux "hacker" le s32 pour avoir une adresse vers une struture de donner perso
ou ajouter un <<void *UserData>> dans ISceneNode et recompiler irrlicht
ISceneNode propose une fonction setName(char*) et getName() -> boff
tu peut crée un class dériver de ISceneNode pour géré ton propre système "typé"
... etc
Hors ligne
ha super merci .
Et du coups, comment çà se passe au niveau code pour attraper un élément de la scéne? Il faut tester la position du personnage et celle de l'élément à attraper en continue ou utiliser un système de detection de collision entre deux node ? Il y a t-il de bonnes pratiques à ce sujet ?
Hors ligne
il y a plusieurs école, ça dépend de ce que tu cherche à faire (en fonction de la vue)
en générale tu laisse l'utilisateur cliquer sur l'objet (raycast) tu deplace le perso et tu lance une animation
ou l'utilisateurs avance sont perso devant, tu cerhce les objects les plus proches qui sont "interatible" et le plus en face, et encore une fois tu lance ton animation
après ont peut améliorer l'animation, par exemple avec << l'inverse kinematics >> (asser compliquer)
Hors ligne
Ok mais ce qui m'inquiète c'est de tester le cerclage de la position de la zone de l'objet et la comparer à la position du personnage en continue, çà mange pas mal de ressources non ?
Je suis entrein de tester ton code, il fonctionne très bien, néanmoins je ne comprends pas comment l'utiliser.
1) Comment tu construit tas suite d'animation ? (Je ne comprend pas le rôle du "c" ?
2) Comment faire si je veux faire une seule animation par touche ? ( ex : Z = marcher ; E = sauter ; A = attaquer)
3) Pour animer avec le ninja.b3d j'utilise setframeloop() ? Car setMD2Animation n'est pas compatible si ?
4) Si je veux mettre une animation par touche, quand j'enléve "&& !event.KeyInput.PressedDown" , si je reste appuyé sur le bouton Z je constate que l'animation est bloquée quand elle arrive à la fin du "walk".
Dernière modification par jonath313 (17-03-2017 22:11:37)
Hors ligne
Ok mais ce qui m'inquiète c'est de tester le cerclage de la position de la zone de l'objet et la comparer à la position du personnage en continue, çà mange pas mal de ressources non ?
uniquement quand l'utilisateur le demande donc trèds rarement par rapport au frame rate ... (event clavier / souris spécifique genre F ou E en générale)
"c" est la pour représenter un état abstrait, ok, c'est un peut étrange au début
d'autant plus que finalement "c" n'a aucune donner (sizeof(AnimationSwitch)==0)...
1) Comment tu construit tas suite d'animation ? (Je ne comprend pas le rôle du "c" ?
il est la en faite uniquement pour avoir une interface commune avec la spécialization de template, donc les différentes fonctions enter/event
mais plus tard, toi tu pourras stocker un pointeur vers NodePlayer
pour la suite d'animation tu regarde les fonctions "event"
exemple template <> int AnimationSwitch::event<ST_ANIM_1>() { cout << "event 1" << endl; return ST_ANIM_2; };
dit exactement: dans l'état ST_ANIM_1 la fonction event doit faire: afficher event 1 donner le prochain état ici ST_ANIM_2
pour l'exemple j'ai fait:
0 -> 0 (bouclage sur l'animation stand)
1 -> 2 -> 3 -> 0 (suite d'animation 1,2,3 et retour sur l'état "normal" ie: stand)
2) Comment faire si je veux faire une seule animation par touche ? ( ex : Z = marcher ; E = sauter ; A = attaquer)
tu auras probablement besoin de plus d'état:
tu rauras probablement, toujours 0 -> 0
tu ajoute l'animation n°"x" et tu retourne à l'état normal: x -> 0
tu ajout simplement le code suivant pour forcer le changement d'état:
case irr::KEY_KEY_BLABLABLA:
Event eOn = {ST_ANIM_X};
sm.work(eOn);
return true;
3) Pour animer avec le ninja.b3d j'utilise setframeloop() ? Car setMD2Animation n'est pas compatible si ?
uhm en faite setMD2Animation utilise des indices de frame prédéfinit et normaliser par MD2, mais si ton perso a les mêmes ça doit probablement marcher
setFrameLoop me semble bien, ça fait longtemp que je n'ai pas regarder de ce coter la ^^, je ne suis pas sûr de l'event irr::scene::IAnimationEndCallBack dans ce cas précis
4) Si je veux mettre une animation par touche, quand j'enléve "&& !event.KeyInput.PressedDown" , si je reste appuyé sur le bouton Z je constate que l'animation est bloquée quand elle arrive à la fin du "walk".
oui, uhm ba simplement par ce qu'il n'y a pas de nouvelle event, donc pas de "répétition"
ce que tu peux faire, dans ce cas, c'est de définir les animations "continue" comme ça:
stand -> stand, run -> run (donc en gros un bouclage)
quand tu detecte un relachement de la touche tu retourne à l'état 0
mais ça ne marcheras pas dans tous les cas ( exemple "stand -> run -> jump", normalement tu retourne a "run")
ça veux dire qu'il faudrais définir une fsm dynamique avec une stack, des prioritées, ... etc
pour ça je te renvoie à notre discution précédante http://irrlicht-fr.org/viewtopic.php?id=1822
après tu pourrais detecter la fin de la frame quelle touche sont encore en état "d'appuie" et relancer l'animation courante avec un forçage: la fonction work() dans AnimationStateMachine
Hors ligne
Merci pour tes réponses bien concises Magun.
Du coups je n'ai pas copié tas façon de faire, elle est très bien mais j'ai du mal à la maîtriser, néanmoins çà me permet de voir et de comprendre d'autres manières de traiter la problématique.
J'ai alors continué d'améliorer mon code et çà donne quelque chose comme çà :
Les nodes "accessoire" et "Terrain" sont uniquement là pour tester le addChild pour attraper ou relâcher un objet. L'objet est soit lié au personnage soit au terrain. On peut les remplacer par des box.
Sinon, c'est pas glorieux mais çà fonctionne, ce code me sert à manager les animations de mon personnage à la manière du topic sur l'animation binding.
Si tu vois d'autres optimisations sur ce code, tes conseils seront les bien venus.
Merci.
Hors ligne
Salut, alors jvais un peut tout décortiquer, et je te donne mes impréssions à "vif"
tous tes define pour configurer l'animation d'un perso, pourquoi pas, quid de la configuration dynamique ?
comment tu fait avec différent modèles, tu fait un header a chaque fois et tu recompile ?
sur un gros projet tu augmente considérablement le temps de dev (puisque conflit de versionning sur repo)
#define RESET 0
#define SET 1
-> "pas bien", tu essaye de masquer l'utilisation d'un type de base, ici les valeurs true et false d'un boolean
en terme de lecture par un tier et donc de maintanance c'est "moche"
et comme ça complique la lecture, ça complique aussi l'analyse et les symplification logique que tu peut apportés au code (donc optimisation)
ce qui m'amène à la première fonction @keypad_KEY_SAVED_STATE_control
peut facilement ce réécrire
et donc
et donc la fonction devient inutile
en passant le keyword static, est ici inutile, a la rigeur inline serait plus approprié
même remarque pour @key_event_test
finalement ça ce simplifi par:
ou encore en supprimant l'appelle de la fonction keypad_KEY_SAVED_STATE_control:
finalement key_event_test n'est pas vraiment utile en soit
du coup même remarque pour OnEvent
donc en gros, évite les fonctions qui n'apporte pas de l'isibilité a ton code,
quand tu écris une fonction, pose toi la question si elle donne:
-plus de "sens" au "flux" d'execution de ton code
-une meilleurs compréhension globale et/ou locale
-------------------------------------------------------
voila pour le début, j'en arrive au système d'animation
première remarque, une classe serais la bienvenu pour la compréhension
ça n'apporte pas grand chose en terme de performance certe,
mais quand même plus simple à lire,
puisque cela exclue les segments de code telque l'initialisation quand on cherche une info
la fonction @fsm_goNextState n'apporte pas une meilleur compréhension
tu peut simplement l'enlever, l'appelle d'une fonction à un cout:
15 à 30 cycle cpu contre 1 cycle pour une affectation
ensuite, on ne comprend pas bien pourquoi tu as 2 fonctions
qui peuvent être syntétiser en seulement une fonction,
@animation_bind_single et @animation_bind_loop
sans parler des paramètres à ralonge (cf première remarque du post)
et donc, pourquoi ne pas faire une structure de donner ?
dans un premier temp tu défini les informations dans un header
ensuite, tu n'a plus cas modifier PlayerAnimationSetting en pointeur
et charger les informations par exemple a partir d'un xml ...
et c'est plus facile a lire
et donc:
bref @animation_bind_idle ne me semble pas tout à fait juste
pour les raison évoquer dans mon précédent poste, que faire si une autre touche est toujours active ?
pour @animation_bind_ended donc je reviens à IAnimationEndCallBack,
pas sur de l'utilité de setLoopMode(false) ici, à tester sens
du coup avec IAnimationEndCallBack cette fonction est inutile tout comme le comptage des frames
@frameEnCour et (NodePlayer->getFrameNr())> (new_anim_end - ANIM_OFFSET), ... etc
pour @animation_bind_idle
ça devient juste:
qui doit probablement pouvoir être fusioné avec @animation_bind
-----------------------------------------
pour la dernière fonctions il suffi de remplacer ...
a noter qu'il y a equivalence entre les etats et les animation, on pourrais n'en garder qu'un
ST_GIVE_5 == EAT_GIVE, ... etc
je te laisse méditer sur cela
cordialemnt Magun
Hors ligne
Ok merci, effectivement çà simplifie pas mal le code, par contre la déclaration static const PlayerAnimationSetting[] ne compile pas :
error: 'PlayerAnimationSetting' does not name a type|
C'est bon j'ai trouvé il manquait le type:
static const AnimationSetting PlayerAnimationSetting[] = ...
Dernière modification par jonath313 (20-03-2017 21:22:54)
Hors ligne
Magun, en essayant ton code je ne constate pas le même comportement que celui de mon code.
Je ne dois pas faire ce qu'il faut.
J'ai déclaré un "AnimationSetting pas " en variable globale car çà ne compilait pas. Je pense que mon erreur est ici.
Pour le reste j'ai remplacé directement dans le code ce qui changeait.
Au début tout vas bien l'animation idle tourne en boucle mais après dès que j'appuie sur une touche l'animation associée ne s'arrête plus et toutes les autres touches sont sans effet. Je ne trouve pas pourquoi.
Dernière modification par jonath313 (23-03-2017 20:46:35)
Hors ligne
Salut !
désolé j'était en déplacement
moi, la seul difference que je voie c'est que tu oublie de reset les variables @combo1 et @anim_busy
quand tu arrive a la fin de l'enchainement des animations, donc quand tu rappuis sur la touche, rien ne ce produit
je ne sais aps si c'était une erreurs, mais je l'ai corriger dans ma version
le reste à le même fonctionement
Hors ligne
Options | Liens officiels | Caractéristiques | Statistiques | Communauté |
---|---|---|---|---|
Corrections |
|
xhtml 1.0 css 2.1 Propulsé par FluxBB Traduit par FluxBB.fr |
882 membres 1429 sujets 11119 messages |
Dernier membre inscrit: LiseBuisson96 19 invités en ligne Aucun membre connecté RSS Feed |