Pembuatan Prosedural Puzzle Sederhana
() translation by (you can also view the original English article)



Puzzle merupakan bagian integral gameplay bagi banyak genre. Apakah sederhana atau kompleks, dengan mengembangkan puzzle secara manual dapat secara cepat menjadi halangan. Tutorial ini bertujuan memudahkan hambatan itu dan memberi jalan untuk aspek desain lainnya yang lebih menyenangkan.
Bersama-sama kita akan membuat generator untuk menyusun prosedural sederhana teka-teki "bersarang". Jenis teka-teki yang akan kita fokuskan adalah "dikunci dan kunci" tradisional yang paling sering diartikan sebagai: dapatkan item x untuk membuka area y. Jenis teka-teki ini bisa menjadi membosankan bagi tim yang mengerjakan beberapa jenis permainan, terutama perayap di bawah tanah, kotak pasir, dan permainan peran di mana teka-teki lebih sering diandalkan untuk konten dan eksplorasi.
Dengan menggunakan generasi prosedural, tujuan kami adalah menciptakan sebuah fungsi yang membutuhkan beberapa parameter dan mengembalikan aset yang lebih kompleks untuk permainan kita. Menerapkan metode ini akan memberikan hasil yang eksponensial pada waktu pengembang tanpa mengorbankan kualitas gameplay. Kekhawatiran pengembang juga bisa menurun sebagai efek samping yang menyenangkan.
Apa Yang Perlu Saya Ketahui?
Untuk mengikuti, Anda harus terbiasa dengan bahasa pemrograman pilihan Anda. Karena sebagian besar dari apa yang kita diskusikan adalah data saja dan digeneralisasi menjadi pseudocode, bahasa pemrograman berorientasi objek pun akan cukup.
Bahkan, beberapa editor drag-and-drop juga akan bekerja. Jika Anda ingin membuat demo generator yang dapat dimainkan yang disebutkan di sini, Anda juga memerlukan keakraban dengan perpustakaan game pilihan Anda.
Membuat Generator
Mari kita mulai dengan melihat beberapa pseudocode. Blok bangunan paling dasar dari sistem kita akan menjadi key (kunci) dan room (ruang). Dalam sistem ini, pemain dilarang memasuki pintu kamar kecuali mereka memiliki kuncinya. Inilah yang kedua benda itu akan terlihat seperti class:
1 |
class Key { |
2 |
|
3 |
Var playerHas; |
4 |
Var location; |
5 |
|
6 |
Function init (setLocation) { |
7 |
Location = setLocation; |
8 |
PlayerHas = false; |
9 |
} |
10 |
|
11 |
Function pickUp() { |
12 |
this.playerHas = true; |
13 |
} |
14 |
} |
15 |
|
16 |
class Room { |
17 |
|
18 |
Var isLocked; |
19 |
Var assocKey; |
20 |
|
21 |
Function init () { |
22 |
isLocked = true; |
23 |
assocKey = new Key (this); |
24 |
} |
25 |
|
26 |
Function unlock() { |
27 |
this.isLocked = false; |
28 |
} |
29 |
|
30 |
Function canUnlock { |
31 |
If (this.key.PlayerHas) { |
32 |
Return true; |
33 |
} |
34 |
Else { |
35 |
Return false; |
36 |
} |
37 |
} |
38 |
} |
Class kunci kami hanya menyimpan dua lembar informasi sekarang: lokasi kuncinya, dan jika pemain memiliki kunci itu dalam inventarisnya. Dua fungsinya adalah inisialisasi dan pengambilan. Inisialisasi menentukan dasar-dasar kunci baru, sedangkan pengambilan adalah saat pemain berinteraksi dengan kuncinya.
Pada gilirannya, class ruang kami juga berisi dua variabel: isLocked
, yang memegang keadaan kunci ruang saat ini, dan assocKey
, yang memegang objek Kunci yang membuka ruang khusus ini. Berisi fungsi untuk inisialisasi juga - satu untuk dipanggil membuka kunci pintu, dan pintu lain untuk memeriksa apakah pintu saat ini dapat dibuka.
Pintu dan kunci tunggal sangat menyenangkan, tapi kami selalu bisa membumbuinya dengan sarang. Menerapkan fungsi ini akan memungkinkan kita untuk membuat pintu di dalam pintu sambil melayani sebagai generator utama kita. Untuk menjaga sarang, kita perlu menambahkan beberapa variabel tambahan ke pintu kita juga:
1 |
class Room { |
2 |
Var isLocked; |
3 |
Var assocKey; |
4 |
Var parentRoom; |
5 |
Var depth; |
6 |
|
7 |
Function init (setParentRoom,setDepth) { |
8 |
If (setParentRoom) { |
9 |
parentRoom = setParentRoom; |
10 |
} |
11 |
Else { |
12 |
parentRoom = none; |
13 |
} |
14 |
Depth = setDepth; |
15 |
isLocked = true; |
16 |
assocKey = new Key (this); |
17 |
} |
18 |
|
19 |
Function unlock() { |
20 |
this.isLocked = false; |
21 |
} |
22 |
|
23 |
Function canUnlock { |
24 |
If (this.key.playerHas) { |
25 |
Return true; |
26 |
} |
27 |
Else { |
28 |
Return false; |
29 |
} |
30 |
} |
31 |
} |
32 |
|
33 |
Function roomGenerator (depthMax) |
34 |
{ |
35 |
Array roomsToCheck; |
36 |
Array finishedRooms; |
37 |
|
38 |
Room initialRoom.init(none,0); |
39 |
roomsToCheck.add(initialRoom); |
40 |
|
41 |
While (roomsToCheck != empty) { |
42 |
If (currentRoom.depth == depthMax) { |
43 |
finishedRooms.add(currentRoom); |
44 |
roomsToCheck.remove(currentRoom); |
45 |
} |
46 |
Else { |
47 |
Room newRoom.init(currentRoom,currentRoom.depth+1); |
48 |
roomsToCheck.add(newRoom); |
49 |
finishedRooms.add(currentRoom); |
50 |
roomsToCheck.remove(currentRoom); |
51 |
} |
52 |
} |
53 |
} |
Kode generator ini melakukan hal berikut:
Mengambil parameter untuk teka-teki yang dihasilkan (khususnya berapa banyak lapisan yang berada di dalam ruangan bersarang harus dilalui).
Membuat dua array: satu untuk ruangan yang sedang diperiksa untuk potensi bersarang, dan satu lagi untuk mendaftarkan kamar yang sudah bersarang.
Membuat ruang awal berisi seluruh pemandangan dan kemudian menambahkannya ke array untuk kita periksa nanti.
Mengambil ruangan di bagian depan array untuk melewati lingkaran.
Memeriksa kedalaman ruangan saat ini terhadap kedalaman maksimum yang diberikan (ini memutuskan apakah kita membuat ruang anak lebih jauh atau jika kita menyelesaikan prosesnya).
Membentuk ruang baru dan mempopulasikannya dengan informasi yang diperlukan dari ruang induk.
Menambahkan ruang baru ke array
RoomsToCheck
dan memindahkan ruang sebelumnya ke array yang telah selesai.Mengulangi proses ini hingga tiap ruang di dalam rangkaian lengkap.
Sekarang kita bisa memiliki banyak ruangan seperti yang mesin kita bisa tangani, tapi kita masih butuh kunci. Penempatan kunci memiliki satu tantangan utama: solvabilitas. Dimanapun kita menempatkan kunci, kita perlu memastikan bahwa pemain dapat mengaksesnya! Tidak peduli seberapa bagus kunci tersembunyi, jika pemain tidak dapat mencapainya, pemain secara efektif terjebak. Agar pemain bisa terus melewati teka-teki, kunci harus bisa didapat.
Metode paling sederhana untuk memastikan solvabilitas dalam teka-teki kita adalah dengan menggunakan sistem hierarki hubungan objek induk dan anak. Karena setiap ruang berada di tempat lain, kami berharap pemain harus memiliki akses ke induk di setiap ruangan untuk mencapainya. Jadi, selama kuncinya berada di atas ruangan pada rantai hirarkis, kita jamin pemain bisa mendapatkan akses.
Untuk menambahkan generasi kunci ke generasi prosedural, kita akan memasukkan kode berikut ke dalam fungsi utama:
1 |
Function roomGenerator (depthMax) |
2 |
{ |
3 |
Array roomsToCheck; |
4 |
Array finishedRooms; |
5 |
|
6 |
Room initialRoom.init(none,0); |
7 |
roomsToCheck.add(initialRoom); |
8 |
|
9 |
While (roomsToCheck != empty) { |
10 |
If (currentRoom.depth == depthMax) { |
11 |
finishedRooms.add(currentRoom); |
12 |
roomsToCheck.remove(currentRoom); |
13 |
} |
14 |
Else { |
15 |
Room newRoom.init(currentRoom,currentRoom.depth+1); |
16 |
roomsToCheck.add(newRoom); |
17 |
finishedRooms.add(currentRoom); |
18 |
roomsToCheck.remove(currentRoom); |
19 |
|
20 |
Array allParentRooms; |
21 |
roomCheck = newRoom; |
22 |
|
23 |
While (roomCheck.parent) |
24 |
{ |
25 |
allParentRooms.add(roomCheck.parent); |
26 |
roomCheck = roomCheck.parent; |
27 |
|
28 |
} |
29 |
Key newKey.init(Random (allParentRooms)); |
30 |
newRoom.Key = newKey; |
31 |
|
32 |
Return finishedRooms; |
33 |
|
34 |
} |
35 |
Else { |
36 |
finishedRooms.add(currentRoom); |
37 |
roomsToCheck.remove(currentRoom); |
38 |
} |
39 |
} |
Kode tambahan ini sekarang akan menghasilkan daftar semua ruangan yang berada di atas ruang Anda saat ini dalam hirarki peta. Kemudian kita memilih salah satu dari mereka secara acak, dan atur lokasi kunci ke ruangan itu. Setelah itu, kita tetapkan kunci ke ruangan yang dibukanya.
Saat dipanggil, fungsi generator kita sekarang akan membuat dan mengembalikan sejumlah kamar dengan kunci, berpotensi menghemat jam waktu pengembangan!
Itu membungkus bagian pseudocode dari generator teka-teki sederhana kita, jadi sekarang mari kita menerapkannya.
Demo Pembuatan Puzzle Prosedural
Kami membuat demo kami menggunakan JavaScript dan perpustakaan Crafty.js agar tetap ringan semaksimal mungkin, memungkinkan kita menyimpan program di bawah 150 baris kode. Ada tiga komponen utama demo kami seperti yang tercantum di bawah ini:
Pemain dapat bergerak di setiap level, tombol ambil, dan membuka pintu.
Generator yang akan kita gunakan untuk membuat peta baru secara otomatis setiap kali demo dijalankan.
Perluasan generator kita untuk digabungkan dengan Crafty.js, yang memungkinkan kita menyimpan informasi objek, tabrakan, dan entitas.
Pseudocode di atas bertindak sebagai alat untuk penjelasan, jadi menerapkan sistem dalam bahasa pemrograman Anda sendiri akan memerlukan beberapa modifikasi.
Untuk demo kami, sebagian class disederhanakan untuk penggunaan yang lebih efisien dalam JavaScript. Ini termasuk menaruh fungsi tertentu yang terkait dengan class, karena JavaScript memungkinkan akses lebih mudah ke variabel di dalam class.
Untuk membuat bagian permainan dari demo kami, kami menginisialisasi Crafty.js, dan kemudian entitas pemain. Selanjutnya, kita memberi entitas pemain kendali empat arah dasar dan beberapa deteksi tabrakan kecil untuk mencegah masuk ke dalam kamar terkunci.
Kamar sekarang diberi entitas Crafty, menyimpan informasi mengenai ukuran, lokasi, dan warna untuk representasi visual. Kami juga akan menambahkan fungsi undian untuk memungkinkan kita membuat sebuah ruangan dan menggambarnya ke layar.
Kita akan memberikan kunci dengan penambahan serupa, termasuk penyimpanan entitas, ukuran, lokasi, dan warnanya pada Crafty. Kunci juga akan diberi kode warna agar sesuai dengan ruangan yang mereka buka. Akhirnya, kita sekarang bisa menempatkan kunci dan membuat entitas mereka menggunakan fungsi menggambar baru.
Terakhir tapi bukan yang akhir, kita akan mengembangkan fungsi pembantu kecil yang menciptakan dan mengembalikan nilai warna heksadesimal acak untuk menghilangkan beban pemilihan warna. Kecuali Anda suka swatch warna, tentu saja.
Apa Yang Saya Lakukan Selanjutnya?
Sekarang setelah Anda memiliki generator sederhana Anda sendiri, inilah beberapa ide untuk memperluas contoh kami:
Port generator untuk memungkinkan penggunaan bahasa pemrograman pilihan Anda.
Perluas generator untuk memasukkan membuat ruang percabangan untuk kustomisasi lebih lanjut.
Tambahkan kemampuan untuk menangani banyak pintu masuk ruangan ke generator kami untuk memungkinkan teka-teki yang lebih kompleks.
Perluas generator untuk memungkinkan penempatan kunci di lokasi yang lebih rumit untuk meningkatkan pemecahan masalah pemain. Hal ini sangat menarik saat dipasangkan dengan beberapa jalur untuk pemain.
Rangkuman
Sekarang kita telah menciptakan generator puzzle ini bersama-sama, gunakan konsep yang diperlihatkan untuk mempermudah siklus pengembangan Anda sendiri. Tugas repetitif apa yang Anda lakukan? Apa yang paling mengganggumu untuk menciptakan permainanmu?
Kemungkinannya, dengan sedikit perencanaan dan generasi prosedural, Anda dapat membuat prosesnya menjadi lebih sederhana. Mudah-mudahan, generator kami akan memungkinkan Anda untuk fokus pada bagian permainan yang lebih menarik sambil memotong hal-hal duniawi.
Semoga beruntung, dan sampai ketemu lagi di bagian komentar!