Làm cách nào để quyết định GameObject nào sẽ xử lý vụ va chạm?


28

Trong bất kỳ va chạm nào, có hai GameObject liên quan phải không? Điều tôi muốn biết là, Làm thế nào để tôi quyết định đối tượng nào nên chứa đối tượng của mình OnCollision*?

Ví dụ: giả sử tôi có đối tượng Người chơi và đối tượng Spike. Suy nghĩ đầu tiên của tôi là đặt một tập lệnh lên trình phát có chứa một số mã như thế này:

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

Tất nhiên, chức năng chính xác tương tự có thể đạt được bằng cách thay vào đó có một tập lệnh trên đối tượng Spike có chứa mã như thế này:

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

Trong khi cả hai đều có giá trị, nó có ý nghĩa hơn đối với tôi để có kịch bản trên chơi bởi vì, trong trường hợp này, khi va chạm xảy ra, một hành động đã được thực hiện trên chơi .

Tuy nhiên, điều khiến tôi nghi ngờ điều này là trong tương lai bạn có thể muốn thêm nhiều vật thể sẽ giết Người chơi khi va chạm, chẳng hạn như Kẻ thù, Lava, Tia Laser, v.v. Những vật thể này có thể sẽ có các thẻ khác nhau. Vì vậy, kịch bản trên Trình phát sẽ trở thành:

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

Trong khi đó, trong trường hợp tập lệnh trên Spike, tất cả những gì bạn cần làm là thêm tập lệnh tương tự vào tất cả các đối tượng khác có thể giết Người chơi và đặt tên cho tập lệnh theo cách tương tự KillPlayerOnContact.

Ngoài ra, nếu bạn có một sự va chạm giữa Người chơi và Kẻ thù, thì bạn có thể muốn thực hiện một hành động trên cả hai . Vậy trong trường hợp đó, đối tượng nào nên xử lý vụ va chạm? Hay cả hai nên xử lý va chạm và thực hiện các hành động khác nhau?

Tôi chưa bao giờ xây dựng một trò chơi có kích thước hợp lý trước đây và tôi tự hỏi liệu mã có thể trở nên lộn xộn và khó bảo trì khi nó phát triển nếu bạn gặp phải loại điều này ngay từ đầu. Hoặc có thể tất cả các cách là hợp lệ và nó không thực sự quan trọng?


Bất kỳ cái nhìn sâu sắc được nhiều đánh giá cao! Cảm ơn bạn đã dành thời gian :)


Đầu tiên tại sao chuỗi ký tự cho các thẻ? Một enum tốt hơn nhiều vì một lỗi đánh máy sẽ biến thành một lỗi biên dịch thay vì một lỗi khó theo dõi (tại sao sự đột biến của tôi không giết chết tôi?).
ratchet freak

Bởi vì tôi đã theo dõi các hướng dẫn của Unity và đó là những gì họ đã làm. Vì vậy, bạn sẽ có một enum công khai có tên Tag chứa tất cả các giá trị thẻ của bạn chứ? Vì vậy, nó sẽ được Tag.SPIKEthay thế?
Redtama

Để tận dụng tốt nhất cả hai thế giới, hãy cân nhắc sử dụng một cấu trúc, với một enum bên trong, cũng như các phương thức ToString và FromString tĩnh
zcabjro

Câu trả lời:


48

Cá nhân tôi thích kiến ​​trúc sư các hệ thống sao cho không có đối tượng nào liên quan đến va chạm xử lý nó, và thay vào đó, một số proxy xử lý va chạm sẽ xử lý nó với một cuộc gọi lại như thế nào HandleCollisionBetween(GameObject A, GameObject B). Theo cách đó tôi không phải suy nghĩ về logic nên ở đâu.

Nếu đó không phải là một lựa chọn, chẳng hạn như khi bạn không kiểm soát kiến ​​trúc phản ứng va chạm, mọi thứ sẽ hơi mờ nhạt. Nói chung câu trả lời là "nó phụ thuộc."

Vì cả hai đối tượng sẽ nhận được các cuộc gọi lại va chạm, tôi đã đặt mã vào cả hai, nhưng chỉ mã có liên quan đến vai trò của đối tượng đó trong thế giới trò chơi , đồng thời cố gắng duy trì khả năng mở rộng càng nhiều càng tốt. Điều này có nghĩa là nếu một số logic có ý nghĩa như nhau trong một trong hai đối tượng, thì thích đặt nó vào đối tượng có thể sẽ dẫn đến ít thay đổi mã hơn trong thời gian dài khi chức năng mới được thêm vào.

Đặt một cách khác, giữa bất kỳ hai loại đối tượng có thể va chạm, một trong những loại đối tượng đó thường có tác dụng tương tự đối với nhiều loại đối tượng hơn loại kia. Đặt mã cho hiệu ứng đó trong loại ảnh hưởng đến nhiều loại khác có nghĩa là ít phải bảo trì trong thời gian dài.

Ví dụ, một đối tượng người chơi và một đối tượng viên đạn. Có thể cho rằng, "nhận sát thương khi va chạm với đạn" có ý nghĩa logic như một phần của người chơi. "Áp dụng sát thương vào thứ nó bắn trúng" có ý nghĩa logic như một phần của viên đạn. Tuy nhiên, một viên đạn có khả năng gây sát thương cho nhiều loại đối tượng khác nhau hơn người chơi (trong hầu hết các trò chơi). Người chơi sẽ không bị thiệt hại từ các khối địa hình hoặc NPC, nhưng một viên đạn vẫn có thể gây sát thương cho những người đó. Vì vậy, tôi muốn đưa việc xử lý va chạm để truyền sát thương vào viên đạn, trong trường hợp đó.


1
Tôi thấy câu trả lời của mọi người thực sự hữu ích nhưng phải chấp nhận câu trả lời của bạn cho rõ ràng. Đoạn cuối của bạn thực sự tóm gọn nó cho tôi :) Vô cùng hữu ích! Cảm ơn bạn rất nhiều!
Redtama

12

TL; DR Nơi tốt nhất để đặt bộ phát hiện va chạm là nơi bạn coi đó là phần tử hoạt động trong vụ va chạm, thay vì phần tử thụ động trong vụ va chạm, bởi vì có lẽ bạn sẽ cần hành vi bổ sung để được thực thi theo logic hoạt động như vậy (và, tuân theo các nguyên tắc OOP tốt như RẮN, là trách nhiệm của yếu tố hoạt động ).

Chi tiết :

Hãy xem ...

Cách tiếp cận của tôi khi tôi có cùng nghi ngờ, bất kể công cụ trò chơi , là xác định hành vi nào đang hoạt động và hành vi nào bị động đối với hành động tôi muốn kích hoạt trên bộ sưu tập.

Xin lưu ý: Tôi sử dụng các hành vi khác nhau như sự trừu tượng của các phần khác nhau trong logic của chúng tôi để xác định thiệt hại và - như một ví dụ khác - hàng tồn kho. Cả hai đều là những hành vi riêng biệt mà tôi sẽ thêm sau này cho Người chơi và không làm lộn xộn Trình phát tùy chỉnh của tôi với các trách nhiệm riêng biệt sẽ khó duy trì hơn. Sử dụng các chú thích như RequireComponent, tôi cũng đảm bảo hành vi Người chơi của tôi cũng có các hành vi tôi đang xác định.

Đây chỉ là ý kiến ​​của tôi: tránh thực thi logic tùy thuộc vào thẻ, mà thay vào đó sử dụng các hành vi và giá trị khác nhau như enum để chọn trong số đó .

Hãy tưởng tượng hành vi này:

  1. Hành vi để xác định một đối tượng là một chồng trên mặt đất. Trò chơi RPG sử dụng điều này cho các vật phẩm trong mặt đất có thể được nhặt lên.

    public class Treasure : MonoBehaviour {
        public InventoryObject inventoryObject = null;
        public uint amount = 1;
    }
  2. Hành vi để chỉ định một đối tượng có khả năng tạo ra thiệt hại. Đây có thể là dung nham, axit, bất kỳ chất độc hại hoặc đạn bay.

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

Trong phạm vi này, xem xét DamageTypeInventoryObjectnhư enums.

Những hành vi này không hoạt động , theo cách mà trên mặt đất hoặc bay xung quanh chúng không tự hành động. Họ không làm gì cả. Ví dụ: nếu bạn có một quả cầu lửa bay trong một không gian trống, nó không sẵn sàng gây sát thương (có lẽ các hành vi khác nên được coi là hoạt động trong một quả cầu lửa, như quả cầu lửa tiêu thụ năng lượng của anh ta trong khi di chuyển và làm giảm sức mạnh thiệt hại của nó trong quá trình, nhưng thậm chí Khi một FuelingOuthành vi tiềm năng có thể được phát triển, đó sẽ là một hành vi khác, và không phải là hành vi Damage sản xuất tương tự) và nếu bạn nhìn thấy một vật thể trong lòng đất, nó không kiểm tra toàn bộ người dùng và họ có thể chọn tôi không?

Chúng ta cần những hành vi tích cực cho điều đó. Các đối tác sẽ là:

  1. Một hành vi gây sát thương (Nó sẽ yêu cầu một hành vi xử lý sự sống và cái chết, vì hạ thấp sự sống và nhận thiệt hại thường là những hành vi riêng biệt, và vì tôi muốn giữ ví dụ này đơn giản nhất có thể) và có lẽ chống lại thiệt hại.

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

    Mã này chưa được kiểm tra và nên hoạt động như một khái niệm . Mục đích gì? Thiệt hại là một cái gì đó mà ai đó cảm thấy , và liên quan đến đối tượng (hoặc dạng sống) bị hư hại, như bạn xem xét. Đây là một ví dụ: trong các game RPG thông thường, một thanh kiếm gây sát thương cho bạn , trong khi trong các game nhập vai khác thực hiện nó (ví dụ Summon Night Swordcraft Story I và II ), một thanh kiếm cũng có thể nhận sát thương bằng cách đánh một phần cứng hoặc chặn áo giáp, và có thể cần sửa chữa . Vì vậy, vì logic thiệt hại liên quan đến người nhận và không liên quan đến người gửi, chúng tôi chỉ đưa dữ liệu liên quan vào người gửi và logic trong người nhận.

  2. Điều tương tự cũng áp dụng cho người giữ hàng tồn kho (một hành vi bắt buộc khác sẽ là một hành vi liên quan đến đối tượng đang ở trên mặt đất và khả năng loại bỏ nó từ đó). Có lẽ đây là hành vi thích hợp:

    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

Như bạn có thể thấy từ nhiều câu trả lời khác nhau ở đây, có một mức độ hợp lý về phong cách cá nhân liên quan đến các quyết định này và phán đoán dựa trên nhu cầu của trò chơi cụ thể của bạn - không có cách tiếp cận tuyệt đối nhất.

Đề nghị của tôi là nghĩ về nó theo cách tiếp cận của bạn sẽ mở rộng khi bạn phát triển trò chơi của mình , thêm và lặp lại các tính năng. Việc đặt logic xử lý va chạm ở một nơi sẽ dẫn đến mã phức tạp hơn sau này? Hoặc nó hỗ trợ hành vi mô-đun nhiều hơn?

Vì vậy, bạn có gai gây thiệt hại cho người chơi. Tự hỏi mình đi:

  • Có những thứ khác ngoài người chơi mà gai có thể muốn gây sát thương không?
  • Có những thứ khác ngoài gai mà người chơi nên nhận sát thương khi va chạm?
  • Mỗi cấp độ với một người chơi sẽ tăng đột biến? Mỗi cấp độ với gai sẽ có một người chơi?
  • Bạn có thể có các biến thể trên các gai cần phải làm những điều khác nhau? (ví dụ: mức độ thiệt hại / loại thiệt hại khác nhau?)

Rõ ràng, chúng tôi không muốn thực hiện điều này và xây dựng một hệ thống kỹ thuật quá mức có thể xử lý một triệu trường hợp thay vì chỉ một trường hợp chúng tôi cần. Nhưng nếu nó hoạt động như nhau dù bằng cách nào (ví dụ: đặt 4 dòng này vào lớp A so với lớp B) nhưng một thang điểm tốt hơn, đó là một quy tắc tốt để đưa ra quyết định.

Trong ví dụ này, cụ thể là trong Unity, tôi nghiêng về việc đưa logic gây sát thương vào các gai , dưới dạng một thứ giống như một CollisionHazardthành phần, khi va chạm gọi phương thức gây sát thương trong một Damageablethành phần. Đây là lý do tại sao:

  • Bạn không cần phải đặt một thẻ cho mỗi người tham gia va chạm khác nhau mà bạn muốn phân biệt.
  • Khi bạn thêm nhiều loại tương tác có thể va chạm (ví dụ: dung nham, đạn, lò xo, sức mạnh ...), lớp người chơi của bạn (hoặc thành phần có thể phá hủy) sẽ không tích lũy một tập hợp lớn các trường hợp if / other / switch để xử lý từng trường hợp.
  • Nếu một nửa cấp độ của bạn kết thúc không có đột biến trong chúng, bạn sẽ không lãng phí thời gian trong trình xử lý va chạm của người chơi để kiểm tra xem có tăng đột biến hay không. Họ chỉ trả giá cho bạn khi họ tham gia vào vụ va chạm.
  • Nếu sau này bạn quyết định rằng gai cũng sẽ tiêu diệt kẻ thù và đạo cụ, bạn không cần sao chép mã ra khỏi lớp dành riêng cho người chơi, chỉ cần dán Damageablethành phần lên chúng (nếu bạn cần một số miễn dịch với gai, bạn có thể thêm loại thiệt hại và để cho các Damageablethành phần xử lý miễn trừ)
  • Nếu bạn đang làm việc trong một nhóm, người cần thay đổi hành vi tăng đột biến và kiểm tra lớp / tài sản nguy hiểm sẽ không chặn tất cả những người cần cập nhật tương tác với người chơi. Nó cũng trực quan đối với một thành viên nhóm mới (đặc biệt là các nhà thiết kế cấp độ) mà kịch bản họ cần xem xét để thay đổi các hành vi tăng đột biến - đó là kịch bản gắn liền với các đột biến!

Hệ thống thực thể-Thành phần tồn tại để tránh IF như thế. Những IF đó luôn sai (chỉ những if đó). Ngoài ra, nếu mũi nhọn đang di chuyển (hoặc là một viên đạn), bộ xử lý va chạm sẽ được kích hoạt mặc dù đối tượng va chạm có hoặc không có người chơi.
Luis Masuelli

2

Nó thực sự không quan trọng ai xử lý vụ va chạm, nhưng phải phù hợp với công việc của ai.

Trong các trò chơi của mình, tôi cố gắng chọn thứ GameObjectđang "làm" thứ gì đó cho đối tượng khác làm người điều khiển vụ va chạm. IE Spike gây thiệt hại cho người chơi để nó xử lý vụ va chạm. Khi cả hai đối tượng "làm" một cái gì đó với nhau, hãy để chúng xử lý hành động của chúng trên đối tượng kia.

Ngoài ra, tôi sẽ đề nghị cố gắng thiết kế các va chạm của bạn để các va chạm được xử lý bởi đối tượng phản ứng với các va chạm với ít thứ hơn. Một mũi nhọn sẽ chỉ cần biết về va chạm với người chơi, làm cho mã va chạm trong tập lệnh Spike đẹp và gọn. Đối tượng người chơi có thể va chạm với nhiều thứ nữa sẽ tạo ra một kịch bản va chạm cồng kềnh.

Cuối cùng, cố gắng làm cho xử lý va chạm của bạn trừu tượng hơn. Một Spike không cần biết rằng nó đã va chạm với người chơi, nó chỉ cần biết rằng nó đã va chạm với thứ gì đó mà nó cần để gây sát thương. Hãy nói rằng bạn có một kịch bản:

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

Bạn có thể phân lớp DamageControllertrong GameObject người chơi của mình để xử lý thiệt hại, sau đó trong Spikemã va chạm của

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

2

Tôi đã gặp vấn đề tương tự khi tôi bắt đầu nên ở đây nó xảy ra: Trong trường hợp cụ thể này, hãy sử dụng Kẻ thù. Bạn thường muốn Collision được xử lý bởi Object thực hiện hành động (trong trường hợp này là kẻ thù, nhưng nếu người chơi của bạn có vũ khí, thì vũ khí đó sẽ xử lý va chạm), bởi vì nếu bạn muốn điều chỉnh các thuộc tính (ví dụ như sát thương của kẻ thù) bạn chắc chắn muốn thay đổi nó trong bản mô tả chứ không phải trong bản chơi (đặc biệt nếu bạn có nhiều Người chơi). Tương tự như vậy nếu bạn muốn thay đổi sát thương của bất kỳ vũ khí nào mà Người chơi sử dụng, bạn chắc chắn không muốn thay đổi nó trong mọi kẻ thù trong trò chơi của mình.


2

Cả hai đối tượng sẽ xử lý vụ va chạm.

Một số vật thể sẽ bị biến dạng ngay lập tức, bị vỡ hoặc bị phá hủy do va chạm. (đạn, mũi tên, bóng nước)

Những người khác sẽ bật ra và lăn sang một bên. (đá) Những thứ này vẫn có thể gây sát thương nếu thứ chúng đánh đủ mạnh. Đá không gây sát thương từ người đánh chúng, nhưng chúng có thể từ búa tạ.

Một số vật thể yếu đuối như con người sẽ bị thiệt hại.

Những người khác sẽ bị ràng buộc với nhau. (nam châm)

Cả hai va chạm sẽ được đặt trên hàng đợi xử lý sự kiện cho một diễn viên hành động trên một vật thể tại một thời điểm và địa điểm cụ thể (và vận tốc). Chỉ cần nhớ rằng người xử lý chỉ nên đối phó với những gì xảy ra với đối tượng. Thậm chí có thể một trình xử lý đặt một trình xử lý khác vào hàng đợi để xử lý các hiệu ứng bổ sung của va chạm dựa trên các thuộc tính của tác nhân hoặc đối tượng được tác động. (Bom nổ tung và vụ nổ kết quả bây giờ phải kiểm tra va chạm với các vật thể khác.)

Khi viết kịch bản, hãy sử dụng các thẻ có thuộc tính sẽ được dịch thành các cài đặt giao diện theo mã của bạn. Sau đó tham khảo các giao diện trong mã xử lý của bạn.

Ví dụ, một thanh kiếm có thể có thẻ Melee, dịch sang giao diện có tên Melee, có thể mở rộng giao diện DamageGiving có thuộc tính cho loại sát thương và lượng sát thương được xác định bằng giá trị hoặc phạm vi cố định, v.v. Và một quả bom có ​​thể có thẻ Explosive có giao diện Explosive cũng mở rộng giao diện DamageGiving có thuộc tính bổ sung cho bán kính và có thể mức độ thiệt hại lan tỏa nhanh như thế nào.

Công cụ trò chơi của bạn sau đó sẽ mã hóa các giao diện, không phải các loại đối tượng cụ thể.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.