Bagaimana cara memutuskan GameObject mana yang harus menangani tabrakan?


28

Dalam tabrakan apa pun, ada dua GameObjects yang terlibat bukan? Yang ingin saya ketahui adalah, Bagaimana saya memutuskan objek mana yang harus berisi objek saya OnCollision*?

Sebagai contoh, misalkan saya memiliki objek Player dan objek Spike. Pikiran pertama saya adalah meletakkan skrip pada pemain yang berisi beberapa kode seperti ini:

OnCollisionEnter(Collision coll) {
    if (coll.gameObject.compareTag("Spike")) {
        Destroy(gameObject);
    }
}

Tentu saja, fungsionalitas yang sama persis dapat dicapai dengan alih-alih memiliki skrip pada objek Spike yang berisi kode seperti ini:

OnCollisionEnter(Collision coll) {
    if (coll.gameObject.compareTag("Player")) {
        Destroy(coll.gameObject);
    }
}

Sementara keduanya valid, lebih masuk akal bagi saya untuk memiliki skrip pada Player karena, dalam hal ini, ketika tabrakan terjadi, suatu tindakan sedang dilakukan pada Player .

Namun, yang membuat saya ragu ini adalah bahwa di masa depan Anda mungkin ingin menambahkan lebih banyak objek yang akan membunuh Player saat bertabrakan, seperti Musuh, Lava, Laser Beam, dll. Objek-objek ini kemungkinan akan memiliki tag yang berbeda. Maka skrip pada Player akan menjadi:

OnCollisionEnter(Collision coll) {
    GameObject other = coll.gameObject;
    if (other.compareTag("Spike") || other.compareTag("Lava") || other.compareTag("Enemy")) {
        Destroy(gameObject);
    }
}

Sedangkan, dalam kasus di mana skrip berada di Spike, yang harus Anda lakukan adalah menambahkan skrip yang sama ke semua objek lain yang dapat membunuh Player dan beri nama skrip seperti KillPlayerOnContact.

Juga, jika Anda memiliki tabrakan antara Player dan Musuh, maka Anda mungkin ingin melakukan aksi pada keduanya . Jadi dalam hal itu, objek mana yang harus menangani tabrakan? Atau haruskah keduanya menangani tabrakan dan melakukan tindakan yang berbeda?

Saya belum pernah membuat game dengan ukuran yang masuk akal sebelumnya dan saya bertanya-tanya apakah kodenya bisa menjadi berantakan dan sulit untuk dipertahankan saat tumbuh jika Anda mendapatkan hal yang salah pada awalnya. Atau mungkin semua cara itu valid dan tidak masalah?


Wawasan apa pun sangat dihargai! Terima kasih atas waktu Anda :)


Pertama mengapa string literal untuk tag? Enum jauh lebih baik karena kesalahan ketik akan berubah menjadi kesalahan kompilasi alih-alih kesulitan melacak bug (mengapa spike saya tidak membunuh saya?).
ratchet freak

Karena saya telah mengikuti tutorial Unity dan itulah yang mereka lakukan. Jadi, apakah Anda hanya memiliki enum publik yang disebut Tag yang berisi semua nilai tag Anda? Jadi itu akan menjadi Tag.SPIKEgantinya?
Redtama

Untuk mendapatkan yang terbaik dari kedua dunia, pertimbangkan untuk menggunakan struct, dengan enum di dalamnya, serta metode ToString dan FromString statis
zcabjro

Jawaban:


48

Saya pribadi lebih suka sistem arsitek sehingga tidak ada objek yang terlibat dalam tabrakan menanganinya, dan sebagai gantinya beberapa proxy penanganan tabrakan bisa menanganinya dengan panggilan balik seperti HandleCollisionBetween(GameObject A, GameObject B). Dengan begitu saya tidak perlu memikirkan logika apa yang seharusnya ada di mana.

Jika itu bukan pilihan, seperti ketika Anda tidak mengendalikan arsitektur respons tabrakan, segalanya menjadi sedikit kabur. Umumnya jawabannya adalah "itu tergantung."

Karena kedua objek akan mendapatkan collback callback, saya akan memasukkan kode di keduanya, tetapi hanya kode yang relevan dengan peran objek itu di dunia game , sementara juga berusaha mempertahankan ekstensibilitas sebanyak mungkin. Ini berarti jika beberapa logika masuk akal sama dalam kedua objek, lebih memilih untuk meletakkannya di objek yang kemungkinan akan menyebabkan lebih sedikit perubahan kode dalam jangka panjang karena fungsi baru ditambahkan.

Dengan kata lain, antara dua jenis objek yang dapat bertabrakan, salah satu dari jenis objek tersebut umumnya memiliki efek yang sama pada lebih banyak jenis objek daripada yang lain. Menempatkan kode untuk efek dalam jenis yang mempengaruhi lebih banyak jenis lain berarti lebih sedikit pemeliharaan dalam jangka panjang.

Misalnya, objek pemain dan objek peluru. Dapat diperdebatkan, "menerima kerusakan akibat tabrakan dengan peluru" masuk akal logis sebagai bagian dari pemain. "Menerapkan kerusakan pada benda yang terkena" masuk akal sebagai bagian dari peluru. Namun, peluru kemungkinan memberikan kerusakan pada jenis objek yang lebih berbeda daripada pemain (di sebagian besar gim). Seorang pemain tidak akan menderita kerusakan karena terrain block atau NPC, tetapi sebuah peluru mungkin masih memberikan kerusakan pada mereka. Jadi saya lebih suka untuk menempatkan penanganan tabrakan karena memberikan kerusakan pada peluru, dalam hal ini.


1
Saya menemukan jawaban semua orang sangat membantu tetapi harus menerima jawaban Anda untuk kejelasannya. Paragraf terakhir Anda benar-benar merangkumnya bagi saya :) Sangat membantu! Terima kasih banyak!
Redtama

12

TL; DR Tempat terbaik untuk meletakkan detektor tumbukan adalah tempat di mana Anda menganggapnya sebagai elemen aktif dalam tumbukan, bukan elemen pasif dalam tumbukan, karena mungkin Anda akan memerlukan perilaku tambahan untuk dijalankan dalam logika aktif tersebut (dan, mengikuti pedoman OOP yang baik seperti SOLID, adalah tanggung jawab elemen aktif ).

Detail :

Ayo lihat ...

Pendekatan saya ketika saya memiliki keraguan yang sama, terlepas dari mesin gim , adalah menentukan perilaku mana yang aktif dan perilaku mana yang pasif sehubungan dengan tindakan yang ingin saya picu saat pengumpulan.

Harap dicatat: Saya menggunakan perilaku yang berbeda sebagai abstraksi dari berbagai bagian dari logika kami untuk mendefinisikan kerusakan dan - sebagai contoh lain - inventaris. Keduanya adalah perilaku terpisah yang saya tambahkan nanti ke Player, dan tidak mengacaukan Player kustom saya dengan tanggung jawab terpisah yang akan lebih sulit untuk dipertahankan. Menggunakan anotasi seperti RequireComponent, saya akan memastikan perilaku Player saya memiliki, juga, perilaku yang saya definisikan sekarang.

Ini hanya pendapat saya: menghindari mengeksekusi logika tergantung pada tag, tetapi sebaliknya menggunakan perilaku dan nilai yang berbeda seperti enum untuk memilih di antara .

Bayangkan perilaku ini:

  1. Perilaku untuk menentukan objek sebagai tumpukan di tanah. Game RPG menggunakan ini untuk item di tanah yang bisa diambil.

    public class Treasure : MonoBehaviour {
        public InventoryObject inventoryObject = null;
        public uint amount = 1;
    }
  2. Perilaku untuk menentukan objek yang mampu menghasilkan kerusakan. Ini bisa lava, asam, zat beracun, atau proyektil terbang.

    public class DamageDealer : MonoBehaviour {
        public Faction facton; //let's use it as well..
        public DamageType damageType = DamageType.BLUNT;
        public uint damage = 1;
    }

Sejauh ini, pertimbangkan DamageTypedan InventoryObjectsebagai enum.

Perilaku ini tidak aktif , dengan cara berada di tanah atau terbang di sekitar mereka tidak bertindak sendiri. Mereka tidak melakukan apa pun. Misalnya, jika Anda memiliki bola api yang terbang di ruang kosong, itu tidak siap untuk melakukan kerusakan (mungkin perilaku lain harus dianggap aktif dalam bola api, seperti bola api memakan kekuatannya saat bepergian dan mengurangi kekuatan kerusakannya dalam proses, tetapi bahkan ketika FuelingOutperilaku potensial dapat dikembangkan, itu masih merupakan perilaku lain, dan bukan perilaku DamageProducer yang sama), dan jika Anda melihat objek di tanah, itu tidak memeriksa seluruh pengguna dan berpikir apakah mereka dapat memilih saya?

Kami membutuhkan perilaku aktif untuk itu. Rekannya adalah:

  1. Satu perilaku untuk mengambil kerusakan (Ini akan memerlukan perilaku untuk menangani hidup dan mati, karena menurunkan kehidupan dan menerima kerusakan biasanya merupakan perilaku yang terpisah, dan karena saya ingin menjaga contoh ini sesederhana mungkin) dan mungkin menolak kerusakan.

    public class DamageReceiver : MonoBehaviour {
        public DamageType[] weakness;
        public DamageType[] halfResistance;
        public DamageType[] fullResistance;
        public Faction facton; //let's use it as well..
    
        private List<DamageDealer> dealers = new List<DamageDealer>(); 
    
        public void OnCollisionEnter(Collision col) {
            DamageDealer dealer = col.gameObject.GetComponent<DamageDealer>();
            if (dealer != null && !this.dealers.Contains(dealer)) {
                this.dealers.Add(dealer);
            }
        }
    
        public void OnCollisionLeave(Collision col) {
            DamageDealer dealer = col.gameObject.GetComponent<DamageDealer>();
            if (dealer != null && this.dealers.Contains(dealer)) {
                this.dealers.Remove(dealer);
            }
        }
    
        public void Update() {
            // actually, this should not run on EVERY iteration, but this is just an example
            foreach (DamageDealer element in this.dealers)
            {
                if (this.faction != element.faction) {
                    if (this.weakness.Contains(element)) {
                        this.SubtractLives(2 * element.damage);
                    } else if (this.halfResistance.Contains(element)) {
                        this.SubtractLives(0.5 * element.damage);
                    } else if (!this.fullResistance.Contains(element)) {
                        this.SubtractLives(element.damage);
                    }
                }
            }
        }
    
        private void SubtractLives(uint lives) {
            // implement it later
        }
    }

    Kode ini belum diuji dan harus berfungsi sebagai konsep . Apa tujuannya? Kerusakan adalah sesuatu yang dirasakan seseorang , dan relatif terhadap objek (atau bentuk kehidupan) yang rusak, seperti yang Anda pertimbangkan. Ini adalah contoh: dalam game RPG biasa, pedang merusak Anda , sedangkan di RPG lain yang mengimplementasikannya (mis. Summon Night Swordcraft Story I dan II ), pedang juga dapat menerima kerusakan dengan memukul bagian yang keras atau memblokir baju besi, dan bisa membutuhkan perbaikan . Jadi, karena logika kerusakan relatif terhadap penerima dan bukan ke pengirim, kami hanya memasukkan data yang relevan di pengirim, dan logika di penerima.

  2. Hal yang sama berlaku untuk pemegang inventaris (perilaku lain yang diperlukan adalah perilaku yang terkait dengan objek yang berada di tanah, dan kemampuan untuk menghapusnya dari sana). Mungkin ini adalah perilaku yang sesuai:

    public class Inventory : MonoBehaviour {
        private Dictionary<InventoryObject, uint> = new Dictionary<InventoryObject, uint>();
        private List<Treasure> pickables = new List<Treasure>();
    
        public void OnCollisionEnter(Collision col) {
            Treasure treasure = col.gameObject.GetComponent<Treasure>();
            if (treasure != null && !this.pickables.Contains(treasure)) {
                this.pickables.Add(treasure);
            }
        }
    
        public void OnCollisionLeave(Collision col) {
            DamageDealer treasure = col.gameObject.GetComponent<DamageDealer>();
            if (treasure != null && this.pickables.Contains(treasure)) {
                this.pickables.Remove(treasure);
            }
        }
    
        public void Update() {
            // when certain key is pressed, pick all the objects and add them to the inventory if you have a free slot for them. also remove them from the ground.
        }
    }

7

Seperti yang dapat Anda lihat dari berbagai jawaban di sini, ada tingkat gaya pribadi yang adil yang terlibat dalam keputusan ini, dan penilaian berdasarkan kebutuhan permainan khusus Anda - tidak ada pendekatan terbaik mutlak.

Rekomendasi saya adalah untuk memikirkannya dalam hal bagaimana pendekatan Anda akan skala saat Anda mengembangkan game Anda , menambah dan mengulangi fitur. Akankah menempatkan logika penanganan benturan di satu tempat akan menghasilkan kode yang lebih rumit nantinya? Atau apakah itu mendukung lebih banyak perilaku modular?

Jadi, Anda memiliki paku yang merusak pemain. Bertanya pada diri sendiri:

  • Apakah ada hal lain selain pemain yang paku mungkin ingin rusak?
  • Apakah ada hal-hal lain selain paku yang pemain harus dapatkan kerusakan akibat tabrakan?
  • Apakah setiap level dengan pemain memiliki paku? Apakah setiap level dengan paku memiliki pemain?
  • Mungkinkah Anda memiliki variasi pada paku yang perlu melakukan hal yang berbeda? (mis. jumlah kerusakan / jenis kerusakan berbeda?)

Jelas, kami tidak ingin terlena dengan hal ini dan membangun sistem over-engineered yang dapat menangani jutaan kasus, bukan hanya satu kasus yang kami butuhkan. Tetapi jika itu sama saja artinya (mis. Letakkan 4 baris ini di kelas A vs kelas B) tetapi satu skala lebih baik, itu adalah aturan praktis yang baik untuk membuat keputusan.

Untuk contoh ini, khususnya di Unity, saya condong ke arah menempatkan logika penanganan kerusakan di paku , dalam bentuk sesuatu seperti CollisionHazardkomponen, yang pada saat tabrakan memanggil metode kerusakan pada Damageablekomponen. Inilah alasannya:

  • Anda tidak perlu menyisihkan tag untuk setiap peserta tabrakan berbeda yang ingin Anda bedakan.
  • Saat Anda menambahkan lebih banyak jenis interaksi yang dapat di collidable (mis. Lava, peluru, pegas, powerups ...) kelas pemain Anda (atau komponen yang Dapat Rusak) tidak menumpuk kumpulan besar jika / selain itu / mengganti case untuk menangani masing-masing.
  • Jika setengah level Anda pada akhirnya tidak memiliki paku di dalamnya, Anda tidak membuang waktu dalam handler collision handler untuk memeriksa apakah ada lonjakan. Mereka hanya dikenakan biaya saat mereka terlibat dalam tabrakan.
  • Jika nanti Anda memutuskan bahwa paku juga harus membunuh musuh & alat peraga, Anda tidak perlu menduplikasi kode dari kelas khusus pemain, cukup tempelkan Damageablekomponen pada mereka (jika Anda memerlukan beberapa yang kebal terhadap paku saja, Anda dapat menambahkan tipe kerusakan & biarkan Damageablekomponen menangani kekebalan)
  • Jika Anda bekerja di tim, orang yang perlu mengubah perilaku spike dan memeriksa kelas bahaya / aset tidak menghalangi semua orang yang perlu memperbarui interaksi dengan pemain. Ini juga intuitif untuk anggota tim baru (terutama desainer level) yang skrip yang mereka butuhkan untuk mengubah perilaku lonjakan - itu yang melekat pada paku!

Sistem Entity-Component ada untuk menghindari IF seperti itu. Mereka Federasi selalu salah (hanya orang-orang ifs). Selain itu jika lonjakan bergerak (atau proyektil), penangan benturan akan dipicu meskipun objek bertabrakan adalah pemain atau bukan.
Luis Masuelli

2

Tidak masalah siapa yang menangani tabrakan, tetapi konsistenlah dengan pekerjaan siapa.

Dalam permainan saya, saya mencoba untuk memilih GameObjectyang "melakukan" sesuatu ke objek lain sebagai orang yang mengendalikan tabrakan. IE Spike merusak pemain sehingga menangani benturan. Ketika kedua objek "melakukan" sesuatu untuk satu sama lain, minta mereka masing-masing menangani aksi mereka pada objek lainnya.

Juga, saya akan menyarankan mencoba merancang tumbukan Anda sehingga tumbukan ditangani oleh objek yang bereaksi terhadap tumbukan dengan lebih sedikit hal. Lonjakan hanya perlu tahu tentang tabrakan dengan pemain, membuat kode tabrakan dalam skrip Spike bagus dan ringkas. Objek pemain dapat bertabrakan dengan lebih banyak hal yang akan membuat skrip tabrakan membengkak.

Terakhir, cobalah untuk membuat penangan benturan Anda lebih abstrak. Spike tidak perlu tahu bahwa itu bertabrakan dengan pemain, ia hanya perlu tahu bahwa itu bertabrakan dengan sesuatu yang ia butuhkan untuk menangani kerusakan. Katakanlah Anda memiliki skrip:

public abstract class DamageController
{
    public Faction faction; //GOOD or BAD
    public abstract void ApplyDamage(float damage);
}

Anda dapat subkelas DamageControllerdi GameObject pemain Anda untuk menangani kerusakan, kemudian dalam Spikekode tabrakan

OnCollisionEnter(Collision coll) {
    DamageController controller = coll.gameObject.GetComponent<DamageController>();
    if(controller){
        if(controller.faction == Faction.GOOD){
          controller.ApplyDamage(spikeDamage);
        }
    }
}

2

Saya memiliki Masalah yang sama ketika saya mulai jadi begini: Dalam kasus khusus ini gunakan Musuh. Anda biasanya ingin Tabrakan ditangani oleh Objek yang melakukan aksi (dalam hal ini - musuh, tetapi jika pemain Anda memiliki senjata, maka senjata tersebut harus menangani tabrakan), karena jika Anda ingin men-tweak atribut (misalnya kerusakan musuh) Anda pasti ingin mengubahnya dalam skrip musuh dan bukan dalam skrip players (terutama jika Anda memiliki beberapa Pemain). Demikian juga jika Anda ingin mengubah kerusakan senjata apa pun yang digunakan Pemain, Anda pasti tidak ingin mengubahnya di setiap musuh permainan Anda.


2

Kedua objek akan menangani tabrakan.

Beberapa objek akan langsung berubah bentuk, rusak, atau dihancurkan oleh tabrakan. (peluru, panah, balon air)

Yang lain hanya akan terpental dan berguling ke samping. (Batu) Ini masih bisa menerima kerusakan jika benda yang mereka pukul cukup keras. Batuan tidak menerima kerusakan dari manusia yang menghantam mereka, tetapi mereka mungkin dari palu godam.

Beberapa benda licin seperti manusia akan mengalami kerusakan.

Yang lain akan terikat satu sama lain. (magnet)

Kedua tabrakan akan ditempatkan pada acara yang menangani antrian untuk aktor yang bertindak pada objek pada waktu dan tempat tertentu (dan kecepatan). Ingatlah bahwa pawang hanya harus berurusan dengan apa yang terjadi pada objek. Bahkan mungkin bagi pawang untuk menempatkan pawang lain pada antrian untuk menangani efek tambahan dari tabrakan berdasarkan pada sifat-sifat aktor atau objek yang sedang ditindaklanjuti. (Bom meledak dan ledakan yang dihasilkan sekarang harus menguji tabrakan dengan benda lain.)

Saat membuat skrip, gunakan tag dengan properti yang akan diterjemahkan ke dalam implementasi antarmuka oleh kode Anda. Kemudian referensi antarmuka dalam kode penanganan Anda.

Misalnya, pedang mungkin memiliki tag untuk Melee yang diterjemahkan ke antarmuka bernama Melee yang mungkin memperluas antarmuka DamageGiving yang memiliki properti untuk tipe kerusakan dan jumlah kerusakan yang ditentukan menggunakan nilai tetap atau rentang, dll. Dan bom mungkin memiliki tag peledak yang memiliki antarmuka Explosive juga memperluas antarmuka DamageGiving yang memiliki properti tambahan untuk radius dan mungkin seberapa cepat kerusakannya berdifusi.

Mesin game Anda kemudian akan mengkodekan antarmuka, bukan tipe objek spesifik.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.