Comment ajouter vos propres outils à l'éditeur de Unity

Dans ce didacticiel, vous apprendrez à étendre l'éditeur de Unity3D afin de mieux l'utiliser dans votre projet. Vous apprendrez à dessiner votre propre gizmo, à créer et à supprimer des objets dans du code, à créer des fenêtres d’éditeur, à utiliser des composants et à autoriser l’utilisateur à annuler toute action entreprise avec votre script..

Ce tutoriel suppose que vous connaissez déjà les bases du workflow Unity. Si vous savez créer des objets, des préfabriqués, des scènes, vous déplacer dans l'éditeur, attacher des composants, vous êtes prêt à partir.!


Aperçu du résultat final

Jetons un coup d'œil au résultat final sur lequel nous allons travailler:

Comme vous pouvez le constater, nous allons créer une fenêtre d’éditeur et un sélecteur de couleurs dont nous utiliserons la sélection pour dessiner une grille. Nous pourrons également créer et supprimer des objets, accrochés à cette grille, et annuler de telles actions..


Étape 1: Gizmos

Nous allons d’abord apprendre à utiliser les gizmos. Voici quelques exemples de gizmos intégrés.

C’est celui que vous verrez probablement le plus souvent dans Unity, car il est dessiné pour chaque objet qui a un Transformer composant attaché à celui-ci - donc, fondamentalement, chaque objet sélectionné aura ce gizmo dessiné.

Voici un autre gizmo, qui nous permet de voir la taille de la BoxCollider attaché à notre objet de jeu.


Étape 2: Créer un script de grille

Créez un script C # que nous pouvons utiliser pour dessiner notre propre gizmo pour un objet; nous allons dessiner une grille simple dans l'éditeur à titre d'exemple.

utiliser UnityEngine; using System.Collections; Classe publique Grid: MonoBehaviour void Start ()  void Update () 

Pour une grille, nous devons ajouter deux variables, la largeur et la hauteur.

public class Grid: MonoBehaviour public float width = 32.0f; hauteur flottante publique = 32.0f; void Démarrer ()  void Update () 

Pour dessiner dans l'éditeur, nous devons utiliser OnDrawGizmos rappel, alors créons-le.

public class Grid: MonoBehaviour public float width = 32.0f; hauteur flottante publique = 32.0f; void Start ()  void Mise à jour ()  void OnDrawGizmos () 

Étape 3: Dessinez la grille

Pour dessiner une grille, nous avons besoin d'un ensemble de lignes horizontales et verticales et de la position de la caméra de l'éditeur afin de savoir autour de quel point nous devrions dessiner notre grille. D'abord, sauvegardons la position de la caméra dans une variable séparée.

void OnDrawGizmos () Vector3 pos = Camera.current.transform.position; 

Comme vous pouvez le constater, nous pouvons obtenir la caméra de l'éditeur en utilisant le Caméra.current référence.

Maintenant, nous aurons besoin de deux boucles for pour tracer les lignes horizontales et verticales.

void OnDrawGizmos () Vector3 pos = Camera.current.transform.position; pour (float y = pos.y - 800.0f; y < pos.y + 800.0f; y+= height)  Gizmos.DrawLine(new Vector3(-1000000.0f, Mathf.Floor(y/height) * height, 0.0f), new Vector3(1000000.0f, Mathf.Floor(y/height) * height, 0.0f));  for (float x = pos.x - 1200.0f; x < pos.x + 1200.0f; x+= width)  Gizmos.DrawLine(new Vector3(Mathf.Floor(x/width) * width, -1000000.0f, 0.0f), new Vector3(Mathf.Floor(x/width) * width, 1000000.0f, 0.0f));  

Pour tracer des lignes, nous utilisons Gizmos.DrawLine (). Notez que le Gizmos class a beaucoup d'autres méthodes d'API de dessin, il est donc possible de dessiner des primitives telles que cube ou sphère ou même leurs structures filaires. Vous pouvez également dessiner une image si vous avez besoin de.

Les lignes de la grille doivent être infiniment longues mais float.positiveInfinity et float.negativeInfinity ne semblait pas bien fonctionner pour tracer les lignes, donc nous pouvons simplement mettre des nombres arbitrairement grands au lieu de ceux-là. De plus, le nombre de lignes dépend strictement des constantes que nous mettons dans le pour définitions de boucles; techniquement, nous ne devrions pas laisser ces constantes comme ça, mais c'est juste un code de test.

Pour voir la grille, créez un objet vide et y attachez notre script:


Étape 4: Créer un inspecteur personnalisé

La prochaine chose à couvrir est la personnalisation de l'inspecteur. Pour ce faire, nous devons créer un script d’éditeur. Créez un nouveau fichier C # et nommez-le Editeur de grille. Ce script doit être placé dans le Éditeur dossier; si vous n'en avez pas, créez-le maintenant.

utiliser UnityEngine; utiliser UnityEditor; using System.Collections; [CustomEditor (typeof (Grid))] classe publique GridEditor: Editor 

Cette fois, nous devons aussi utiliser UnityEditor pour pouvoir utiliser les classes et les fonctions de l'éditeur. Pour remplacer l'inspecteur par défaut de notre la grille objet nous avons besoin d'ajouter un attribut avant notre déclaration de classe, [CustomEditor (typeof (Grid))]] fait savoir à Unity que nous allons personnaliser le la grille'inspecteur. Pour pouvoir utiliser les callbacks de l'éditeur, il faut dériver de la Éditeur classe au lieu de MonoBehaviour.

Pour changer l'inspecteur actuel, nous devons remplacer l'ancien.

Classe publique GridEditor: Editor Public override void OnInspectorGUI () 

Si vous vérifiez l'inspecteur de l'objet de grille dans l'éditeur maintenant, il sera vide même si l'objet a lui-même des membres publics. C'est parce qu'en annulant la OnInspectorGUI () nous avons supprimé l'inspecteur par défaut afin d'en faire un personnalisé à la place.


Étape 5: Utilisez GUILayout pour remplir l’inspecteur personnalisé.

Avant de créer des champs, nous devons obtenir une référence à l'objet auquel l'inspecteur s'applique. Nous avons déjà sa référence - elle porte le nom cible - mais par commodité, nous allons créer une référence à la la grille composant de cet objet. Tout d'abord, déclarons-le.

Classe publique GridEditor: Editor Grid Grid;

Nous devrions l'assigner dans OnEnable () fonction appelée dès que l'inspecteur est activé.

Classe publique GridEditor: Editor Grid Grid; public void OnEnable () grid = (Grid) target; 

Créons maintenant quelques champs d’inspecteur. Nous allons utiliser les classes GUILayout et EditorGUILayout pour cela.

remplacement public void OnInspectorGUI () GUILayout.BeginHorizontal (); GUILayout.Label ("largeur de grille"); grid.width = EditorGUILayout.FloatField (grid.width, GUILayout.Width (50)); GUILayout.EndHorizontal (); 

La première ligne, GUILayout.BeginHorizontal (); indique que nous voulons placer les éléments d'inspecteur suivants l'un à côté de l'autre, de gauche à droite. Comme vous pouvez l’imaginer, la dernière ligne, GUILayout.EndHorizontal (); indique que nous ne voulons plus faire cela. Les éléments réels sont entre ces deux lignes. Le premier est une étiquette simple (dans notre cas, il affichera Largeur de la grille texte), puis à côté, nous créons un EditorGUILayout.FloatField ce qui est comme vous pouvez imaginer un champ float. Notez que nous assignons grid.width à la valeur de cela FloatField, et le champ float lui-même montre la valeur de grid.width. Nous avons également réglé sa largeur sur 50 pixels.

Voyons si le champ est ajouté à l'inspecteur:


Étape 6: Remplissez l'inspecteur et repeignez la scène

Ajoutons maintenant un autre élément à l'inspecteur; cette fois ce sera hauteur de grille.

remplacement public void OnInspectorGUI () GUILayout.BeginHorizontal (); GUILayout.Label ("largeur de grille"); grid.width = EditorGUILayout.FloatField (grid.width, GUILayout.Width (50)); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Hauteur de la grille"); grid.height = EditorGUILayout.FloatField (grid.height, GUILayout.Width (50)); GUILayout.EndHorizontal (); 

Ce serait tout pour nos champs d'objet de grille. Si vous souhaitez connaître d'autres champs et éléments que vous pouvez utiliser dans l'inspecteur, vous pouvez consulter les pages de référence Unity sur EditorGUILayout et GUILayout..

Notez que les modifications apportées dans notre nouvel inspecteur ne sont visibles que lorsque nous avons sélectionné la fenêtre Vue de la scène. Pour les rendre visibles une fois qu’ils sont fabriqués, nous pouvons appeler SceneView.RepaintAll ().

remplacement public void OnInspectorGUI () GUILayout.BeginHorizontal (); GUILayout.Label ("largeur de grille"); grid.width = EditorGUILayout.FloatField (grid.width, GUILayout.Width (50)); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Hauteur de la grille"); grid.height = EditorGUILayout.FloatField (grid.height, GUILayout.Width (50)); GUILayout.EndHorizontal (); SceneView.RepaintAll (); 

Maintenant, nous n'avons plus besoin de cliquer en dehors de l'inspecteur pour voir les résultats des modifications..


Étape 7: Gérez l'entrée de l'éditeur

Essayons maintenant de gérer les entrées de l'éditeur, comme nous le ferions dans le jeu. Tous les états de clé ou de souris doivent être disponibles. Pour avoir cette fonctionnalité, nous devons ajouter un onSceneGUIDelegate rappel à notre SceneView. Appelons notre fonction de mise à jour GridUpdate ().

public void OnEnable () grid = (Grid) target; SceneView.onSceneGUIDelegate = GridUpdate;  Annuler GridUpdate (SceneView Sceneview) 

Maintenant, il ne nous reste plus qu'à obtenir l'entrée un événement.

void GridUpdate (SceneView Sceneview) Event e = Event.current; 

Étape 8: Créer un préfabriqué

Pour continuer à jouer avec les scripts de l'éditeur, nous avons besoin d'un objet de jeu que nous pourrons utiliser. Créons un cube simple et faisons-en un préfabriqué.

Vous pouvez faire correspondre la taille de la grille au cube ou inversement et l'aligner sur une grille.

Comme vous pouvez le constater, dans la vue hiérarchique, cube le texte est coloré en bleu; cela signifie qu'il est connecté à un préfabriqué. Vous pouvez voir ce préfabriqué dans la fenêtre de projet.


Étape 9: Créer un objet à partir du script de l'éditeur

Nous allons maintenant créer un objet à partir du script de l'éditeur. Revenons à notre GridEditor.cs et étendre le GridUpdate () une fonction.

Créons l'objet quand la clé une est pressé.

void GridUpdate (SceneView Sceneview) Event e = Event.current; if (e.isKey && e.character == 'a') GameObject obj; 

Comme vous pouvez le constater, nous vérifions simplement si l'événement correspond à un changement d'état de clé et si le caractère sur lequel vous avez appuyé est 'une'. Nous créons également une référence pour notre nouvel objet. Maintenant instancions le.

void GridUpdate (SceneView Sceneview) Event e = Event.current; if (e.isKey && e.character == 'a') GameObject obj; if (Selection.activeObject) obj = (GameObject) Instantiate (Selection.activeObject); obj.transform.position = new Vector3 (0.0f, 0.0f, 0.0f); 

Selection.activeObject est une référence à l'objet actuellement sélectionné dans l'éditeur. Si un objet est sélectionné, il suffit de le cloner et de changer la position du clone pour (0.0, 0.0, 0.0).

Testons si cela fonctionne. Vous devez être conscient d'une chose: notre GridUpdate () cesse de fonctionner chaque fois que les éléments d'actif sont réimportés / actualisés et, pour le réactiver, vous devez sélectionner l'objet (par exemple, dans la vue hiérarchique) auquel le script de l'éditeur fait référence. la grille objet. Vous devez également vous rappeler que les événements d'entrée ne seront interceptés que si la vue Scène est sélectionnée..


Étape 10: Instanciez un préfabriqué à partir du script de l'éditeur

Bien que nous ayons réussi à cloner l'objet, le lien de l'objet cloné vers le préfabriqué est inexistant..

Comme vous pouvez le voir, le Cube (Clone) name est affiché avec la police noire simple et cela signifie qu'il n'est pas connecté au préfabriqué comme le cube d'origine. Si nous devions dupliquer manuellement le cube d'origine dans l'éditeur, le cube cloné serait lié au cube préfabriqué Pour que cela fonctionne ainsi pour nous, nous devons utiliser InstantiatePrefab () fonction de EditorUtility classe.

Avant d'utiliser cette fonction, nous devons obtenir le préfabriqué de l'objet sélectionné. Pour ce faire, nous devons utiliser GetPrefabParent () qui appartient aussi à la EditorUtility classe.

void GridUpdate (SceneView Sceneview) Event e = Event.current; if (e.isKey && e.character == 'a') GameObject obj; Object prefab = EditorUtility.GetPrefabParent (Selection.activeObject); si (préfabriqué) 

Nous pouvons également arrêter de vérifier si le Selection.activeObject existe, parce que si ce n'est pas le cas préfabriqué sera égal à nul, et donc nous pouvons nous en sortir en vérifiant seulement la préfabriqué référence.

Maintenant instancions notre préfabriqué et fixons sa position.

void GridUpdate (SceneView Sceneview) Event e = Event.current; if (e.isKey && e.character == 'a') GameObject obj; Object prefab = EditorUtility.GetPrefabParent (Selection.activeObject); if (prefab) obj = (GameObject) EditorUtility.InstantiatePrefab (prefab); obj.transform.position = new Vector3 (0.0f, 0.0f, 0.0f); 

Et voilà - vérifions si le cube cloné est maintenant lié au préfabriqué.


Étape 11: Traduire les souris d'écran en coordonnées mondiales

le un événement class ne nous dit pas où se trouve la souris dans l’espace mondial, elle ne fournit que les coordonnées de la souris dans l’espace. Voici comment nous les convertissons afin d'obtenir une position approximative de la souris spatiale mondiale.

void GridUpdate (SceneView Sceneview) Event e = Event.current; Ray r = Camera.current.ScreenPointToRay (nouveau vecteur3 (e.mousePosition.x, -e.mousePosition.y + Camera.current.pixelHeight)); Vector3 mousePos = r.origin;

D'abord nous utilisons la caméra de l'éditeur ScreenPointToRay pour obtenir le rayon à partir des coordonnées de l'écran, mais malheureusement avant cela, nous devons traduire l'espace de l'écran de l'événement en un espace acceptable pour ScreenPointToRay ().

e.mousePosition maintient la position de la souris dans un espace de coordonnées où le coin supérieur gauche est le (0, 0) le point et le coin inférieur droit est égal à (Camera.current.pixelWidth, -Camera.current.pixelHeight). Nous devons le traduire dans l'espace où le bas le coin gauche est le (0, 0) et en haut à droite est (Camera.current.pixelWidth, Camera.current.pixelHeight), ce qui est assez simple.

La prochaine chose que nous devrions faire est de sauvegarder l’origine du rayon à notre MousePos vecteur, donc il est facilement accessible.

Maintenant, nous pouvons assigner la position du clone à l'endroit où se trouve la souris.

if (prefab) obj = (GameObject) EditorUtility.InstantiatePrefab (prefab); obj.transform.position = new Vector3 (mousePos.x, mousePos.y, 0.0f); 

Notez que lorsque la caméra est réglée à plat, l’approximation de la position de la souris sur l’un des axes est vraiment mauvaise, c’est pourquoi je règle le z position du clone manuellement. Maintenant, les cubes doivent être créés partout où la souris est.


Étape 12: Alignez les cubes sur la grille

Depuis que notre réseau est en place, il serait dommage de ne pas l'utiliser. utilisons la position de la souris pour aligner les cubes créés sur la grille.

if (prefab) obj = (GameObject) EditorUtility.InstantiatePrefab (prefab); Vector3 aligné = nouveau Vector3 (Mathf.Floor (mousePos.x / grid.width) * grid.width + grid.width / 2.0f, Mathf.Floor (mousePos.y / grid.height) * grid.height + grid.height / 2,0f, 0,0f); obj.transform.position = aligné; 

Regardez le résultat:


Étape 13: Détruire un objet à partir du script de l'éditeur

Dans cette étape, nous allons supprimer des objets par programme dans l'éditeur. Nous pouvons le faire en utilisant DétruireImmediate (). Dans cet exemple, utilisons davantage le Sélection classe et supprimer tous les objets sélectionnés lorsque le ''touche est enfoncée.

if (e.isKey && e.character == 'a') GameObject obj; Object prefab = EditorUtility.GetPrefabParent (Selection.activeObject); if (prefab) obj = (GameObject) EditorUtility.InstantiatePrefab (prefab); Vector3 aligné = nouveau Vector3 (Mathf.Floor (mousePos.x / grid.width) * grid.width + grid.width / 2.0f, Mathf.Floor (mousePos.y / grid.height) * grid.height + grid.height / 2,0f, 0,0f); obj.transform.position = aligné;  else if (e.isKey && e.character == 'd') foreach (GameObject obj dans Selection.gameObjects) DestroyImmediate (obj); 

Quand le 'Lorsque vous appuyez sur la touche, nous parcourons tous les objets sélectionnés et les supprimons. Bien sûr, nous pourrions aussi appuyer sur Effacer dans l'éditeur pour supprimer ces objets, mais ceux-ci ne seraient pas supprimés par notre script. Testez-le dans l'éditeur.


Étape 14: Annuler l'instanciation d'objet

Dans cette étape, nous allons utiliser le annuler classe, ce qui nous permet essentiellement d'annuler chaque action effectuée par notre script d'édition. Commençons par annuler la création de l'objet.

Pour pouvoir détruire un objet que nous avons créé dans l'éditeur, nous devons appeler Undo.RegisterCreatedObjectUndo (). Il faut deux arguments: le premier est l'objet qui a été créé et le second est le nom de l'annulation. Le nom de l’action à annuler est toujours affiché sous Édition-> Annuler prénom.

if (prefab) obj = (GameObject) EditorUtility.InstantiatePrefab (prefab); Vector3 aligné = nouveau Vector3 (Mathf.Floor (mousePos.x / grid.width) * grid.width + grid.width / 2.0f, Mathf.Floor (mousePos.y / grid.height) * grid.height + grid.height / 2,0f, 0,0f); obj.transform.position = aligné; Undo.RegisterCreatedObjectUndo (obj, "Create" + obj.name); 

Si vous créez quelques cubes en utilisant le une clé et essayez ensuite d'annuler, vous remarquerez que tous les cubes créés ont été supprimés. En effet, tous ces cubes créés sont entrés dans un seul événement d'annulation..


Étape 15: Annuler l'instanciation d'un seul objet

Si nous voulons placer chaque objet créé sur un autre événement d'annulation et permettre d'annuler leur création un par un, nous devons utiliser Undo.IncrementCurrentEventIndex ().

if (prefab) Undo.IncrementCurrentEventIndex (); obj = (GameObject) EditorUtility.InstantiatePrefab (prefab); Vector3 aligné = nouveau Vector3 (Mathf.Floor (mousePos.x / grid.width) * grid.width + grid.width / 2.0f, Mathf.Floor (mousePos.y / grid.height) * grid.height + grid.height / 2,0f, 0,0f); obj.transform.position = aligné; Undo.RegisterCreatedObjectUndo (obj, "Create" + obj.name); 

Si vous testez le script maintenant, vous constaterez que les cubes sont supprimés un par un en annulant leur création..


Étape 16: Annuler la suppression d'un objet

Pour annuler la suppression d'objet, nous devons utiliser Undo.RegisterSceneUndo (). C'est une fonction très lente qui enregistre essentiellement l'état de la scène afin que nous puissions y revenir ultérieurement en effectuant une action d'annulation. Malheureusement, cela semble être le seul moyen pour le moment de récupérer les objets supprimés sur la scène..

else if (e.isKey && e.character == 'd') Undo.IncrementCurrentEventIndex (); Undo.RegisterSceneUndo ("Supprimer les objets sélectionnés"); foreach (GameObject obj dans Selection.gameObjects) DestroyImmediate (obj); 

Undo.RegisterSceneUndo () prend un seul argument, et c'est le nom de l'annulation. Après avoir supprimé quelques cubes à l’aide des touches clé vous pouvez annuler cette suppression.


Étape 17: Créer un script de fenêtre d'édition

Créez un nouveau script, et étendons celui-ci EditorWindow au lieu de Éditeur. Nommons-le GridWindow.cs.

utiliser UnityEngine; utiliser UnityEditor; using System.Collections; Classe publique GridWindow: EditorWindow public void Init () 

Créons une référence à notre la grille objet afin que nous puissions y accéder depuis la fenêtre.

Classe publique GridWindow: EditorWindow Grille grid; public void Init () grid = (Grid) FindObjectOfType (typeof (Grid)); 

Maintenant, nous devons créer la fenêtre, nous pouvons le faire à partir de notre Editeur de grille scénario.


Étape 18: Créer la GridWindow

Dans notre OnInspectorGUI () ajoutons un bouton qui va créer le GridWindow.

remplacement public void OnInspectorGUI () GUILayout.BeginHorizontal (); GUILayout.Label ("largeur de grille"); grid.width = EditorGUILayout.FloatField (grid.width, GUILayout.Width (50)); GUILayout.EndHorizontal (); GUILayout.BeginHorizontal (); GUILayout.Label ("Hauteur de la grille"); grid.height = EditorGUILayout.FloatField (grid.height, GUILayout.Width (50)); GUILayout.EndHorizontal (); if (GUILayout.Button ("Ouvrir la fenêtre de grille", GUILayout.Width (255)) (fenêtre GridWindow = (GridWindow) EditorWindow.GetWindow (typeof (GridWindow)); window.Init ();  SceneView.RepaintAll (); 

Nous utilisons GUILayout pour créer un bouton, nous définissons également le nom et la largeur du bouton. le GUILayout.Boutton résultats vrai lorsque le bouton est enfoncé, si tel est le cas, nous ouvrons notre GridWindow.

Vous pouvez revenir à l'éditeur et appuyer sur le bouton dans notre la grille inspecteur d'objets.

Une fois que vous faites cela, le GridWindow devrait apparaître.


Étape 19: Créer un champ de couleur dans la GridWindow

Avant de modifier quoi que ce soit de notre fenêtre, ajoutons un champ de couleur dans notre la grille classe, afin que nous puissions le modifier plus tard.

public class Grid: MonoBehaviour public float width = 32.0f; hauteur flottante publique = 32.0f; public Couleur couleur = Couleur.blanc;

Maintenant assigner le Gizmos.color dans le OnDrawGizmos () une fonction.

void OnDrawGizmos () Vector3 pos = Camera.current.transform.position; Gizmos.color = color;

Et maintenant, revenons à GridWindow script et créer un champ de couleur afin que nous puissions choisir la couleur dans la fenêtre. Nous pouvons le faire dans le OnGUI () rappeler.

Classe publique GridWindow: EditorWindow Grille grid; public void Init () grid = (Grid) FindObjectOfType (typeof (Grid));  void OnGUI () grid.color = EditorGUILayout.ColorField (grid.color, GUILayout.Width (200)); 

Très bien, maintenant vous pouvez vérifier si tout fonctionne correctement dans l'éditeur:


Étape 20: Ajouter un délégué

Pour le moment, nous avons défini un délégué pour obtenir les événements d'entrée à partir de la vue de la scène utilisée. = signe, ce qui n’est pas une bonne méthode pour le faire, car il remplace tous les autres rappels. Nous devrions utiliser += signe à la place. Allons à notre GridEditor.cs script et faire ce changement.

public void OnEnable () grid = (Grid) target; SceneView.onSceneGUIDelegate + = GridUpdate; 

Nous devons également créer un OnDisable () rappel pour supprimer notre GridUpdate (), si nous ne le faisons pas alors il va empiler et être appelé plusieurs fois à la fois.

public void OnEnable () grid = (Grid) target; SceneView.onSceneGUIDelegate + = GridUpdate;  public void OnDisable () SceneView.onSceneGUIDelegate - = GridUpdate; 

Conclusion

Voilà pour l'introduction aux scripts d'éditeur. Si vous souhaitez développer vos connaissances, vous trouverez beaucoup de choses à lire sur ce sujet dans le document Unity Script Reference - vous voudrez peut-être vérifier la Ressources, AssetDatabase ou FileUtil cours en fonction de vos besoins.

Malheureusement, certaines classes ne sont pas encore documentées et sont donc sujettes à changer sans travailler. Par exemple le SceneView classe et ses fonctions ou Undo.IncrementCurrentEventIndex () fonction de la annuler classe. Si la documentation ne fournit pas les réponses que vous cherchez, vous pouvez essayer de chercher dans UnityAnswers ou Unity Forum.

Merci pour votre temps!