7 days of WordPress themes, graphics & videos - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Game Development
  2. Programming

Buat Shooter Vektor Neon di XNA: Lebih Banyak Gameplay

Read Time: 18 mins
This post is part of a series called Cross-Platform Vector Shooter: XNA.
Make a Neon Vector Shooter in XNA: Basic Gameplay
Make a Neon Vector Shooter in XNA: Bloom and Black Holes

Indonesian (Bahasa Indonesia) translation by Suci Rohini (you can also view the original English article)

Dalam seri tutorial ini, saya akan menunjukkan cara membuat shooter tongkat neon, seperti Geometri Wars, di XNA. Tujuan dari tutorial ini bukan untuk meninggalkan Anda dengan replika Perang Geometri yang sama persis, melainkan untuk membahas elemen-elemen penting yang memungkinkan Anda membuat varian berkualitas tinggi sendiri.


Overview

Pada bagian ini kita akan membangun pada tutorial sebelumnya dengan menambahkan musuh, deteksi tabrakan dan scoring.

Inilah yang akan kita miliki pada akhirnya:

Kita akan menambahkan kelas-kelas baru berikut untuk menangani ini:

  • Musuh
  • EnemySpawner: Bertanggung jawab untuk menciptakan musuh dan secara bertahap meningkatkan kesulitan permainan.
  • PlayerStatus: Trek Skor pemain, tinggi Skor dan kehidupan.

Anda mungkin telah memperhatikan ada dua jenis musuh dalam video, tetapi hanya ada satu kelas Enemy. Kita dapat menurunkan subclass dari Enemy untuk setiap tipe musuh. Namun, saya lebih memilih untuk menghindari hierarki kelas dalam karena mereka memiliki beberapa kelemahan:

  • Mereka menambahkan lebih banyak kode boiler.
  • Mereka dapat meningkatkan kompleksitas kode dan membuatnya lebih sulit untuk dipahami. Keadaan dan fungsionalitas suatu objek menjadi tersebar di seluruh rantai warisannya.
  • Mereka tidak sangat fleksibel. Anda tidak bisa berbagi potongan fungsi antara cabang-cabang yang berbeda dari pohon warisan jika fungsi itu tidak di kelas dasar. Misalnya, pertimbangkan membuat dua kelas, Mamalia dan Burung, yang keduanya berasal dari Hewan. Kelas Burung memiliki metode Fly(). Kemudian Anda memutuskan untuk menambahkan kelas Kelelawar yang berasal dari Mamalia dan juga bisa terbang. Untuk berbagi fungsi ini hanya dengan menggunakan warisan, Anda harus memindahkan metode Fly() ke kelas Hewan yang bukan miliknya. Selain itu, Anda tidak dapat menghapus metode dari kelas turunan, jadi jika Anda membuat kelas Pinguin yang berasal dari Burung, itu juga akan memiliki metode Fly().

Untuk tutorial ini, kita akan mendukung komposisi lebih dari warisan untuk mengimplementasikan berbagai jenis musuh. Kita akan melakukan ini dengan menciptakan berbagai perilaku yang dapat digunakan kembali yang dapat kita tambahkan ke musuh. Kita kemudian dapat dengan mudah mencampur dan mencocokkan perilaku ketika kita membuat jenis musuh baru. Sebagai contoh, jika kita sudah memiliki perilaku FollowPlayer dan perilaku DodgeBullet, kita bisa membuat musuh baru yang melakukan keduanya hanya dengan menambahkan kedua perilaku.


Musuh

Musuh akan memiliki beberapa properti tambahan atas entitas. Untuk memberikan pemain beberapa waktu untuk bereaksi, kita akan membuat musuh secara bertahap memudar sebelum mereka menjadi aktif dan berbahaya.

Mari kode struktur dasar dari kelas Musuh.

Kode ini akan membuat musuh memudar untuk 60 frame dan akan memungkinkan kecepatan mereka berfungsi. Mengalikan kecepatan sebesar 0.8 memunculkan efek gesekan. Jika kita membuat musuh berakselerasi dengan laju yang konstan, gesekan ini akan menyebabkan mereka dengan lancar mendekati kecepatan maksimum. Saya suka kesederhanaan dan kehalusan dari jenis gesekan ini, tetapi Anda mungkin ingin menggunakan rumus yang berbeda tergantung pada efek yang Anda inginkan.

Metode WasShot() akan dipanggil ketika musuh ditembak. Kita akan menambahkan lebih banyak nanti di seri.

Kita ingin jenis musuh untuk berperilaku berbeda. Kita akan mencapai hal ini dengan menetapkan perilaku. Perilaku akan menggunakan beberapa fungsi kustom yang berjalan setiap frame untuk mengontrol musuh. Kita akan menerapkan perilaku yang menggunakan sebuah iterator.

Iterators (juga disebut generator) dalam C# adalah metode khusus yang dapat berhenti di tengah jalan dan kemudian melanjutkan di mana mereka tinggalkan. Anda dapat membuat iterator dengan membuat metode dengan jenis pengembalian IEnumerable<> dan menggunakan kata kunci yield di mana Anda ingin mengembalikannya dan kemudian melanjutkan. Iterator di C# mengharuskan Anda untuk mengembalikan sesuatu ketika Anda menghasilkan. Kita tidak benar-benar perlu mengembalikan apa pun, sehingga iterator kita hanya akan menghasilkan nol.

Perilaku paling sederhana kita adalah perilaku FollowPlayer() yang ditunjukkan di bawah ini.

Ini hanya membuat musuh berakselerasi ke arah pemain dengan laju yang konstan. Gesekan yang kita tambahkan sebelumnya akan memastikannya akhirnya mencapai kecepatan maksimum (5 piksel per frame saat akselerasi 1 sejak \(0,8 \ 5 + 1 = 5 \)). Setiap frame, metode ini akan berjalan sampai menyentuh pernyataan hasil dan kemudian akan melanjutkan di mana ia meninggalkan frame berikutnya.

Anda mungkin bertanya-tanya mengapa kita terganggu dengan iterator sama sekali, karena kita bisa menyelesaikan tugas yang sama lebih mudah dengan delegasi yang sederhana. Menggunakan iterator terbayar dengan metode yang lebih kompleks yang akan mengharuskan kita untuk menyimpan status dalam variabel anggota di kelas.

Misalnya, di bawah ini adalah perilaku yang membuat musuh bergerak dalam pola persegi:

Apa yang baik tentang ini adalah bahwa itu tidak hanya menyelamatkan kita beberapa variabel instan, tetapi juga menyusun kode dengan cara yang sangat logis. Anda dapat langsung melihat bahwa musuh akan bergerak ke kanan, lalu ke bawah, lalu ke kiri, lalu ke atas, dan kemudian ulangi. Jika Anda menerapkan metode ini sebagai state machine, aliran kontrol akan kurang jelas.

Mari kita tambahkan perancah yang diperlukan untuk membuat perilaku bekerja. Musuh perlu menyimpan perilaku mereka, jadi kita akan menambahkan variabel ke kelas Musuh.

Perhatikan bahwa perilaku memiliki tipe IEnumerator<int>, bukan IEnumerable<int>. Anda dapat menganggap IEnumerable sebagai template untuk perilaku dan IEnumerator sebagai contoh yang sedang berjalan. IEnumerator ingat di mana kita berada dalam perilaku dan akan mengambil di mana ia tinggalkan ketika Anda memanggil metode MoveNext(). Setiap frame kita akan melalui semua perilaku musuh dan memanggil MoveNext() pada masing-masing. Jika MoveNext() mengembalikan false, itu berarti perilaku telah selesai sehingga kita harus menghapusnya dari daftar.

Kita akan menambahkan metode berikut ke kelas Musuh:

Dan kita akan memodifikasi metode Update() untuk memanggil ApplyBehaviours():

Sekarang kita bisa membuat metode statis untuk menciptakan musuh yang mencari. Yang harus kita lakukan adalah memilih gambar yang kita inginkan dan menambahkan perilaku FollowPlayer().

Untuk membuat musuh yang bergerak secara acak, kita akan memilih arah dan kemudian melakukan sedikit penyesuaian acak ke arah itu. Namun, jika kita menyesuaikan arah setiap frame, gerakan akan gelisah, jadi kami hanya akan menyesuaikan arah secara berkala. Jika musuh berlari ke tepi layar, kita akan memilih arah acak baru yang menjauh dari dinding.

Kita sekarang dapat membuat metode pabrik untuk menciptakan musuh yang berkeliaran, seperti yang kita lakukan untuk para pencari:


Deteksi Tabrakan

Untuk deteksi tabrakan, kita akan memodelkan kapal pemain, musuh, dan peluru sebagai lingkaran. Deteksi tabrakan sirkular bagus karena sederhana, cepat, dan tidak berubah ketika objek berputar. Jika Anda ingat, kelas Entitas memiliki radius dan posisi (posisi mengacu pada pusat entitas). Ini yang kita butuhkan untuk mendeteksi tabrakan melingkar.

Menguji setiap entitas terhadap semua entitas lain yang berpotensi bertabrakan bisa sangat lambat jika Anda memiliki sejumlah besar entitas. Ada banyak teknik yang dapat Anda gunakan untuk mempercepat deteksi tabrakan fase luas, seperti quadtrees, sweep dan prune, dan pohon BSP. Namun, untuk saat ini, kita hanya akan memiliki beberapa lusin entitas di layar pada satu waktu, jadi kita tidak akan khawatir tentang teknik yang lebih rumit ini. Kita selalu dapat menambahkannya nanti jika kita membutuhkannya.

Dalam Shape Blaster, tidak setiap entitas dapat bertabrakan dengan setiap jenis entitas lainnya. Peluru dan kapal pemain bisa bertabrakan hanya dengan musuh. Musuh juga dapat bertabrakan dengan musuh lainnya - ini akan mencegah mereka dari tumpang tindih.

Untuk menangani berbagai jenis tabrakan ini, kita akan menambahkan dua daftar baru ke EntityManager untuk melacak peluru dan musuh. Setiap kali kita menambahkan entitas ke EntityManager, kita ingin menambahkannya ke daftar yang sesuai, jadi kita akan membuat metode AddEntity() pribadi untuk melakukannya. Kita juga akan menghapus entitas yang telah kedaluwarsa dari semua daftar setiap frame.

Ganti panggilan ke entity.Add() di EntityManager.Add() dan EntityManager.Update() dengan panggilan ke AddEntity().

Sekarang mari tambahkan metode yang akan menentukan apakah dua entitas bertabrakan:

Untuk menentukan apakah dua lingkaran tumpang tindih, cukup periksa apakah jarak antara mereka kurang dari jumlah jari-jari mereka. Metode kita mengoptimalkan ini sedikit dengan memeriksa apakah kuadrat jarak kurang dari kuadrat dari jumlah jari-jari. Ingat bahwa itu sedikit lebih cepat untuk menghitung jarak kuadrat dari jarak sebenarnya.

Hal yang berbeda akan terjadi tergantung pada dua benda yang bertabrakan. Jika dua musuh bertabrakan, kita ingin mereka saling mendorong. Jika peluru menghantam musuh, peluru dan musuh harus dihancurkan. Jika pemain menyentuh musuh, pemain harus mati dan levelnya harus disetel ulang.

Kita akan menambahkan metode HandleCollision() ke kelas Musuh untuk menangani tabrakan antar musuh:

Metode ini akan mendorong musuh saat ini menjauh dari musuh lainnya. Semakin dekat mereka, semakin sulit untuk didorong, karena besarnya (d / d.LengthSquared()) hanya satu dari kejauhan.

Merespons Pemain

Selanjutnya kita membutuhkan metode untuk menangani kapal pemain yang terbunuh. Ketika ini terjadi, kapal pemain akan menghilang untuk waktu yang singkat sebelum respawning.

Kita mulai dengan menambahkan dua anggota baru ke PlayerShip.

Pada awal PlayerShip.Update(), tambahkan yang berikut:

Dan kita menimpa Draw() seperti yang ditunjukkan:

Akhirnya, kita menambahkan metode Kill() ke PlayerShip.

Sekarang bahwa semua potongan berada di tempat, kita akan menambahkan sebuah metode untuk EntityManager yang melewati semua entitas dan cek untuk tabrakan.

Panggil metode ini dari Update() segera setelah pengaturan IsUpdating ke true.


Musuh Spawner

Hal terakhir yang harus dilakukan adalah membuat kelas EnemySpawner, yang bertanggung jawab untuk menciptakan musuh. Kita ingin permainan dimulai dengan mudah dan menjadi lebih sulit, sehingga EnemySpawner akan menciptakan musuh dengan laju yang semakin meningkat seiring berjalannya waktu. Ketika pemain itu mati, kita akan mereset EnemySpawner ke kesulitan awalnya.

Setiap frame, ada satu di inverseSpawnChance menghasilkan masing-masing jenis musuh. Peluang pemijahan musuh secara bertahap meningkat sampai mencapai maksimum satu dari dua puluh. Musuh selalu dibuat setidaknya 250 piksel dari pemain.

Hati-hati tentang loop sementara di GetSpawnPosition(). Ini akan bekerja secara efisien selama area di mana musuh dapat bertelur lebih besar dari area di mana mereka tidak dapat bertelur. Namun, jika Anda membuat area terlarang terlalu besar, Anda akan mendapatkan lingkaran tak terbatas.

Hubungi EnemySpawner.Update() dari GameRoot.Update() dan panggil EnemySpawner.Reset() saat pemain terbunuh.


Skor dan Nyawa

Dalam Shape Blaster, Anda akan memulai dengan empat kehidupan, dan akan mendapatkan kehidupan tambahan setiap 2.000 poin. Anda menerima poin untuk menghancurkan musuh, dengan berbagai jenis musuh yang bernilai poin dalam jumlah yang berbeda. Setiap musuh yang hancur juga meningkatkan pengganda nilai Anda dengan satu. Jika Anda tidak membunuh musuh dalam waktu singkat, pengali Anda akan disetel ulang. Jumlah total poin yang diterima dari setiap musuh yang Anda hancurkan adalah jumlah poin musuh bernilai dikalikan dengan pengganda Anda saat ini. Jika Anda kehilangan seluruh hidup Anda, permainan berakhir dan Anda memulai permainan baru dengan skor Anda disetel ke nol.

Untuk menangani semua ini, kita akan membuat kelas statis yang disebut PlayerStatus.

Panggil PlayerStatus.Update() dari GameRoot.Update() saat game tidak dijeda.

Selanjutnya kita ingin menampilkan skor Anda, kehidupan, dan pengganda di layar. Untuk melakukan ini, kita perlu menambahkan SpriteFont di proyek Konten dan variabel yang sesuai di kelas Seni, yang akan kita beri nama Font. Muat font di Art.Load() seperti yang kita lakukan dengan tekstur.

Catatan: Ada font bernama Nova Square yang disertakan dengan file sumber Shape Blaster yang mungkin Anda gunakan. Untuk menggunakan font, Anda harus menginstalnya terlebih dahulu dan kemudian restart Visual Studio jika terbuka. Anda kemudian dapat mengubah nama font di file font sprite menjadi "Nova Square". Proyek demo tidak menggunakan font ini secara default karena akan mencegah proyek dari kompilasi jika font tidak diinstal.

Ubah akhir GameRoot.Draw() di mana kursor digambar seperti yang ditunjukkan di bawah ini.

DrawRightAlignedString() adalah metode pembantu untuk menggambar teks sejajar di sisi kanan layar. Tambahkan ke GameRoot dengan menambahkan kode di bawah ini.

Sekarang hidup Anda, skor, dan pengganda akan ditampilkan di layar. Namun, kita masih perlu memodifikasi nilai-nilai ini sebagai tanggapan terhadap acara game. Tambahkan properti yang disebut PointValue ke kelas Musuh.

Atur nilai poin untuk musuh yang berbeda untuk sesuatu yang Anda rasa sesuai. Saya membuat musuh yang mengembara bernilai satu poin, dan musuh yang mencari bernilai dua poin.

Selanjutnya, tambahkan dua baris berikut ke Enemy.WasShot() untuk meningkatkan skor dan pengganda pemain:

Panggil PlayerStatus.RemoveLife() di PlayerShip.Kill(). Jika pemain kehilangan seluruh hidup mereka, panggil PlayerStatus.Reset() untuk mengatur ulang skor mereka dan tinggal di awal permainan baru.

Skor tinggi

Mari tambahkan kemampuan game untuk melacak skor terbaik Anda. Kita ingin skor ini bertahan sepanjang permainan jadi kita akan menyimpannya ke file. Kita akan membuatnya sangat sederhana dan menyimpan skor tinggi sebagai nomor teks-tunggal dalam file di direktori kerja saat ini (ini akan menjadi direktori yang sama yang berisi file .exe permainan).

Tambahkan metode berikut ke PlayerStatus:

Metode LoadHighScore() pertama-tama memeriksa apakah file skor tinggi ada, dan kemudian memeriksa apakah file tersebut berisi bilangan bulat yang valid. Pemeriksaan kedua kemungkinan besar tidak akan pernah gagal kecuali pengguna secara manual mengedit file skor tinggi ke sesuatu yang tidak valid, tetapi ada baiknya untuk berhati-hati.

Kita ingin memuat skor tinggi saat pertandingan dimulai, dan menyimpannya ketika pemain mendapat skor tinggi baru. Kita akan memodifikasi konstruktor statis dan Reset() metode di PlayerStatus untuk melakukannya. Kami juga akan menambahkan properti pembantu, IsGameOver yang akan kami gunakan sebentar lagi.

Itu membutuhkan pelacakan skor tinggi. Sekarang kita perlu menampilkannya. Tambahkan kode berikut ke GameRoot.Draw() di blok SpriteBatch yang sama di mana teks lain diambil:

Ini akan membuatnya menampilkan skor Anda dan skor tinggi di gim, berpusat di layar.

Sebagai penyesuaian akhir, kita akan meningkatkan waktu sebelum kapal bereaksi pada game untuk memberi waktu pemain untuk melihat skor mereka. Ubah PlayerShip.Kill() dengan mengatur waktu respawn menjadi 300 frame (lima detik) jika pemain sudah tidak aktif.

Game ini sekarang siap dimainkan. Mungkin tidak terlihat banyak, tetapi memiliki semua mekanisme dasar yang diterapkan. Di tutorial masa depan kita akan menambahkan filter mekar dan efek partikel untuk membumbui. Tapi sekarang, mari kita cepat menambahkan beberapa suara dan musik untuk membuatnya lebih menarik.


Suara dan Musik

Memutar suara dan musik sangatlah mudah di XNA. Pertama, kami menambahkan efek suara dan musik ke saluran konten. Di panel Properties, pastikan prosesor konten diatur ke Song untuk musik dan Efek Suara untuk bunyi.

Selanjutnya, kita membuat kelas pembantu statis untuk bunyi.

Karena kita memiliki beberapa variasi dari setiap suara, properti Explosion, Shot, dan Spawn akan memilih suara secara acak di antara varian.

Call Sound.Load() di GameRoot.LoadContent(). Untuk memutar musik, tambahkan dua baris berikut di akhir GameRoot.Initialize().

Untuk memutar suara di XNA, Anda cukup memanggil metode Play() pada SoundEffect. Metode ini juga memberikan beban berlebih yang memungkinkan Anda mengatur volume, pitch, dan pan suara. Trik untuk membuat suara kita lebih bervariasi adalah menyesuaikan jumlah ini pada setiap permainan.

Untuk memicu efek suara untuk pemotretan, tambahkan baris berikut di PlayerShip.Update(), di dalam pernyataan if di mana peluru dibuat. Perhatikan bahwa kita menggeser pitch ke atas atau ke bawah secara acak, hingga seperlima oktaf, untuk membuat suara kurang berulang.

Demikian juga, memicu efek suara ledakan setiap kali musuh dihancurkan dengan menambahkan yang berikut ke Enemy.WasShot ().

Anda sekarang memiliki suara dan musik dalam game Anda. Mudah, bukan?


Kesimpulan

Itu membungkus mekanika permainan dasar. Dalam tutorial berikutnya, kita akan menambahkan filter mekar untuk membuat lampu neon besinar.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Scroll to top
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.