Hangi GameObject'in çarpışmayı ele alması gerektiğine nasıl karar verilir?


28

Herhangi bir çarpışmada, iki GameObject hakkı var mı? Bilmek istediğim şey, hangi nesnenin hangi nesneyi içermesi gerektiğine nasıl karar verebilirim OnCollision*?

Örnek olarak, bir Player nesnesinin ve bir Spike nesnesinin olduğunu varsayalım. İlk düşüncem, böyle bir kod içeren oynatıcıya bir senaryo koymak.

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

Tabii ki, aynı işlevsellik yerine Spike nesnesinde böyle bir kod içeren bir betik bulundurularak elde edilebilir:

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

Her ikisi de geçerli olsa da, betiğin Player'da olması bana daha mantıklı geldi çünkü bu durumda, çarpışma gerçekleştiğinde, Player'da bir işlem gerçekleştiriliyor .

Bununla birlikte, bu beni şüphe ettiren şey, gelecekte Düşmanı, Lav, Lazer Işını, vs. gibi Çarpışmada Oyuncu’u öldürecek daha fazla nesne eklemek isteyebileceğinizdir. Bu nesnelerin muhtemelen farklı etiketleri olacaktır. Böylece Player'daki senaryo şöyle olur:

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

Oysa, senaryonun Spike'da olduğu durumda, tek yapmanız gereken aynı script'i Player'ı öldürebilen ve betiğe benzer bir şekilde isimlendirmek KillPlayerOnContact.

Ayrıca, Oyuncu ve Düşman arasında bir çarpışma varsa, o zaman muhtemelen her ikisinde de bir işlem yapmak istersiniz . Öyleyse, bu durumda hangi nesne çarpışmayı ele almalı? Ya da her ikisi de çarpışmayı ele almalı ve farklı eylemler gerçekleştirmeli midir?

Daha önce hiç makul büyüklükte bir oyun yapmadım ve başlangıçta bu tür bir şeyleri yanlış yaparsanız kodun dağınık hale gelip gelmemesi zorlaştığını merak ediyorum. Ya da belki tüm yollar geçerlidir ve bu gerçekten önemli değil?


Herhangi bir fikir çok takdir edilmektedir! Zaman ayırdığınız için teşekkür ederim :)


İlk neden dize etiketleri için hazır bilgi? Bir enum daha iyidir çünkü bir yazım hatası izlemek zor bir hata yerine derleme hatasına dönüşür (neden spike'ım beni öldürmüyor?).
cırcır ucube

Çünkü Unity eğitimlerini takip ediyordum ve yaptıkları da buydu. Öyleyse, tüm etiket değerlerinizi içeren Tag adında ortak bir numaraya sahip misiniz? Yani bunun Tag.SPIKEyerine olur?
Redtama

Her iki dünyanın da en iyisini elde etmek için, içinde bir enum bulunan bir yapı, statik ToString ve FromString yöntemleri kullanmayı
düşünün

Yanıtlar:


48

Şahsen , çarpışmada yer alan herhangi bir nesnenin onu idare etmeyeceği ve bazı çarpışma yönetimi proxy'lerinin bunun gibi bir çağrıyla başa çıkamayacağı şekilde sistemleri geliştirmeyi tercih ediyorum HandleCollisionBetween(GameObject A, GameObject B). Bu şekilde, hangi mantığın nerede olması gerektiği üzerine düşünmek zorunda değilim.

Eğer bu bir seçenek değilse, çarpışma-cevap mimarisinin kontrolü sizin elinizde değilse, işler biraz bulanıklaşır. Genel olarak cevap "bağlıdır".

Her iki nesne de çarpışma geri aramaları alacağından, her ikisine de kod koyardım, ancak oyunun dünyasında oynadığı rolle ilgili yalnızca kodu koyarım ve aynı zamanda genişletilebilirliği mümkün olduğunca korumaya çalışırdım. Bu, eğer bir mantık her iki nesnede de eşit derecede anlamlıysa, yeni işlevler eklendikçe uzun vadede daha az kod değişikliğine neden olacak nesneyi yerleştirmeyi tercih edin.

Başka bir yolla, çarpışabilecek herhangi iki nesne türü arasında, bu nesne türlerinden birinin genellikle diğerinden daha fazla nesne türü üzerinde aynı etkiye sahip olduğunu söyleyin. Bu etki için daha fazla başka türü etkileyen türe kod koymak, uzun vadede daha az bakım demektir.

Örneğin, bir oyuncu nesnesi ve bir madde işareti nesnesi. Muhtemelen, "madde işaretleri ile çarpışmada hasar almak" oyuncunun bir parçası olarak mantıklı geliyor. “Vurduğu şeye zarar vermek” merminin bir parçası olarak mantıklı geliyor. Bununla birlikte, bir merminin bir oyuncudan daha farklı türde nesnelere hasar vermesi muhtemeldir (çoğu oyunda). Bir oyuncu arazi bloklarından veya NPC'lerden zarar görmez, ancak bir kurşun hala bunlara hasar verebilir. Bu yüzden, mermiye hasar vermek için çarpışma işlemlerini bu durumda yapmayı tercih ederim.


1
Herkesin cevaplarını gerçekten faydalı buldum, ancak açık olması için sizinkini kabul etmek zorundayım. Son paragrafınız gerçekten benim için özetliyor :) Son derece yardımcı! Çok teşekkür ederim!
Redtama

12

TL; DR Çarpışma detektörünü yerleştirmek için en iyi yer, çarpışmadaki pasif eleman yerine çarpışmadaki aktif eleman olduğunu düşündüğünüz yerdir , çünkü bu tür aktif mantıkta yürütülecek ilave davranışa ihtiyaç duyabilirsiniz. (ve SOLID gibi iyi OOP kurallarına uymak, aktif unsurun sorumluluğundadır ).

Ayrıntılı :

Bakalım ...

Aynı şüpheye sahip olduğumda yaklaşımım , oyun motoruna bakılmaksızın, koleksiyonda tetiklemek istediğim harekete göre hangi davranışın aktif olduğunu ve hangi davranışların pasif olduğunu belirlemek .

Lütfen dikkat: Hasarın tanımlanmasında ve envanterin bir başka örneği olarak, mantığımızın farklı bölümlerinin soyutlamaları olarak farklı davranışlar kullanıyorum. Her ikisi de daha sonra bir Player'a ekleyeceğim ayrı davranışlardır ve özel Player'ımı sürdürmesi daha zor olacak ayrı sorumluluklarla karıştırmaz. RequireComponent gibi ek açıklamalar kullanarak, Player davranışımın şu anda tanımladığım davranışların da olmasını sağlarım.

Bu sadece benim görüşüm: etikete bağlı olarak mantık yürütmekten kaçının, fakat bunun yerine seçim için enums gibi farklı davranışlar ve değerler kullanın .

Bu davranışları hayal edin:

  1. Bir nesneyi yerde yığın olarak belirtme davranışı. RPG oyunları, toplanabilecek eşyalar için bunu kullanır.

    public class Treasure : MonoBehaviour {
        public InventoryObject inventoryObject = null;
        public uint amount = 1;
    }
  2. Hasar üretebilecek bir nesneyi belirtme davranışı. Bu lav, asit, herhangi bir toksik madde veya uçan mermi olabilir.

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

Bu ölçüde, düşün DamageTypeve InventoryObjectenums olarak.

Bu davranışlar , zeminde kalarak veya etrafa uçarak kendi başlarına hareket etmeyecek şekilde aktif değildir. Hiçbir şey yapmıyorlar. Örneğin, boş bir alanda uçan bir ateş topu varsa, hasar vermeye hazır değildir (belki de başkalarının davranışları bir ateş topunda aktif olarak kabul edilmelidir, örneğin seyahat ederken gücünü tüketen ateş topu ve işlemdeki hasar gücünü azaltmak gibi) Potansiyel bir FuelingOutdavranış geliştirilebildiği zaman, aynı DamageProducer davranışı değil, başka bir davranış tarzıdır) ve zeminde bir nesne görürseniz, tüm kullanıcıları kontrol etmiyor ve beni seçebileceklerini düşünüyor mu?

Bunun için aktif davranışlara ihtiyacımız var. Meslektaşları olacaktır:

  1. Hasar alma davranışlarından biri (Yaşamı ve ölümü ele almak bir davranış gerektirir, çünkü yaşamı azaltmak ve hasar almak genellikle ayrı davranışlardır, çünkü bu örnekleri mümkün olduğunca basit tutmak istiyorum) ve belki de hasara karşı koymak istiyorum .

    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
        }
    }

    Bu kod test edilmemiştir ve bir kavram olarak çalışmalıdır . Amaç nedir? Hasar, birinin hissettiği bir şeydir ve düşündüğünüz gibi nesnenin (veya canlı biçimin) hasar görmesine bağlıdır. Bu bir örnektir: normal RPG oyunlarında, bir kılıç size zarar verirken diğer RPG'lerde (örneğin Summon Gece Kılıçları Hikayesi I ve II ), bir kılıç sert bir zırhı vurarak veya zırhı bloke ederek hasar alabilir ve ihtiyaç duyabilir. onarımlar . Bu nedenle, hasar mantığı, göndericiye değil alıcıya göre olduğundan, yalnızca gönderene ilgili verileri ve alıcıdaki mantığı koyarız.

  2. Aynısı bir envanter sahibi için de geçerlidir (başka bir gerekli davranış, zemindeki yerde olması ve onu oradan kaldırma yeteneği ile ilgili olabilir). Belki de uygun davranış budur:

    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

Buradaki cevapların çeşitliliğinden görebileceğiniz gibi, bu kararlarla ilgili oldukça kişisel bir tarz vardır ve kendi oyununuzun ihtiyaçlarına dayanarak yargılama yapar - mutlak en iyi yaklaşım yoktur.

Benim tavsiyem, oyununuzu büyütürken yaklaşımınızın nasıl ölçekleneceği , özelliklerin eklenmesi ve yinelenmesi açısından düşünmektir . Çarpışma işleme mantığını bir yere koymak, daha sonra daha karmaşık bir koda neden olur mu? Yoksa daha modüler davranışları destekliyor mu?

Yani, oyuncuya zarar veren çiviler var. Kendine sor:

  • Çivilerin oyuncuya zarar vermek isteyebileceği başka şeyler var mı?
  • Oyuncunun çarpışma sırasında hasar alması gereken dikenlerden başka şeyler var mı?
  • Bir oyuncu ile her seviyede sivri olacak mı? Dikenli her seviyenin bir oyuncusu olacak mı?
  • Farklı şeyler yapmanız gereken çivilerde çeşitlemeler olabilir mi? (örn. farklı hasar miktarları / hasar tipleri?)

Açıkçası, bununla uğraşmak istemiyoruz ve ihtiyaç duyduğumuz bir dava yerine milyonlarca olayı idare edebilecek, aşırı yapılandırılmış bir sistem kurmak istemiyoruz. Ancak, eğer her iki şekilde de eşit işe yararsa (yani, bu 4 satırı A sınıfına karşı B sınıfına koy), ancak biri daha iyi ölçeklenirse, karar vermede iyi bir kuraldır.

Bu örnek için, özellikle Unity'de, hasar veren mantığı , bir CollisionHazardbileşen gibi çarpışma üzerine bir hasar alma yöntemi olarak adlandırılan, bir bileşen gibi bir şey biçiminde , sivri uçlara yerleştirmeye doğru eğildim Damageable. İşte nedeni:

  • Ayırt etmek istediğiniz her farklı katılımcıya bir etiket ayırmanız gerekmez.
  • Daha fazla çarpışabilir etkileşimler (örn. Lav, mermiler, yaylar, powerups ...) eklerken oyuncu sınıfı (veya Hasarlanabilir bileşen), her birini ele almak için dev bir if / else / switch kutusu kümesi biriktirmez.
  • Eğer seviyelerinizin yarısı bunlarda hiç çivisiz kalırsa, bir çivinin olup olmadığını kontrol etmek için oyuncu çarpışma eylemcisinde zaman kaybetmezsiniz. Sadece çarpışmaya girdiklerinde sana maloluyorlar.
  • Daha sonra, sivri uçların düşmanları ve direkleri de öldürmesi gerektiğine karar verirseniz, oyuncuya özel bir sınıftan kodu kopyalamanıza gerek kalmaz, sadece Damageableonlara bileşeni yapıştırmanız yeterlidir (sadece sivri bağışıklığa ihtiyacınız varsa, hasar tipleri ve Damageablebileşenin bağışıklıkları idare etmesine izin verin )
  • Bir ekip üzerinde çalışıyorsanız, spike davranışını değiştirmek ve tehlike sınıfını / varlıklarını kontrol etmek isteyen kişi, oyuncu ile etkileşimlerini güncellemesi gereken herkesi engellemiyordur. Ayrıca, başak davranışlarını değiştirmek için bakmaları gereken senaryolara bakmaları gereken yeni bir ekip üyesi (özellikle seviye tasarımcıları) için sezgiseldir.

Varlık Bileşeni sistemi böyle IF'leri önlemek için var. Bu UF'ların hep yanlış (sadece vardır bu IFS). Ek olarak, eğer başak hareket ediyorsa (veya bir mermi ise), çarpışan nesne oyuncu olsa da olmasa da çarpışma işleyicisi tetiklenecektir.
Luis Masuelli

2

Çarpışmayı kimin ele aldığının önemi yok, ama kimin işi olduğu ile tutarlı olun.

Oyunlarımda, GameObjectçarpışmayı kontrol eden kişi olarak diğer nesneye bir şey yapan şeyin seçimini yapmaya çalışıyorum. IE Spike, oyuncuya zarar verir ve çarpışmayı ele alır. Her iki nesne de birbirine "bir şey" yaptığında, her birinin diğer nesne üzerindeki eylemlerini gerçekleştirmelerini sağlayın.

Ayrıca, çarpışmalarınızı daha az şeyle çarpışmalara tepki veren cisim tarafından ele alınacak şekilde çarpışmalarınızı tasarlamaya çalışmanızı öneririm. Bir spike, yalnızca Spike komut dosyasındaki çarpışma kodunu hoş ve kompakt hale getiren, oynatıcıyla olan çarpışmaları bilmesi gerekir. Oyuncu nesnesi, şişirilmiş bir çarpışma komut dosyası yapacak birçok şeyle çarpışabilir.

Son olarak, çarpışma işleyicilerinizi daha soyut hale getirmeye çalışın. Bir Spike bir oyuncu ile çarpıştığını bilmek zorunda değildir, sadece hasar vermesi gereken bir şeyle çarpıştığını bilmesi gerekir. Bir komut dosyanız olduğunu varsayalım:

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

DamageControllerHasarın üstesinden gelmek için oyuncu GameObject içinde alt sınıf , sonra Spikeçarpışma kodunda

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

2

Başladığımda aynı Problem vardı, işte böyle: Bu özel durumda Düşmanı kullanın. Çarpışmanın genellikle eylemi gerçekleştiren Nesne tarafından ele alınmasını istersiniz (bu durumda - düşman, ama eğer oyuncunun bir silahı varsa, o zaman silah çarpışmayı ele almalıdır), çünkü özelliklerin ince ayarını yapmak istiyorsanız (örneğin hasar düşmanın içinde) kesinlikle onu düşmanı değiştirirsiniz, onun da yapısını değiştiremezsiniz (özellikle birden fazla oyuncunuz varsa). Aynı şekilde, eğer oyuncunun kullandığı herhangi bir silahın hasarını değiştirmek istiyorsan, oyunun her düşmanı için kesinlikle onu değiştirmek istemezsin.


2

Her iki nesne de çarpışmayı ele alır.

Bazı nesneler çarpışma sonucu hemen deforme olur, kırılır veya tahrip olur. (madde işaretleri, oklar, su balonları)

Diğerleri hemen sekerek yana doğru yuvarlanırdı. (kayalar) Vurdukları şey yeterince zor olsaydı bunlar yine de zarar görebilir. Kayalar bir insanın kendisine çarpmasına zarar vermez, ancak bir kızak çekiçinden zarar görebilirler.

İnsanlar gibi bazı cılız nesneler zarar görür.

Diğerleri birbirine bağlı olacaktır. (Mıknatıslar)

Her iki çarpışma, belirli bir zamanda ve yerde (ve hızda) bir nesneye etki eden bir aktör için bir olay işleme kuyruğuna yerleştirilecektir. Sadece işleyicinin yalnızca nesneye olanlarla başa çıkması gerektiğini unutmayın. Bir işleyicinin, aktörün özelliklerine veya etkilenen nesneye bağlı olarak, çarpışmanın ek etkilerini ele almak için sıraya başka bir işleyici yerleştirmesi bile mümkündür. (Bomba patladı ve ortaya çıkan patlama şimdi diğer nesnelerle çarpışmalar için test etmek zorunda.)

Komut dosyası yazarken, kodunuzla arabirim uygulamalarına çevrilecek özelliklere sahip etiketleri kullanın. Ardından işlem kodunuzdaki arayüzlere bakın.

Mesela bir kılıç, Melee için Melee adında, hasar tipi özellikleri ve sabit bir değer veya aralıklar vb. Patlayıcı arayüzü aynı zamanda yarıçap için ek bir özelliğe sahip olan ve hasarın ne kadar çabuk yayıldığını gösteren HasageGiving arayüzünü de genişleten bir Patlayıcı etiketi.

Oyun motorunuz daha sonra belirli nesne tiplerine değil arayüzlere karşı kodlanır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.