L’obscurité naît de la lumière

Le rendu actuel est bien joli, mais ça manque d’effet de lumière et donc d’obscurité. Du coup j’ai tenté l’expérience, pleuré, cassé, et réussi à avoir un premier résultat.

D’abord c’est quoi cette notion de lumière/obscurité ? C’est tout simplement le fait d’avoir une image lumineuse ou assombrie sur base d’une source supposée de lumière et d’un calcul pour définir l’effet.

Une source ? N’importe quel Element peut être une source de lumière, j’ai créé un interface Illuminate pour qu’il se prenne pour un truc radioactif.

Un calcul ? J’ai déclaré,pour le test, que la lumière allait à 6 cases de distance en décroissant de 20% de manière arbitraire. Cependant j’ai prévu une interface (pas finie) dont le but sera de définir des paramètres pour l’éclairage, permettant ainsi des variables d’affichage (couleur, force, …).

Il s’agit donc de définir pour chaque Element de chaque Zones de tous les GridBlocks du subsetMap une valeur de « Brightness » (luminosité) sur base du calcul et des sources. Le tout sans faire de calculs inutiles.

J’ai opté pour le moment de la création du subsetMap pour agir. Ceci arrive uniquement lors d’un changement d’offset de la caméra (déplacement du héro en bord d’écran), on peut alors profiter de la boucle de création pour trouver les lumières de la Scene. Ensuite il faut parcourir à nouveau le subsetMap pour mettre le « Brightness » de tout le monde à 0 : le noir total. Oui deux fois, car nous agissons dans PlayerScene basé sur un IsoScene et la création du subsetMap est dans IsoScene.

Ensuite pour savoir ce que la lumière va affecter je me suis dit que le PathFinder ferait un bon système de propagation, respectant les murs et les objets. Ce n’est pas au point, et cette idée doit être poussée plus loin, mais le départ est pas mal. On imagine une source de 100% qui perd 20% à chaque changement de case, et on ajoute ceci à l’éclairage déjà présent.

La théorie est pas mal, on est en 2D isométrique quand même, on ne peut pas faire trop de folies et on est limité par l’architecture de TARS et des possibilités de « Canvas » (notre support HTML5 de dessin). En pratique : les performances s’effondre…

Mais que se passe-t-il ? Il faut savoir que pour avoir cet effet j’utilise le « filter » « brightness » de « Canvas », en gros une propriété au moment du dessin permettant d’affecter la luminosité de l’image que j’ajout pour composer la Scene. Je le fais déjà avec l’alpha sans soucis (cf titre daaboo). Bref, ça fonctionne mais prend un temps de dingue, ce qui rend le résultat non réactif et donne du mal au navigateur pour répondre.

Il n’y a pas d’autre façon, l’accès au données de l’image n’est permis qu’au niveau du rendu final et non à l’image que l’on va ajouter et on ne peut pas travailler sur l’image stockée en mémoire, d’une part car c’est pas permis et de l’autre car il nous faut l’original pour modifier les effets.

Une seule solution : la protestation ! À chanter comme une manif de métallo un jour de pont entre deux grèves…

Comment améliorer les performances ? Comment faire quand les outils manquent, les accès limités, les possibilités réduites… ? Il y a bien quelques solutions, mais l’idée n’est pas de demander plus de travail aux utilisateurs de TARS, enfin, le moins possible.

Je suis parti sur l’idée d’un cache, le soucis étant le temps de rendu, et on sait que les mises à jour ne sont que ponctuelles par comparaison. Mais comment ? Nous allons créer un « Canvas » par IsoElement (on va surement le descendre en SpriteElement par la suite (c’est fait :p) et on fait le rendu dans ce « Canvas » invisible. Au moment de composer la scène on copie le contenu à sa destination comme on le faisait avant, et on ne fait plus que ça jusqu’à ce que l’on doive régénérer le cache.

Pour vérifier que ça fonctionne bien avant de péter tous mes calculs, j’ai simulé un rendu dans mon cache, un carré rouge et au lieu de dessiner comme l’image ci-avant, on utilise le contenu du cache.

Je vous épargne le retour du bug de hauteur, le curseur qui fait bouger les Elements, les soucis liés à la division du calcul en 2 étapes, etc. On a déjà connu, déjà fixé, et j’ai dû le refaire encore une fois…

Une anecdote cependant, si vous ne dite pas à votre cache de se régénérer sur base des paramètres de votre Element (PlayerElement, TreasureElement, etc.), il restera tel quel depuis son premier rendu, en gros Squellettore ne marchait plus, il glissait, droit comme un pique sans changer d’orientation. Logique si vous avez lu. Du coup on doit penser à invalider le cache lors de nos mises à jour.

Vu qu’on parle d’animation, il faudra permettre de savoir quand une animation change afin de régénérer le cache à point nommé. Et hop un TODO de plus…

Une fois tout cela fait, on a un rendu avec des performances bien meilleures, cela demande un peu plus de manipulations car il faut penser au cache mais globalement c’est une nette différence.

Concernant le cache, certains automatismes pourront être mis en place en interne au lieu de demander à l’utilisateur de TARS d’y penser. Comme invalider le cache si vous changez d’orientation, l’alpha ou la luminosité. Ce qui est fait en même temps qu’écrire cet article :p et ajouter 3 TODOs… comme si j’en avais pas assez… ^^

Revenons à nos lumières. On a un rendu correct sur base du calcul déclaré plus avant, simple, efficace, même si pas fini (murs opposés, murs d’une case supérieure à la distance prévue, …). Du coup je décide d’en ajouter une seconde pour voir si le cumul fonctionne bien et c’est la fête du slip… Je n’ai pas fait de photo, mais en gros le point d’origine est assombri et génère de l’obscurité, les cases éclairées sont plus loin, bref ça ne va pas.

Mais pourquoi ? C’est une sacrée boucle à investiguer. Ce n’est pas la fonction d’action sur les Elements, ce n’est pas les préparatifs, ni les accès aux variables. C’est le propagateur (par extension le Pathfinder). En fait, par un malheureux hasard, il se peut que vous passiez plusieurs fois sur la même case au cours d’une boucle de propagation (ce qui peut arriver). Le truc c’est que je pensais que je les avais bien exclues, et c’est vrai, mais il faut aussi vérifier en préventif.

Mes tests montraient une quantité astronomique de calculs pour ma petite zone de 10×10, ce qui ne me semblait pas vraisemblable. C’est ce qui m’a mit la puce à l’oreille en gros. Un « If » de vérification et d’un coup la folie s’arrête, les lumières apparaissent et se cumulent proprement. En plus dans l’histoire on a augmenté les performances du Pathfinder et du propagateur pour les lumières !


Voici un résultat avec 2 lumières, le coffre statique et le héro mobile.

Il ne faut pas perdre de vue que ce n’est pas fini. Il y a un gros soucis de performance à la mise en place des lumières quand on s’occupe du subsetMap. Il faut considérer les lumières animées et s’occuper également des occlusions (ça serait top) et de l’orientations des objets, par exemple les murs, opposés ou de la case d’à côté nous tournant le dos. Là il va falloir creuser les théories les meilleures pour augmenter la qualité de l’effet sans défoncer les performances.

Une autre chose aussi, vu que nous travaillons en 2D isométrique, serait d’ombrer selon le caractère cubique de nos objets et de prévoir ainsi, soit une image South, une image East et pourquoi pas une image Top pour ombrer selon des calculs d’angle (les normales) et de distance. Le tout serait de fournir à ces objets des informations plus complète qu’actuellement.

Ceci dit, en l’état, c’est déjà chouette comme rendu !

Angular library – how to – snippet

Suite à mon article précédent j’ai souhaité investiguer, attiré par la curiosité maladive et la difficulté jusqu’à lors de compréhension de cette mécanique. Je vous liste ci dessous quelques articles qui m’ont guidé tant pour démarrer que pour me mélanger les pinceaux.

Pour résumer, si comme moi, vous êtes perdu voici un snippet de démarrage et une copie d’un conseil donné dans le dernier lien.

ng new tars --directory . --create-application=false
ng generate library tars-lib --prefix=tars
ng generate application tars-test

La dernière ligne est utile plus tard quand il faudra tester via une app angular la librairie que l’on souhaite publier.

À noter que mon but n’est pas de passer par NPM pour la publication ni d’aller sur GitHub pour distribuer mon code.

Le conseil du dernier article proposait les lignes ci-dessous dans votre package.json (le premier en root) dans l’espace des scripts.

    "build_lib": "ng build tars-lib",
    "npm_pack": "cd dist/tars-lib && npm pack",
    "package": "npm run build_lib && npm run npm_pack",

Ma prochaine étape est le déplacement du code TARS au sein de l’espace tars-lib créé et de refaire les liens vers le fichier public_api.ts. Ensuite de créer, comme actuellement, une app démo/test, sans inclure l’éditeur, juste une scène et un rendu fonctionnel, simple et expressif.

Une fois validé, je pense que l’on peut alors faire le build de la version de la librairie TARS et créé un nouveau projet dédié pour HeroQuest et là, voir si l’éditeur et la base du jeu prenne bien la lib’.

Réorientation des Sprites

Il y a un point gênant qui traînait depuis un moment du fait que ce monstre était difficile à cerner. Le problème est que chaque POC (preuve de concept) doit être intégré proprement une fois validé, mais pour certains d’entre eux vous ne voyez pas toujours la portée de ce vous venez de faire, les impactes et incidences, ça vous arrive souvent que quand vous y êtes confronté, ce qui a été mon cas.

Premièrement je dois vous informer que j’ai divisé Element en Element et SpriteElement, car si on pense 3D, Element tel qu’il était ne convenait pas, et il doit rester la base, du coup nous avions pas mal de contenu spécifique aux POC et monde 2D (pour le POC iso), donc en résumé j’ai sorti tout le spécifique 2D dans SpriteElement, sur lequel vient s’accrocher à son tour IsoElement qui l’étend plus encore. Element quant à lui est une identité multi-dimensionnel correspondant à la fois à la 2D et à la 3D, soit XYZ (z = précédemment elevationFactor en 2D), le delta de chaque pour les animations notamment et les interactions qui ne dépendent pas de la dimension.

SpriteElement va alors se compléter avec la gestion des spriteRefs, l’orientation (on va y revenir), l’alpha que j’ai ajouté en refondant le titre pour maximiser l’usage de la librairie TARS, le getSize, les positions d’interactions, des get/set Coord adaptés au monde 2D, ce qui concerne les animations, l’update et le draw du/des sprites (spriteRefs encore) du SpriteElement et le isTouch. Pensez 2D plate, pas encore iso, donc on a des méthodes que l’IsoElement va remplacer car il induit une notion de représentation que SpriteElement n’a pas encore. Ainsi on a sous forme d’étage une complétion structurelle cohérente.

Pour les curieux, IsoElement contient des méthodes comme le draw, isTouch et l’update comme cités avant mais remplacées, ainsi qu’une fonction de rotation, c’est tout. 🙂 Simple, basique, toussa.

Pour revenir à l’orientation, la problématique est simple, le POC se déroule en isométrique avec 4 directions, résumées sous la forme NESW (North, East, South, West), vous aurez compris 😉 (j’espère). Du coup, vu que nous traitons en interne des caractères ‘n’, ‘e’, ‘s’ et ‘w’, comment savoir qui est à l’est du nord de l’objet ? Comment connaître la coordonnée au nord de l’objet ? Au final 2 questions qui deviendront 5 réponses, on en reparle plus tard. En gros, pendant plusieurs mois, suite au développement du déplacement et de la réorientation, le code était dans l’actuel SpriteElement et anciennement dans Element.

Si maintenant je vous demande, avec TARS, de faire un jeu de plateforme 2D latéral ce que je viens de vous dire déconnera plein tube car vous avez un 2D isométrique en dur dans Element, hors à la base c’est l’idée, « être capable de tout ».

« Être capable de tout »

TARS

On a donc un soucis connu, le système d’orientation n’a pas sa place à cet endroit. On peut même imaginer un personnage se déplaçant en 8 directions sur un environnement isométrique en 4 directions, tout à fait réaliste. Donc on se trouve au niveau de Element comme dit, on sait qu’on est au bon endroit mais pas de la bonne manière, ni de manière flexible/variable.

Je vous passe la réflexion interminable, la solution est un service sur mesure, donc une Factory (une classe qui vous donne la bonne selon critère). J’ai donc crée SpriteFactoryService qui donne accès à SpriteOrientationSystem (lol) basé sur un critère simple : combien de direction veux-tu pour cet Element, que j’ai nommé SpriteOrientationType et vous donne accès à une liste du style SO0, SO2, SO4, etc. En gros, une image plate, une image plate qui va à gauche ou droite, une image ayant 4 directions quelque soit le système de rendu, 2D haut, 2D iso, etc. Le résultat nous donnera une classe SpriteOrientation0, SpriteOrientation4, etc.

On a donc l’actuel SpriteElement qui demande ce fameux service et selon sa configuration passée au type de l’Element, on a la DTD qui donne des infos générales et s’initialise. On peut désormais avoir un SpriteElement en 2 directions à côté d’un autre en 4 directions etc. C’est la même classe, avec un service sur mesure. Le tout est cadenassé par une interface bien sur (modèle à respecter), ce qui garanti que SpriteElement peut travailler avec n’importe quel système de SpriteOrientation.

Je vous ai épargné beaucoup de détails car au final c’est +17 fichiers qui ont été impactés, adaptés, corrigés et nettoyés. Une aventure difficile mais dont je suis content d’être sorti avec une certaine élégance dans le résultat.

Après ces développements et tentatives de diminuer les TODOs présents, de +40 on est passé à 31, ce qui permet de respirer un peu et de voir de l’avancement, même si rien de nouveau en visuel.

À propos de visuel, l’éditeur avait un peu avancé avant tout ça, voici une capture. En gros, le chargement de scène fonctionne, la suppression d’un Element aussi, la rotation selon molette de souris et si l’Element le permet, fonctionne. J’ai également ajouté la notion des Zones du gridBlock que vous voyez sous la forme BCTNESW avec le C de ‘center’ sélectionné et j’ai ajouté une sélection, que vous voyez en fuchsia pointillé, fonctionnelle mais ne rend pas encore la sélection sur base du filtre de Zones.

Évidemment, tout ceci a permis de découvrir des problèmes, d’amender certains points pour les rendre accessible ou carrément meilleur.

J’avais tenté une sélection multiple d’objets du catalogue pour préparer un pinceau dynamique mais la manière n’était pas optimale. L’idée sera plutôt d’avoir un onglet spécifique aux outils permettant en leur sein d’avoir des options à choisir, via des listes et autres. Par exemple, vu qu’on est un éditeur pour HeroQuest, on peut imaginer un outil « création de pièce » avec en option, « quel mur et sol voulez vous« , on peut sélectionner plusieurs sols et jouer sur un aléatoire de style et de rotation si coché par exemple. Je pense que c’est une meilleure approche.

Je vais déjà tenter de faire un ajout simple puis on verra les outils plus avancés. Quand je dis « tenter », le truc n’est pas compliqué, mais il faut penser à beaucoup de chose avant de se lancer et de devoir recommencer. Ce qui a donné tous ces développements depuis la dernières fois en gros, même si parfois très indirect.

L’idée futur proche reste la diminution des TODOs pour être stable et propre et l’ajout dans l’éditeur. Vu que nous sommes en DEV nous utilisons un dataProvider Fake (faux) qui renvoie des données brutes stockées localement en JSON (et non venant d’un serveur comme cela sera à terme), ce qui veut aussi dire que la partie sauvegarde du résultat de l’éditeur ne peut pas se faire. J’imagine passer par la console pour avoir une sortie texte brute à mettre moi-même dans un fichier local, ce n’est que temporaire et pour contrôler de toutes façons. On sait ce que temporaire signifie…

On est de plus en plus proche d’un résultat exploitable pour commencer le POC du jeu HeroQuest comme dit, il faudra penser la mécanique manquante des actions au sein d’une Scene déplaçant le joueur d’une Scene à une autre, maîtriser d’un point de vue macro l’ensemble du processus de jeu au delà des limites des Scenes. Typiquement le joueur arrive en début de donjon et doit aller à la fin, le fait de toucher la case ou d’interagir avec le fait quitter le donjon pour aller en zone neutre avant d’attaquer un nouveau donjon. Le tout sans perdre l’état du joueur, ses découvertes et équipements.

Et là je n’ai pas encore parlé de la problématique organisationnelle que ceci est un POC dans un POC, la librairie TARS se doit d’être séparée dans un module spécifique, versionné et récupérable au travers d’un projet NPM propre. Ce qui n’a pas l’air évident du tout.

Que ça soit en mode « player » ou en « editor » on a encore du boulot. 🙂

L’antithèse d’Oppenheimer

Ça y est, l’éditeur a été débuté ! Il s’agit donc d’un outil propre au projet HeroQuest, car un éditeur est spécifique, du moins pour le moment. Nous ne gérons que des IsoScenes au travers d’un IsoEditor.

L’interface est pour le moment très rudimentaire mais fonctionnelle (sauf le bouton « nouveau » à côté du titre). Une zone centrale de vue et de travail, un menu supérieur barre d’outils, un pied qui sera je pense une liste d’indicateurs et enfin la partie latérale à droite, un systèmes d’onglets.

Ce dernier contient actuellement 2 choses : une liste des scènes disponibles et la liste des éléments à mettre sur celle-ci. Un double clic sur un Element transforme votre curseur en l’objet et vous permettra de le placer.

Évidemment il vous vient directement à l’esprit qu’il y a plusieurs zones et donc un simple clic ne suffira pas, il faudra définir la zone impactée. Là j’imagine dans la barre d’outil, à l’image des alignements dans Photoshop, un moyen de préciser la zone via des icônes. On peut toujours aller plus loin car l’objet affiché, un mur par exemple est affiché au nord par défaut, mais est-ce que le sens du mur affecte la zone d’impacte ou est-ce que la zone sélectionnée affecte l’orientation. Des comme ça on en aura surement un paquet et il faudra être bien cohérent sur la définition de l’expérience utilisateur, heureusement à force d’usage je verrais vite ce qui fonctionne le mieux.

Prochaine étape donc, ce placement sur la map. Il faut s’avoir que le composant Editor doit discuter avec l’IsoEditor, ils travaillent de concert et doivent chacun gérer leur histoire, et à moi de ne pas tout mélanger ou mal placer. À venir par exemple il faudra que je divise les onglets en composant pour respecter les idées Angular (mieux que tout dans un seul ^^).

Il faudra également penser au chargement/sauvegarde (fake), déplacement de ce qui est sur la carte, sélection d’un Element et modification de ses propriétés (comme l’élévation) et même accéder au GridBlock. Enfin, ce n’est que le haut de l’iceberg actuel comme toujours :).

Squellettore mon Héro

Le caddie ayant bien roulé, tellement qu’il en a les roues carrées, j’ai décidé de le remplacer par un héro ayant un peu plus de capacité d’animation. C’est tout naturellement que j’ai pensé à mon Héro de toujours : Squellettore.

Faisant directement suite au coffre, il m’aura juste fallu l’encoder dans la bibliothèque et ajouter 2 lignes de code pour activer les 2 animations.

Le coffre de la tourmente

Comme vous l’aurez compris j’ai ajouté un coffre au trésor dans le set d’objet actuellement disponible. Mais pourquoi ?

  1. Nous n’avons rien qui teste le système d’animation depuis qu’il a été créé, et le « target » en vague bleue était l’animation principale.
  2. Rien n’a été mis en place pour le changement d’animation.
  3. Un problème de boucle se soulève en imaginant l’ouverture du coffre, actuellement toutes les animations sont bouclées.

Mais d’abord il faut le dessiner, dans les 4 directions, l’animer, ce qui est une autre sinécure et enfin en faire des Sprites NESW. Là il n’y a pas d’outils actuellement dans mes mains, rien de facile, gratuit pour les POC. Cependant j’ai trouvé un script (Layer2SpriteSheet) un peu bancale mais qui m’a bien aidé. C’est pour Adobe Photoshop et ça utilise vos calques pour générer votre Sprite final.

Et voilà un coffre que je vous anime ci-dessus. Ça c’était la partie facile, même si ça m’a déjà pris quelques heures de chipotages. Maintenant il faut l’intégrer au POC HeroQuest/TARS. Donc comme d’habitude on manipule la bibliothèque en précisant les images NESW, les 4 animations disponibles et les dimensions des images, on édite la liste des Elements disponibles, on ajoute une DTD (définition) pour ce qu’est un « Treasure », donc la coordonnée d’interaction, les interactions disponible comme pour la porte et pas de référence de Sprite spécifique, celui par défaut convient très bien.

Jusque là c’est nickel, j’ai juste dû corriger quelques typos dans mes modifications et ça roule, mais comme on a un objet avec lequel on peut interagir et même si c’est IsoPlayer qui fait la réaction des interactions pour le moment, il va nous falloir un objet spécifique : TreasureElement. En gros c’est la copie d’une porte pour la partie interaction et on retire la gestion multi-sprite au profit de l’usage du sprite par défaut.

Pour rappel, le Sprite par défaut, est l’image ou la somme d’image composant l’ensemble des possibilités d’animations de l’objet. Avoir plusieurs Sprites au sein d’un élément est une construction dynamique, une volonté spécifique pour obtenir un résultat dynamique qu’un Sprite ne peut offrir.

Nous voilà donc avec notre classe TreasureElement à laquelle on va déplacer la gestion des interactions en son sein au lieu de IsoPlayer, c’est quand même IsoPlayer qui l’appelle, mais la partie spécifique est du côté spécifique, meilleure séparation et clarté du code. Nous avons donc une méthode « doInteraction », comme dans IsoPlayer, avec les mêmes paramètres etc. qui va nous permettre de faire les tests de cas en fonction du verbe reçu.

C’est bien joli, mais l’animation dans tout ça ? On y arrive enfin, on a 4 animations : ouvert, fermé, s’ouvre, se ferme; et voici le schéma qu’on aimerait mettre en place :

  1. Le coffre s’initialise avec l’état fermé et l’animation « fermé ».
  2. Je clique pour interagir avec le coffre et choisi de l’ouvrir : on lance l’animation « s’ouvre » et on aimerait qu’après ça passe en « ouvert » et que ça reste ainsi.
  3. Je clique pour interagir avec le coffre et choisi de le fermer : on lance l’animation « se ferme » et on aimerait qu’après ça passe en « fermé » et que ça reste ainsi.

Je ne revient pas sur le fonctionnement de l’interaction, il vous suffit de lire l’article précédent. Donc nous en sommes à recevoir l’action « ouvrir » et on souhaite changer l’animation courante. BIIIIP faux départ ! Cette fonction n’existe pas.

Premier problème rencontré, rien n’a été prévu pour changer l’animation. Je vous passe toutes les réflexions, j’ai ajouté dans Element une fonction setAnimation dont le but est de manipuler la collection des animations et mettre les paramètres dans les bonnes conditions (nom de l’animation, timing, frame courante).

J’ajoute directement ici qu’après réflexions sur les améliorations utiles, le fait de pouvoir attendre la fin d’une animation est très intéressante. Que ce soit d’être prévenu tout simplement par un événement auquel on peut s’accrocher, mais également en interne, initier le changement d’animation en attendant la fin de la précédente, on crée alors en interne l’attente de l’événement et on change alors les mêmes paramètres comme dit avant.

Maintenant que la possibilité existe on peut demander notre changement d’animation suite à l’interaction. Il nous reste à utiliser également l’événement de fin d’animation pour appliquer le changement d’animation vers les « animations » fixes « ouvert » ou « fermé ».

À cela, j’ajouterai que j’ai prévu un paramètre d’animation « loop » qu’il me reste à implémenter pour définir qu’une animation ne boucle pas forcément. On arrêterait ainsi l’animation avec un « flag » (drapeau d’état) et ainsi ne pas boucler sur la dernière image et envoyer X alertes d’événement de fin.

Pour simplifier, parmi les 4 animations citées, nous pourrions supprimer « ouvert » et modifier l’animation « s’ouvre » avec le « flag » « loop » à « false » (non), elle resterait ainsi sur sa dernière image, sans chercher à poursuivre et nous n’aurions qu’une seule fois l’alerte de fin d’animation. Ceci nous économise une définition d’animation et plus de facilités pour l’avenir.

Petite anecdote, j’avais défini tous mes changements d’animation en demandant d’attendre que l’animation courante se termine avant de changer, mais du coup mon coffre s’ouvrait ou se fermait 2 fois avant de rester dans son état ouvert ou fermé. Mais pourquoi ? En fait, en faisant ça, au moment où l’animation se termine, l’événement nous alerte et on lui demande de changer d’animation quand elle sera finie, mais l’événement vient de passer, du coup il relance l’animation une seconde fois. Vu que nous sommes déjà dans l’état de fin, il ne fallait donc pas utiliser « l’attente de fin » encore une fois. Ça vous évitera beaucoup de recherches inutiles.

Tout ceci commença une dizaine d’heures plus tôt en dessinant un personnage pour remplacer notre ami le caddie dans le même objectif de manipuler les animations. Ce que je vais faire suite à ceci ainsi que coder le paramètre « loop ».