La carte
Dans un 
article précédent, j'ai examiné ce qu'est le nouveau 
système de travail , comment il fonctionne, comment créer des tâches, les remplir avec des données et effectuer des calculs multithread, et je n'ai expliqué que brièvement où vous pouvez utiliser ce système. Dans cet article, je vais essayer d'analyser un exemple spécifique de l'endroit où vous pouvez utiliser ce système pour obtenir plus de performances.
Étant donné que le système a été initialement développé dans le but de travailler avec des données, il est idéal pour résoudre les tâches de recherche de chemin.
Unity possède déjà un bon 
explorateur NavMesh , mais il ne fonctionne pas dans les projets 2D, bien qu'il existe de nombreuses solutions prêtes à l'emploi sur le même 
actif . Eh bien, et nous essaierons de créer non seulement un système qui cherchera des moyens sur la carte créée, mais rendra cette carte très dynamique, de sorte que chaque fois que quelque chose change, le système crée une nouvelle carte, et tout cela, bien sûr, nous calculerons en utilisant un nouveau système de tâches, afin de ne pas charger le thread principal.
Exemple de fonctionnement du système Dans l'exemple, une grille est construite sur la carte, il y a un bot et un obstacle. La grille est reconstruite chaque fois que nous modifions les propriétés de la carte, que ce soit sa taille ou sa position.
Pour les avions, j'ai utilisé un simple 
SpriteRenderer , ce composant a une excellente propriété de 
limites avec laquelle vous pouvez facilement trouver la taille de la carte.
C'est essentiellement tout pour commencer, mais nous ne nous arrêterons pas et nous passerons immédiatement aux choses sérieuses.
Commençons par les scripts. Et le premier est le script d'obstruction 
Obstacle .
Obstaclepublic class Obstacle : MonoBehaviour { } 
 À l'intérieur de la classe 
Obstacle , nous verrons tous les changements dans les obstacles sur la carte, par exemple, en changeant la position ou la taille d'un objet.
Ensuite, vous pouvez créer la classe 
Map map, sur laquelle la grille sera construite, et l'hériter de la classe 
Obstacle .
La carte public sealed class Map : Obstacle { } 
 La classe 
Map suivra également toutes les modifications sur la carte afin de reconstruire la grille si nécessaire.
Pour ce faire, remplissez la classe de base 
Obstacle avec toutes les variables et méthodes nécessaires pour suivre les modifications d'objets.
Obstacle public class Obstacle : MonoBehaviour { public new SpriteRenderer renderer { get; private set;} private Vector2 tempSize; private Vector2 tempPos; protected virtual void Awake() { this.renderer = GetComponent<SpriteRenderer>(); this.tempSize = this.size; this.tempPos = this.position; } public virtual bool CheckChanges() { Vector2 newSize = this.size; float diff = (newSize - this.tempSize).sqrMagnitude; if (diff > 0.01f) { this.tempSize = newSize; return true; } Vector2 newPos = this.position; diff = (newPos - this.tempPos).sqrMagnitude; if (diff > 0.01f) { this.tempPos = newPos; return true; } return false; } public Vector2 size { get { return this.renderer.bounds.size;} } public Vector2 position { get { return this.transform.position;} } } 
 Ici, la variable de 
rendu aura une référence au composant 
SpriteRenderer , et les 
variables tempSize et 
tempPos seront utilisées pour suivre les changements de taille et de position de l'objet.
La méthode virtuelle 
Awake sera utilisée pour initialiser les variables, et la méthode virtuelle 
CheckChanges suivra les changements actuels dans la taille et la position de l'objet et retournera un résultat 
booléen .
Pour l'instant, laissons le script 
Obstacle et passons au script 
Map map lui-même, où nous le remplissons également avec les paramètres nécessaires pour le travail.
La carte public sealed class Map : Obstacle { [Range(0.1f, 1f)] public float nodeSize = 0.5f; public Vector2 offset = new Vector2(0.5f, 0.5f); } 
 La variable 
nodeSize indiquera la taille des cellules sur la carte, ici j'ai limité sa taille de 0,1 à 1 pour que les cellules de la grille ne soient pas trop petites, mais aussi trop grandes. La variable de 
décalage sera utilisée pour indenter la carte lors de la construction de la grille afin que la grille ne se construise pas le long des bords de la carte.
Comme il y a maintenant deux nouvelles variables sur la carte, il s'avère que leurs changements devront également être suivis. Pour ce faire, ajoutez quelques variables et surchargez la méthode 
CheckChanges dans la classe 
Map .
La carte public sealed class Map : Obstacle { [Range(0.1f, 1f)] public float nodeSize = 0.5f; public Vector2 offset = new Vector2(0.5f, 0.5f); private float tempNodeSize; private Vector2 tempOffset; protected override void Awake() { base.Awake(); this.tempNodeSize = this.nodeSize; this.tempOffset = this.offset; } public override bool CheckChanges() { float diff = Mathf.Abs(this.tempNodeSize - this.nodeSize); if (diff > 0.01f) { this.tempNodeSize = this.nodeSize; return true; } diff = (this.tempOffset - this.offset).sqrMagnitude; if (diff > 0.01f) { this.tempOffset = this.offset; return true; } return base.CheckChanges(); } } 
 C'est fait. Vous pouvez maintenant créer un sprite de carte sur la scène et lancer un script de 
carte dessus.

Nous ferons de même avec un obstacle - créez un simple sprite sur scène et lancez le script 
Obstacle dessus.

Nous avons maintenant des objets cartographiques et des obstacles sur scène.
Le script de 
carte sera responsable du suivi de toutes les modifications sur la carte, où dans la méthode de 
mise à jour , nous vérifierons chaque cadre pour les modifications.
La carte public sealed class Map : Obstacle {  private bool requireRebuild; private void Update() { UpdateChanges(); } private void UpdateChanges() { if (this.requireRebuild) { print(“  ,   !”); this.requireRebuild = false; } else { this.requireRebuild = CheckChanges(); } }  } 
 Ainsi, dans la méthode 
UpdateChanges, la carte ne suivra que ses modifications jusqu'à présent. Vous pouvez même démarrer le jeu maintenant et essayer de modifier la taille de la carte ou le 
décalage de décalage pour vous assurer que tous les changements sont suivis.
Maintenant, vous devez en quelque sorte suivre les changements des obstacles eux-mêmes sur la carte. Pour ce faire, nous mettrons chaque obstacle dans une liste sur la carte, qui à son tour mettra à jour chaque cadre dans la méthode 
Update .
Dans la classe 
Map , créez une liste de tous les obstacles possibles sur la carte et quelques méthodes statiques pour les enregistrer.
La carte public sealed class Map : Obstacle {  private static Map ObjInstance; private List<Obstacle> obstacles = new List<Obstacle>();  public static bool RegisterObstacle(Obstacle obstacle) { if (obstacle == Instance) return false; else if (Instance.obstacles.Contains(obstacle) == false) { Instance.obstacles.Add(obstacle); Instance.requireRebuild = true; return true; } return false; } public static bool UnregisterObstacle(Obstacle obstacle) { if (Instance.obstacles.Remove(obstacle)) { Instance.requireRebuild = true; return true; } return false; } public static Map Instance { get { if (ObjInstance == null) ObjInstance = FindObjectOfType<Map>(); return ObjInstance; } } } 
 Dans la méthode 
RegisterObstacle statique, nous allons enregistrer un nouvel 
obstacle Obstacle sur la carte et l'ajouter à la liste, mais tout d'abord, il est important de considérer que la carte elle-même est également héritée de la classe 
Obstacle et nous devons donc vérifier si nous essayons d'enregistrer la carte elle-même comme obstacle.
La méthode statique 
UnregisterObstacle , au contraire, élimine l'obstacle de la carte et le supprime de la liste lorsque nous permettons qu'il soit détruit.
Dans le même temps, chaque fois que nous ajoutons ou supprimons un obstacle de la carte, il est nécessaire de recréer la carte elle- 
même.Ainsi , après avoir exécuté ces méthodes statiques, définissez la variable 
requireRebuild sur 
true .
De plus, pour avoir un accès facile au script 
Map à partir de n'importe quel script, j'ai créé une propriété 
Instance statique qui me rendra cette instance même de 
Map .
Revenons maintenant au script 
Obstacle où nous allons enregistrer un obstacle sur la carte. Pour ce faire, ajoutez-y quelques méthodes 
OnEnable et 
OnDisable .
Obstacle public class Obstacle : MonoBehaviour {  protected virtual void OnEnable() { Map.RegisterObstacle(this); } protected virtual void OnDisable() { Map.UnregisterObstacle(this); } } 
 Chaque fois que nous créons un nouvel obstacle en jouant sur la carte, il s'enregistrera automatiquement dans la méthode 
OnEnable , où il sera pris en compte lors de la construction d'une nouvelle grille et nous supprimera de la carte dans la méthode 
OnDisable lorsqu'il sera détruit ou désactivé.
Il ne reste plus qu'à suivre les changements des obstacles eux-mêmes dans le script 
Map dans la méthode 
CheckChanges surchargée.
La carte public sealed class Map : Obstacle {  public override bool CheckChanges() { float diff = Mathf.Abs(this.tempNodeSize - this.nodeSize); if (diff > 0.01f) { this.tempNodeSize = this.nodeSize; return true; } diff = (this.tempOffset - this.offset).sqrMagnitude; if (diff > 0.01f) { this.tempOffset = this.offset; return true; } foreach(Obstacle obstacle in this.obstacles) { if (obstacle.CheckChanges()) return true; } return base.CheckChanges(); }  } 
 Nous avons maintenant une carte, des obstacles - en général, tout ce dont vous avez besoin pour construire une grille et maintenant vous pouvez passer à la chose la plus importante.
Maillage
La grille, dans sa forme la plus simple, est un tableau bidimensionnel de points. Pour le construire, vous devez connaître la taille de la carte et la taille des points dessus, après quelques calculs, nous obtenons le nombre de points horizontalement et verticalement, voici notre grille.
Il existe de nombreuses façons de trouver un chemin sur une grille. Dans cet article, cependant, l'essentiel est de comprendre comment utiliser correctement les capacités du système de tâches, donc ici je ne considérerai pas différentes options pour trouver le chemin, leurs avantages et leurs inconvénients, mais je prendrai l'option de recherche la plus simple 
A * .
Dans ce cas, tous les points de la grille doivent avoir, en plus de la position, les coordonnées et la propriété de perméabilité.
Avec perméabilité, je pense que tout est clair pourquoi il est nécessaire, mais les coordonnées indiqueront l'ordre du point sur la grille, ces coordonnées ne sont pas liées spécifiquement à la position du point dans l'espace. L'image ci-dessous montre une grille simple montrant les différences de coordonnées d'une position.
 Pourquoi les coordonnées?
Pourquoi les coordonnées?Le fait est que dans l'unité, pour indiquer la position d'un objet dans l'espace, un 
flottant simple est très inexact et peut être un nombre fractionnaire ou négatif, il sera donc difficile de l'utiliser pour implémenter une recherche de chemin sur la carte. Les coordonnées sont faites sous la forme d'un 
int clair qui sera toujours positif et avec lequel il est beaucoup plus facile de travailler lors de la recherche de points voisins.
Tout d'abord, définissons un objet point, ce sera une structure de 
nœud simple.
Noeud public struct Node { public int id; public Vector2 position; public Vector2Int coords; } 
 Cette structure contiendra la position 
position sous la forme de 
Vector2 , où avec cette variable nous dessinerons un point dans l'espace. La 
variable de coordonnées 
coords sous la forme de 
Vector2Int indiquera les coordonnées d'un point sur la carte, et la variable 
id , son numéro de compte numérique, en l'utilisant, nous comparerons différents points sur la grille et vérifierons l'existence d'un point.
La perméabilité du point sera indiquée sous la forme de sa propriété 
booléenne , mais comme nous ne pouvons pas utiliser les 
types de données 
convertibles dans le système de tâches, nous indiquerons sa perméabilité sous la forme d'un nombre 
entier , pour cela j'ai utilisé une énumération simple 
NodeType , où: 0 n'est pas un point passable, et 1 est passable.
NodeType et Node public enum NodeType { NonWalkable = 0, Walkable = 1 } public struct Node { public int id; public Vector2 position; public Vector2Int coords; private int nodeType; public bool isWalkable { get { return this.nodeType == (int)NodeType.Walkable;} } public Node(int id, Vector2 position, Vector2Int coords, NodeType type) { this.id = id; this.position = position; this.coords = coords; this.nodeType = (int)type; } } 
 De plus, pour la commodité de travailler avec un point, je surchargerai la méthode 
Equals pour faciliter la comparaison des points et compléter la méthode de vérification de l'existence d'un point.
Noeud public struct Node {  public override bool Equals(object obj) { if (obj is Node) { Node other = (Node)obj; return this.id == other.id; } else return base.Equals(obj); } public static implicit operator bool(Node node) { return node.id > 0; } } 
 Étant donné que le numéro d' 
identification du point sur la grille commencera par 1 unité, je vérifierai l'existence du point comme condition que son 
identifiant soit supérieur à 0.
Allez dans la classe 
Map où nous allons tout préparer pour créer une carte.
Nous avons déjà une vérification pour changer les paramètres de la carte, maintenant nous devons déterminer comment le processus de construction de la grille sera exécuté. Pour ce faire, créez une nouvelle variable et plusieurs méthodes.
La carte public sealed class Map : Obstacle {  public bool rebuilding { get; private set; } public void Rebuild() {} private void OnRebuildStart() {} private void OnRebuildFinish() {}  } 
 La propriété de 
reconstruction indiquera si le processus de 
maillage est en cours. La méthode 
Rebuild collectera les données et les tâches pour la construction de la grille, puis la méthode 
OnRebuildStart démarrera le processus de construction de la grille et la méthode 
OnRebuildFinish collectera les données des tâches.
Modifions maintenant un 
peu la méthode 
UpdateChanges pour que l'état de la grille soit pris en compte.
La carte public sealed class Map : Obstacle {  public bool rebuilding { get; private set; } private void UpdateChanges() { if (this.rebuilding) { print(“  ...”); } else { if (this.requireRebuild) { print(“  ,   !”); Rebuild(); } else { this.requireRebuild = CheckChanges(); } } } public void Rebuild() { if (this.rebuilding) return; print(“ !”); OnRebuildStart(); } private void OnRebuildStart() { this.rebuilding = true; } private void OnRebuildFinish() { this.rebuilding = false; }  } 
 Comme vous pouvez le voir maintenant dans la méthode 
UpdateChanges , 
il y a une condition que lors de la construction de l'ancien maillage ne commence pas à commencer à en créer un nouveau, et également dans la méthode 
Rebuild , la première action vérifie si le processus de maillage est déjà en cours.
Résolution de problèmes
Maintenant, un peu sur le processus de construction d'une carte.
Comme nous allons utiliser le système de tâches et construire la grille en parallèle pour construire la carte, j'ai utilisé le type de la tâche 
IJobParallelFor , qui sera exécutée un certain nombre de fois. Afin de ne pas charger le processus de construction avec une seule tâche distincte, nous utiliserons le pool de tâches regroupées dans un 
JobHandle .
Le plus souvent, pour construire une grille, utilisez deux cycles imbriqués l'un dans l'autre pour construire, par exemple, horizontalement et verticalement. Dans cet exemple, nous allons également construire la grille d'abord horizontalement puis verticalement. Pour ce faire, nous calculons le nombre de points horizontaux et verticaux dans la méthode 
Reconstruire , puis dans la méthode 
Reconstruire , nous parcourons le cycle le long des points verticaux, et nous construirons des points horizontaux en parallèle dans la tâche. Pour mieux imaginer le processus de construction, regardez l'animation ci-dessous.
Le nombre de points verticaux indiquera le nombre de tâches, à son tour, chaque tâche ne construira des points que horizontalement, après avoir terminé toutes les tâches, les points sont additionnés dans une liste. C'est pourquoi j'ai besoin d'utiliser une tâche comme 
IJobParallelFor pour passer 
horizontalement l'index du point sur la grille dans la méthode 
Execute .
Et donc nous avons la structure de points, maintenant vous pouvez créer la structure de la tâche 
Job et l'hériter de l'interface 
IJobParallelFor , tout est simple ici.
Job public struct Job : IJobParallelFor { public void Execute(int index) {} } 
 Nous revenons à la méthode de 
reconstruction de la classe 
Map , où nous effectuerons les calculs nécessaires pour la mesure de la grille.
La carte public sealed class Map : Obstacle {  public void Rebuild() { if (this.rebuilding) return; print(“ !”); Vector2 mapSize = this.size - this.offset * 2f; int horizontals = Mathf.RoundToInt(mapSize.x / this.nodeSize); int verticals = Mathf.RoundToInt(mapSize.y / this.nodeSize); if (horizontals <= 0) { OnRebuildFinish(); return; } Vector2 center = this.position; Vector2 origin = center - (mapSize / 2f); OnRebuildStart(); }  } 
 Dans la méthode 
Rebuild , nous calculons la taille exacte de la carte 
mapSize , en tenant compte de l'indentation, puis dans les 
verticales nous écrivons le nombre de points verticalement, et en 
horizontal le nombre de points horizontalement. Si le nombre de points verticaux est égal à 0, nous arrêtons de construire la carte et appelons la méthode 
OnRebuildFinish pour terminer le processus. La variable d' 
origine indiquera l'endroit à partir duquel nous commencerons à construire la grille - dans l'exemple, il s'agit du point inférieur gauche sur la carte.
Vous pouvez maintenant accéder aux tâches elles-mêmes et les remplir de données.
Lors de la construction de la grille, la tâche aura besoin d'un tableau 
NativeArray où nous 
placerons les points, également puisque nous avons des obstacles sur la carte, nous devrons également les passer à la tâche, pour cela nous utiliserons un autre tableau 
NativeArray , puis nous aurons besoin de la taille des points dans le problème , la position initiale à partir de laquelle nous allons construire les points, ainsi que les coordonnées initiales de la série.
Job public struct Job : IJobParallelFor { [WriteOnly] public NativeArray<Node> array; [ReadOnly] public NativeArray<Rect> bounds; public float nodeSize; public Vector2 startPos; public Vector2Int startCoords; public void Execute(int index) {} } 
 J'ai marqué le tableau de points avec l'attribut 
WriteOnly, car dans la tâche, il ne sera nécessaire que d '« 
écrire » les points reçus dans le tableau, au contraire, le tableau des 
limites d' obstacles 
est marqué avec l'attribut 
ReadOnly car dans la tâche, nous ne « 
lirons » que les données de ce tableau.
Eh bien, pour l'instant, procédons au calcul des points eux-mêmes plus tard.
Revenons maintenant à la classe 
Map , où nous désignons toutes les variables impliquées dans les tâches.
Ici, tout d'abord, nous avons besoin d'une 
poignée de tâche globale, d'un tableau d'obstacles sous la forme d'un 
tableau natif , d'une liste de tâches qui contiendra tous les points reçus sur la grille et du 
dictionnaire avec toutes les coordonnées et les points sur la carte, de sorte qu'il serait plus pratique de les rechercher plus tard.
La carte public sealed class Map : Obstacle {  private JobHandle handle; private NativeArray<Rect> bounds; private HashSet<NativeArray<Node>> jobs = new HashSet<NativeArray<Node>>(); private Dictionary<Vector2Int, Node> nodes = new Dictionary<Vector2Int, Node>();  } 
 Maintenant, nous revenons à la méthode 
Rebuild et continuons à construire la grille.
Tout d'abord, initialisez le tableau des 
limites des obstacles pour le passer à la tâche.
Reconstruire public void Rebuild() {  Vector2 center = this.position; Vector2 origin = center - (mapSize / 2f); int count = this.obstacles.Count; if (count > 0) { this.bounds = new NativeArray<Rect>(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); } OnRebuildStart(); } 
 Ici, nous créons une instance de 
NativeArray via un nouveau constructeur avec trois paramètres. J'ai examiné les deux premiers paramètres dans un article précédent, mais le troisième paramètre nous aidera à gagner un peu de temps en créant un tableau. Le fait est que nous allons écrire des données dans le tableau immédiatement après sa création, ce qui signifie que nous n'avons pas besoin de nous assurer qu'elles sont effacées. Ce paramètre est utile pour 
NativeArray qui ne sera utilisé qu'en mode 
lecture dans la tâche.
Et donc, nous remplissons le tableau de 
limites avec des données.
Reconstruire public void Rebuild() {  Vector2 center = this.position; Vector2 origin = center - (mapSize / 2f); int count = this.obstacles.Count; if (count > 0) { this.bounds = new NativeArray<Rect>(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); for(int i = 0; i < count; i++) { Obstacle obs = this.obstacles[i]; Vector2 position = obs.position; Rect rect = new Rect(Vector2.zero, obs.size); rect.center = position; this.bounds[i] = rect; } } OnRebuildStart(); } 
 Nous pouvons maintenant passer à la création de tâches, pour cela nous allons parcourir un cycle à travers toutes les lignes verticales de la grille.
Reconstruire public void Rebuild() {  Vector2 center = this.position; Vector2 origin = center - (mapSize / 2f); int count = this.obstacles.Count; if (count > 0) { this.bounds = new NativeArray<Rect>(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); for(int i = 0; i < count; i++) { Obstacle obs = this.obstacles[i]; Vector2 position = obs.position; Rect rect = new Rect(Vector2.zero, obs.size); rect.center = position; this.bounds[i] = rect; } } for (int i = 0; i < verticals; i++) { float xPos = origin.x; float yPos = origin.y + (i * this.nodeSize) + this.nodeSize / 2f; } OnRebuildStart(); } 
 Pour commencer, dans 
xPos et 
yPos, nous obtenons la position horizontale initiale de la série.
Reconstruire public void Rebuild() {  Vector2 center = this.position; Vector2 origin = center - (mapSize / 2f); int count = this.obstacles.Count; if (count > 0) { this.bounds = new NativeArray<Rect>(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); for(int i = 0; i < count; i++) { Obstacle obs = this.obstacles[i]; Vector2 position = obs.position; Rect rect = new Rect(Vector2.zero, obs.size); rect.center = position; this.bounds[i] = rect; } } for (int i = 0; i < verticals; i++) { float xPos = origin.x; float yPos = origin.y + (i * this.nodeSize) + this.nodeSize / 2f; NativeArray<Node> array = new NativeArray<Node>(horizontals, Allocator.Persistent); Job job = new Job(); job.startCoords = new Vector2Int(i * horizontals, i); job.startPos = new Vector2(xPos, yPos); job.nodeSize = this.nodeSize; job.bounds = this.bounds; job.array = array; } OnRebuildStart(); } 
 Ensuite, nous créons un tableau 
NativeArray simple où les points de la tâche seront placés, ici pour le tableau de 
tableau, vous devez spécifier le nombre de points qui seront créés horizontalement et le type d'allocation 
Persistant , car la tâche peut prendre plus d'une image.
Après cela, créez l'instance de tâche 
Job elle-même, placez les coordonnées initiales de la série 
startCoords , la position initiale de la série 
startPos , la taille des points 
nodeSize , le tableau de 
limites des obstacles et le tableau de points lui-même à la fin.
Il ne reste plus qu'à mettre la tâche en 
poignée et la liste globale des tâches.
Reconstruire public void Rebuild() {  Vector2 center = this.position; Vector2 origin = center - (mapSize / 2f); int count = this.obstacles.Count; if (count > 0) { this.bounds = new NativeArray<Rect>(count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); for(int i = 0; i < count; i++) { Obstacle obs = this.obstacles[i]; Vector2 position = obs.position; Rect rect = new Rect(Vector2.zero, obs.size); rect.center = position; this.bounds[i] = rect; } } for (int i = 0; i < verticals; i++) { float xPos = origin.x; float yPos = origin.y + (i * this.nodeSize) + this.nodeSize / 2f; NativeArray<Node> array = new NativeArray<Node>(horizontals, Allocator.Persistent); Job job = new Job(); job.startCoords = new Vector2Int(i * horizontals, i); job.startPos = new Vector2(xPos, yPos); job.nodeSize = this.nodeSize; job.bounds = this.bounds; job.array = array; this.handle = job.Schedule(horizontals, 3, this.handle); this.jobs.Add(array); } OnRebuildStart(); } 
 C'est fait. Nous avons une liste de tâches et leur 
handle commun, maintenant nous pouvons exécuter ce 
handle en appelant sa méthode 
Complete dans la méthode 
OnRebuildStart .
Onrebuildstart private void OnRebuildStart() { this.rebuilding = true; this.handle.Complete(); } 
 Étant donné que la variable de 
reconstruction indiquera que le processus de 
maillage est en cours, la méthode 
UpdateChanges elle-même doit également spécifier la condition à laquelle ce processus se terminera à l'aide de 
handle et de sa propriété 
IsCompleted .
Mises à jour private void UpdateChanges() { if (this.rebuilding) { print(“  ...”); if (this.handle.IsCompleted) OnRebuildFinish(); } else { if (this.requireRebuild) { print(“  ,   !”); Rebuild(); } else { this.requireRebuild = CheckChanges(); } } } 
 Après avoir terminé les tâches, la méthode 
OnRebuildFinish sera appelée où nous rassemblerons déjà les points reçus dans une liste de 
dictionnaires générale, et surtout, pour effacer les ressources occupées.
OnRebuildFinish  private void OnRebuildFinish() { this.nodes.Clear(); foreach (NativeArray<Node> array in this.jobs) { foreach (Node node in array) this.nodes.Add(node.coords, node); array.Dispose(); } this.jobs.Clear(); if (this.bounds.IsCreated) this.bounds.Dispose(); this.requireRebuild = this.rebuilding = false; } 
 Tout d'abord, nous effaçons le dictionnaire de nœuds des points précédents, puis utilisons la boucle foreach pour trier tous les points que nous avons reçus des tâches et les mettons dans le dictionnaire de nœuds , où la clé est les coordonnées ( PAS la position !) Du point, et la valeur est le point lui-même. Avec l'aide de ce dictionnaire, il nous sera plus facile de rechercher des points voisins sur la carte. Après le remplissage, nous effaçons le tableau tableau à l'aide de la méthode Dispose et à la fin, nous effaçons la liste des tâches de travaux elle-même .Vous devrez également effacer le tableau des limites des obstacles s'il a été créé précédemment.Après toutes ces actions, nous obtenons une liste de tous les points sur la carte et maintenant vous pouvez les dessiner sur la scène.Pour ce faire, dans la classe Map , créez la méthode OnDrawGizmos où nous allons dessiner les points.La carte public sealed class Map : Obstacle {  #if UNITY_EDITOR private void OnDrawGizmos() {} #endif } 
 Maintenant, dans la boucle, nous dessinons chaque point.La carte public sealed class Map : Obstacle {  #if UNITY_EDITOR private void OnDrawGizmos() { foreach (Node node in this.nodes.Values) { Gizmos.DrawWireSphere(node.position, this.nodeSize / 10f); } } #endif } 
 Après toutes ces actions, notre carte semble en quelque sorte ennuyeuse, afin d'avoir vraiment une grille, vous avez besoin que les points soient connectés les uns aux autres.Pour rechercher des points voisins, nous avons juste besoin de trouver le point souhaité par ses coordonnées dans 8 directions, donc dans la classe Map , nous allons créer un simple tableau statique de directions Directions et une méthode de recherche de cellules par ses coordonnées GetNode .La carte public sealed class Map : Obstacle { public static readonly Vector2Int[] Directions = { Vector2Int.up, new Vector2Int(1, 1), Vector2Int.right, new Vector2Int(1, -1), Vector2Int.down, new Vector2Int(-1, -1), Vector2Int.left, new Vector2Int(-1, 1), };  public Node GetNode(Vector2Int coords) { Node result = default(Node); try { result = this.nodes[coords]; } catch {} return result; } #if UNITY_EDITOR private void OnDrawGizmos() {} #endif } 
 La méthode GetNode renverra un point par coordonnées de la liste des nœuds , mais vous devez le faire avec précaution, car si les coordonnées Vector2Int sont incorrectes, une erreur se produira, nous utilisons donc ici le bloc try bypass exception , qui aidera à contourner l'exception et à ne pas " bloquer " l'application entière avec une erreur.Ensuite, nous allons parcourir le cycle dans toutes les directions et essayer de trouver des points voisins dans la méthode OnDrawGizmos , et surtout, n'oubliez pas de considérer la perméabilité du point.Ondrawgizmos  #if UNITY_EDITOR private void OnDrawGizmos() { Color c = Gizmos.color; foreach (Node node in this.nodes.Values) { Color newColor = Color.white; if (node.isWalkable) newColor = new Color32(153, 255, 51, 255); else newColor = Color.red; Gizmos.color = newColor; Gizmos.DrawWireSphere(node.position, this.nodeSize / 10f); newColor = Color.green; Gizmos.color = newColor; if (node.isWalkable) { for (int i = 0; i < Directions.Length; i++) { Vector2Int coords = node.coords + Directions[i]; Node connection = GetNode(coords); if (connection) { if (connection.isWalkable) Gizmos.DrawLine(node.position, connection.position); } } } } Gizmos.color = c; } #endif 
 Vous pouvez maintenant démarrer le jeu en toute sécurité et voir ce qui s'est passé.Dans cet exemple, nous avons construit uniquement le graphique lui-même à l'aide de tâches, mais c'est ce qui s'est produit après avoir vissé sur le système l'algorithme A * lui - même , qui utilise également le système Job pour trouver le chemin, la source à la fin de l'article .Recherche de carte et de chemin Vous pouvez donc utiliser le nouveau système de tâches pour vos objectifs et créer des systèmes intéressants sans trop d'effort.Comme dans l'article précédent, le système de tâches est utilisé sans ECS , mais si vous utilisez ce système en conjonction avec ECS , vous pouvez obtenir des résultats tout simplement incroyables en termes de gains de performances. Bonne chance !Source du projet Path Finder