Peta
Dalam
artikel sebelumnya, saya melihat apa
sistem Job yang baru, bagaimana cara kerjanya, cara membuat tugas, mengisinya dengan data dan melakukan perhitungan multi-threaded, dan hanya menjelaskan secara singkat di mana Anda dapat menggunakan sistem ini. Pada artikel ini, saya akan mencoba mem-parsing contoh spesifik di mana Anda dapat menggunakan sistem ini untuk mendapatkan lebih banyak kinerja.
Karena sistem ini awalnya dikembangkan dengan tujuan bekerja dengan data, itu bagus untuk menyelesaikan tugas-tugas pencarian jalur.
Unity telah memiliki
pathfinder NavMesh yang baik, tetapi tidak berfungsi dalam proyek 2D, meskipun ada banyak solusi siap pakai pada
aset yang sama. Baiklah, dan kami akan mencoba untuk membuat bukan hanya sistem yang akan mencari cara pada peta yang dibuat, tetapi membuat peta ini sangat dinamis, sehingga setiap kali ada perubahan, sistem akan membuat peta baru, dan semua ini tentu saja akan kami hitung menggunakan sistem tugas baru, agar tidak memuat utas utama.
Contoh pengoperasian sistem Dalam contoh ini, kotak dibangun di peta, ada bot dan hambatan. Kotak dibangun kembali setiap kali kami mengubah properti peta apa pun, baik ukuran maupun posisinya.
Untuk pesawat, saya menggunakan
SpriteRenderer sederhana, komponen ini memiliki properti
batas yang luar biasa yang dengannya Anda dapat dengan mudah mengetahui ukuran peta.
Pada dasarnya itu semua hanya untuk permulaan, tetapi kami tidak akan berhenti dan segera memulai bisnis.
Mari kita mulai dengan skrip. Dan yang pertama adalah naskah penghalang
Rintangan .
Rintanganpublic class Obstacle : MonoBehaviour { }
Di dalam kelas
Rintangan , kita akan menangkap semua perubahan hambatan pada peta, misalnya, mengubah posisi atau ukuran suatu objek.
Selanjutnya, Anda bisa membuat kelas peta peta, di mana kotak akan dibangun, dan mewarisinya dari kelas
Kendala .
Peta public sealed class Map : Obstacle { }
Kelas
Peta juga akan melacak semua perubahan pada peta untuk membangun kembali kisi jika perlu.
Untuk melakukan ini, isi kelas dasar
Rintangan dengan semua variabel dan metode yang diperlukan untuk melacak perubahan objek.
Rintangan 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;} } }
Di sini, variabel
renderer akan memiliki referensi ke komponen
SpriteRenderer , dan
variabel tempSize dan
tempPos akan digunakan untuk melacak perubahan dalam ukuran dan posisi objek.
Metode virtual
Sedar akan digunakan untuk menginisialisasi variabel, dan metode virtual
CheckChanges akan melacak perubahan saat ini dalam ukuran dan posisi objek dan mengembalikan hasil
boolean .
Untuk saat ini, mari kita tinggalkan skrip
Rintangan dan beralih ke skrip peta
Peta itu sendiri, di mana kita juga mengisinya dengan parameter yang diperlukan untuk pekerjaan.
Peta public sealed class Map : Obstacle { [Range(0.1f, 1f)] public float nodeSize = 0.5f; public Vector2 offset = new Vector2(0.5f, 0.5f); }
Variabel
nodeSize akan menunjukkan ukuran sel pada peta, di sini saya membatasi ukurannya dari 0,1 hingga 1 sehingga sel-sel pada grid tidak terlalu kecil, tetapi juga terlalu besar. Variabel
offset akan digunakan untuk membuat inden peta saat membuat kisi sehingga kisi tidak membangun di sepanjang tepi peta.
Karena sekarang ada dua variabel baru di peta, ternyata perubahan mereka juga perlu dilacak. Untuk melakukan ini, tambahkan beberapa variabel dan overload metode
CheckChanges di kelas
Peta .
Peta 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(); } }
Selesai Sekarang Anda dapat membuat sprite peta di atas panggung dan melemparkan skrip
Peta di atasnya.

Kami akan melakukan hal yang sama dengan penghalang - buat sprite sederhana di atas panggung dan lemparkan skrip
Hambatan ke atasnya.

Sekarang kita memiliki objek peta dan rintangan di atas panggung.
Skrip
Peta akan bertanggung jawab untuk melacak semua perubahan pada peta, di mana dalam metode
Pembaruan kami akan memeriksa setiap frame untuk perubahan.
Peta 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(); } } }
Jadi, dalam metode
UpdateChanges, peta hanya akan melacak perubahannya sejauh ini. Anda bahkan dapat memulai permainan sekarang dan mencoba mengubah ukuran peta atau
mengimbangi offset untuk memastikan bahwa semua perubahan dilacak.
Sekarang Anda perlu melacak perubahan hambatan itu sendiri di peta. Untuk melakukan ini, kami akan menempatkan setiap rintangan dalam daftar di peta, yang pada gilirannya akan memperbarui setiap bingkai dalam metode
Pembaruan .
Di kelas
Peta , buat daftar semua hambatan yang mungkin ada di peta dan beberapa metode statis untuk mendaftarkannya.
Peta 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; } } }
Dalam metode
RegisterObstacle statis, kami akan mendaftarkan hambatan
Kendala baru di peta dan menambahkannya ke daftar, tetapi pertama-tama penting untuk mempertimbangkan bahwa peta itu sendiri juga diwarisi dari kelas
Kendala dan oleh karena itu kita perlu memeriksa apakah kita mencoba mendaftarkan kartu itu sendiri sebagai hambatan.
Metode statis
UnregisterObstacle , sebaliknya, menghilangkan hambatan dari peta dan menghilangkannya dari daftar ketika kita membiarkannya dihancurkan.
Pada saat yang sama, setiap kali kita menambah atau menghapus hambatan dari peta, kita perlu membuat ulang peta itu sendiri, jadi setelah menjalankan metode statis ini, setel variabel
requireRebuild menjadi
true .
Juga, untuk memiliki akses mudah ke skrip
Peta dari skrip apa pun, saya membuat properti
Instance statis yang akan mengembalikan kepada saya instance dari
Peta ini .
Sekarang, mari kita kembali ke skrip
Rintangan di mana kita akan mendaftarkan penghalang pada peta. Untuk melakukan ini, tambahkan beberapa metode
OnEnable dan
OnDisable untuk itu.
Rintangan public class Obstacle : MonoBehaviour { protected virtual void OnEnable() { Map.RegisterObstacle(this); } protected virtual void OnDisable() { Map.UnregisterObstacle(this); } }
Setiap kali kita membuat penghalang baru saat bermain di peta, itu akan secara otomatis mendaftar di metode
OnEnable , di mana ia akan diperhitungkan ketika membangun kisi baru dan menghapus diri kita dari peta dalam metode
OnDisable ketika dihancurkan atau dinonaktifkan.
Tetap hanya melacak perubahan hambatan itu sendiri dalam skrip
Peta dalam metode
CheckChanges yang kelebihan beban.
Peta 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(); } }
Sekarang kita memiliki peta, rintangan - secara umum, semua yang Anda butuhkan untuk membangun kisi dan sekarang Anda dapat beralih ke hal yang paling penting.
Meshing
Kotak, dalam bentuknya yang paling sederhana, adalah susunan titik dua dimensi. Untuk membangunnya, Anda perlu mengetahui ukuran peta dan ukuran titik di atasnya, setelah beberapa perhitungan kami mendapatkan jumlah titik secara horizontal dan vertikal, ini adalah kisi kami.
Ada banyak cara untuk menemukan jalur di grid. Dalam artikel ini, bagaimanapun, hal utama adalah untuk memahami bagaimana cara yang benar menggunakan kemampuan sistem tugas, jadi di sini saya tidak akan mempertimbangkan opsi yang berbeda untuk menemukan jalan, kelebihan dan kekurangannya, tetapi saya akan mengambil opsi pencarian paling sederhana
A * .
Dalam hal ini, semua titik pada grid harus memiliki, selain posisi, koordinat dan properti patensi.
Dengan patensi, saya pikir semuanya jelas mengapa diperlukan, tetapi koordinat akan menunjukkan urutan titik pada grid, koordinat ini tidak terikat secara khusus dengan posisi titik di ruang angkasa. Gambar di bawah ini menunjukkan grid sederhana yang menunjukkan perbedaan koordinat dari suatu posisi.
Kenapa koordinatnya?Faktanya adalah bahwa dalam kesatuan, untuk menunjukkan posisi suatu objek di ruang angkasa,
float sederhana
digunakan yang sangat tidak akurat dan dapat berupa angka pecahan atau negatif, sehingga akan sulit untuk menggunakannya untuk mengimplementasikan pencarian jalur pada peta. Koordinat dibuat dalam bentuk
int yang jelas yang akan selalu positif dan dengan itu lebih mudah untuk bekerja dengan ketika mencari titik tetangga.
Pertama, mari kita tentukan objek titik, ini akan menjadi struktur
Node sederhana.
Node public struct Node { public int id; public Vector2 position; public Vector2Int coords; }
Struktur ini akan berisi posisi
posisi dalam bentuk
Vector2 , di mana dengan variabel ini kita akan menggambar titik di ruang angkasa.
Variabel koordinat koordinat dalam bentuk
Vector2Int akan menunjukkan koordinat suatu titik di peta, dan variabel
id , nomor akun numeriknya, dengan menggunakannya kita akan membandingkan titik-titik yang berbeda di grid dan memeriksa keberadaan suatu titik.
Patensi titik akan ditunjukkan dalam bentuk properti
booleannya , tetapi karena kita tidak dapat menggunakan
tipe data yang dapat
dikonversi dalam sistem tugas, kami akan menunjukkan patennya dalam bentuk nomor
int , karena ini saya menggunakan enumerasi sederhana
NodeType , di mana: 0 bukan titik yang bisa dilewati, dan 1 lumayan.
NodeType dan 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; } }
Juga, untuk kenyamanan bekerja dengan suatu titik, saya akan membebani metode
Persamaan untuk membuatnya lebih mudah untuk membandingkan titik dan juga melengkapi metode verifikasi untuk keberadaan suatu titik.
Node 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; } }
Karena nomor
id titik di grid akan mulai dengan 1 unit, saya akan memeriksa keberadaan titik sebagai syarat bahwa
idnya lebih besar dari 0.
Pergi ke kelas
Peta di mana kami akan menyiapkan segalanya untuk membuat peta.
Kami sudah memiliki pemeriksaan untuk mengubah parameter peta, sekarang kita perlu menentukan bagaimana proses membangun grid akan dilakukan. Untuk melakukan ini, buat satu variabel baru dan beberapa metode.
Peta public sealed class Map : Obstacle { public bool rebuilding { get; private set; } public void Rebuild() {} private void OnRebuildStart() {} private void OnRebuildFinish() {} }
Properti
pembangunan kembali akan menunjukkan apakah proses
meshing sedang berlangsung. Metode
Rebuild akan mengumpulkan data dan tugas untuk membangun grid, maka metode
OnRebuildStart akan memulai proses pembuatan grid dan metode
OnRebuildFinish akan mengumpulkan data dari tugas.
Sekarang mari kita ubah metode
UpdateChanges sedikit sehingga kondisi grid diperhitungkan.
Peta 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; } }
Seperti yang Anda lihat sekarang dalam metode
UpdateChanges ada kondisi bahwa saat membangun mesh lama tidak mulai membangun yang baru, dan juga dalam metode
Rebuild , tindakan pertama memeriksa apakah proses meshing sudah dalam proses.
Pemecahan masalah
Sekarang sedikit tentang proses membangun peta.
Karena kita akan menggunakan sistem tugas dan membangun grid secara paralel untuk membangun peta, saya menggunakan jenis tugas
IJobParallelFor , yang akan dieksekusi beberapa kali. Agar tidak memuat proses konstruksi dengan satu tugas terpisah, kami akan menggunakan kumpulan tugas yang dikemas ke dalam satu
JobHandle .
Paling sering, untuk membangun kisi, gunakan dua siklus yang saling bersarang untuk membangun, misalnya, secara horizontal dan vertikal. Dalam contoh ini, kita juga akan membangun grid terlebih dahulu secara horizontal dan kemudian secara vertikal. Untuk melakukan ini, kita menghitung jumlah titik horizontal dan vertikal dalam metode
Rebuild , kemudian dalam metode
Rebuild kita melalui siklus di sepanjang titik-titik vertikal, dan kita akan membangun yang horisontal secara paralel dalam tugas. Untuk lebih membayangkan proses pembangunan, lihat animasi di bawah ini.
Jumlah titik vertikal akan menunjukkan jumlah tugas, pada gilirannya, setiap tugas akan membangun poin hanya secara horizontal, setelah menyelesaikan semua tugas, poin-poin tersebut dijumlahkan dalam satu daftar. Itulah sebabnya saya perlu menggunakan tugas seperti
IJobParallelFor untuk melewati indeks titik di grid secara horizontal ke metode
Execute .
Jadi kami memiliki struktur titik, sekarang Anda dapat membuat struktur tugas
Pekerjaan dan mewarisinya dari antarmuka
IJobParallelFor , semuanya sederhana di sini.
Ayub public struct Job : IJobParallelFor { public void Execute(int index) {} }
Kami kembali ke metode
Rebuild kelas
peta , di mana kami akan membuat perhitungan yang diperlukan untuk pengukuran grid.
Peta 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(); } }
Dalam metode
Rebuild , kami menghitung ukuran persis peta
mapSize , dengan mempertimbangkan lekukan, kemudian secara
vertikal kami menulis jumlah titik secara vertikal, dan dalam
horizontal jumlah titik secara horizontal. Jika jumlah titik vertikal adalah 0, maka kita berhenti membangun peta dan memanggil metode
OnRebuildFinish untuk menyelesaikan proses. Variabel
asal akan menunjukkan tempat dari mana kita akan mulai membangun kisi - dalam contoh, ini adalah titik kiri bawah pada peta.
Sekarang Anda dapat pergi ke tugas sendiri dan mengisinya dengan data.
Selama konstruksi grid, tugas akan membutuhkan array
NativeArray di mana kita akan menempatkan titik, juga karena kita memiliki hambatan pada peta, kita juga akan perlu untuk meneruskannya ke tugas, untuk ini kita akan menggunakan array
NativeArray lain, maka kita perlu ukuran poin dalam masalah , posisi awal dari tempat kita akan membangun poin, serta koordinat awal seri.
Ayub 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) {} }
Saya menandai array poin dengan atribut
WriteOnly, karena dalam tugas itu hanya perlu untuk "
menulis " poin yang diterima ke array, sebaliknya, array
batas rintangan ditandai dengan atribut
ReadOnly karena dalam tugas kita hanya akan "
membaca " data dari array ini.
Nah, untuk sekarang, mari kita lanjutkan ke penghitungan poin sendiri nanti.
Sekarang kembali ke kelas
Peta , di mana kami menunjukkan semua variabel yang terlibat dalam tugas.
Di sini, pertama-tama, kita perlu
menangani tugas
- tugas global, serangkaian hambatan dalam bentuk
NativeArray , daftar tugas yang akan berisi semua poin yang diterima di grid dan
Kamus dengan semua koordinat dan poin di peta, sehingga akan lebih mudah untuk mencari mereka nanti.
Peta 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>(); }
Sekarang lagi, kita kembali ke metode
Rebuild dan terus membangun grid.
Pertama, inisialisasi array
batas rintangan untuk meneruskannya ke tugas.
Bangun kembali 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(); }
Di sini kita membuat instance
NativeArray melalui konstruktor baru dengan tiga parameter. Saya memeriksa dua parameter pertama dalam artikel sebelumnya, tetapi parameter ketiga akan membantu kita menghemat sedikit waktu membuat array. Faktanya adalah bahwa kita akan menulis data ke array segera setelah pembuatannya, yang berarti kita tidak perlu memastikan bahwa itu dihapus. Parameter ini berguna untuk
NativeArray yang hanya akan digunakan dalam mode
baca dalam tugas.
Jadi, kemudian kita mengisi array
batas dengan data.
Bangun kembali 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(); }
Sekarang kita dapat beralih ke membuat tugas, untuk ini kita akan melalui siklus melalui semua baris vertikal grid.
Bangun kembali 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(); }
Untuk mulai dengan, di
xPos dan
yPos kita mendapatkan posisi horizontal awal seri.
Bangun kembali 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(); }
Selanjutnya, kita membuat array
NativeArray sederhana di mana titik-titik dalam tugas akan ditempatkan, di sini untuk array
array Anda perlu menentukan berapa banyak poin yang akan dibuat secara horizontal dan jenis alokasi
Persistent , karena tugas dapat memakan waktu lebih dari satu frame.
Setelah itu, buat contoh tugas
Pekerjaan itu sendiri, letakkan koordinat awal dari seri
startCoords , posisi awal dari seri
startPos , ukuran titik
nodeSize ,
batas array hambatan, dan pada akhirnya array titik itu sendiri.
Tinggal
menangani tugas dan daftar tugas global.
Bangun kembali 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(); }
Selesai Kami memiliki daftar tugas dan
pegangan umum mereka, sekarang kami dapat menjalankan
pegangan ini
dengan memanggil metode
Lengkapnya di metode
OnRebuildStart .
Onrebuildstart private void OnRebuildStart() { this.rebuilding = true; this.handle.Complete(); }
Karena variabel
pembangunan kembali akan menunjukkan bahwa proses
meshing sedang berlangsung, metode
UpdateChanges itu sendiri juga harus menentukan kondisi ketika proses ini akan berakhir menggunakan
pegangan dan properti
IsCompleted -nya.
Pembaruan perubahan private void UpdateChanges() { if (this.rebuilding) { print(“ ...”); if (this.handle.IsCompleted) OnRebuildFinish(); } else { if (this.requireRebuild) { print(“ , !”); Rebuild(); } else { this.requireRebuild = CheckChanges(); } } }
Setelah menyelesaikan tugas, metode
OnRebuildFinish akan dipanggil di mana kami akan mengumpulkan poin yang diterima ke dalam satu daftar
Kamus umum, dan yang paling penting, untuk menghapus sumber daya yang ditempati.
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; }
Pertama, kita menghapus kamus node dari titik sebelumnya, kemudian menggunakan foreach loop untuk memilah-milah semua poin yang kita terima dari tugas dan menempatkannya dalam kamus node , di mana kuncinya adalah koordinat ( BUKAN posisi !) Dari titik tersebut, dan nilainya adalah titik itu sendiri. Dengan bantuan kamus ini, akan lebih mudah bagi kami untuk mencari titik tetangga di peta. Setelah mengisi, kami menghapus array array menggunakan metode Buang dan pada akhirnya kami menghapus daftar tugas pekerjaan itu sendiri .Anda juga perlu menghapus batasan batasan jika itu sebelumnya dibuat.Setelah semua tindakan ini, kami mendapatkan daftar semua poin di peta dan sekarang Anda dapat menggambarnya di atas panggung.Untuk melakukan ini, di kelas Peta , buat metode OnDrawGizmos di mana kita akan menggambar poin.Peta public sealed class Map : Obstacle { #if UNITY_EDITOR private void OnDrawGizmos() {} #endif }
Sekarang melalui loop kita menggambar setiap titik.Peta 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 }
Setelah semua tindakan ini, peta kami terlihat agak membosankan, untuk benar-benar mendapatkan kisi, Anda perlu titik untuk terhubung satu sama lain.Untuk mencari titik tetangga, kita hanya perlu menemukan titik yang diinginkan dengan koordinatnya dalam 8 arah, jadi di kelas Peta kita akan membuat array statis arah Arah dan metode pencarian sel dengan koordinat GetNode -nya .Peta 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 }
Metode GetNode akan mengembalikan titik dengan koordinat dari daftar node , tetapi Anda harus melakukan ini dengan hati-hati, karena jika koordinat Vector2Int salah, kesalahan akan terjadi, jadi di sini kami menggunakan blok pintas try catch exception , yang akan membantu memotong pengecualian dan tidak " menggantung " seluruh aplikasi dengan kesalahan.Selanjutnya, kita akan melalui siklus di semua arah dan mencoba untuk menemukan titik-titik tetangga dalam metode OnDrawGizmos , dan yang paling penting, jangan lupa untuk mempertimbangkan paten dari titik tersebut.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
Sekarang Anda dapat dengan aman memulai permainan dan melihat apa yang terjadi.Dalam contoh ini, kami hanya membuat grafik itu sendiri menggunakan tugas, tetapi inilah yang terjadi setelah saya mengacaukan sistem algoritma A * itu sendiri , yang juga menggunakan sistem Pekerjaan untuk menemukan jalan, sumber di akhir artikel .Jadi Anda dapat menggunakan sistem tugas baru untuk tujuan Anda dan membangun sistem yang menarik tanpa banyak usaha.Seperti pada artikel sebelumnya, sistem tugas digunakan tanpa ECS , tetapi jika Anda menggunakan sistem ini bersama dengan ECS , Anda dapat mencapai hasil yang luar biasa dalam keuntungan kinerja. Good luck !Sumber Proyek Path Finder