Indonesian (Bahasa Indonesia) translation by Kinanthi Hayuningtyas (you can also view the original English article)
Biasanya digunakan pada permainan berbasis tile, masker tile memungkinkan Anda mengganti tile tergantung pada sebelahnya, memungkinkan Anda menggabungkan medan, mengganti ubin, dan lainnya. Pada tutorial ini, saya akan menunjukkan metode terukur dan bisa digunakan lagi untuk mendeteksi apakah tetangga dekat tile sesuai dengan salah satu dari sejumlah pola yang Anda tetapkan.
Catatan: Walaupun tutorial ini ditulis dengan C# dan Unity, Anda harus bisa menggunakan teknik dan konsep yang sama di hampir semua lingkup pengembangan game.
Apa Itu Masker Tile?
Pertimbangkan hal berikut: Anda membuat mirip Terraria, dan Anda ingin blok tanah berubah menjadi blok lumpur jika berada di samping air. Anggap saja dunia Anda memiliki lebih banyak air daripada kotorannya, sehingga cara termurah yang bisa Anda lakukan adalah dengan memeriksa setiap blok kotoran untuk melihat apakah itu di samping air, dan bukan sebaliknya. Ini saat yang tepat untuk menggunakan sebuah tile mask.
Tile mask setua pegunungan, dan didasarkan pada gagasan sederhana. Dalam grid benda 2D, tile bisa memiliki delapan tile lain yang bersebelahan dengannya. Kita akan menyebut ini kisaran lokal tile tengah tersebut (Gambar 1).

Gambar 1. Kisaran lokal sebuah tile
Untuk memutuskan apa yang harus Anda lakukan dengan tile kotor Anda, Anda akan membandingkan kisaran lokalnya dengan seperangkat peraturan. Misalnya, Anda mungkin melihat langsung di atas blok tanah dan melihat apakah ada air di sana (Gambar 2). Kumpulan aturan yang Anda gunakan untuk mengevaluasi rentang lokal Anda adalah masker tile Anda.

Gambar 2. Tile mask untuk mengidentifikasi sebuah blok tanah sebagai blok lumpur. Tile abu-abu bisa menjadi tile lain.
Tentu, Anda juga ingin mengecek air pada arah yang lain dengan baik, jadi kebanyakan tile mas punya lebih dari satu susuna spasial (Gambar 3).

Gambar 3. Empat tipe orientasi tile mask: 0°, 90°, 180°, 270°.
Dan kadang-kadang Anda akan menemukan bahwa pengaturan tile mask yang benar-benar sederhana dapat dicerminkan dan tidak superposabel (Gambar 4).

Gambar 4. Sebuah contoh kilaritas pada tile mask. Baris atas tidak bisa dirotasi untuk cocok dengan baris bawah.
Struktur Penyimpanan dari Tile Mask
Menyimpan Elemennya
Setiap sel di tile mask memiliki elemen di dalamnya. Elemen pada sel itu dibandingkan dengan elemen yang sesuai di kisaran lokal untuk melihat apakah mereka cocok.
Saya menggunakan daftar sebagai elemen. Daftar memungkinkan saya melakukan perbandingan kompleksivitas semaunya, dan C# Linq menyediakan cara yang sangat praktis untuk menggabungkan daftar ke dalam daftar yang lebih besar, mengurangi jumlah perubahan yang kemungkinan akan saya lupakan.
Misalnya, daftar dinding tile bisa dikombinasikan dengan daftar lantai tile dan daftar tile terbuka (pintu dan jendela) untuk menghasilkan daftar tile struktural, atau daftar tile furnitur dari masing-masing ruangan dapat dikombinasikan untuk membuat list semua furnitur. Permainan lainnya mungkin memiliki daftar tile yang bisa terbakar, atau terpengaruh oleh gravitasi.
Menyimpan Tile Mask Sendiri
Cara intuitif untuk menyimpan tile mask adalah sebagai susunan 2D, tapi mengakses susunan 2D bisa jadi lambat, dan Anda akan sering melakukannya. Kita tahu bahwa kisaran lokal dan tile mask akan selalu terdiri dari sembilan elemen, jadi kita bisa menghindari penalti waktu dengan menggunakan susunan 1D polos, membaca kisaran lokal dari atas ke bawah, kiri ke kanan (Gambar 4).

Gambar 5. Menyimpan tile mask 2D dalam struktur 1D.
Hubungan spasial antar elemen dipertahankan jika Anda membutuhkannya (saya belum membutuhkannya sampai saat ini), dan susunan bisa menyimpan banyak hal. Tile mask dalam format ini juga bisa dengan mudah diputar offset baca/tulis.
Menyimpan Informasi Rotasi
Dalam beberapa kasus, Anda mungkin ingin memutar tile sentral sesuai dengan sekitarnya, seperti saat meletakkan dinding di samping dinding lain. Saya suka menggunakan susunan bergerigi untuk menahan empat putaran tile mask di tempat yang sama, menggunakan susunan indeks luar untuk menunjukkan rotasi saat ini (lebih banyak di dalam kode).
Persyaratan Kode
Dengan dasar yang dijelaskan, kita dapat menjelaskan kode apa yang perlu dilakukan:
- Tentukan dan simpan tile mask.
- Putar tile mask secara otomatis, daripada kita harus secara manual mengatur empat salinan yang diputar pada pad definisi yang sama.
- Ambil kisaran lokal sebuah tile.
- Evaluasilah kisaran lokal dengan sebuah tile mask.
Kode berikut ditulis dalam C# untuk Unity, tapi konsepnya harus cukup portabel. Misalnya, salah satu dari karya saya sendiri dalam mengubah peta teks roguelike menjadi 3D secara prosedural (menempatkan bagian dinding lurus, dalam kasus ini).
Tentukan, Putar dan Simpan Tile Mask
Saya mengotomatisasi semua dengan satu metode disebut metode DefineTilemask
. Berikut contoh penggunaannya, dengan metode deklarasi di bawah ini.
public static readonly List<char> any = new List<char>() {}; public static readonly List<char> ignored = new List<char>() {' ', '_'}; public static readonly List<char> wall = new List<char>() {'#', 'D', 'W'}; public static List<char>[][] outerWallStraight = MapHelper.DefineTilemask ( any, ignored, any, wall, any, wall, any, any, any );
Saya menentukan tile mask dalam bentuk tidak-berputar. Daftarnya disebut karakter ignored stores yang tidak menggambarkan apapun dalam program saya: spasi, yang dilewati; dan garis bawah, yang saya gunakan untuk menunjukkan susunan indeks yang tidak valid. Sebuah tile di (0,0) (sudut kiri atas) dari susunan 2D tidak akan punya apapun ke Utara atau Barat, misalnya, sehingga jangkauan lokalnya malah mendapat garis bawah. any
adalah daftar kosong yang selalu dievaluasi sebagai kecocokan positif.
public static List<char>[][] DefineTilemask (List<char> nW, List<char> n, List<char> nE, List<char> w, List<char> centre, List<char> e, List<char> sW, List<char> s, List<char> sE) { List<char>[] template = new List<char>[9] { nW, n, nE, w, centre, e, sW, s, sE }; return new List<char>[4][] { RotateLocalRange (template, 0), RotateLocalRange (template, 1), RotateLocalRange (template, 2), RotateLocalRange (template, 3) }; } public static List<char>[] RotateLocalRange (List<char>[] localRange, int rotations) { List<char>[] rotatedList = new List<char>[9] {localRange[0], localRange[1], localRange[2], localRange[3], localRange[4], localRange[5], localRange[6], localRange[7], localRange[8]}; for (int i = 0; i < rotations; i++) { List<char>[] tempList = new List<char>[9] {rotatedList[6], rotatedList[3], rotatedList[0], rotatedList[7], rotatedList[4], rotatedList[1], rotatedList[8], rotatedList[5], rotatedList[2]}; rotatedList = tempList; } return rotatedList; }
Butuh dijelaskan implementasi dari kode ini. Pada DefineTilemask
, saya menyediakan sembilan daftar sebagai argumen. Daftar ini ditempatkan ke dalam sebuah susunan 1D sementara, dan diputar pada +90°langkah dengan menulis sebuah susunan baru pada urutan yang berbeda. Tilemask yang diputar kemudian disimpan ke dalam susunan bergerigi, yang strukturnya saya gunakan untuk menyampaikan informasi rotasi. Jika tilemask pada indeks luar 0 cocok, kemudian tile ditempatkan tanpa rotasi. Pencocokan pada indeks luar memberikan tile sebuah +90° rotasi, dan seterusnya.
Ambil sebuah Kisaran Lokal Tile
Yang ini sangat mudah. Ini membaca kisaran lokal pada tile yang akif menjadi susunan karakter 1D, mengganti indeks yang tidak valid yang bergaris bawah.
/* Usage: char[] localRange = GetLocalRange (blueprint, row, column); blueprint is the 2D array that defines the building. row and column are the array indices of the current tile being evaluated. */ public static char[] GetLocalRange (char[,] thisArray, int row, int column) { char[] localRange = new char[9]; int localRangeCounter = 0; // Iterators start counting from -1 to offset the reading up and to the left, placing the requested index in the centre. for (int i = -1; i < 2; i++) { for (int j = -1; j < 2; j++) { int tempRow = row + i; int tempColumn = column + j; if (IsIndexValid (thisArray, tempRow, tempColumn) == true) { localRange[localRangeCounter] = thisArray[tempRow, tempColumn]; } else { localRange[localRangeCounter] = '_'; } localRangeCounter++; } } return localRange; } public static bool IsIndexValid (char[,] thisArray, int row, int column) { // Must check the number of rows at this point, or else an OutOfRange exception gets thrown when checking number of columns. if (row < thisArray.GetLowerBound (0) || row > (thisArray.GetUpperBound (0))) return false; if (column < thisArray.GetLowerBound (1) || column > (thisArray.GetUpperBound (1))) return false; else return true; }
Evaluasi Kisaran Lokal dengan Tile Mask
Dan di sinilah keajaiban terjadi! TrySpawningTile
adalah pemberian kisaran lokal, sebuah tile mask, bagian dinding untuk ditumbuhkan jika tile masknya cocok, dan baris dan kolom tilenya dievaluasi.
Yang penting, metode yang menampilkan perbandingan aktual antara kisaran lokal dan tile mask (TileMatchesTemplate
) membuang tile mask dengan cepat ketika menemukan ketidakcocokan. Tidak ditampilkan adalah logika dasar yang menentukan tile mask yang akan digunakan untuk jenis tilenya (Anda tidak akan menggunakan dinding tile pada bagian furnitur, misalnya).
/* Usage: TrySpawningTile (localRange, TileIDs.outerWallStraight, outerWallWall, floorEdgingHalf, row, column); */ // These quaternions have a -90 rotation along X because models need to be // rotated in Unity due to the different up axis in Blender. public static readonly Quaternion ROTATE_NONE = Quaternion.Euler(-90, 0, 0); public static readonly Quaternion ROTATE_RIGHT = Quaternion.Euler(-90, 90, 0); public static readonly Quaternion ROTATE_FLIP = Quaternion.Euler(-90, 180, 0); public static readonly Quaternion ROTATE_LEFT = Quaternion.Euler(-90, -90, 0); bool TrySpawningTile (char[] needleArray, List<char>[][] templateArray, GameObject tilePrefab, int row, int column) { Quaternion horizontalRotation; if (TileMatchesTemplate (needleArray, templateArray, out horizontalRotation) == true) { SpawnTile (tilePrefab, row, column, horizontalRotation); return true; } else { return false; } } public static bool TileMatchesTemplate (char[] needleArray, List<char>[][] tileMaskJaggedArray, out Quaternion horizontalRotation) { horizontalRotation = ROTATE_NONE; for (int i = 0; i < (tileMaskJaggedArray.Length); i++) { for (int j = 0; j < 9; j++) { if (j == 4) continue; // Skip checking the centre position (no need to ascertain that a block is what it says it is). if (tileMaskJaggedArray[i][j].Count != 0) { if (tileMaskJaggedArray[i][j].Contains (needleArray[j]) == false) break; } if (j == 8) // The loop has iterated nine times without stopping, so all tiles must match. { switch (i) { case 0: horizontalRotation = ROTATE_NONE; break; case 1: horizontalRotation = ROTATE_RIGHT; break; case 2: horizontalRotation = ROTATE_FLIP; break; case 3: horizontalRotation = ROTATE_LEFT; break; } return true; } } } return false; } void SpawnTile (GameObject tilePrefab, int row, int column, Quaternion horizontalRotation) { Instantiate (tilePrefab, new Vector3 (column, 0, -row), horizontalRotation); }
Penilaian dan Kesimpulan
Manfaat dari Penerapan Ini
- Sangat terstruktur; hanya tambahkan definisi tile mask.
- Pengecekan pada tile mask dapat menjadi sesulit yang Anda suka.
- Kode ini cukup transparan, dan kecepatannya dapat ditingkatkan dengan menampilkan beberapa ekstra cek pada kisaran lokal sebelum Anda mulai menemukan tile mask.
Kekurangan Implementasi Ini
- Bisa berpotensi sangat mahal. Pada kasus terburuk mutlak Anda akan mempunyai (36 x n) + 1 susunan akses dan panggilan pada
List.Contains()
sebelum menemukan sebuah kecocokan, dimana n adalah jumlah definisi tile mask yang Anda cari. Penting untuk melakukan apa yang Anda bisa untuk mempersempit daftar tile mask sebelum Anda mulai mencari.
Kesimpulan
Tile mask tidak hanya digunakan pada dunia generasi atau estetika, tapi juga pada elemen yang mempengaruhi permainan game. Tidak sulit untuk membayangkan sebuah permainan puzzle dimana tile mask mungkin digunakan untuk menentukan keberadaan papan atau gerakan potensial bagian-bagian, atau alat pengedit mungkin menggunakan sistem yang mirip untuk menangkap blok satu sama lain. Artikel ini mendemonstrasikan sebuah implementasi dasar dari ide dan saya harap berguna untuk Anda.
Envato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post