MENAMBAHKAN MUSUH DAN MENGEMBANGKAN GRAFIS GAME 3D
4.7 STRUKTUR KODE AI
Gambar 4.25 Daftar Skrip WanderingAI dengan status "hidup" ditambahkan
Skrip ReactiveTarget sekarang dapat memberi tahu skrip WanderingAI saat musuh hidup atau tidak (lihat daftar berikut).
Gambar 4.26 Daftar ReactiveTarget memberi tahu WanderingAI saat mati
Spawning musuh prefab
Saat ini hanya ada satu musuh di scene, dan ketika mati, tempat itu kosong. Mari kita buat game menelurkan musuh sehingga setiap kali musuh mati, musuh baru muncul. Ini mudah dilakukan di Unity menggunakan konsep yang disebut prefab. Seperti yang dijelaskan si bab sebelumnya, Prefab adalah pendekatan yang fleksibel untuk mendefinisikan objek interaktif secara visual. Singkatnya, prefab adalah objek game yang sepenuhnya disempurnakan (dengan komponen yang sudah terpasang dan diatur) yang tidak ada dalam scene tertentu melainkan ada sebagai aset yang dapat disalin ke scene mana pun. Penyalinan ini dapat dilakukan secara manual, untuk memastikan bahwa objek musuh (atau prefab lainnya) sama di setiap scene. Lebih penting lagi, Prefab juga dapat dihasilkan dari kode; Anda dapat menempatkan salinan objek ke dalam scene menggunakan perintah dalam skrip dan tidak hanya dengan melakukannya secara manual di editor visual.
Definisi Aset adalah file apa pun yang muncul di tampilan Proyek; ini bisa berupa gambar 2D, model 3D, file kode, scene, dan sebagainya. Saya menyebutkan istilah aset secara singkat di bab 1, tetapi saya tidak menekankannya sampai sekarang. Istilah untuk salah satu salinan prefab ini adalah instance, analog dengan bagaimana kata instance mengacu pada objek kode tertentu yang dibuat dari suatu kelas. Cobalah untuk menjaga terminologi tetap lurus; prefab mengacu pada objek game yang ada di luar scene apa pun, sedangkan instance mengacu pada salinan objek yang ditempatkan di sebuah scene.
Membuat Prefab musuh
Untuk membuat prefab, pertama buat objek di scene yang akan menjadi prefab.
Karena objek musuh kita akan menjadi prefab, kita sudah melakukan langkah pertama ini.
Sekarang yang kita lakukan hanyalah menyeret objek ke bawah dari tampilan Hierarchy dan meletakkannya di tampilan Project; ini akan secara otomatis menyimpan objek sebagai Prefab.
Kembali ke tampilan Hierarki, nama objek asli akan berubah menjadi biru untuk menandakan bahwa itu sekarang ditautkan ke prefab. Jika Anda ingin mengedit prefab lebih lanjut (seperti dengan menambahkan komponen baru), Anda akan membuat perubahan tersebut pada objek dalam scene dan kemudian pilih GameObject > Apply Changes To Prefab. Tapi kita tidak ingin objek di scene lagi (kita akan spawn prefab, tidak menggunakan instance yang sudah ada di scene), jadi hapus objek musuh sekarang.
Gambar 4.27 Drag objek dari Hierarki ke Proyek untuk membuat Prefab.
Peringatan Antarmuka untuk bekerja dengan Prefab agak canggung, dan hubungan antara Prefab dan contoh mereka dalam scene bisa rapuh. Misalnya, Anda sering kali harus menyeret prefab ke dalam scene untuk mengeditnya, lalu menghapus objek setelah Anda selesai mengedit. Dalam bab pertama saya menyebutkan ini sebagai kelemahan Unity, dan saya berharap alur kerja dengan Prefab meningkat di versi Unity yang akan datang. Sekarang kita memiliki objek prefab yang sebenarnya untuk dimunculkan di scene, jadi mari kita menulis kode untuk membuat instance dari prefab.
Membuat instance dari SceneController yang tidak terlihat
Meskipun Prefab itu sendiri tidak ada di scene, harus ada beberapa objek di scene untuk dilampirkan kode spawning musuh. Apa yang akan kita lakukan adalah membuat objek game kosong; kita dapat melampirkan skrip untuk itu, tetapi objek tidak akan terlihat di scene.
Tip, Penggunaan GameObjects kosong untuk melampirkan komponen skrip adalah pola umum dalam developeran Unity. Trik ini digunakan untuk tugas abstrak yang tidak berlaku untuk objek tertentu di scene. Skrip kesatuan dimaksudkan untuk dilampirkan ke objek yang terlihat, tetapi tidak setiap tugas masuk akal seperti itu.
Pilih GameObject > Create Empty, ganti nama objek baru menjadi Controller, lalu atur posisinya menjadi 0, 0, 0 (secara teknis posisinya tidak masalah karena objek tidak terlihat, tetapi meletakkannya di asal akan membuat hidup lebih sederhana jika Anda pernah menjadi orang tua untuk itu). Buat skrip bernama SceneController, seperti yang ditunjukkan pada daftar berikut.
Gambar 4.28 SceneController yang memunculkan Prefab musuh
Lampirkan skrip ini ke objek controller, dan di Inspector Anda akan melihat slot variabel untuk Prefab musuh. Ini bekerja mirip dengan variabel publik, tetapi ada perbedaan penting (lihat peringatan berikut). Peringatan Saya merekomendasikan variabel personal dengan SerializeField ke objek referensi di editor Unity karena Anda ingin mengekspos variabel itu di Inspector tetapi tidak ingin nilainya diubah oleh skrip lain. Seperti yang dijelaskan di bab 2, variabel publik muncul di Inspector secara default (dengan kata lain, variabel tersebut diserialkan oleh Unity), jadi sebagian besar tutorial dan kode contoh yang Anda lihat menggunakan variabel publik untuk semua nilai yang diserialisasi. Tetapi variabel- variabel ini juga dapat dimodifikasi oleh skrip lain (bagaimanapun juga, ini adalah variabel publik); dalam banyak kasus, Anda tidak ingin nilai diubah dalam kode tetapi hanya ditetapkan di Inspector.
Drag aset Prefab dari Proyek ke slot variabel kosong; ketika mouse mendekat, Anda akan melihat sorot slot untuk menunjukkan bahwa objek dapat dihubungkan di sana. Setelah Prefab musuh ditautkan ke skrip SceneController, mainkan scene untuk melihat kode beraksi.
Musuh akan muncul di tengah ruangan sama seperti sebelumnya, tetapi sekarang jika Anda
menembak musuh maka akan digantikan oleh musuh baru. Jauh lebih baik daripada hanya satu musuh yang hilang selamanya!
Gambar 4.29 Tarik prefab musuh dari Project ke slot Enemy Prefab di Inspector.
Tip Pendekatan menyeret objek ke slot variabel Inspector ini adalah teknik praktis yang muncul di banyak skrip berbeda. Di sini kami menautkan prefab ke skrip, tetapi Anda juga dapat menautkan ke objek dalam scene, atau bahkan komponen tertentu (karena kode perlu memanggil metode publik dalam komponen spesifik itu). Dalam bab-bab selanjutnya kita akan menggunakan teknik ini lagi.
Tip Pendekatan menyeret objek ke slot variabel Inspector ini adalah teknik praktis yang muncul di banyak skrip berbeda. Di sini kami menautkan prefab ke skrip, tetapi Anda juga dapat menautkan ke objek dalam scene, atau bahkan komponen tertentu (karena kode perlu memanggil metode publik dalam komponen spesifik itu). Dalam bab-bab selanjutnya kita akan menggunakan teknik ini lagi.
Inti dari skrip ini adalah metode Instantiate(), jadi perhatikan baris itu. Saat kami membuat instance prefab, itu membuat salinan di scene. Secara default, Instantiate() mengembalikan objek baru sebagai tipe Object generik, tetapi Object sangat tidak berguna secara langsung dan kita perlu menanganinya sebagai GameObject. Di C#, gunakan kata kunci as untuk typecasting untuk mengkonversi dari satu jenis objek kode ke jenis lain (ditulis dengan sintaks objek asli sebagai tipe baru).
Objek instantiated disimpan di _enemy, variabel personal dari tipe GameObject (dan sekali lagi, tetap luruskan perbedaan antara prefab dan instance dari prefab; musuhPrefab menyimpan prefab sedangkan _enemy menyimpan instance). Pernyataan if yang memeriksa objek yang disimpan memastikan bahwa Instantiate() dipanggil hanya ketika _enemy kosong (atau null, dalam coderspeak). Variabel mulai kosong, sehingga kode instantiating berjalan sekali sejak awal sesi. Objek yang dikembalikan oleh Instantiate() kemudian disimpan di _enemy sehingga kode instantiate tidak akan berjalan lagi. Karena musuh menghancurkan dirinya sendiri saat ditembak, itu mengosongkan variabel _enemy dan menyebabkan Instantiate() dijalankan lagi. Dengan cara ini, selalu ada musuh di scene.
Tip Pendekatan menyeret objek ke slot variabel Inspector ini adalah teknik praktis yang muncul di banyak skrip berbeda. Di sini kami menautkan prefab ke skrip, tetapi Anda juga dapat menautkan ke objek dalam scene, atau bahkan komponen tertentu (karena kode perlu memanggil metode publik dalam komponen spesifik itu). Dalam bab-bab selanjutnya kita akan menggunakan teknik ini lagi.
Inti dari skrip ini adalah metode Instantiate(), jadi perhatikan baris itu. Saat kami membuat instance prefab, itu membuat salinan di scene. Secara default, Instantiate() mengembalikan objek baru sebagai tipe Object generik, tetapi Object sangat tidak berguna secara langsung dan kita perlu menanganinya sebagai GameObject. Di C#, gunakan kata kunci as untuk typecasting untuk mengkonversi dari satu jenis objek kode ke jenis lain (ditulis dengan sintaks objek asli sebagai tipe baru).
Objek instantiated disimpan di _enemy, variabel personal dari tipe GameObject (dan sekali lagi, tetap luruskan perbedaan antara prefab dan instance dari prefab; musuhPrefab menyimpan prefab sedangkan _enemy menyimpan instance). Pernyataan if yang memeriksa objek yang disimpan memastikan bahwa Instantiate() dipanggil hanya ketika _enemy kosong (atau null, dalam coderspeak). Variabel mulai kosong, sehingga kode instantiating berjalan sekali sejak awal sesi. Objek yang dikembalikan oleh Instantiate() kemudian disimpan di _enemy sehingga kode instantiate tidak akan berjalan lagi. Karena musuh menghancurkan dirinya sendiri saat ditembak, itu mengosongkan variabel _enemy dan menyebabkan Instantiate() dijalankan lagi. Dengan cara ini, selalu ada musuh di scene.
Menghancurkan GameObjects dan manajemen memori
Agak tidak terduga bahwa referensi yang ada menjadi nol ketika sebuah objek menghancurkan dirinya sendiri. Dalam bahasa pemrograman yang dikelola memori seperti C#, biasanya Anda tidak dapat menghancurkan objek secara langsung; Anda hanya dapat melakukan dereferensi sehingga dapat dihancurkan secara otomatis. Ini masih berlaku di Unity, tetapi cara GameObjects ditangani di belakang layar membuatnya terlihat seperti dihancurkan secara langsung.
Untuk menampilkan objek dalam scene, Unity harus memiliki referensi ke semua objek dalam grafik scenenya. Jadi, bahkan jika Anda menghapus semua referensi ke GameObject dalam kode Anda, masih akan ada referensi grafik scene ini yang mencegah objek dihancurkan secara otomatis. Karena itu, Unity menyediakan metode Destroy() untuk memberi tahu game engine “Hapus objek ini dari grafik scene.” Sebagai bagian dari fungsionalitas di balik layar itu, Unity juga membebani operator == untuk mengembalikan nilai true saat memeriksa null.
Secara teknis objek itu masih ada di memori, tetapi mungkin juga tidak ada lagi, jadi Unity membuatnya tampak nol. Anda dapat mengonfirmasi ini dengan memanggil GetInstanceID() pada objek yang dihancurkan.
Namun, perhatikan bahwa developer Unity sedang mempertimbangkan untuk mengubah perilaku ini ke manajemen memori yang lebih standar. Jika ya, maka kode spawning juga perlu diubah, mungkin dengan menukar centang (_enemy==null) dengan parameter baru seperti (_enemy.isDestroyed). Lihat blog/halaman Facebook mereka:
https://www.facebook.com/unity3d/posts/10152271098591773
(Jika sebagian besar dari diskusi ini adalah bahasa Yunani bagi Anda, maka jangan khawatir;
ini adalah diskusi teknis tangensial untuk orang-orang yang tertarik dengan detail yang tidak jelas ini.)
Memotret melalui pembuatan objek
Baiklah, mari tambahkan sedikit fungsionalitas ke musuh. Sama seperti yang kita lakukan dengan player, pertama-tama kita buat mereka bergerak—sekarang mari kita buat mereka menembak! Seperti yang saya sebutkan kembali saat memperkenalkan raycasting, itu
hanyalah salah satu pendekatan untuk menerapkan pemotretan. Pendekatan lain melibatkan pembuatan prefab, jadi mari kita lakukan pendekatan itu untuk membuat musuh membalas.
Tujuan dari bagian ini adalah untuk melihat gambar 4.30 saat bermain.
Gambar 4.30 Musuh menembakkan "bola api" ke player
Membuat Prefab proyektil
Jika pemotretan sebelumnya tidak melibatkan proyektil yang sebenarnya di scene, pemotretan kali ini akan melibatkan proyektil di scene. Menembak dengan raycasting pada dasarnya instan, mencatatkan pukulan saat mouse diklik, tapi kali ini musuh a kan memancarkan bola api yang terbang di udara. Memang, mereka akan bergerak cukup cepat, tetapi itu tidak akan instan, memberi player kesempatan untuk menghindar. Alih-alih menggunakan raycasting untuk mendeteksi hit, kami akan menggunakan deteksi collision (sistem collision yang sama yang mencegah player yang bergerak melewati dinding).
Kode akan menelurkan bola api dengan cara yang sama seperti musuh bertelur:
dengan membuat Prefab. Seperti yang sudah dijelaskan di bagian sebelumnya, langkah pertama saat membuat prefab adalah membuat objek di scene yang akan menjadi prefab, jadi mari kita buat bola api. Untuk memulai, pilih GameObject > 3D Object > Sphere. Ganti nama objek baru Fireball. Sekarang buat skrip baru, juga disebut Fireball, dan lampirkan skrip itu ke objek ini. Akhirnya kami akan menulis kode dalam skrip ini, tetapi biarkan default untuk saat ini sementara kami mengerjakan beberapa bagian lain dari objek bola api. Agar tampak seperti bola api dan bukan hanya bola abu-abu, kita akan memberi objek warna oranye terang. Sifat permukaan seperti warna dikendalikan menggunakan bahan. Definisi, Bahan adalah paket informasi yang mendefinisikan sifat permukaan objek 3D apa pun yang dilampirkan material.
Sifat permukaan ini dapat mencakup warna, kilau, dan bahkan kekasaran yang halus.
Pilih Aset > Buat > Bahan. Beri nama materi baru seperti Flame. Pilih material di tampilan Project untuk melihat properti material di Inspector. Seperti yang ditunjukkan gambar 3.10, klik contoh warna berlabel Albedo (itu adalah istilah teknis yang mengacu pada warna utama permukaan). Mengklik yang akan memunculkan pemilih warna di jendelanya sendiri; geser kedua bilah berwarna pelangi di sisi kanan dan area pengambilan utama untuk mengatur warnanya menjadi oranye.
Gambar 4.31 Mengatur warna material
Kami juga akan mencerahkan material agar terlihat lebih seperti api. Sesuaikan nilai Emission (salah satu atribut lain di Inspector). Ini default ke 0, jadi ketik .3 untuk mencerahkan materi. Sekarang Anda dapat mengubah objek bola api menjadi prefab dengan menyeret objek ke bawah dari Hierarchy ke Project, seperti yang Anda lakukan dengan prefab musuh.
Bagus, kami memiliki Prefab baru untuk digunakan sebagai proyektil! Selanjutnya adalah menulis kode untuk menembak menggunakan proyektil itu.
Menembak proyektil dan bercollision dengan target
Mari kita membuat beberapa penyesuaian pada musuh untuk mengeluarkan bola api.
Karena kode untuk mengenali player akan memerlukan skrip baru (seperti ReactiveTarget yang diperlukan oleh kode untuk mengenali target), pertama buat skrip baru dan beri nama skrip itu PlayerCharacter. Lampirkan skrip ini ke objek pemutar di scene.
Sekarang buka WanderingAI dan tambahkan kode dari daftar berikut.
Gambar 4.32 Daftar Penambahan WanderingAI untuk memancarkan bola api
Anda akan melihat bahwa semua anotasi dalam daftar ini merujuk pada bit yang serupa (atau sama) di skrip sebelumnya. Daftar kode sebelumnya sudah menunjukkan semua yang diperlukan untuk memancarkan bola api; sekarang kami sedang menumbuk bersama- sama dan remixing bit kode agar sesuai dengan konteks baru. Sama seperti di SceneController, Anda perlu menambahkan dua bidang GameObject di bagian atas skrip: variabel serial untuk menautkan prefab ke, dan variabel personal untuk melacak instance yang disalin oleh kode.
Setelah melakukan raycast, kode memeriksa PlayerCharacter pada objek yang dipukul; ini berfungsi seperti ketika kode pemotretan memeriksa ReactiveTarget pada objek yang dipukul.
Kode yang membuat bola api ketika belum ada satu pun di scene berfungsi seperti kode yang
memberi contoh musuh. Namun, pemosisian dan rotasinya berbeda; kali ini, Anda menempatkan instance tepat di depan musuh dan mengarahkannya ke arah yang sama.
Setelah semua kode baru terpasang, slot Fireball Prefab baru akan muncul saat Anda melihat komponen di Inspector, seperti slot Enemy Prefab di komponen SceneController. Klik Prefab musuh di tampilan Proyek dan Inspector akan menunjukkan komponen objek itu, seolah-olah Anda telah memilih objek di scene. Meskipun peringatan sebelumnya tentang kecanggungan antarmuka sering berlaku saat mengedit Prefab, antarmuka memudahkan untuk menyesuaikan komponen pada objek, dan hanya itu yang kami lakukan. Seperti yang ditunjukkan pada gambar 3.11, drag Prefab bola api dari Project ke slot Fireball Prefab di Inspector (sekali lagi, seperti yang Anda lakukan dengan SceneController).
Gambar 4.33 Drag Prefab bola api dari Project ke slot Fireball Prefab di Inspector.
Sekarang musuh akan menembak player saat player berada tepat di depannya...yah, coba tembak; bola oranye terang muncul di depan musuh, tetapi hanya duduk di sana karena kami belum menulis scriptnya. Ayo lakukan sekarang. Daftar berikutnya menunjukkan kode untuk skrip Fireball.
Gambar 4.34 Daftar Skrip bola api yang bereaksi terhadap collision
Bit baru yang penting untuk kode ini adalah metode OnTriggerEnter(). Metode itu dipanggil secara otomatis ketika objek mengalami collision, seperti menabrak dinding atau dengan player. Saat ini kode ini tidak akan berfungsi sepenuhnya; jika Anda menjalankannya, bola api akan terbang ke depan berkat baris Translate(), tetapi pemicunya tidak akan berjalan, mengantrekan bola api baru dengan menghancurkan yang sekarang. Perlu ada beberapa penyesuaian lain yang dilakukan pada komponen pada objek bola api. Perubahan pertama adalah menjadikan collider sebagai pemicu. Untuk menyesuaikannya, klik kotak centang Is Trigger di komponen Sphere Collider.
Tip Komponen Collider yang ditetapkan sebagai pemicu masih akan bereaksi terhadap sentuhan/tumpang tindih objek lain, tetapi tidak akan lagi menghentikan objek lain untuk lewat secara fisik. Bola api juga membutuhkan Rigidbody, komponen yang digunakan oleh sistem fisika di Unity. Dengan memberi bola api komponen Rigidbody, Anda memastikan bahwa sistem fisika mampu mendaftarkan pemicu collision untuk objek itu. Di Inspector, klik Add Component dan pilih Physics > Rigidbody. Pada komponen yang ditambahkan, hapus pilihan Use Gravity (lihat gambar 4.35) agar bola api tidak tertarik ke bawah karena gravitasi.
Gambar 4.35 Matikan gravitasi di komponen Rigidbody.
Mainkan sekarang, dan bola api dihancurkan ketika mereka mengenai sesuatu. Karena kode pemancar bola api berjalan setiap kali tidak ada bola api di scene, musuh akan menembakkan lebih banyak bola api ke player. Sekarang hanya ada satu lagi yang tersisa untuk menembak player: membuat player bereaksi saat dipukul.
Damage player
Sebelumnya Anda membuat skrip PlayerCharacter tetapi membiarkannya kosong.
Sekarang Anda akan menulis kode untuk membuatnya bereaksi saat dipukul, seperti yang ditunjukkan daftar berikut.
Gambar 4.36 Daftar Player yang dapat menerima kerusakan
Daftar tersebut mendefinisikan bidang untuk kesehatan player dan mengurangi kesehatan sesuai perintah. Di bab selanjutnya kita akan membahas tampilan teks untuk menampilkan informasi di layar, tetapi untuk saat ini kita hanya dapat menampilkan informasi tentang kesehatan player menggunakan pesan debug. Sekarang kita perlu kembali ke skrip Fireball untuk memanggil metode Hurt() player. Ganti baris debug dalam skrip Fireball dengan player.Hurt(damage) untuk memberi tahu player bahwa mereka telah terkena. Dan itu adalah bagian terakhir dari kode yang kami butuhkan!
Kami sebagian besar berfokus pada bagaimana game berfungsi dan tidak terlalu fokus pada tampilan game. Itu bukan kebetulan—buku ini kebanyakan tentang pemrograman game di Unity. Namun, penting untuk memahami cara mengerjakan dan meningkatkan visual.
Sebelum kita kembali ke fokus utama buku ini pada pengkodean berbagai bagian permainan, mari kita habiskan satu bab untuk mempelajari seni permainan sehingga proyek Anda tidak akan selalu berakhir hanya dengan kotak kosong yang meluncur. Semua konten visual dalam game terdiri dari apa yang disebut aset seni. Tapi apa sebenarnya artinya itu?