Kata Pengantar
Jika Anda terlalu
malas untuk mengurus waktu Anda, membuat level untuk permainan Anda, maka Anda telah datang ke tempat yang tepat.
Artikel ini akan memberi tahu Anda secara terperinci bagaimana Anda dapat menggunakan salah satu dari
banyak metode generasi
lainnya menggunakan contoh dataran tinggi dan gua. Kami akan mempertimbangkan algoritma
Aldous-Broder dan bagaimana membuat gua yang dihasilkan lebih indah.
Di akhir membaca artikel, Anda harus mendapatkan sesuatu seperti ini:
Teori
Gunung
Sejujurnya, gua bisa dihasilkan dari awal, tetapi apakah itu entah bagaimana jelek? Dalam peran
"platform" untuk penempatan tambang, saya memilih pegunungan.
Gunung ini dihasilkan cukup sederhana: mari kita memiliki
array dua dimensi dan
tinggi variabel , awalnya sama dengan setengah panjang
array di dimensi kedua; kita cukup menelusuri kolomnya dan mengisi sesuatu dengan semua baris di kolom ke nilai
tinggi variabel , mengubahnya dengan peluang acak ke atas atau ke bawah.
Gua
Untuk menghasilkan ruang bawah tanah sendiri, saya memilih -
seperti yang saya kira - algoritma yang sangat baik. Secara sederhana, dapat dijelaskan sebagai berikut: mari kita memiliki dua (mungkin sepuluh) variabel
X dan
Y , dan array dua dimensi dari 50 oleh 50, kami memberikan variabel ini nilai acak dalam array kami, misalnya,
X = 26 , dan
Y = 28 . Setelah itu, kami melakukan tindakan yang sama beberapa kali: kami mendapatkan angka acak dari nol hingga
, dalam kasus kami, hingga
empat ; dan kemudian, tergantung pada jumlah yang keluar, kami berubah
variabel kami:
switch (Random.Range(0, 4)) { case 0: X += 1; break; case 1: X -= 1; break; case 2: Y += 1; break; case 3: Y -= 1; break; }
Kemudian, tentu saja, kami memeriksa untuk melihat apakah ada variabel yang jatuh di luar batas bidang kami:
X = X < 0 ? 0 : (X >= 50 ? 49 : X); Y = Y < 0 ? 0 : (Y >= 50 ? 49 : Y);
Setelah semua pemeriksaan ini, kami melakukan sesuatu dalam nilai
X dan
Y baru untuk array kami
(misalnya: tambahkan satu ke elemen) .
array[X, Y] += 1;
Persiapan
Untuk kesederhanaan implementasi dan visualisasi metode kami, akankah kita menggambar objek yang dihasilkan? Saya sangat senang Anda tidak keberatan! Kami akan melakukan ini dengan
Texture2D .
Untuk bekerja, kita hanya perlu dua skrip:
ground_libray adalah artikel yang akan diputar . Di sini kita menghasilkan, dan membersihkan, dan menggambar
ground_generator adalah apa yang akan digunakan oleh ground_libray kami
Biarkan yang pertama menjadi
statis dan tidak akan diwarisi dari apa pun:
public static class ground_libray
Dan yang kedua adalah normal, hanya saja kita tidak perlu metode
Pembaruan .
Juga, mari kita buat objek game di atas panggung, dengan komponen
SpriteRendererBagian praktis
Terdiri dari apa itu?
Untuk bekerja dengan data, kami akan menggunakan array dua dimensi. Anda dapat mengambil larik tipe yang berbeda, dari
byte atau
int , ke
Warna , tetapi saya percaya ini akan lebih baik dilakukan:
Tipe baruKami menulis hal ini di
ground_libray .
[System.Serializable] public class block { public float[] color = new float[3]; public block(Color col) { color = new float[3] { col.r, col.g, col.b }; } }
Saya akan menjelaskan ini dengan fakta bahwa itu akan memungkinkan kita untuk
menyimpan array kita dan
memodifikasinya jika perlu.
Massif
Mari kita, sebelum kita mulai menghasilkan gunung, tentukan tempat di mana kita akan
menyimpannya .
Dalam skrip
ground_generator, saya menulis ini:
public int ground_size = 128; ground_libray.block[,] ground; Texture2D myT;
ground_size - ukuran bidang kami (yaitu, array akan terdiri dari 16384 elemen).
ground_libray.block [,] ground - ini adalah bidang kami untuk pembuatan.
Texture2D myT adalah apa yang akan kita gambar.
Bagaimana cara kerjanya?Prinsip kerja bersama kami adalah sebagai berikut - kami akan memanggil beberapa metode ground_libray dari ground_generator , memberikan yang pertama bidang ground kami.
Mari kita buat metode pertama dalam skrip ground_libray:
Pembuatan gunung public static float mount_noise = 0.02f; public static void generate_mount(ref block[,] b) { int h_now = b.GetLength(1) / 2; for (int x = 0; x < b.GetLength(0); x++) for (int y = 0; y < h_now; y++) { b[x, y] = new block(new Color(0.7f, 0.4f, 0)); h_now += Random.value > (1.0f - mount_noise) ? (Random.value > 0.5 ? 1 : -1) : 0; } }
Dan segera kami akan mencoba memahami apa yang terjadi di sini: seperti yang saya katakan, kami hanya pergi ke kolom array kami
b , pada saat yang
sama mengubah variabel ketinggian
h_now , yang awalnya sama dengan setengah
128 (64) . Namun masih ada sesuatu yang baru -
mount_noise. Variabel ini bertanggung jawab atas kemungkinan mengubah
h_now , karena jika Anda sering mengubah ketinggian, gunung akan terlihat seperti
sisir .
WarnaSaya segera mengatur warna yang agak kecoklatan , biarkan setidaknya beberapa - di masa depan kita tidak akan membutuhkannya.
Sekarang mari kita pergi ke
ground_generator dan menulis ini di metode
Mulai :
ground = new ground_libray.block [ground_size, ground_size]; ground_libray.generate_mount(ref ground);
Kami menginisialisasi variabel
tanah setelah itu perlu dilakukan .
Setelah, tanpa penjelasan, kirimkan ke
ground_libray .
Jadi kami menghasilkan gunung.
Mengapa saya tidak bisa melihat gunung saya?
Sekarang mari kita menggambar apa yang kita dapatkan!
Untuk menggambar, kita akan menulis metode berikut di
ground_libray kami:
Menggambar public static void paint(block[,] b, ref Texture2D t) { t = new Texture2D(b.GetLength(0), b.GetLength(1)); t.filterMode = FilterMode.Point; for (int x = 0; x < b.GetLength(0); x++) for (int y = 0; y < b.GetLength(1); y++) { if (b[x, y] == null) { t.SetPixel(x, y, new Color(0, 0, 0, 0)); continue; } t.SetPixel(x, y, new Color( b[x, y].color[0], b[x, y].color[1], b[x, y].color[2] ) ); } t.Apply(); }
Di sini kita tidak akan lagi memberi seseorang bidang kita, kita hanya akan memberikan salinannya
(meskipun, karena kelas kata, kita memberi sedikit lebih dari sekadar salinan) . Kami juga akan memberikan
Texture2D kami
untuk metode ini.
Dua baris pertama: kami membuat tekstur
ukuran lapangan dan
menghapus penyaringan .
Setelah itu kita pergi melalui seluruh bidang array dan di mana kita tidak membuat apa-apa
(kelas perlu diinisialisasi) - kita menggambar kotak kosong, jika tidak, jika tidak kosong - kita menggambar apa yang kita simpan ke dalam elemen.
Dan, tentu saja, ketika selesai, kita pergi ke
ground_generator dan menambahkan ini:
ground = new ground_libray.block [ground_size, ground_size]; ground_libray.generate_mount(ref ground);
Tetapi tidak peduli berapa banyak kita menggambar pada tekstur kita, dalam permainan kita dapat melihatnya hanya dengan menempatkan kanvas ini pada sesuatu:
SpriteRenderer tidak menerima
Texture2D di mana pun , tetapi tidak ada yang mencegah kita membuat
sprite dari tekstur ini -
Sprite.Create (
tekstur ,
persegi panjang dengan koordinat sudut kiri bawah dan kanan atas ,
koordinat sumbu ).
Garis-garis ini akan disebut terbaru, kami akan menambahkan sisanya di atas metode
cat !
Milik saya
Sekarang kita perlu mengisi ladang kita dengan gua acak. Untuk tindakan seperti itu, kami juga akan membuat metode terpisah di
ground_libray . Saya ingin segera menjelaskan parameter metode ini:
ref block[,] b - . int thick - int size - Color outLine -
Gua public static void make_cave(ref block[,] b, int thick, int size, Color outLine) { int xNow = Random.Range(0, b.GetLength(0)); int yNow = Random.Range(0, b.GetLength(1) / 2); for (int i = 0; i < size; i++) { b[xNow, yNow] = null; make_thick(ref b, thick, new int[2] { xNow, yNow }, outLine); switch (Random.Range(0, 4)) { case 0: xNow += 1; break; case 1: xNow -= 1; break; case 2: yNow += 1; break; case 3: yNow -= 1; break; } xNow = xNow < 0 ? 0 : (xNow >= b.GetLength(0) ? b.GetLength(0) - 1 : xNow); yNow = yNow < 0 ? 0 : (yNow >= b.GetLength(1) ? b.GetLength(1) - 1 : yNow); } }
Untuk memulainya, kami mendeklarasikan variabel kami
X dan
Y , tapi saya hanya memanggil mereka
xNow dan
yNow, masing-masing.
Yang pertama, yaitu
xNow , mendapat nilai acak dari nol ke ukuran bidang di dimensi pertama.
Dan yang kedua -
yNow - juga mendapat nilai acak: dari nol ke tengah bidang di dimensi kedua.
Mengapa Kami menghasilkan gunung kami dari tengah, kemungkinan itu akan tumbuh ke "langit-langit"
tidak besar . Berdasarkan hal ini, saya tidak menganggapnya relevan untuk menghasilkan gua di udara.
Setelah itu, satu loop langsung berjalan, jumlah kutu tergantung pada parameter
ukuran . Setiap centang kami perbarui bidang pada posisi
xNow dan
yNow , dan hanya dengan demikian kami memperbaruinya sendiri
(pembaruan bidang dapat dilakukan di akhir - Anda tidak akan merasakan perbedaannya)Ada juga metode
make_thick , dalam parameter yang kami lewati
bidang kami,
lebar stroke gua ,
posisi pembaruan gua saat ini dan
warna stroke :
Stroke static void make_thick (ref block[,] b, int t, int[] start, Color o) { for (int x = (start[0] - t); x < (start[0] + t); x++) { if (x < 0 || x >= b.GetLength(0)) continue; for (int y = (start[1] - t); y < (start[1] + t); y++) { if (y < 0 || y >= b.GetLength(1)) continue; if (b[x, y] == null) continue; b[x, y] = new block(o); } } }
Metode ini mengambil koordinat
awal yang dilewatinya, dan di sekitarnya dengan jarak
t mengecat ulang semua blok dalam warna
o - semuanya sangat sederhana!
Sekarang mari kita
tambahkan baris ini ke
ground_generator kami:
ground_libray.make_cave(ref ground, 2, 10000, new Color(0.3f, 0.3f, 0.3f));
Anda dapat menginstal skrip
ground_generator sebagai komponen pada objek kami dan memeriksa cara kerjanya!

Lebih lanjut tentang gua ...- Untuk membuat lebih banyak gua, Anda dapat memanggil metode make_cave beberapa kali (gunakan loop)
- Mengubah ukuran parameter tidak selalu meningkatkan ukuran gua, tetapi sering mendapat lebih besar
- Dengan mengubah parameter tebal , Anda secara signifikan meningkatkan jumlah operasi:
jika parameternya 3, maka jumlah kuadrat dalam radius 3 akan menjadi 36 , jadi dengan ukuran parameter = 40.000 , jumlah operasi akan menjadi 36 * 40.000 = 1440000
Koreksi Gua

Pernahkah Anda memperhatikan bahwa dalam pandangan ini gua tidak terlihat terbaik? Terlalu banyak detail tambahan
(mungkin Anda berpikir berbeda) .
Untuk menghilangkan inklusi dari beberapa
# 4d4d4d kita akan menulis metode ini di
ground_libray :
Bersih public static void clear_caves(ref block[,] b) { for (int x = 0; x < b.GetLength(0); x++) for (int y = 0; y < b.GetLength(1); y++) { if (b[x, y] == null) continue; if (solo(b, 2, 13, new int[2] { x, y })) b[x, y] = null; } }
Tetapi akan sulit untuk memahami apa yang terjadi di sini jika Anda tidak tahu apa fungsi
solo :
static bool solo (block[,] b, int rad, int min, int[] start) { int cnt = 0; for (int x = (start[0] - rad); x <= (start[0] + rad); x++) { if (x < 0 || x >= b.GetLength(0)) continue; for (int y = (start[1] - rad); y <= (start[1] + rad); y++) { if (y < 0 || y >= b.GetLength(1)) continue; if (b[x, y] == null) cnt += 1; else continue; if (cnt >= min) return true; } } return false; }
Dalam parameter fungsi ini,
bidang kami,
jari -
jari verifikasi titik ,
"ambang kehancuran" dan
koordinat titik yang akan diperiksa harus ada.
Berikut adalah penjelasan terperinci tentang apa fungsi ini:
int cnt adalah penghitung "ambang" saat ini
Berikutnya adalah dua siklus yang memeriksa semua titik di sekitar titik yang koordinatnya dilewati untuk memulai . Jika ada titik kosong , maka kita tambahkan satu ke cnt , setelah mencapai "ambang kehancuran" kita mengembalikan kebenaran - titik itu berlebihan . Kalau tidak, kami tidak menyentuhnya.
Saya menetapkan ambang kehancuran menjadi 13 poin kosong, dan radius verifikasi adalah 2 (artinya, ia akan memeriksa 24 poin, tidak termasuk yang pusat)
ContohYang ini akan tetap tidak terluka, karena hanya ada
9 poin kosong.

Tapi yang ini tidak beruntung - sekitar sebanyak
14 poin kosong

Uraian singkat algoritme:
kami menelusuri seluruh bidang dan memeriksa semua poin untuk melihat apakah diperlukan. Selanjutnya, kita cukup menambahkan baris berikut ke
ground_generator kami:
ground_libray.clear_caves(ref ground);
Seperti yang bisa kita lihat, sebagian besar partikel yang tidak perlu hilang begitu saja.Tambahkan beberapa warna
Gunung kami terlihat sangat monoton, menurut saya membosankan.
Mari tambahkan beberapa warna. Tambahkan metode
level_paint ke
ground_libray :
Melukis di atas pegunungan public static void level_paint(ref block[,] b, Color[] all_c) { for (int x = 0; x < b.GetLength(0); x++) { int lvl_div = -1; int counter = 0; int lvl_now = 0; for (int y = b.GetLength(1) - 1; y > 0; y--) { if (b[x, y] != null && lvl_div == -1) lvl_div = y / all_c.Length; else if (b[x, y] == null) continue; b[x, y] = new block(all_c[lvl_now]); lvl_now += counter >= lvl_div ? 1 : 0; lvl_now = (lvl_now >= all_c.Length) ? (all_c.Length - 1) : lvl_now; counter = counter >= lvl_div ? 0 : (counter += 1); } } } </ <cut />source> . , , . , . <b>Y </b> , . </spoiler> <b>ground_generator </b> : <source lang="cs"> ground_libray.level_paint(ref ground, new Color[3] { new Color(0.2f, 0.8f, 0), new Color(0.6f, 0.2f, 0.05f), new Color(0.2f, 0.2f, 0.2f), });
Saya hanya memilih 3 warna:
Hijau ,
merah tua , dan
abu-abu gelap .
Tentu saja, Anda dapat mengubah jumlah warna dan nilai masing-masing. Ternyata seperti ini:
Tapi tetap terlihat terlalu ketat untuk menambahkan sedikit keacakan warna, kita akan menulis properti ini di
ground_libray :
Warna acak public static float color_randomize = 0.1f; static float crnd { get { return Random.Range(1.0f - color_randomize, 1.0f + color_randomize); } }
Dan sekarang di metode
level_paint dan
make_thick , di baris tempat kami menetapkan warna, misalnya di
make_thick :
b[x, y] = new block(o);
Kami akan menulis ini:
b[x, y] = new block(o * crnd);
Dan di
level_paint b[x, y] = new block(all_c[lvl_now] * crnd);
Pada akhirnya, semuanya akan terlihat seperti ini:
Kekurangan
Misalkan kita memiliki bidang 1024 kali 1024, kita perlu menghasilkan 24 gua, ketebalan ujungnya 4, dan ukurannya 80.000.
1024 * 1024 + 24 * 64 * 80.000 = 5.368.832.000 000 operasi.
Metode ini hanya cocok untuk menghasilkan modul kecil untuk dunia game, tidak
mungkin untuk menghasilkan sesuatu yang sangat besar
pada suatu waktu .