![](https://i.imgur.com/pW7K0Xp.png) Projet Greydom <font size="4pt"><sup>*Semestre automne 2022*</sup></font> ###### tags: `Projet - Greydom` # <font size="8pt"><b>Rapport</b></font> <font size="4pt"><strong>Nom du projet : </strong>Greydom</font> <font size="3pt">_**Professeur**_ : Benoit Le Callennec</font> --- <font size="3pt">_**Auteurs :**_</font> @Alex.Mozerski @Milan.Cervino @William.Bikuta @Yasmine.Margueron <font size="5pt"><b>Table des matières : </b></font> [ToC] --- ## Abstract Ce présent document contient toutes les informations quant au développement du jeu "Greydom", réalisé dans le cadre du cours d'infographie. Il s'agit d'un jeu d'action-aventure à la troisième personne dans un monde médiéval fantastique. ## 1. Introduction Lors de parties de jeu vidéo, il est attrayant d'imaginer créer son propre jeu sorti tout droit de son imagination et avec une histoire intéressante. L'utilisation d'une plateforme de développement 3D en temps réel est une solution pratique et efficace pour réaliser un jeu. Durant la troisième année de formation en Développement logiciel et multimédia à la Haute École Arc Ingénierie, il a été demandé de réaliser un travail pratique dans le cadre du cours d'infographie, à l'aide du logiciel Unity. Ce cours s'inscrit dans l'apprentissage des principes de l'imagerie et des animations. Le choix du sujet s'est porté sur un jeu d'action-aventure à la troisième personne dans un monde d'inspiration médiéval fantastique. Il a été réalisé sur une durée de travail du 20 janvier 2022 jusqu'au 6 mai 2022. Il est supervisé par l'enseignant Benoit Le Callennec. Le joueur peut déplacer un personnage en vue à la troisième personne. L'objectif est de voyager à travers le monde et d'affronter les menaces souhaitant entraver le joueur. ## 1.1 Contexte Ce projet est basé sur un jeu d'action-aventure à la troisème personne. Le concept se base sur l'évolution d'un personnage dans un monde qui se situe dans l'univers médiéval-fantastique. Le joueur incarne une chevalière partie de son village pour effectuer une mission. Elle reçoit alors un message du roi lui demandant de revenir de toute urgence, car des ennemis se rapprochent dangereusement de leur peuple. Sans aucune hésitation, la héroine se mis en route et c'est au joueur de réaliser cette mission de la plus haute importance. Le jeu commence tout d'abord dans une forêt, à proximité du village que le joueur tente de rejoindre, qui se fait actuellement attaquer par une armée. Le joueur devra alors traverser cette forêt et affronter les ennemis sur son passage afin d'atteindre le village pour sauver les habitants et expulser les ennemis. ## 1.2 Inspiration L'inspiration principale de ce projet est le jeu d'action aventure développé et édité par Ember Lab "Kena bridge of spirits". Il s'agit d'un jeu narratif combiné à de l'exploration et des combats. ![](https://i.imgur.com/bPKvJm4.png) Source : https://store.epicgames.com/fr/p/kena-bridge-of-spirits ## 2. Analyse Ce chapitre présente l'analyse détaillée du projet, avec les objectifs principaux et secondaires, ainsi que les contraintes. ### 2.1 Objectifs #### 2.1.1 Principaux - Menu principal - Bouton "Jouer" pour lancer le jeu - Bouton "Crédits" pour afficher les crédits du jeu - Bouton "Quitter" pour quitter l'application - Jeu - Map - Partie forêt pour débuter - Partie château et village pour la suite - Joueur - Déplacements - Gestion collisions - Saut - Attaque - Défense - Animations - Ennemis - Déplacements sur le joueur - Attaque - Animations #### 2.1.2 Secondaires - Ajouter une musique de menu - Ajouter une musique de jeu - Ajouter des sons (déplacement, combat, etc.) - Ajouter plusieurs types d'ennemis - Ajouter des personnages se déplaçants aléatoirement dans la map (NPC) - Déplacements aléatoires dans la map - Gestion collisions - Animations #### 2.1.3 Besoins et contraintes Ce projet se déroule par groupe de 3 ou de 4 personnes. Le but est de démontrer la connaissance de bases de Unity en intégrant plusieurs notions de manière pertinente dans un projet global. Voici les points importants introduits en cours : - Utiliser le moteur de jeu Unity - Prefab - Viewport Rect - Depth + Culling Mask - Lumières en temps réel - Ombres - Lightmap - Ambient occlusion - Coroutines - Animations - Matériaux - Shadergraph - Triggers - Post-processing ## 3. Implémentation Ce chapitre présente la partie implémentation du projet, qui est divisé en plusieurs grands points. ### 3.1 Map Pour ce qui est de l'implémentation de la map, il n'y a pas de concept technique très avancé. Toutefois, voici les différents principes et méthodologies utilisés. Dans un premier temps, les différents objets et terrains composants la map proviennent principalement d'Assets Unity. #### 3.1.1 Terrain Afin de créer une structure de base à la map, des terrains de montagnes et rivières ont été assemblés. Les différents terrains sont eux-mêmes des superpositions de terrains avec peu de polygones qui forment ensemble un élément du type "Low Poly". Etant donnée que la structure de la map permet d'avoir un arrière-plan et de donner du volume au jeu, il n'est pas nécessaire d'utiliser des objets très détaillés. De ce fait, l'utilisation d'éléments "Low Poly" permet de gagner en ressources et en performances rendant ainsi le jeu moins lourd qu'avec des terrains trop détaillés. Les terrains ont été créés à l'aide d'un module permettant de générer l'environnement avec des montagnes et d'autres éléments. La map a donc été créée entièrement en assemblant plusieurs morceaux de terrain. #### 3.1.2 Forêt Pour la première partie du jeu, l'environnement est une forêt dont les objets la composant proviennent d'Assets Unity. La structure de base de la forêt est un terrain Unity. Le terrain est un objet représenté par une surface plane. Cet objet comporte des fonctionnalités, telles que "paint trees" qui permet de "peupler" la forêt avec un objet (en principe un arbre, mais il est possible d'utiliser d'autres objets), qui est dupliqué sur le terrain. Ceci permet donc de créer une forêt rapidement et simplement. #### 3.1.3 Château Pour la seconde partie du jeu dont l'environnement est un château, il a été complètement composé d'objets provenants d'Assets Unity. Le château est principalement composé de maisons ou d'objets statiques. Il existe tout de même des objets animés tels que du feu ou de la fumée. Ces objets sont des systèmes de particules animées de Unity. #### 3.1.4 Téléporteur Sur la map se trouve également un téléporteur permettant de déplacer le joueur de la première partie se situant dans la forêt vers la deuxième partie avec le château. L'aspect et l'animation du téléporteur proviennent d'une Asset Unity, mais le script a été créé pour qu'il s'adapte au cas de ce jeu. Le script comporte deux méthodes _OnTriggerEnter()_ et _OnTriggerExit()_ qui gèrent les évènements entre le collider du joueur et celui du téléporteur. Dans la méthode _OnTriggerEnter_, un booléen "inside" est passé à vrai et l'attribut "transform" de l'objet entrant est enregistré. Ensuite, la méthode _Update()_ gère un compteur qui diminue lorsqu'il y a un objet à l'intérieur. Une fois que le compteur est à 0, il déplace le "transform" de l'objet vers un autre téléporteur cible. Lorsque l'objet ressort du téléporteur avant que le compteur n'atteigne 0, celui-ci est remis à sa valeur initiale. ### 3.2 Joueur #### 3.2.1 Animations Les animations du joueur ont tout d'abord été séparées en différents layers qui regroupent les animations d'une action générale, comme par exemple les attaques. La séparation en layers permet dans un premier temps de structurer les animations du joueur en différentes couches. Puis, la séparation en layers permet aussi d'appliquer des masques sur les différentes couches permettant ainsi à des animations de ne pas affecter certaines parties du corps du joueur. Par exemple, l'animation d'armement et désarmement ne doit être appliquer uniquement sur le haut du corps pour permettre ainsi au joueur de courir tout en dégainant son arme. Sans ce masque, l'animation de course sur les jambes serait bloquée. ![](https://i.imgur.com/T9rPP1I.png) La transition entre les animations a été réalisée de manière à ce qu'elle ne s'exécute que sous certaines conditions. En effet, il est possible de créer un paramètre de condition, pouvant être basées sur des états du joueur, directement sur une transition. Par exemple, l'animation de chute ne transite sur celle de l'atterrissage uniquement lorsque le joueur touche le sol. ![](https://i.imgur.com/S9C3LOC.png) #### 3.2.2 Camera manager L'objet "Camera Manager" permet de gérer le déplacement et l'orientation de la caméra principale de la scène pour que la caméra suive le joueur et s'oriente toujours autour du personnage principal. Cet objet contient dans un premier temps, un objet "Camera Pivot" qui sera utilisé pour effectuer les rotations de la caméra autour du joueur. Puis, ce pivot contient, quant à lui, la caméra principale de la scène. Le "Camera Manager" possède également un script avec les fonctions suivantes: - **Awake()** qui est appelée au début du jeu et qui permet d'initialiser les objets, tels que le joueur qui sera la cible, mais aussi l'affichage des vies et le menu pause afin de toujours pouvoir les afficher face caméra. - **FollowTarget()** permet, comme son nom l'indique, de suivre la cible, qui est le personnage. Elle utilise la méthode _"SmoothDamp"_ qui permet de modifier progressivement un vecteur vers un objectif souhaité au fil du temps et qui est souvent utilisée pour le lissage d'une caméra suiveuse. Le vecteur est lissé par une fonction de type ressort-amortisseur, qui ne dépassera jamais l'objectif. Cette méthode s'applique également sur les informations qui appraissent à l'écran, comme la vie du joueur, la barre d'énergie et le menu pause qui se déplace avec la caméra pour qu'ils apparaissent toujours à l'écran. - **CameraRotation()** permet de calculer la rotation de la caméra en calculant l'angle de rotation vertical et horizontal en fonction de l'input du joueur (souris ou joystick) et d'une sensibilité. Ensuite, les rotations finales sont calculées à l'aide de quaternions et de Euler pour être appliquées sur le "Camera Pivot" et le "Camera Manager". - **HandleCameraCollision()** gère la collision du "Camera Pivot" avec l'environnement, comme par exemple les murs. Pour ce faire, la fonction _Physics.SphereCast_ est utilisée et permet notamment de trouver le point de contact de la collision. Ensuite, en calculant la distance du "Camera Pivot" et ce point de contact, il est possible de replacer la caméra sur le joueur de sorte à ce que la vision de l'utilisateur ne soit pas gênée par un obstacle. Plus précisément, c'est la coordonnée "z" de la caméra qui est modifiée avec une interpolation linéaire entre la position en "z" actuelle et la position déduite de la distance précédemment calculée. #### 3.2.3 Système d'inputs Le système d'inputs appliqué sur le joueur s'adapte pour que le jeu soit jouable autant avec un clavier et une souris, qu'avec tout autre contrôleur de console de jeu vidéo (Xbox, Switch, etc.). La logique des inputs se trouve dans le fichier **InputManger**, qui est appliqué sur le joueur. La récupération des inputs se fait dans le script **PlayerControls**, permettant de gérer les inputs des deux types d'entrée possible (manette et clavier/souris). Pour la logique des inputs, le script **InputManager** détecte simplement quel input est appelé et modifie le joueur en appelant les méthodes du "PlayerLocomotion", le menu pause ou encore le "PlayerAttack". ### 3.3 Ennemis Ce sous-chapitre présente la partie implémentation des ennemies. Les prefabs utilisés pour les ennemis proviennent d'Assets Unity, se nommant "Forest pack" de "Malbers animations". Les prefabs utilisées sont celles de l'ours et du tigre. Les ennemis sont découpés en quatre parties : - La partie contenant le modèle 3D - La partie gérant les déplacements et les attaques - La partie d'animationa - Le partie du scriptable object Cette dernière partie, qui est le scriptable object, permet de définir l'ensemble des paramètres propres à chacun des ennemis. Ces paramètres contiennent une description et un nom, ainsi que de l'efficacité de la gestion des déplacements sur le joueur délégué et également à l'intelligence artificielle. L'idée de départ de l'utilisation des scriptables objets est de construire de manière la plus générale possible les ennemis. #### 3.3.1 Navmesh Des agents NavMesh permettent le déplacement des ennemis dans la carte. Un NavMesh est un "mesh" créé dans la scène servant à spécifier les zones navigables dans l'environnement, comme par exemple les zones où les personnages peuvent marcher ou encore les obstacles. L'utilisation de ceux-ci devient particulièrement utile lors de la mise en place de _pathfinding_ et de navigation contrôlée par l'IA. Le composant NavMesh agent aide les personnages à se déplacer et faire fonctionner le _pathfinding_ dans la scène vers un objectif commun. Ils permettent de créer plusieurs comportements de déplacements en fonction du type de personnage. Ce projet utilise le package expérimental _Navigation IA 1.0.0-exp.4_, plus spécifiquement "NavMeshSurface" et "NavMeshLink", qui fournissent des contrôles supplémentaires pour générer les Navmesh. Les déplacements des ennemis vers la cible, qui est le joueur, se font avec un navmesh Agent présent dans le script **EnnemyMovement**. Chaque chargement du chemin se fait à un rythme défini. La commande _agent.setDestination_ permet de définir et mettre à jour la destination. Cela permet ainsi le calcul d'un nouveau chemin vers la cible, devant êtres de type structure Vector3. ```csharp= agent.SetDestination(player.transform.position); ``` #### 3.3.2 Scriptable object Les objets "ScriptableObject" sont des conteneurs de données. Ils permettent de stocker les données pour y accéder par référence par toutes les autres instances de prefabs. Ceux-ci permettent de stocker des données partagées, indépendamment des instances de script. Une seule copie des données est présente en mémoire. Ici, ils servent à définir les types d'ennemis et les paramètres qui leur sont propres. #### 3.3.3 Spawner Les spawners vont être utilisés pour créer les ennemis et les initialiser dans la scène. Les spawners définissent la cible des ennemis qui en sortiront, le délai de création, une liste des prefabs pouvant être créées et le nombre d'ennemis souhaités. Lors de la création, une instance "ObjectPool" est produite pour chacune des prefabs. Une coroutine permet de respecter le délai fixé entre chaque création. L'énumération "SpawnMethod" permet de définir la manière dont les spawns seront effectués, soit "RoundRobin" ou en "Random". La méthode _SpawnRoundRobinEnemy_ permet de calculer l'index de création à l'aide d'un modulo entre le nombre d'ennemis créés et le nombre de préfabs présentes. Si aucun ennemi n'a été créé pour le moment et que deux prefabs sont disponibles, alors le résultat est "zéro". Par contre, si un ennemi est déjà créé, cela va retourner "un" et donc si deux ennemis sont créés cela va retourner "zéro". La méthode de _spawn aléatoire_ va créer un ennemi choisi aléatoirement entre zéro et le nombre total de prefabs présentes. La méthode _DoSpawnEnemy_ permet de créer un ennemi avec un index choisi à l'aide d'une des deux méthodes présentées ci-dessus. L'objet à placer dans la carte doit être positionné sur un emplacement où le *NavMesh* se trouve. La méthode *NavMesh.CaculateTriangulation* permet de choisir spécifiquement un vertex sur le *NavMesh* et retourner la position où l'ennemi peut être créé. *NavMesh.SamplePosition* trouve la position valide la plus proche sur le *NavMesh*. Ensuite, "Warp" sert à aligner la position *Hit* sur le "NavMesh". Une fois l'ennemi positionné, il est activé et commence à suivre directement sa cible. ```csharp= int VertexIndex = Random.Range(0,triangulation.vertices.Length); NavMeshHit Hit; if (NavMesh.SamplePosition(triangulation.vertices[VertexIndex], out Hit, 2f, -1)) { enemy.agent.Warp(Hit.position); // enemy needs to get enabled and start chasing now. enemy.movement.player = player; enemy.agent.enabled = true; enemy.movement.StartChasing(); } ``` #### 3.3.4 Remarques La première version de l'implémentation des ennemis n'était pas concluante et difficilement intégrable dans le jeu. Par manque de temps, cette implémentation a été mise de côté (dans la branche "feature_enemy") et une version simplifiée a été implémentée en fin de projet. Les problèmes principaux de la première version se trouve dans le comportement des ennemis qui n'aggissent pas correctement et les animations qui ne fonctionnent pas entre elles. ### 3.4 NPC Ce chapitre présente la partie implémentation de la partie "NPC", signifiant "Non-player character". Un personnage non-joueur désigne tout personnage d'un jeu qui n'est pas contrôlé par le joueur, mais par l'ordinateur avec un comportement algorithmique, prédéterminé ou réactif. Dans le jeu "Greydom", les NPC représentent des villageois courants dans le château dans le but d'échapper aux explosions et à l'attaque de leur habitation. Pour ce faire, plusieurs types de personnages ont été ajoutés afin d'avoir de la diversité. Ces personnages n'ont aucune interaction avec le joueur ou entre eux. Ils permettent uniquement d'ajouter de l'animation et un effet "catastrophique" à la situation lors du jeu. #### 3.4.1 Prefabs Les prefabs des personnages proviennent de l'asset nommée "Polygon Fantasy Kingdom" de Unity. Plusieurs prefabs ont été utilisé afin d'avoir des personnages différents : - Princess - Fairy - Peasant - Blacksmith ![](https://i.imgur.com/WcRgLvC.jpg) #### 3.4.2 Animations Plusieurs types d'animations ont été utilisées pour ces personnages, comme "Run" et "Sprint". Ces animations ont été reprises de celles créer pour le joueur, qui ont été décrites dans le chapitre "Joueur - animations". Une boucle a été réalisée avec les animations "Run" et "Sprint" afin d'avoir un déplacement en continu mais tout de même varié, grâce à ce changement de type d'animation. ![](https://i.imgur.com/WwR1glK.png) L'état "New State" dans l'image ci-dessus, représenté par le bloc orange, permet de réaliser la première animation avec le mouvement "Run". Cet état utilise le script "IntParameterRandomSetter" qui permet de choisir aléatoirement entre les trois prochaines animations. Chaque transition possède un identifiant permettant de les différencier et de sélectionner celle correspondant au nombre aléatoire générer précédemment. Voici la manière dont le script est utilisé avec le paramètre "State Count" : ![](https://i.imgur.com/834ec7T.png) Génération du nombre aléatoire entre 0 et "State Count": ```csharp= override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { animator.SetInteger(IntParameterName, Random.Range(0, StateCount)); } } ``` Chaque transition contient une condition pour savoir si elle sera lancée ou non, en fonction du résultat du "Idle State Index" : ![](https://i.imgur.com/Xy3AmSH.png) #### 3.4.3 Spawn et déplacements Le point du spawner de la map utilise le script **"SpawnScript"**, qui prend plusieurs paramètres : - Des prefabs pour obtenir le GameObject du personnage - Le nombre de chaque personnage souhaité - Le temps d'intervalle entre deux apparitions de personnages - Distance minimale et maximale pour que les personnages apparaissent à des emplacements différents et aléatoirement choisi ![](https://i.imgur.com/WjwGsYQ.png) En fonction de ces paramètres, les personnages seront initialisés sur le terrain de jeu et se déplaceront aléatoirement sur la map. Pour ce faire, une liste de GameObject est créé pour chaque type de personnage. Ceux-ci vont apparaître les uns après les autres en fonction du "spawnInterval" et de la position choisie en fonction de la distance minimale et maximale. Afin d'avoir une variation dans les déplacements, la rotation initiale du personnage est également aléatoire afin qu'ils n'aillent pas tous dans la même direction dès l'initialisation. Des fonctions "ChangeDirection" et "RandomDirection" sont appelées toutes les frames grâce à la fonction "Update" pour que chaque personnage change de direction aléatoirement. L'utilisation de "Quaternion.Slerp" a été nécessaire pour avoir une rotation fluide et dans un certain angle choisi aléatoirement également. <br> Le script **"CharacterControl"** permet de gérer les animations avec le component "Animator" présenté précédemment. Pour ce faire, nous allons récupérer l'attribut "Runspeed" et ainsi calculer la nouvelle position selon l'axe "z" pour que chaque personnage se déplace correctement sur la map. ```csharp= void OnAnimatorMove() { Animator animator = GetComponent<Animator>(); if (animator) { Vector3 newPosition = transform.position; newPosition.z += animator.GetFloat("Runspeed") * Time.deltaTime; Vector3 deltaPos = new Vector3(0,0,1) * animator.GetFloat("Runspeed") * Time.deltaTime; transform.Translate(deltaPos); } } ``` L'un des problèmes rencontrés lors de cette partie, était que tous les personnages se déplaçaient autour d'un point fixe et toujours tous dans la même direction. Après maintes recherches, la solution consistait à simplement utiliser les coordonnées locales plutôt que les globales. ### 3.5 Interfaces graphiques Les différents menus sont réalisés à l'aide de différents canvas. Les boutons sont placés sur le canvas, qui est ensuite appelé directement en premier plan de la scène aux moments voulus. Un script permettant la disposition en plein écran du canvas est ajouté directement sur ce dernier. La particularité du menu pause est que celui-ci doit arrêter le jeu en arrière plan. Pour ce faire, le paramètre "time scale" qui est un paramètre global au jeu est mis temporairement à zéro. Ainsi, les objets de la scène sont statiques, car toutes les fonctions qui dépendent du frame rate (comme _Update()_), ne sont plus exécutées, permettant ainsi de mettre chaque objet en pause. ## 4. Déploiement Une build du jeu existe dans le dossier build du projet. ## 5. Problèmes et solutions ### 5.1 Problème physique en mode dynamique Les personnages dérivent lorsqu'ils sont immobiles ou en mouvement. #### 5.1.1 Solution La solution pour ce problème est de sélectionner l'option isKinematic dans le composant rigidbody. En effet, celui-ci n'a pas l'option cochée par défaut. Cela permet de ne plus être affectée par la physique présente dans la scène. Une rigidbody donne à un gameobject une présence physique dans la scène, et lui donne la posibilitée de répondre aux différentes forces, ainsi qu'aux collisions avec d'autres rigidbody. De base, le rigidbody répond aux forces exterieures. ## 6. Améliorations ### Techniques - Améliorer les ennemis - Fluidifier les animations - Ajouter des attaques - Améliorer le déplacement des NPCs - Eviter le danger ou le joueur - Améliorer les zones de combats et de spawn ### Features - Ajouter de nouveaux types d'ennemis (humain par exemple) - Ajouter des sons sur les actions des personnages et une musique d'ambiance - Ajouter un boss final en fin de niveau ## 7. Conclusion Le but de ce projet était de développer un jeu d'action-aventure à la troisième personne avec le moteur de jeu Unity. L'objectif principal était de réaliser un jeu en 3D et de voyager à travers les différents environnements. La première partie du projet était consacrée à la mise en place de la carte de base. Celle-ci est composée d'un terrain, d'une forêt et d'un château, elle contient également des téléporteurs pour permettre aux joueurs de se déplacer dans différents endroits de la carte. En parallèle, le joueur a été créé avec ses animations et la gestion de la caméra permettant de s'orienter autour du personnage. Afin d'amener plus de vie et du gameplay, des ennemis et des personnages secondaires ont été mis en place. Pour conclure, ce projet a permis de découvrir les bases de l'animation 3D et de l'utilisation d'un moteur de jeu. Malgré les problèmes survenus, le scénario du jeu est sensé et respecte l'idée de base et le gameplay du jeu propose une experience intéressante. ## 8. Sources ### 8.1 Sources web Unity3d. *NavMesh Building Components - docs.unity3d.com*. 2022. Disponible à l'adresse : https://docs.unity3d.com/Packages/com.unity.ai.navigation@1.0/manual/index.html#details. [En ligne; Page dispnible le 05 mai 2022] Unity3d. *Building a NavMesh - docs.unity3d.com*. 2022. Disponible à l'adresse : https://docs.unity3d.com/Manual/nav-BuildingNavMesh.html. [En ligne; Page dispnible le 05 mai 2022] Unity3d. *NavMesh Surface - docs.unity3d.com*. 2022. Disponible à l'adresse : https://docs.unity3d.com/Packages/com.unity.ai.navigation@1.0/manual/NavMeshSurface.html. [En ligne; Page dispnible le 05 mai 2022] ### 8.2 Assets #### Map https://assetstore.unity.com/packages/3d/environments/low-poly-modular-terrain-pack-91558 https://assetstore.unity.com/packages/3d/vegetation/trees/polygon-nature-low-poly-3d-art-by-synty-120152 https://assetstore.unity.com/packages/3d/environments/fantasy/polygon-fantasy-kingdom-low-poly-3d-art-by-synty-164532 https://assetstore.unity.com/packages/tools/custom-teleporter-pad-script-20098 #### Joueur https://assetstore.unity.com/packages/3d/environments/fantasy/polygon-fantasy-kingdom-low-poly-3d-art-by-synty-164532 #### Ennemis https://assetstore.unity.com/packages/3d/characters/animals/poly-art-animal-forest-set-128568 #### NPC https://assetstore.unity.com/packages/3d/environments/fantasy/polygon-fantasy-kingdom-low-poly-3d-art-by-synty-164532 https://assetstore.unity.com/publishers/16163 #### Autres https://assetstore.unity.com/packages/2d/textures-materials/sky/skybox-series-free-103633 https://assetstore.unity.com/packages/vfx/particles/polygon-arsenal-109286