359 lines
22 KiB
Markdown
359 lines
22 KiB
Markdown
# Guide de développement pour [Khaganat](https://khaganat.net)
|
||
|
||
Ce manuel est rédigé collaborativement en Markdown à l’adresse suivante : <https://git.numenaute.org/khaganat/mmorpg_khanat/khanat_gamedev_guide>
|
||
|
||
## Sources et inspirations
|
||
|
||
Très inspiré par et basé sur le « [Gamemakin](https://gamemak.in) UE4 Style Guide » disponible sur [GitHub](https://github.com/Allar/ue5-style-guide).
|
||
|
||
Godot propose quelques conseils sur la façon de travailler pour son propre projet dans une section [Les meilleures pratiques](https://docs.godotengine.org/fr/stable/tutorials/best_practices/index.html) ainsi qu’un [Guide de style](https://docs.godotengine.org/fr/stable/development/editor/editor_style_guide.html#editor-style-guide) et un [Bonnes pratiques pour les contributeurs au moteur](https://docs.godotengine.org/fr/stable/community/contributing/best_practices_for_engine_contributors.html) destinés à celles qui souhaitent contribuer au développement du moteur lui-même.
|
||
|
||
## Principes de base
|
||
|
||
### Un manuel vivant
|
||
|
||
Ce manuel a vocation à rendre la contribution plus efficace, Khaganat est ouvert à la discussion et possède un canal XMPP où on peut échanger sur tout cela : [https://khaganat.net/irc/](https://khaganat.net/irc/).
|
||
|
||
### Respectez la loi
|
||
|
||
Khaganat respecte la loi française et vous demande de faire de même, donc merci de ne rien introduire d’illégal dans le projet par vos actions. En particulier :
|
||
|
||
- ne réemployez pas le travail d’autrui si vous n’en avez pas reçu l’autorisation explicite
|
||
- respectez les licences d’usage des éléments que vous souhaitez copier depuis un autre projet, en particulier si l’attribution est demandée (comme avec les licences CC BY)
|
||
- avant d’y apporter des éléments externes, vérifiez-en la compatibilité avec les licences du projet Khaganat
|
||
|
||
## Terminologie
|
||
|
||
### Identifiants
|
||
|
||
Un `Identifiant` est ce qu’on appelle communément un « nom » : celui d’un objet 3D, d’un fichier de son, d’un répertoire, d’une variable ou d’un script…
|
||
|
||
#### Caractères autorisés dans les identifiants
|
||
|
||
Dans un `Identifiant`, jamais on n’utilise un de ces caractères :
|
||
|
||
* les espaces
|
||
* les barres obliques ou barres obliques inverses `\`, `/`
|
||
* des symboles du genre `#!@$%`
|
||
* des caractères Unicode
|
||
|
||
Un `Identifiant` ne doit donc comprendre que les caractères suivants :
|
||
|
||
* ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||
* abcdefghijklmnopqrstuvwxyz
|
||
* 1234567890
|
||
* _ (avec parcimonie)
|
||
|
||
Pour résumer, un `Identifiant` doit pouvoir répondre à l’expression régulière `[A-Za-z0-9_]+`)
|
||
|
||
Ces règles permettent la plus grande compatibilité entre systèmes et pays.
|
||
|
||
#### L’usage des casses
|
||
|
||
Il y a plusieurs façon d’écrire les `Identifiants`, dont voici les plus courants (dénommés par leurs désignation en anglais, conventionnelle).
|
||
|
||
##### PascalCase
|
||
|
||
On met une majuscule à chaque mot et on supprime les espaces : `Coffre avec poignees` devient `CoffreAvecPoignees`.
|
||
|
||
##### camelCase
|
||
|
||
C’est similaire au PascalCase, sauf que la première lettre du premier mot est en minuscule : `Coffre avec poignees` devient `coffreAvecPoignees`.
|
||
|
||
Plus de précisions sur [Wikipédia](https://fr.wikipedia.org/wiki/Camel_case).
|
||
|
||
##### snake_case
|
||
|
||
Les espaces sont remplacés par des underscores « _ » : `Coffre avec poignees` devient `coffre_avec_poignees`
|
||
|
||
Bien que par convention, on puisse choisir de mettre ou pas des majuscules aux mots, nous retenons l’idée de tout mettre en minuscule pour nos usages du snake_case. `Coffre avec poignees` devient donc `coffre_avec_poignees`
|
||
|
||
Plus de précisions sur [Wikipédia](https://fr.wikipedia.org/wiki/Snake_case).
|
||
|
||
##### lisp-case / kebab-case
|
||
|
||
Les espaces sont remplacés par des tirets (le signe moins) « - » : `Coffre avec poignees` devient `coffre-avec-poignees`.
|
||
|
||
La désignation de cette casse n’est pas définitive, *kebab-case* semblant le plus fréquent, mais lisp l’utilise aussi depuis de très nombreuses années.
|
||
|
||
#### Variables / Propriétés
|
||
|
||
Les termes `Variable` et `Propriété` sont généralement interchangeables, mais si ils sont utilisés dans le même contexte, cela signifie plus précisément :
|
||
|
||
- pour `Propriété` que cela désigne une variable dans une classe.
|
||
- pour `Variable`, cela désigne généralement un argument de fonction ou une variable locale dans une fonction. Dans une classe, cela peut indiquer un statut à préciser/ définir.
|
||
|
||
## Les types de fichiers
|
||
|
||
### Godot
|
||
|
||
- **.escn** : *Exported Scene Data*. Format utilisé pour exporter des scènes depuis Blender vers Godot.
|
||
- **.gd** : *Source Code*. Fichiers avec le code source natif de Godot.
|
||
- **.gdc** : *Compiled Script*. Fichiers compilés.
|
||
- **.gdshader** : *Shader Data*. Shader spécifique à Godot (du GLSL modifié).
|
||
- **.godot** : *Project. Fichier descriptif du projet.
|
||
- **.import** : *Import Settings Data*. Fichier sidecar aux formats externes, qui contient les paramètres d’import.
|
||
- **.material**: *Godot material*. Matériau.
|
||
- **.mesh** : *Mesh Data*. Informations sur la géométrie d’un objet 3D.
|
||
- **.meshlib** : *Mesh Library*. Catalogue de meshes.
|
||
- **.oggstr** : *OGG Audio Data*. Fichier audio.
|
||
- **.pck** : *Game Package*. Paquet de jeu
|
||
- **.profile** : *Feature Profile Data*
|
||
- **.res** : *Binary Resource Data*. Ressource binaire.
|
||
- **.sample** : *Audio Sample Data*. Fichier audio.
|
||
- **.scn** : *Binary Scene Data*. Scène sous forme binaire.
|
||
- **.stex** : *Stream Texture Data*.
|
||
- **.tpz** : *Export Template Archive*.
|
||
- **.tres** : *Text Resource Data*. Fichier descriptif d’une ressource.
|
||
- **.tscn** : *Text Scene Data*. Scène, élément de base d’un projet.
|
||
|
||
## Convention de nommage
|
||
|
||
### Noms de fichiers
|
||
|
||
Indépendamment de leur extension, les fichiers peuvent comporter trois parties, séparées chacune de la précédente par un **underscore** : préfixe, corps et suffixe. Le préfixe et le suffixe sont déterminés par le type de fichier (son extension donc).
|
||
|
||
#### Nommage des éléments internes aux fichiers
|
||
|
||
##### Blender
|
||
|
||
Sauf indication contraire, tous les éléments internes aux fichiers Blender doivent être écrits en *kebab-case*. Le recours à un préfixe ou suffixe sera indiqué le cas échéant.
|
||
|
||
###### Collections
|
||
|
||
Les objets destinés au jeu Khanat doivent être placés dans une collection nommée « khanat », située à la racine de la collection principale.
|
||
|
||
###### Objets
|
||
|
||
Les *Objects* doivent être rassemblé dans des sous-collections pertinentes, et il faut veiller à ce que l’*Object Data qui y est associé soit nommé de façon identique.*
|
||
|
||
#### Godot
|
||
|
||
Les signaux doivent avoir un nom avec un verbe au passé, car cela indique un état qui vient d’être connu par l’émetteur.
|
||
|
||
Les fonctions doivent commencer par un verbe, voire un verbe avec un auxiliaire si elles retournent un booléen (« is_green », « has_been_healed »).
|
||
|
||
Les variables et les classes commencent généralement plutôt par un nom.
|
||
|
||
###### Export glTF
|
||
|
||
Les objets à destination du moteur de jeu Godot doivent être exportés au format glTF. Il faut pour cela sélectionner tous les objets à exporter puis aller dans `File > Export > glTF`, avec les options suivantes à vérifier particulièrement :
|
||
- `Format > glTF Embeddded`.
|
||
- `Copyright : Khaganat`.
|
||
- On peut cocher `Remember export settings` pour ne pas avoir à revérifier à chaque export de ces modèles.
|
||
- `Include > Limit to Selected Objects` coché.
|
||
- `Geometry > Apply modifiers` coché.
|
||
|
||
Les autres options par défaut conviennent.
|
||
|
||
## Le suivi de version avec Git
|
||
### Le dépôt principal
|
||
Le dépôt de référence et sa branche principale « main » doivent avoir un historique le plus propre possible. C’est la raison pour laquelle il convient de toujours travailler par le biais de forks et de merge requests via la forge gitlab de Khaganat : https://git.numenaute.org/khaganat.
|
||
|
||
Cela permet à chaque contributrice de travailler en local via son propre fork distant. Elle peut ensuite proposer son code une fois prêt via une merge request qui sera auditée par une autre contributrice ayant l’accès au dépôt principal (rôle de **developper**, **maintainer** voire **owner** du dépôt principal). C’est lors de cette étape de validation avant fusion/merge qu’un contrôle sera opéré non seulement sur le code en lui-même, mais également sur la rédaction du message de [commit](#les-commits).
|
||
|
||
### Forker sur gitlab / cloner en local
|
||
#### Forker : copie distante
|
||
|
||
En cliquant sur le bouton « Fork » sur le projet principal, vous allez créer une copie distante sur la forge Gitlab, ce sera votre dépôt de développement depuis lequel vous pourrez opérer des emrge request. Vous pouvez le gérer comme bon vous semble, mais dans le doute, vous pouvez laisser les options proposées par défaut si vous comptez simplement vous en servir comme base pour contribuer au code.
|
||
|
||
### Cloner : copie locale
|
||
Une fois votre dépôt distant créé (appelé _origin remote_ dans le jargon Git), vous allez le copier sur votre ordinateur local pour travailler, cela se fait via la commande `git clone`.
|
||
|
||
Pour obtenir l’adresse à cloner, il faut cliquer sur le bouton « Clone » de l’interface gitlab sur la page d’accueil du dépôt. Vous pourrez choisir soit par _https_, soit par _ssh_. Préférez toujours de passer par _ssh_.
|
||
|
||
git clone ssh://git@port.numenaute.org:3543/yannk/khanat_gamedev_guide.git
|
||
|
||
Cela va copier le dépôt dans un sous-répertoire de l’endroit où vous avez tapé la commande. Il vous faudra alors vous y déplacer pour travailler.
|
||
|
||
Une étape essentielle est ensuite d’ajouter le dépôt principal sur une branche locale de façon à pouvoir récupérer la dernière version du code du dépôt principal quand vous travaillez sur vos propres implémentations. Cela se fait en ajoutant une référence, appelée conventionnellement _upstream_ et pointant vers le dépôt principal, dont l’adresse est indiquée par le même bouton « Clone » sur sa page gitlab.
|
||
|
||
git remote add upstream ssh://git@port.numenaute.org:3543/khaganat/mmorpg_khanat/khanat_gamedev_guide.git
|
||
git fetch upstream
|
||
|
||
Ces commandes passées lors de la création vous permettront de mettre toute branche de votre dépôt local en tapant :
|
||
|
||
git pull --rebase upstream main
|
||
|
||
L’option `rebase` n’est pas obligatoire, mais sera nécessaire dans notre flux de travail.
|
||
|
||
Et si jamais vous souhaitez ensuite mettre à jour votre dépôt distant (votre _fork_) :
|
||
|
||
git push
|
||
|
||
Cela enverra les modifications du dépôt principal que vous avez récupérées en local (via _upstream_) vers votre dépôt distant, ce qui le mettra à jour avec le dépôt du projet.
|
||
|
||
|
||
#### Le travail en local
|
||
|
||
Vous avez par défaut sur votre dépôt local la branch `main`. Vous pouvez le vérifier en tapant la commande :
|
||
|
||
git branch
|
||
|
||
et vous obtenez :
|
||
|
||
* main
|
||
|
||
Ce qui signifie qu’il n’existe que la branche `main` et que vous êtes actuellement positionné dessus (présence de l’astérique devant)
|
||
|
||
Il faudra veiller à sa mise à jour régulière en tapant la commande :
|
||
|
||
git pull --rebase upstream main
|
||
|
||
### Gestion des branches
|
||
Pour implémenter votre code, il va falloir créer une branche dédiée, depuis la branche `main` :
|
||
|
||
git branch preference-file-creation
|
||
|
||
Vous pouvez ensuite basculer dessus pour commencer le travail
|
||
|
||
git checkout preference-file-creation
|
||
|
||
#### Mise à jour de la branche
|
||
|
||
Pensez à la mettre à jour régulièrement avec `upstream` afin de garantir la compatibilité de votre code avec les derniers ajouts :
|
||
|
||
git pull --rebase upstream main
|
||
|
||
L’option `rebase` permet de garantir que les changements que vous avez apportés seront intégrés à la fin de la branche, ce qui fait que seuls vos commits vous feront dévier du code de la branche `main` du dépôt officiel, sans occasionner de merge préalable. Cela simplifiera le merge ultérieur.
|
||
|
||
Si jamais lors du rebase, des conflits apparaissent, git s’arrêtera et vous demandera de régler le conflit (comme lors d’un merge classique), ce que vous ferez avec votre éditeur habituel. Puis vous reprendrez la suite du `rebase` avec la commande :
|
||
|
||
git rebase --continue
|
||
|
||
Recommencez autant de fois que nécessaire la gestion des conflits jusqu’à la fin de l’opération.
|
||
|
||
En cas de doute, si vous pensez avoir fait une erreur, vous pouvez annuler toute l’opération très aisément pour retrouver votre dépôt dans son état précédant le rebase avec la commande :
|
||
|
||
git rebase --abort
|
||
|
||
### Le merge request
|
||
### Les commits
|
||
|
||
#### Généralités
|
||
Les messages de commit se rédigent en anglais de façon à faciliter la collaboration internationale.
|
||
|
||
#### Rédaction des commits
|
||
La rédaction des commits se base sur les [conventionnal commits](https://www.conventionalcommits.org/en/v1.0.0/#specification) avec des spécificités.
|
||
|
||
Structure d’un message de commit :
|
||
|
||
<type>(portée, facultative): sujet
|
||
<ligne vide> (si élément suivant présent)
|
||
Corps de description plus complète (un ou plusieurs paragraphes), facultatif
|
||
<ligne vide> (si élément suivant présent)
|
||
<pied de page>, facultatif
|
||
|
||
##### Les _types_
|
||
Les types de commits possibles sont les suivants (par ordre alphabétique):
|
||
|
||
- **ci** : concerne le fonctionnement de la CI du dépôt (entraîne un changement de type _bugfix_ dans le versionnement).
|
||
- **docs** : reprise de la documentation du code sans toucher aux fonctionnalités (entraîne un changement de type _bugfix_ dans le versionnement).
|
||
- **feat** : ajoute une fonctionnalité qui doit être mentionnée dans le pied de page avec un hashtag et son numéro de ticket (entraîne un changement de type _minor_ ou _major_, selon le cas, dans le versionnement).
|
||
- **fix** : résoud un bug qui doit être mentionné dans le pied de page avec un hashtag et son numéro de ticket (entraîne un changement de type _bugfix_ dans le versionnement).
|
||
- **refactor** : refactorisation du code sans modifier son comportement, ajouter une fonctionnalité ou réparer un bug (entraîne un changement de type _bugfix_ dans le versionnement).
|
||
- **removed** : effacement d’une partie jugée inutile, redondante ou obsolète (entraîne un changement de type _minor_ ou _major_, selon le cas, dans le versionnement).
|
||
- **security** : gestion d’une vulnérabilité (entraîne un changement de type _minor_ ou _major_, selon le cas dans le versionnement).
|
||
- **style** : reprise formelle du code sans en modifier le fonctionnement (typos, espacements, tabulations etc.) (entraîne un changement de type _bugfix_ dans le versionnement).
|
||
|
||
Le cas échéant, il est possible d’ajouter de nouveaux types selon les besoins (se référer à [la convention Angular](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#type) ou [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) par exemple). Il faut alors penser à modifier les scripts de génération de changelog en conséquence (ainsi que les conséquences sur les séquences de versionnement).
|
||
|
||
Le type doit être écrit en minuscule, sans espace.
|
||
|
||
##### La portée (facultatif)
|
||
Indiquée entre parenthèses, en minuscule sans espace, elle permet de préciser éventuellement sur quel partie du code la modification porte.
|
||
|
||
Elle peut être choisie dans cette liste :
|
||
|
||
- **core** : concerne le code de base, les systèmes essentiels au fonctionnement du jeu
|
||
- **ui** : concerne l’interface utilisateur
|
||
- **shading** : concerne les shaders d’affichage
|
||
- **ia** : concerne l’intelligence artificielle des créatures en jeu
|
||
|
||
D’autres portées peuvent être ajoutées à la liste au fur et à mesure des besoins.
|
||
|
||
##### Rupture de compatibilité
|
||
Si le commit entraîne une rupture de compatibilité dans le code, le type doit se voir accoler un point d’exclamation avant les deux points, quel que soit son type (entraîne un changement de type _majeur_ dans le versionnement).Il faut alors également lui ajouter un pied de page avec la mention « BREAKING CHANGE: » suivi de la mention de l’endroit où la rupture s’opère.
|
||
|
||
##### Le sujet
|
||
Laissant une espace après les deux points, il ne comporte pas de majuscule au début ni de point à la fin.
|
||
|
||
Le sujet doit décrire en peu de mot le changement qu’opère le nouveau code.
|
||
|
||
Exemples de première ligne correcte :
|
||
|
||
feat(ui)!: change methods for IG windows, close #18
|
||
fix(core): address collision problems for player, close #37
|
||
feat: add color wheel for windows background, close #16
|
||
|
||
##### Corps de description
|
||
|
||
Facultatif, il permet de décrire de façon détaillée ce qui a motivé les choix opérés et mettre l’emphase sur les améliorations espérées par rapport à l’ancien état.
|
||
|
||
##### Pied de page
|
||
|
||
Il peut comporter deux éléments :
|
||
|
||
- si le commit clôt un ticket, il faut indiquer sur une ligne son numéro de la façon suivante : `« Close #58 »`
|
||
- si le commit entraîne une rupture de compatibilité, il doit comporter une ligne avec la mention « BREAKING CHANGES » en majuscules.
|
||
|
||
### Projets Godot
|
||
|
||
Il n’est pas utile de versionner le répertoire `.import/` situé à la racine d’un projet car son contenu peut être généré depuis les fichiers originaux et leur fichier de configuration annexe (avec l’extension `.import`, par exemple pour `icone.png` cela donne `icone.png.import`). Il convient donc d’ajouter le répertoire `.import/` dans le `.gitignore` comme c’est proposé dans [la documentation Godot](https://docs.godotengine.org/en/latest/tutorials/assets_pipeline/import_process.html#files-generated).
|
||
|
||
## Le pipeline pour les objets animés
|
||
|
||
Les objets animés avec une armature de déformation sont modélisés et conçus dans Blender, puis exportés vers Godot. Les principes appliqués proviennent des méthodes initiées par [Richard Lico](https://www.animationsherpa.com/), en particulier pour le développement du jeu [Moss](https://www.polyarcgames.com/moss/). Il s’agit de différencier les ressources et les outils, et de ne pas se préoccuper de ces derniers tant qu’ils permettent aux intervenants de travailler efficacement avec leurs outils propres et, surtout, façons personnelles. La documentation ne comporte que des instructions pour travailler sous Blender.
|
||
|
||
### Le mesh de base
|
||
|
||
Le mesh de base est sauvegardé dans son propre fichier, et sera inséré avec un `Link` depuis le fichier où se trouve son armature de déformation. Il doit se situer à la racine de la collection `Khanat`, avec un weight painting adapté à son armature de déformation.
|
||
|
||
### L’armature de déformation
|
||
L’armature de déformation est conservée dans son propre fichier, sans rien d’autre dans sa collection`Khanat`. Elle peut être ainsi être utilisée par plusieurs meshes en créant un `Link`.
|
||
|
||
### Les animations
|
||
Les animations sont conservées uniquement sous format glTF, avec une seule animation par fichier.
|
||
|
||
#### Importer une animation dans Blender
|
||
|
||
Afin de travailler une animation, il est possible de la récupérer dans le dépôt, où elle est sous format glTF. Une fois le fichier à récupérer identifié, il faut l’importer avec les préférences suivantes :
|
||
![Import d’une animation glTF dans Blender](./img/blender_import_gltf_spec.png)
|
||
|
||
#### Éditer/ créer une animation dans Blender
|
||
Une animation peut être éditée depuis n’importe quel fichier local, avec ses propre préférences de travail. Il suffit d’y importer le mesh avec son armature de déformation, puis d’appliquer à ce dernier le rig de contrôle souhaité qui permettra de procéder au travail de création/édition souhaité. Chaque animateur pourra recourir à la méthode qu’il préfère, comme le propose Richard Lico dans une conférence du GDC en 2018 ( [Animating Quill: Creating an Emotional Experience](https://www.youtube.com/watch?v=u3CzLVpuE4k&t=2011s)). Le studio [Dypsloom](https://dypsloom.com/) propose un addon spécialement développé pour ce genre de travail : [Rig On the Fly](https://gitlab.com/dypsloom/rigonthefly).
|
||
|
||
#### Sauvegarder une animation depuis Blender
|
||
|
||
Pour sauvegarder l’animation créée pour une armature depuis Blender :
|
||
|
||
1. sélectionner l’armature de déformation en mode objet, puis `Object > Animation > Bake action`
|
||
2. indiquer les préférences suivantes et valider par `OK`:
|
||
![Bake action dans Blender](img/blender_bake_action.png)
|
||
3. `File > Export > glTF 2.0 (.glb/.gltf)
|
||
4. Les préférences d’exportation à indiquer sont les suivantes :
|
||
![Spécifications d’export glTF des animations depuis Blender](img/blender_export_gltf_spec.png)
|
||
Il est conseillé de se créer un `Operator preset` afin de ne renseigner ces éléments qu’une fois.
|
||
5. Indiquer le nom ainsi que le chemin de destination appropriés et valider (voir [Les conventions de nommage](#convention-de-nommage))
|
||
|
||
#### Importer les animations dans Godot en tant que ressources
|
||
|
||
Il faut faire en sorte que l’animation devienne une ressource qui pourra être appelée dans un `Animation Player`. Travailer ainsi permettra de ne plus avoir à se soucier des scènes héritées, n’importe quel élément de ces dernières pouvant avoir été manipulée dans Godot depuis son importation. Appeler des ressources secondaires permet d’ajouter de nouveaux éléments importés qui se rapportent à d’autres éléments anciennement ajoutés également depuis une source externe.
|
||
|
||
Une fois les fichiers visibles dans l’arborescence de Godot (fichiers _.glb_) :
|
||
|
||
1. double-cliquer dessus permettra d’afficher la fenêtre des paramètres d’importations.
|
||
2. sélectionner à droite l’action à importer (`bone_turn`dans l’exemple ci-dessous) et paramétrer la partie `Save To File`en cochant `Enabled`et `Keep Custom Tracks`et en indiquant le chemin où la ressource sera enregistrée dans `Path`.
|
||
![Importer une animation dans Godot en tant que ressource](./img/import_godot.png)
|
||
#### Ajouter manuellement une animation dans une node AnimationPlayer sous Godot
|
||
Lorsqu’on a importé un certain nombre de ressources d’animations qui peuvent concerner le même objet (typiquement les actions d’un mesh avec son armature), il est possible de les ajouter à la node `AnimationPlayer`qui l’anime :
|
||
|
||
1. sélectionner la node `AnimationPlayer`à compléter
|
||
2. aller en bas dans l’onglet `Animation`et cliquer sur le menu de cette zone, sélectionner `Manage Animations`
|
||
![Gérer les animations d’un AnimationPlayer dans Godot](./img/godot_modify_animation_01.png)
|
||
3. Dans la fenêtre popup qui s’ouvre, cliquer sur l’icône pour ouvrir une ressource existante et aller là où se trouve l’action à ajouter.
|
||
![Ajotuer une animation à un AnimationPlayer dans Godot](./img/godot_modify_animation_02.png)
|
||
|
||
## License
|
||
|
||
CC BY SA Khaganat - 2022
|