Bagaimana saya bisa mendesain banyak tipe serangan berbeda yang bisa digabungkan?

17

Saya membuat game 2D top-down dan saya ingin memiliki banyak tipe serangan yang berbeda. Saya ingin membuat serangan sangat fleksibel dan bisa dikombinasikan seperti cara Binding of Isaac bekerja. Ini dia daftar semua koleksi dalam permainan . Untuk menemukan contoh yang baik, mari kita lihat item Spoon Bender .

Spoon Bender memberi Isaac kemampuan untuk menembakkan air mata di rumah.

Jika Anda melihat bagian "sinergi", Anda akan melihatnya dapat dikombinasikan dengan koleksi lain untuk efek yang menarik namun intuitif. Misalnya, jika digabungkan dengan Mata Bagian Dalam , itu "Akan memungkinkan Ishak untuk menembakkan beberapa tembakan homing sekaligus". Ini masuk akal, karena The Inner Eye

Memberikan Isaac tembakan tiga kali lipat

Apa arsitektur yang baik untuk merancang hal-hal seperti ini? Inilah solusi brute force:

if not spoon bender and not the inner eye then ...
if spoon bender and not the inner eye then ...
if not spoon bender and the inner eye then ...
if spoon bender and the inner eye then ...

Tapi itu akan lepas kendali dengan sangat cepat. Apa cara yang lebih baik untuk merancang sistem seperti ini?

Daniel Kaplan
sumber
Secara pribadi saya hanya akan menyimpan semua item yang dilengkapi dalam daftar dan meminta mereka mengimplementasikan semacam antarmuka umum yang mengambil objek yang Anda mutasi dari objek ke objek. "untuk setiap item memodifikasi serangan yang direncanakan" sehingga satu item dapat menduplikasi jumlah proyektil, satu dapat menambahkan itu warna dan mengubah kerusakan (jadi jika satu item membuat baut merah dan yang lain kuning Anda akan memiliki serangan oranye setelah keduanya memodifikasi serangan) . Anda juga bisa hanya memiliki satu kelas item umum yang memiliki parameter untuk memutuskan bagaimana ia memodifikasi serangan yang direncanakan.
Benjamin Danger Johnson
2
Saya ingin menunjukkan bahwa Kirby 64 mengambil pendekatan brute-force dan efek berbeda yang diprogram untuk semua kemungkinan kombinasi kemampuan, jadi itu bisa dilakukan.
Kevin

Jawaban:

16

Anda sama sekali tidak perlu menggunakan kombinasi kode tangan. Sebagai gantinya, Anda dapat fokus pada properti yang diberikan setiap item kepada Anda. Misalnya, Item A set Projectile=Fireball,Targetting=Homing. Item B set FireMode=ArcShot,Count=3. The ArcShotlogika bertanggung jawab untuk mengirimkan Countjumlah Projectileitem dalam busur.

Dua item ini dapat dikombinasikan dengan item lain yang memodifikasi properti (atau lainnya) ini secara bebas. Jika Anda menambahkan proyektil jenis baru, ia hanya akan bekerja secara otomatis ArcShot, dan jika Anda menambahkan mode pembakaran baru, ia akan secara otomatis bekerja dengan Fireballproyektil. Demikian juga, Targettingadalah properti yang mengatur pengontrol untuk proyektil sementaraFireMode membuat proyektil, sehingga mereka dapat dengan mudah dan sepele dikombinasikan dalam kombinasi apa pun yang masuk akal.

Anda juga dapat mengatur dependensi properti dan semacamnya. Misalnya, ArcShotmengharuskan Anda memiliki penyedia Projectile(yang mungkin saja merupakan default). Anda dapat menetapkan prioritas sehingga jika Anda memiliki dua item aktif yang keduanya memberikan Projectilekode tahu mana yang akan digunakan. Atau Anda dapat menyediakan UI untuk memungkinkan pengguna memilih jenis proyektil mana yang akan digunakan, atau hanya meminta pemain untuk melepaskan item prioritas tinggi yang tidak ia inginkan, atau menggunakan item terbaru, dll. Anda selanjutnya dapat memungkinkan sistem yang tidak kompatibel. , mis. sedemikian rupa sehingga dua item yang baru saja dimodifikasiProjectile tidak dapat keduanya dilengkapi secara bersamaan.

Secara umum, jika mungkin, lebih suka segala jenis pendekatan yang didorong data (atau deklaratif ) daripada pendekatan prosedural (masalah besar jika ada) ketika datang ke objek dan semacamnya dalam game Anda. Logika generik tingkat tinggi yang dapat dikonfigurasi oleh data sederhana jauh lebih disukai daripada daftar hard-coded aturan khusus.

Sean Middleditch
sumber
Nit kecil, tetapi Anda tampaknya tidak memiliki properti yang benar untuk contoh yang Anda gunakan. "Spoon Bender" menambahkan homing dan "Inner Eye" menambahkan triple shot. Tidak menambahkan busur dan keduanya air mata. Jika Anda menggunakan properti sewenang-wenang dalam contoh Anda untuk abstrak desain, akan lebih mudah dibaca jika mereka tidak disebutkan dalam cara yang menyesatkan. Saya lebih suka "Item A" dan "Item B" untuk ini.
Daniel Kaplan
1
@tieTYT: Saya menggeneralisasi nama-nama dan memperluas contoh untuk memasukkan mode penargetan di samping jenis proyektil dan mode pembakaran. Tidak pernah bermain BoI selama lebih dari beberapa menit jadi saya tidak memiliki nama yang diinternalisasi seperti yang lain mungkin. :)
Sean Middleditch
Menurut saya bagian yang sulit adalah mengidentifikasi kategori properti. misalnya: Penargetan adalah satu, FireMode adalah yang lain, Count mungkin yang ke-3 ... atau mungkin tidak. Mungkin 3 proyektil bisa menjadi bola api dan 5 bisa menjadi granat walaupun itu satu senjata.
Daniel Kaplan
@tTT: benar-benar. Anda dapat memperluas sistem lebih jauh untuk memungkinkan kombinasi atau logika khusus, tentu saja, tetapi itu harus menjadi pengecualian daripada aturan. Optimalkan untuk kasus umum, bukan kasus sudut.
Sean Middleditch
Berikut ini adalah video yang bagus tentang mengapa Anda salah tentang pemrograman prosedural youtu.be/QM1iUe6IofM , juga cara data driven Anda menggambarkan peta jika / selain itu memblokir ke set data individual, pada dasarnya tidak melakukan apa pun untuk mengurangi berapa banyak skenario kasus khusus yang harus Anda buat program, karena Anda harus memprogram mereka semua ...
RenaissanceProgrammer
8

Jika Anda menggunakan bahasa OOP, ini terdengar seperti tempat yang baik untuk menggunakan Pola Penghias . Saat Anda ingin memodifikasi bagaimana serangan terjadi, hiasi saja dengan augmentasi yang sesuai.

Crude c ++ Contoh:

class AttackBehaviour
{
    /* other code */
    virtual void Attack(double angle);
};

class TearAttack: public AttackBehaviour
{
    /* other code */
    void Attack(double angle);
};

class TripleAttack: public AttackBehaviour
{
    /* other code */
    AttackBehaviour* baseAttackBehaviour;
    void Attack(double angle);
};

void TripleAttack::Attack(angle)
{
    baseAttackBehaviour->Attack(angle-30);
    baseAttackBehaviour->Attack(angle);
    baseAttackBehaviour->Attack(angle+30);
}

Metode ini akan lebih baik jika Anda memiliki jumlah serangan yang sangat besar dan Anda harus memiliki mereka semua berperilaku kurang lebih dengan cara yang sama. Jika Anda ingin secara substansial mengubah cara serangan terjadi dengan pengubah (mis. Animasi baru dengan pengubah) maka metode ini bukan untuk Anda.

Mil laut
sumber
Jika saya ingin mengubah animasi, mengapa ini bukan untuk saya? Juga, bagaimana jika saya bekerja dalam bahasa yang lebih fungsional? Apakah Anda tahu pola desain yang cocok untuk mereka?
Daniel Kaplan
Ini lebih kaku karena pengubah akan selalu memanggil Attackmetode objek yang dikumpulkannya. The TripleAttackkelas tidak harus tahu tentang TearAttackkelas. Jika ini benar maka itu akan menyebabkan sakit kepala sebanyak else-ifblok. Ini berarti bahwa setiap animasi air mata harus berada di dalam TearAttackBehaviourobjek. Objek ini tidak (dan seharusnya tidak) tahu itu telah didekorasi oleh TripleAttackobjek. Hasilnya adalah bahwa 3 animasi air mata melanjutkan independen, karena yang independen.
NauticalMile
Saya mengalami kesulitan menjelaskan hal ini dengan kata-kata, jika orang lain ingin menusuknya, jadilah tamu saya.
NauticalMile
Mengenai penerapan ini dalam bahasa yang lebih fungsional, saya akan memikirkannya sebentar dan mengubah jawaban saya ketika saya siap.
NauticalMile
1

Sebagai penggemar Binding of Isaac, saya juga bertanya-tanya bagaimana caranya seperti ini. Sistem dalam permainan cukup kuat di mana perilaku yang muncul muncul dari kombinasi efek (salah satu yang muncul di benak saya adalah mendapatkan cermin, penyok sendok, dan beberapa penguat jangkauan menghasilkan dinding air mata berputar-putar di sekitar Ishak, gaya Magneto ). Jumlah mereka yang banyak akan membuat blok "jika" tidak praktis.

Kesimpulan saya adalah bahwa Ishak dan air matanya adalah dua entitas di tengah Kerangka Kerja Entitas-Entitas besar . Entitas memiliki beberapa statistik dasar (movepeed, life, range, damage, sprite, dll) dan setiap komponen akan membawa pengubah stat dan kata kerja.

Dalam kode, Isaac dan air matanya masing-masing akan memiliki daftar yang akan berisi hal-hal antarmuka. Isaac akan memiliki daftar hal-hal yang berlangganan antarmuka IsaacMutator, dan air matanya tearMutator. IsaacMutator akan memiliki fungsi untuk memodifikasi kesehatan, kecepatan, jangkauan, penampilan, dan beberapa kata kerja khusus Isaacs. TearMutator akan serupa. Sekali per game loop, Isaac akan mengulangi semua IsaacMutators yang dimilikinya, dan semua air mata hidup juga. Untuk menggunakan contoh bahasa Inggris Anda, itu akan berbunyi seperti:

Isaac has IsaacMutators:
--spoonbender which gives no stat change and: Tears are made homing
--MeatEater which give +1 health, +1 damage and: nothing
--MagicMirror which gives no stat change and: Tears are made reflecting

Tears have tearMutators:
--(depends on MeatEater) +1 damage and: nothing
--(depends on MagicMirror) no stat change and: +1 vector towards isaac
--(depnds on spoonbender) no stat change and: +1 vector towards enemytype

dan seterusnya. Karena jenisnya adalah aditif, Anda dapat menumpuk dan menambah dan menghapus isi hati Anda.

Kirbinator
sumber
-5

Saya pikir cara Anda bekerja paling baik. Barang-barang semacam ini masing-masing memberikan kondisi, jika digunakan bersama-sama menghasilkan kondisi yang berbeda, maka Anda akan secara efektif membutuhkan ketiga kondisi yang mungkin ditentukan.

Anda juga bisa melakukannya dengan membuat jenis definisi baru ketika kedua item hadir, tetapi ini sebenarnya menambah konvolusi:

if spoon bender and the inner eye then new spoon bender inner eye

if spoon bender inner eye then ...
Program Renaissance
sumber