Salah satu artikel paling populer di situs saya dikhususkan untuk
pembuatan peta poligon (
terjemahan dalam Habré). Membuat kartu semacam itu membutuhkan banyak usaha. Tetapi saya tidak memulai dengan ini, tetapi dengan tugas yang
jauh lebih sederhana, yang akan saya uraikan di sini. Teknik sederhana ini memungkinkan Anda membuat kartu semacam itu dalam kurang dari 50 baris kode:
Saya tidak akan menjelaskan cara
menggambar kartu seperti itu: itu tergantung pada bahasa, perpustakaan grafik, platform, dll. Saya hanya akan menjelaskan cara
mengisi array dengan data peta.
Kebisingan
Cara standar untuk menghasilkan peta 2D adalah menggunakan noise dengan pita frekuensi terbatas sebagai blok penyusun, seperti noise Perlin atau noise simplex. Seperti inilah fungsi noise:
Kami menetapkan angka dari 0,0 hingga 1,0 untuk setiap titik di peta. Dalam gambar ini, 0,0 berwarna hitam dan 1,0 berwarna putih.
Berikut ini cara mengatur warna setiap titik kisi dalam sintaks bahasa mirip-C:
for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { double nx = x/width - 0.5, ny = y/height - 0.5; value[y][x] = noise(nx, ny); } }
Pengulangan akan bekerja dengan cara yang sama dalam Javascript, Python, Haxe, C ++, C #, Java dan sebagian besar bahasa populer lainnya, jadi saya akan menunjukkannya dalam sintaks mirip-C sehingga Anda dapat mengonversinya ke bahasa yang Anda butuhkan. Di sisa tutorial, saya akan menunjukkan bagaimana isi siklus berubah (
value[y][x]=…
garis
value[y][x]=…
) saat menambahkan fungsi baru. Demo akan menunjukkan contoh lengkap.
Di beberapa perpustakaan, perlu untuk menggeser atau mengalikan nilai yang dihasilkan untuk mengembalikannya dalam kisaran 0,0-1,0.
Tinggi
Kebisingan itu sendiri hanyalah kumpulan angka. Kita harus memberinya
makna . Hal pertama yang dapat Anda pikirkan adalah untuk mengikat nilai noise ke ketinggian (ini disebut "peta ketinggian"). Mari kita ambil suara yang ditunjukkan di atas dan menggambarnya sebagai ketinggian:
Kode tetap hampir sama, dengan pengecualian loop dalam. Sekarang terlihat seperti ini:
elevation[y][x] = noise(nx, ny);
Ya, dan hanya itu. Data peta tetap sama, tetapi sekarang saya akan menyebutnya
elevation
(tinggi), bukan
value
.
Kami punya banyak bukit, tetapi tidak lebih. Apa yang salah
Frekuensi
Kebisingan dapat dihasilkan pada
frekuensi apa pun. Sejauh ini saya hanya memilih satu frekuensi. Mari kita lihat bagaimana pengaruhnya.
Coba ubah nilainya dengan slider (dalam artikel asli) dan lihat apa yang terjadi pada frekuensi yang berbeda:
Itu hanya mengubah skala. Awalnya ini tampaknya tidak terlalu berguna, tetapi ternyata tidak. Saya punya satu lagi
tutorial (
terjemahan dalam Habré), yang menjelaskan
teori : konsep seperti frekuensi, amplitudo, oktaf, noise pink dan biru, dan sebagainya.
elevation[y][x] = noise(freq * nx, freq * ny);
Kadang-kadang juga berguna untuk mengingat
panjang gelombang , yang merupakan kebalikan dari besarnya. Ketika frekuensinya dua kali lipat, ukurannya hanya dibelah dua. Menggandakan panjang gelombang semua menjadi dua kali lipat. Panjang gelombang adalah jarak yang diukur dalam piksel / ubin / meter atau unit lain yang telah Anda pilih untuk peta. Ini terkait dengan frekuensi:
wavelength = map_size / frequency
.
Oktaf
Untuk membuat peta ketinggian lebih menarik, kami akan
menambahkan noise dengan frekuensi berbeda :
elevation[y][x] = 1 * noise(1 * nx, 1 * ny); + 0.5 * noise(2 * nx, 2 * ny); + 0.25 * noise(4 * nx, 2 * ny);
Mari kita gabungkan bukit frekuensi tinggi besar dengan bukit frekuensi tinggi kecil di satu peta.
Pindahkan penggeser (dalam artikel asli) untuk menambahkan bukit kecil ke dalam campuran:
Sekarang jauh lebih seperti bantuan fraktal yang kita butuhkan! Kita bisa mendapatkan bukit dan gunung yang kasar, tetapi kita masih tidak memiliki dataran datar. Untuk melakukan ini, Anda perlu sesuatu yang lain.
Redistribusi
Fungsi noise memberi kita nilai antara 0 dan 1 (atau dari -1 hingga +1, tergantung pada perpustakaan). Untuk membuat dataran datar, kita dapat
menaikkan ketinggian menjadi kekuatan .
Gerakkan slider (dalam artikel asli) untuk mendapatkan derajat yang berbeda.
e = 1 * noise(1 * nx, 1 * ny); + 0.5 * noise(2 * nx, 2 * ny); + 0.25 * noise(4 * nx, 4 * ny); elevation[y][x] = Math.pow(e, exponent);
Nilai
tinggi ketinggian rata-rata lebih rendah ke dataran , dan nilai rendah menaikkan ketinggian rata-rata menuju puncak gunung. Kita harus menghilangkannya. Saya menggunakan fungsi daya karena lebih sederhana, tetapi Anda dapat menggunakan kurva apa pun; Saya memiliki
demo yang lebih rumit.
Sekarang kita memiliki peta ketinggian yang realistis, mari kita tambahkan bioma!
Bioma
Noise memberi angka, tetapi kita membutuhkan peta dengan hutan, gurun, dan lautan. Hal pertama yang dapat Anda lakukan adalah mengubah ketinggian kecil menjadi air:
function biome(e) { if (e < waterlevel) return WATER; else return LAND; }
Wow, ini sudah menjadi seperti dunia yang dihasilkan secara prosedural! Kami memiliki air, rumput, dan salju. Tetapi bagaimana jika kita membutuhkan lebih banyak? Mari kita membuat urutan air, pasir, rumput, hutan, sabana, padang pasir dan salju:
Relief berdasarkan ketinggian function biome(e) { if (e < 0.1) return WATER; else if (e < 0.2) return BEACH; else if (e < 0.3) return FOREST; else if (e < 0.5) return JUNGLE; else if (e < 0.7) return SAVANNAH; else if (e < 0.9) return DESERT; else return SNOW; }
Wow, itu terlihat hebat! Untuk game Anda, Anda dapat mengubah nilai dan bioma. Crysis akan memiliki lebih banyak hutan; Skyrim memiliki lebih banyak es dan salju. Tetapi tidak peduli bagaimana Anda mengubah angka, pendekatan ini sangat terbatas. Jenis bantuan sesuai dengan ketinggian, oleh karena itu membentuk strip. Untuk membuatnya lebih menarik, kita perlu memilih bioma berdasarkan sesuatu yang lain. Mari kita buat peta kebisingan
kedua untuk kelembaban.
Di atas adalah suara ketinggian; bawah - kebisingan kelembabanSekarang mari kita gunakan tinggi dan kelembaban
bersama . Pada gambar pertama yang ditunjukkan di bawah ini, sumbu y adalah tinggi (diambil dari gambar di atas), dan sumbu x adalah kelembaban (gambar kedua lebih tinggi). Ini memberi kita peta yang menarik:
Relief berdasarkan pada dua nilai noiseKetinggian kecil adalah samudera dan pantai. Ketinggian yang tinggi berbatu dan bersalju. Di antaranya, kami mendapatkan beragam bioma. Kode ini terlihat seperti ini:
function biome(e, m) { if (e < 0.1) return OCEAN; if (e < 0.12) return BEACH; if (e > 0.8) { if (m < 0.1) return SCORCHED; if (m < 0.2) return BARE; if (m < 0.5) return TUNDRA; return SNOW; } if (e > 0.6) { if (m < 0.33) return TEMPERATE_DESERT; if (m < 0.66) return SHRUBLAND; return TAIGA; } if (e > 0.3) { if (m < 0.16) return TEMPERATE_DESERT; if (m < 0.50) return GRASSLAND; if (m < 0.83) return TEMPERATE_DECIDUOUS_FOREST; return TEMPERATE_RAIN_FOREST; } if (m < 0.16) return SUBTROPICAL_DESERT; if (m < 0.33) return GRASSLAND; if (m < 0.66) return TROPICAL_SEASONAL_FOREST; return TROPICAL_RAIN_FOREST; }
Jika perlu, Anda dapat mengubah semua nilai ini sesuai dengan persyaratan game Anda.
Jika kita tidak membutuhkan bioma, maka gradien halus (lihat
artikel ini ) dapat membuat warna:
Untuk kedua bioma dan gradien, nilai noise tunggal tidak memberikan variabilitas yang cukup, tetapi dua sudah cukup.
Iklim
Di bagian sebelumnya, saya menggunakan
ketinggian sebagai pengganti
suhu . Semakin tinggi, semakin rendah suhu. Namun, garis lintang geografis juga memengaruhi suhu. Mari kita gunakan ketinggian dan garis lintang untuk mengontrol suhu:
Di dekat kutub (garis lintang besar) iklimnya lebih dingin, dan di puncak gunung (ketinggian besar) iklimnya juga lebih dingin. Sejauh ini saya telah mengusahakannya tidak terlalu sulit: untuk pendekatan yang tepat untuk parameter ini, Anda memerlukan banyak pengaturan halus.
Ada juga perubahan iklim
musiman . Di musim panas dan musim dingin, belahan utara dan selatan menjadi lebih hangat dan lebih dingin, tetapi di khatulistiwa situasinya tidak banyak berubah. Banyak yang juga dapat dilakukan di sini, misalnya, seseorang dapat mensimulasikan angin dan arus laut yang berlaku, efek bioma pada iklim, dan efek rata-rata lautan pada suhu.
Pulau-pulau
Dalam beberapa proyek, saya membutuhkan batas peta menjadi air. Ini mengubah dunia menjadi satu atau lebih pulau. Ada banyak cara untuk melakukan ini, tetapi saya menggunakan solusi yang cukup sederhana dalam generator peta poligon saya: Saya mengubah ketinggian sebagai
e = e + a - b*d^c
, di mana
d
adalah jarak dari pusat (pada skala 0-1). Pilihan lain adalah mengubah
e = (e + a) * (1 - b*d^c)
. Konstanta
a
menaikkan semuanya,
b
menurunkan tepian ke bawah, dan
c
mengontrol laju penurunan.
Saya tidak sepenuhnya puas dengan ini dan masih banyak yang harus dieksplorasi. Haruskah jarak Manhattan atau Euclidean? Haruskah itu tergantung pada jarak ke pusat atau pada jarak ke tepi? Haruskah jarak kuadrat, atau linier, atau memiliki derajat lain? Haruskah itu penambahan / pengurangan, atau perkalian / pembagian, atau sesuatu yang lain? Dalam artikel asli,
coba Tambah, a = 0,1, b = 0,3, c = 2,0, atau
coba Lipatgandakan, a = 0,05, b = 1,00, c = 1,5. Opsi yang sesuai dengan Anda bergantung pada proyek Anda.
Mengapa tetap menggunakan fungsi matematika standar? Seperti yang saya katakan di
artikel saya
tentang kerusakan pada RPG (
terjemahan pada Habré), semua orang (termasuk saya) menggunakan fungsi matematika, seperti polinomial, distribusi eksponensial, dll., Tetapi di komputer kita tidak dapat dibatasi pada mereka. Kita dapat mengambil fungsi formasi
apa saja dan menggunakannya di sini, menggunakan tabel pencarian
e = e + height_adjust[d]
. Sejauh ini saya belum mempelajari masalah ini.
Kebisingan runcing
Alih-alih menaikkan ketinggian ke kekuatan, kita dapat menggunakan nilai absolut untuk membuat puncak yang tajam:
function ridgenoise(nx, ny) { return 2 * (0.5 - abs(0.5 - noise(nx, ny))); }
Untuk menambahkan oktaf, kita dapat memvariasikan amplitudo frekuensi tinggi sehingga hanya pegunungan yang menerima suara yang ditambahkan:
e0 = 1 * ridgenoise(1 * nx, 1 * ny); e1 = 0.5 * ridgenoise(2 * nx, 2 * ny) * e0; e2 = 0.25 * ridgenoise(4 * nx, 4 * ny) * (e0+e1); e = e0 + e1 + e2; elevation[y][x] = Math.pow(e, exponent);
Saya tidak punya banyak pengalaman dengan teknik ini, jadi saya perlu bereksperimen untuk belajar bagaimana menggunakannya dengan baik. Mungkin juga menarik untuk mencampur kebisingan frekuensi rendah runcing dengan kebisingan frekuensi tinggi non-runcing.
Teras
Jika kita membulatkan ketinggian ke tingkat n berikutnya, kita mendapatkan teras:
Ini adalah hasil dari penerapan fungsi redistribusi tinggi dalam bentuk
e = f(e)
. Di atas, kami menggunakan
e = Math.pow(e, exponent)
untuk mempertajam puncak gunung; di sini kita menggunakan
e = Math.round(e * n) / n
untuk membuat teras. Jika Anda menggunakan fungsi non-langkah, maka teras dapat dibulatkan atau terjadi hanya pada ketinggian tertentu.
Penempatan pohon
Biasanya kami menggunakan noise fraktal untuk ketinggian dan kelembaban, tetapi juga dapat digunakan untuk menempatkan objek yang berjarak tidak merata, seperti pohon dan batu. Untuk ketinggian kami menggunakan amplitudo tinggi dengan frekuensi rendah ("red noise"). Untuk menempatkan objek Anda perlu menggunakan amplitudo tinggi dengan frekuensi tinggi ("noise biru"). Di sebelah kiri adalah pola noise biru; di sebelah kanan adalah tempat-tempat di mana noise lebih besar dari nilai yang berdekatan:
for (int yc = 0; yc < height; yc++) { for (int xc = 0; xc < width; xc++) { double max = 0;
Memilih R yang berbeda untuk setiap bioma, kita bisa mendapatkan kerapatan variabel pohon:
Sangat menyenangkan bahwa noise seperti itu dapat digunakan untuk menempatkan pohon, tetapi algoritma lain seringkali lebih efektif dan membuat distribusi yang lebih merata: Tempat Poisson, ubin Van atau dithering grafik.
Hingga tak terbatas dan seterusnya
Perhitungan bioma pada posisi (x, y) tidak tergantung pada perhitungan semua posisi lainnya.
Perhitungan lokal ini memiliki dua sifat yang mudah digunakan: ia dapat dihitung secara paralel, dan dapat digunakan untuk medan tanpa akhir.
Tempatkan kursor mouse di minimap (dalam artikel asli) di sebelah kiri untuk menghasilkan peta di sebelah kanan. Anda dapat membuat bagian mana pun dari kartu tanpa menghasilkan (dan bahkan tanpa menyimpan) seluruh kartu.


Implementasi
Menggunakan noise untuk menghasilkan medan adalah solusi yang populer, dan di Internet Anda dapat menemukan tutorial untuk berbagai bahasa dan platform. Kode untuk menghasilkan kartu dalam berbagai bahasa kurang lebih sama. Inilah loop paling sederhana dalam tiga bahasa berbeda:
- Javascript:
let gen = new SimplexNoise(); function noise(nx, ny) {
- C ++:
module::Perlin gen; double noise(double nx, double ny) {
- Python:
from opensimplex import OpenSimplex gen = OpenSimplex() def noise(nx, ny):
Semua perpustakaan noise hampir sama. Coba
opensimplex untuk Python , atau
libnoise untuk C ++ , atau
simplex-noise untuk Javascript. Untuk sebagian besar bahasa populer, ada banyak perpustakaan kebisingan. Atau Anda dapat mempelajari cara kerja Perlin noise atau merealisasikannya sendiri.
Saya tidak melakukannya.Di pustaka suara yang berbeda untuk bahasa Anda, detail aplikasi mungkin sedikit berbeda (beberapa angka kembali dalam kisaran 0,0 hingga 1,0, yang lain dalam kisaran dari -1,0 hingga +1,0), tetapi gagasan dasarnya sama. Untuk proyek nyata, Anda mungkin perlu membungkus fungsi
noise
dan objek
gen
dalam kelas, tetapi detail ini tidak relevan, jadi saya membuatnya global.
Untuk proyek sederhana seperti itu, tidak masalah suara apa pun yang Anda gunakan: Perlin noise, noise simplex, noise OpenSimplex, nilai noise, offset titik tengah, algoritme berlian, atau transformasi Fourier terbalik. Masing-masing dari mereka memiliki pro dan kontra, tetapi untuk generator kartu yang sama mereka semua membuat nilai output yang kurang lebih sama.
Render peta tergantung pada platform dan gim, jadi saya tidak menerapkannya; kode ini hanya diperlukan untuk menghasilkan ketinggian dan bioma, rendering yang tergantung pada gaya yang digunakan dalam permainan. Anda dapat menyalin, port dan menggunakannya dalam proyek Anda.
Eksperimennya
Saya melihat pencampuran oktaf, menaikkan derajat ke kekuatan, dan menggabungkan ketinggian dengan kelembaban untuk membuat bioma.
Di sini Anda dapat mempelajari grafik interaktif yang memungkinkan Anda untuk bereksperimen dengan semua parameter ini, yang menunjukkan kode terdiri dari:
Berikut ini contoh kode:
var rng1 = PM_PRNG.create(seed1); var rng2 = PM_PRNG.create(seed2); var gen1 = new SimplexNoise(rng1.nextDouble.bind(rng1)); var gen2 = new SimplexNoise(rng2.nextDouble.bind(rng2)); function noise1(nx, ny) { return gen1.noise2D(nx, ny)/2 + 0.5; } function noise2(nx, ny) { return gen2.noise2D(nx, ny)/2 + 0.5; } for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var nx = x/width - 0.5, ny = y/height - 0.5; var e = (1.00 * noise1( 1 * nx, 1 * ny) + 0.50 * noise1( 2 * nx, 2 * ny) + 0.25 * noise1( 4 * nx, 4 * ny) + 0.13 * noise1( 8 * nx, 8 * ny) + 0.06 * noise1(16 * nx, 16 * ny) + 0.03 * noise1(32 * nx, 32 * ny)); e /= (1.00+0.50+0.25+0.13+0.06+0.03); e = Math.pow(e, 5.00); var m = (1.00 * noise2( 1 * nx, 1 * ny) + 0.75 * noise2( 2 * nx, 2 * ny) + 0.33 * noise2( 4 * nx, 4 * ny) + 0.33 * noise2( 8 * nx, 8 * ny) + 0.33 * noise2(16 * nx, 16 * ny) + 0.50 * noise2(32 * nx, 32 * ny)); m /= (1.00+0.75+0.33+0.33+0.33+0.50); } }
Ada kesulitan: untuk kebisingan ketinggian dan kelembaban perlu menggunakan benih yang berbeda, jika tidak mereka akan berubah menjadi sama, dan kartu tidak akan terlihat begitu menarik. Dalam Javascript, saya menggunakan
png-parkmiller library ; di C ++, Anda dapat menggunakan dua
objek linear_congruential_engine yang terpisah; dengan Python, Anda bisa membuat dua instance terpisah dari
kelas random.Random .
Pikiran
Saya suka pendekatan ini untuk memetakan generasi karena
kesederhanaannya . Cepat dan hanya memerlukan sedikit kode untuk menghasilkan hasil yang layak.
Saya tidak suka keterbatasannya dalam pendekatan ini. Perhitungan lokal berarti bahwa setiap titik tidak tergantung pada yang lainnya. Area peta yang berbeda
tidak saling terhubung . Setiap tempat di peta “sepertinya” sama. Tidak ada batasan global, misalnya, “harus ada 3 hingga 5 danau di peta” atau fitur global, seperti sungai yang mengalir dari puncak tertinggi ke lautan. Juga, saya tidak suka fakta bahwa untuk mendapatkan gambar yang bagus Anda perlu mengkonfigurasi parameter untuk waktu yang lama.
Mengapa saya merekomendasikannya? Saya pikir ini adalah titik awal yang baik, terutama untuk game indie dan game jams. Dua teman saya menulis versi awal
Realm of the Mad God hanya
dalam 30 hari untuk
kontes game . Mereka meminta saya untuk membantu membuat peta. Saya menggunakan teknik ini (ditambah beberapa fitur yang ternyata tidak terlalu berguna) dan membuat peta untuk mereka. Beberapa bulan kemudian, setelah menerima umpan balik dari para pemain dan dengan hati-hati mempelajari desain permainan, kami menciptakan generator peta yang lebih maju berdasarkan poligon Voronoi, yang dijelaskan di
sini (
terjemahan dalam Habré). Generator kartu ini tidak menggunakan teknik yang dijelaskan dalam artikel ini. Menggunakan noise untuk membuat peta dengan cara yang sama sekali berbeda.
Informasi tambahan
Ada
banyak hal keren yang dapat Anda lakukan dengan fungsi noise. Jika Anda mencari di Internet, Anda dapat menemukan opsi seperti turbulensi, ombak, multifractal bergerigi, redaman amplitudo, bertingkat, derau voronoi, turunan analitik, warping domain, dan lainnya. Anda dapat menggunakan
halaman ini sebagai sumber inspirasi. Saya tidak mempertimbangkannya di sini, artikel saya berfokus pada kesederhanaan.
Proyek ini dipengaruhi oleh proyek pembuatan peta saya sebelumnya:
- Saya menggunakan suara keseluruhan Perlin untuk generator kartu Realm of the Mad God pertama saya . Kami menggunakannya untuk enam bulan pertama pengujian alpha, dan kemudian menggantinya dengan generator peta pada poligon Voronoi , khusus dibuat untuk persyaratan gameplay yang kami tentukan selama pengujian alpha. Bioma dan warna mereka untuk artikel diambil dari proyek-proyek ini.
- Saat mempelajari pemrosesan sinyal audio, saya menulis tutorial derau yang menjelaskan konsep seperti frekuensi, amplitudo, oktaf, dan "warna" derau. Konsep yang sama yang berfungsi untuk suara juga berlaku untuk pembuatan kartu berbasis kebisingan. Pada saat itu, saya membuat beberapa generasi bantuan demo mentah, tetapi saya tidak menyelesaikannya.
- Terkadang saya bereksperimen untuk menemukan batasan. Saya ingin tahu berapa banyak kode yang minimal diperlukan untuk membuat peta yang meyakinkan. Dalam proyek mini ini, saya mencapai nol baris kode - semuanya dilakukan dengan filter gambar (turbulensi, ambang batas, gradien warna). Ini membuat saya bahagia dan sedih. Sejauh mana pembuatan peta dapat dilakukan oleh filter gambar? Cukup besar. Segala sesuatu yang dijelaskan di atas tentang "skema gradien warna halus" diambil dari percobaan ini. Lapisan noise adalah filter gambar turbulensi; oktaf adalah gambar yang saling bertumpukan; Alat derajat disebut "koreksi kurva" di Photoshop.
Yang sedikit mengganggu saya adalah bahwa sebagian besar kode yang ditulis pengembang game untuk generasi medan berbasis kebisingan (termasuk perpindahan titik tengah) ternyata sama dengan filter suara dan gambar. Di sisi lain, itu menciptakan hasil yang cukup baik hanya dalam beberapa baris kode, itulah sebabnya saya menulis artikel ini. Ini adalah
titik referensi yang cepat dan mudah . Biasanya saya tidak menggunakan kartu seperti itu untuk waktu yang lama, tetapi menggantinya dengan generator peta yang lebih kompleks segera setelah saya mengetahui jenis kartu apa yang lebih cocok untuk desain permainan. Bagi saya, ini adalah pola standar: mulai dengan sesuatu yang sangat sederhana, dan kemudian menggantinya setelah saya lebih memahami sistem yang saya gunakan.
Ada
banyak hal yang dapat dilakukan dengan kebisingan, dalam artikel yang saya sebutkan hanya sedikit. Coba
Noise Studio untuk menguji berbagai fitur secara interaktif.