Isometric Depth Sorting untuk Platform Bergerak

() translation by (you can also view the original English article)



Depth sorting atau mengurutkan kedalaman bisa dijelaskan secara sederhana sebagai cara untuk menentukan elemen mana yang lebih dekat ke kamera dan mana yang lebih jauh, sehingga menentukan urutan elemen-elemen tersebut agar bisa menggambarkan kedalaman yang benar pada scene.
Dalam tutorial ini kita akan menggali lebih jauh tentang depth sorting untuk level isometric sembari kita menambahkan platform bergerak. Ini bukan tutorial pemula untuk teori isometric dan bukan tentang kode. Fokusnya adalah untuk memahami logika dan teori dibandingkan membedah kode. Alat yang kita gunakan untuk tutorial ini adalah Unity, sehingga depth sorting adalah mengubah sortingOrder
dari sprite yang terlibat. Pada framework yang berbeda, mungkin hal ini berhubungan dengan mengubah urutan z atau urutan menggambar objek ke layar.
Untuk memulai dengan teori isometric, lihatlah seri tutorial berikut. Kode dan struktur scene mengikuti tutorial isometric saya sebelumnya. Kembalilah ke artikel tersebut jika kamu merasa tutorial ini sulit diikuti karena saya akan fokus pada logika pada tutorial ini.
1. Level Tanpa Gerakan
Jika level isometric kamu tidak memiliki elemen bergerak atau hanya ada beberapa karakter melewati level tersebut, depth sortingnya sederhana. Dalam kasus tersebut, karakter yang mengisi petak isometric akan lebih kecil dari petak tersebut dan karakter bisa mengikuti urutan gambar/kedalaman dengan tile yang mereka tempati.
Kita menyebut level seperti itu sebagai level statik. Ada beberapa cara menggambar level agar kedalaman yang tepat bisa disampaikan. Biasanya, data level berupa array dua dimensi di mana baris dan kolom mewakili baris dan kolom dalam level.
Pertimbangkan level isometric berikut dengan hanya dua baris dan tujuh kolom.



Nomor pada petak menunjukkan sortingOrder
atau kedalaman atau z order, dengan kata lain, urutan mereka perlu digambar. Dengan cara ini, kita menggambar semua kolom di baris pertama, mulai dari kolom dengan sortingOrder
1.
Setelah semua kolom baris pertama digambar, kolom terdekat dengan kamera memiliki sortingOrder
7, dan kita lanjut ke baris berikutnya. Setiap elemen di baris kedua memiliki sortingOrder
lebih tinggi daripada elemen manapun pada baris pertama.
Ini adalah bagimana petak perlu diatur untuk menggambarkan kedalaman yang tepat karena gambar dengan sortingOrder
yang lebih tinggi akan menimpa gambar dengan sortingOrder
yang lebih rendah.
Untuk kodenya, kita hanya mengiterasi baris dan kolom pada array level dan memberi nilai sortingOrder
secara berurutan dan meningkat. Hal ini akan tetap benar walau kita menukar baris dan kolom, seperti yang kita lihat pada gambar di bawah.



Di sini kita menggambar kolom lengkap sebelum bergerak ke baris berikutnya. Efek kedalaman tetap terlihat benar. Jadi logika untuk level statis adalah untuk menggambar baris atau kolom dengan lengkap lalu melanjutkan ke bagian berikutnya sambil memberi nilai sortingOrder
yang membesar secara berurutan.
Menambahkan Ketinggian
Jika kita mempertimbangkan level sebagai sebuah level, kita sedang menggambar lantai dasar. Jika kita perlu menambahkan lantai baru, kita perlu menggambar lantai dasar terlebih dahulu dan mengikuti metode yang sama untuk lantai berikutnya.
Untuk kedalaman yang benar, kita menunggu sampai baris lengkap selesai baru melanjutkan ke baris berikutnya, dan begitu pula kita menunggu sampai semua baris selesai sebelum kita pindah ke lantai berikutnya. Jadi untuk level dengan hanya satu baris dan dua lantai, akan terlihat seperti gambar di bawah.



Pada dasarnya, semua petak di lantai yang lebih tinggi akan memiliki sortingOrder
yang lebih tinggi dari semua petak di lantai lebih rendah. Untuk kode menambahkan lantai, kita hanya perlu menambah nilai y
dari koordinat petak di layar, tergantung lantai berapa petak tersebut berada.
1 |
float floorHeight=tileSize/2.2f; |
2 |
float currentFloorHeight=floorHeight*floorLevel; |
3 |
//
|
4 |
tmpPos=GetScreenPointFromLevelIndices(i,j); |
5 |
tmpPos.y+=currentFloorHeight; |
6 |
tile.transform.position=tmpPos; |
Nilai floorHeight
menunjukkan ketinggian dari gambar blok isometric, dan floorLevel
menunjukkan lantai mana petak tersebut berada.
2. Petak yang Bergerak pada Sumbu X
Mengurutkan kedalaman pada level isometrik statis tidak rumit. Sekarang kita tentukan untuk mengikuti metoda baris pertama, di mana kita memberi nilai sortingOrder
pada baris pertama sampai selesai baru lanjut ke yang berikutnya. Kita anggap petak atau platform bergerak kita yang pertama bergerak pada satu sumbu, sumbu x.
Saat saya bilang gerakan pada sumbu X, kamu perlu menyadari bahwa kita membicarakan tentang koordinat kartesian dan bukan sistem koordinat isometrik. Kita bayangkan level dengan hanya lantai dasar dan tiga baris dan tujuh kolom. Kita bayangkan juga baris kedua hanya memiliki satu petak, yang merupakan petak bergerak kita. Level tersebut akan terlihat seperti gambar di bawah.



Perak yang gelap adalah petak bergerak kita, dan sortingOrder
yang dimilikinya adalah 8 karena baris pertama memiliki 7 petak. Jika petak bergerak pada sumbu x kartesian, maka petak tersebut akan bergerak diantara dua baris. Pada setiap posisi yang ditempatinya sepanjang jalur, semua petak pada baris 1 akan memiliki sortingOrder
yang lebih kecil.
Begitu pula, semua petak pada baris dua akan memiliki sortingOrder
lebih tinggi, tidak peduli posisi petak gelap pada jalur yang dimaksud. Karena kita mengikuti aturan baris terlebih dahulu dalam memberi sortingOrder
, kita tidak perlu melakukan apapun untuk gerakan pada sumbu x. Itu cukup mudah.
3. Petak yang Bergerak pada Sumbu Y
Masalah mulai muncul saat kita mempertimbangkan sumbu Y. Kita bayangkan level sebuah level di mana petak gelap kita bergerak pada celah persegi, seperti gambar di bawah. Kamu bisa melihat scene yang sama pada file sumber Unity pada scene MovingSortingProblem
.



Menggunakan pendekatan 'baris terlebih dahulu', kita bisa memberikan sortingOrder
untuk petak yang bergerak berdasarkan baris yang ditempatinya. Saat berada diantara dua baris, petak tersebut akan mendapat sortingOrder
berdasarkan baris sebelumnya. Dalam kasus tersebut, petak tidak bisa mengikuti sortingOrder
berurutan dalam baris yang akan didatangi. Ini akan merusak pendekatan sorting kita.
Sorting dalam Blok
Untuk memecahkan masalah ini, kita perlu membagi level kita menjari beberapa blok berbeda, salah satunya adalah blok yang bermasalah, yang merusak visual pada pendekatan pertama, dan blok lain yang bisa mengikuti pendekatan 'baris terlebih dahulu' tanpa gangguan. Lihat gambar berikut agar lebih jelas.



Blok petak 2x2 pada area berwarna biru adalah blok yang bermasalah. Semua blok lain bisa mengikuti pendekatan 'baris terlebih dahulu'. Tolong jangan bingung karena gambar ditampilkan dengan benar menggunakan pendekatan blok yang akan kita buat. Blok biru berisi dua kolom petak diantara jalur petak gelap bergerak, dan petak di sebelah kirinya.
Untuk memecahkan masalah kedalaman untuk blok problem itu, kita bisa menggunakan pendekatan 'kolom lebih dahulu' hanya untuk blok ini. Jadi untuk blok hijau, pink, dan kuning, kita menggunakan 'baris lebih dahulu', dan untuk blok biru, kita gunakan pendekatan 'kolom lebih dahulu'.
Perhatikan bahwa kita tetap harus menentukan sortingOrder
berurutan. Pertama untuk blok hijau, lalu blok pink ke kiri, lalu blok biru, lalu blok pink ke kanan, dan akhirnya blok kuning. Kita merusak urutan hanya untuk berpindah menjadi pendekatan 'kolom lebih dahulu' saat berada di blok biru.
Selain itu, kita juga bisa mempertimbangkan blok 2x2 di kanan kolom petak yang bergerak. (Yang menarik, kamu tidak perlu mengubah pendekatan karena membagi area menjadi blok sudah memecahkan masalah kita dalam kasus ini.) Solusi ini bisa dilihat pada scene BlockSort
.



Hal tersebut dibuat menjadi kode di bawah ini.
1 |
private void DepthSort(){ |
2 |
Vector2 movingTilePos=GetLevelIndicesFromScreenPoint(movingGO.transform.position); |
3 |
int blockColStart=(int)movingTilePos.y; |
4 |
int blockRowStart=(int)movingTilePos.x; |
5 |
int depth=1; |
6 |
|
7 |
//sort rows before block
|
8 |
for (int i = 0; i < blockRowStart; i++) { |
9 |
for (int j = 0; j < cols; j++) { |
10 |
depth=AssignDepth(i,j,depth); |
11 |
}
|
12 |
}
|
13 |
//sort columns in same row before the block
|
14 |
for (int i = blockRowStart; i < blockRowStart+2; i++) { |
15 |
for (int j = 0; j < blockColStart; j++) { |
16 |
depth=AssignDepth(i,j,depth); |
17 |
}
|
18 |
}
|
19 |
//sort block
|
20 |
for (int i = blockRowStart; i < blockRowStart+2; i++) { |
21 |
for (int j = blockColStart; j < blockColStart+2; j++) { |
22 |
if(movingTilePos.x==i&&movingTilePos.y==j){ |
23 |
SpriteRenderer sr=movingGO.GetComponent<SpriteRenderer>(); |
24 |
sr.sortingOrder=depth;//assign new depth |
25 |
depth++;//increment depth |
26 |
}else{ |
27 |
depth=AssignDepth(i,j,depth); |
28 |
}
|
29 |
}
|
30 |
}
|
31 |
//sort columns in same row after the block
|
32 |
for (int i = blockRowStart; i < blockRowStart+2; i++) { |
33 |
for (int j = blockColStart+2; j < cols; j++) { |
34 |
depth=AssignDepth(i,j,depth); |
35 |
}
|
36 |
}
|
37 |
//sort rows after block
|
38 |
for (int i = blockRowStart+2; i < rows; i++) { |
39 |
for (int j = 0; j < cols; j++) { |
40 |
depth=AssignDepth(i,j,depth); |
41 |
}
|
42 |
}
|
43 |
}
|
4. Petak Bergerak pada Sumbu Z
Gerakan pada sumbu Z adalah gerakan palsu pada sebuah level isometric. Hal ini hanyalah gerakan pada sumbu Y layar. Untuk level isometric dengan hanya satu lantai, tidak ada yang perlu dilakukan untuk menambahkan gerakan pada sumbu Z jika kamu sudah menerapkan metode block sorting di atas. Kamu bisa melihat prakteknya pada scene SingleLayerWave
, di mana saya menambahkan gerakan wave pada sumbu Z bersama gerakan yang sudah ada.
Gerakan Sumbu Z pada Level dengan Banyak Lantai
Menambahkan lantai tambahan pada level menyangkut menambahkan posisi koordinat Y pada layar seperti yang sudah disebutkan sebelumnya. Jika petak tidak bergerak pada sumbu Z maka tidak ada hal lain yang perlu dilakukan untuk depth sorting. Kita bisa melakukan block sort pada lantai dasar yang memiliki gerakan lalu melakukan sorting 'baris lebih dahulu' pada setiap lantai berikutnya. Kamu bisa melihat prakteknya pada Unity scene BlockSortWithHeight
.



Masalah kedalaman yang mirip muncul saat petak bergerak antar lantai. Hal ini hanya dapat mengikuti urutan dalam satu lantai dengan pendekatan kita, dan akan merusak depth sorting di lantai lain. Kita perlu mengembangkan atau memodifikasi block sorting menjadi tiga dimensi untuk menangani masalah kedalaman dengan beberapa lantai.
Masalah ini pada dasarnya hanya melibatkan dua lantai di mana sebuah petak bergerak. Untuk lantai lainnya, kita bisa mengikuti pendekatan sorting kita saat ini. Kebutuhan khusus hanya berlaku untuk dua lantai ini, dan kita bisa menentukan lantai bawah dengan cara berikut, di mana tileZOffset
adalah jumlah gerakan pada sumbu Z untuk petak bergerak yang bersangkutan.
1 |
float whichFloor=(tileZOffset/floorHeight); |
2 |
float lower=Mathf.Floor(whichFloor); |
Ini artinya lower
dan lower+1
adalah lantai yang membutuhkan pendekatan spesial. Triknya adalah untuk menentukan sortingOrder
untuk kedua lantai ini bersamaan, seperti yang ditampilkan pada kode di bawah. Hal ini memperbaiki urutan agar masalah kedalaman bisa diperbaiki.
1 |
if(floor==lower){ |
2 |
// we need to sort lower floor and the floor just above it together in one go
|
3 |
depth=(floor*(rows*cols))+1; |
4 |
int nextFloor=floor+1; |
5 |
if(nextFloor>=totalFloors)nextFloor=floor; |
6 |
//sort rows before block
|
7 |
for (int i = 0; i < blockRowStart; i++) { |
8 |
for (int j = 0; j < cols; j++) { |
9 |
depth=AssignDepth(i,j,depth,floor); |
10 |
depth=AssignDepth(i,j,depth,nextFloor); |
11 |
}
|
12 |
}
|
13 |
//sort columns in same row before the block
|
14 |
for (int i = blockRowStart; i < blockRowStart+2; i++) { |
15 |
for (int j = 0; j < blockColStart; j++) { |
16 |
depth=AssignDepth(i,j,depth,floor); |
17 |
depth=AssignDepth(i,j,depth,nextFloor); |
18 |
}
|
19 |
}
|
20 |
//sort block
|
21 |
|
22 |
for (int i = blockRowStart; i < blockRowStart+2; i++) { |
23 |
for (int j = blockColStart; j < blockColStart+2; j++) { |
24 |
if(movingTilePos.x==i&&movingTilePos.y==j){ |
25 |
SpriteRenderer sr=movingGO.GetComponent<SpriteRenderer>(); |
26 |
sr.sortingOrder=depth;//assign new depth |
27 |
depth++;//increment depth |
28 |
}else{ |
29 |
depth=AssignDepth(i,j,depth,floor); |
30 |
depth=AssignDepth(i,j,depth,nextFloor); |
31 |
}
|
32 |
}
|
33 |
}
|
34 |
|
35 |
//sort columns in same row after the block
|
36 |
for (int i = blockRowStart; i < blockRowStart+2; i++) { |
37 |
for (int j = blockColStart+2; j < cols; j++) { |
38 |
depth=AssignDepth(i,j,depth,floor); |
39 |
depth=AssignDepth(i,j,depth,nextFloor); |
40 |
}
|
41 |
}
|
42 |
//sort rows after block
|
43 |
for (int i = blockRowStart+2; i < rows; i++) { |
44 |
for (int j = 0; j < cols; j++) { |
45 |
depth=AssignDepth(i,j,depth,floor); |
46 |
depth=AssignDepth(i,j,depth,nextFloor); |
47 |
}
|
48 |
}
|
49 |
}
|
Pada dasarnya, kita menganggap dua lantai sebagai satu lantai dan melakukan block sort pada satu lantai tersebut. Lihatlan kodenya beraksi pada scene BlockSortWithHeightMovement
. Dengan pendekatan ini, petak kita sekarang bebas bergerak pada dua sumbu apapun tanpa merusak kedalaman scene, seperti yang ditampilkan di bawah.



Kesimpulan
Tujuan dari tutorial ini adalah menjelaskan logika pendekatan depth sorting, dan saya harap kamu sudah cukup memahaminya. Terlihat jelas bahwa kita baru mempertimbangkan level sederhana dengan hanya satu petak bergerak.
Tidak ada bidang miring pula, karena akan membuat tutorial ini jauh lebih panjang. Tapi setelah kamu memahami logika sorting, kamu bisa mengembangkan logika bidang miring dua dimensi untuk tampilan isometric.
Unity memiliki perekonomian yang aktif. Ada banyak produk yang bisa membantu kamu membangun projekmu. Platform Unity juga adalah pilihan yang tepat untuk melatih kemampuan kamu. Apapun kebutuhan kamu, kamu bisa lihat apa yang tersedia di Envato Market.