충돌을 처리 할 게임 오브젝트를 결정하는 방법은 무엇입니까?


28

어떤 충돌에도 두 개의 게임 오브젝트가 관련되어 있습니까? 내가 알고 싶은 것은, 어떤 객체에 내 객체를 포함시켜야하는지 어떻게 결정 OnCollision*합니까?

예를 들어 Player 객체와 Spike 객체가 있다고 가정합니다. 내 생각은 다음과 같은 코드가 포함 된 스크립트를 플레이어에 넣는 것입니다.

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

물론 Spike 객체에 다음과 같은 코드가 포함 된 스크립트를 대신 사용하여 동일한 기능을 수행 할 수 있습니다.

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

둘 다 유효하지만 ,이 경우 충돌이 발생하면 Player 에서 작업이 수행되기 때문에 Player에 스크립트를 작성하는 것이 더 의미가 있습니다.

그러나 이것이 의심스러운 것은 장래에 Enemy, Lava, Laser Beam 등과 같이 충돌시 플레이어를 죽일 수있는 더 많은 객체를 추가 할 수 있다는 것입니다.이 객체에는 다른 태그가있을 수 있습니다. 따라서 플레이어의 스크립트는 다음과 같습니다.

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

스크립트가 스파이크에있는 경우 플레이어를 죽이고 스크립트 이름을 다음과 같이 지정할 수있는 다른 모든 개체에 동일한 스크립트를 추가하기 만하면 KillPlayerOnContact됩니다.

또한 플레이어와 적 사이에 충돌이 있으면 두 가지 모두에 대한 작업을 수행 할 수 있습니다 . 그렇다면 어떤 오브젝트가 충돌을 처리해야합니까? 아니면 충돌을 처리하고 다른 동작을 수행해야합니까?

나는 전에 적당한 크기의 게임을 만든 적이 없으며 처음에 이런 종류의 문제가 발생하면 코드가 지저분 해지고 유지하기 어려워 지는지 궁금합니다. 아니면 모든 방법이 유효하고 실제로 중요하지 않습니까?


모든 통찰력은 대단히 감사합니다! 시간 내 주셔서 감사합니다 :)


먼저 왜 태그의 문자열 리터럴입니까? 열거 형은 오타가 버그를 추적하기 어려운 대신 컴파일 오류로 바뀌기 때문에 훨씬 좋습니다 (내 스파이크가 나를 죽이지 않는 이유는 무엇입니까?).
ratchet freak

Unity 튜토리얼을 따르고 있었기 때문에 그랬습니다. 그렇다면 모든 태그 값을 포함하는 Tag라는 공개 열거 형이 있습니까? 그 Tag.SPIKE대신에?
Redtama

두 세계를 최대한 활용하려면 정적 ToString 및 FromString 메서드뿐만 아니라 열거 형이 포함 된 구조체를 사용하는 것이 좋습니다.
zcabjro

답변:


48

나는 개인적으로 그런 건축가 시스템을 선호하는 어느 객체가 충돌 핸들에 참여, 대신 약간의 충돌 처리 프록시가 같은 콜백을 처리하기 위해 얻는다 HandleCollisionBetween(GameObject A, GameObject B). 그렇게하면 논리가 어디에 있어야하는지 생각할 필요가 없습니다.

충돌 응답 아키텍처를 제어하지 않을 때와 같이 옵션이 아닌 경우 상황이 약간 흐려집니다. 일반적으로 대답은 "의존합니다"입니다.

두 객체 모두 충돌 콜백을 받기 때문에 코드를 둘 다 넣었지만 게임 세계에서 해당 객체의 역할과 관련된 코드 만 사용 하고 가능한 한 확장 성을 유지하려고합니다. 이것은 어떤 논리가 어느 한 객체에서 똑같이 의미가있는 경우, 새로운 기능이 추가됨에 따라 장기적으로 코드 변경을 줄일 수있는 객체에 배치하는 것을 선호합니다.

다른 방법으로, 충돌 할 수있는 두 객체 유형 사이에서 이러한 객체 유형 중 하나는 일반적으로 다른 유형보다 더 많은 객체 유형에 동일한 영향을 미칩니다. 다른 유형에 영향을 미치는 유형에 해당 효과에 대한 코드를 넣으면 장기적으로 유지 관리가 줄어 듭니다.

예를 들어 플레이어 객체와 불릿 객체입니다. "탄알과의 충돌로 피해를 입는 것"은 플레이어의 일부로서 논리적으로 의미가 있습니다. "타격에 피해를 가하는 것"은 총알의 일부로 논리적으로 의미가 있습니다. 그러나 총알은 플레이어 (대부분의 게임에서)보다 다른 유형의 물체에 피해를 줄 수 있습니다. 플레이어는 지형 블록이나 NPC의 피해를받지 않지만 총알은 여전히 ​​피해를 줄 수 있습니다. 이 경우 총알에 손상을 주도록 충돌 처리를하는 것이 좋습니다.


1
모든 사람의 답변이 실제로 도움이되었지만 명확성을 위해 귀하의 답변을 받아 들여야합니다. 당신의 마지막 단락은 정말로 나를 위해 요약합니다 :) 매우 도움이됩니다! 정말 고맙습니다!
Redtama

12

TL; DR 충돌 감지기를 배치하는 가장 좋은 장소는 충돌 의 수동 요소가 아니라 충돌 의 능동 요소로 간주되는 장소입니다. (SOLID와 같은 올바른 OOP 지침을 따르는 것이 활성 요소 의 책임입니다 ).

세부 사항 :

보자 ...

게임 엔진에 관계없이 동일한 의심이있을 때 나의 접근 방식 은 어떤 행동이 활성화되어 있고 어떤 행동이 수집에서 트리거하려는 행동과 관련하여 수동적 인지를 결정하는 것 입니다.

참고 : 다른 행동을 로직의 다른 부분에 대한 추상화로 사용하여 손상과 인벤토리를 정의합니다. 둘 다 나중에 플레이어에 추가 할 별도의 동작이며 유지하기 어려운 별도의 책임으로 내 사용자 지정 플레이어를 어지럽히 지 않습니다. RequireComponent와 같은 주석을 사용하여 플레이어 동작과 지금 정의하고있는 동작을 확인했습니다.

이것은 내 의견 일뿐입니다. 태그에 따라 논리를 실행하지 말고 대신 열거 형과 같은 다른 동작과 값을 사용하여 선택하십시오 .

이 동작을 상상해보십시오.

  1. 지면에있는 스택으로 개체를 지정하는 동작입니다. 롤 플레잉 게임은 지상에있는 아이템을 집을 수있는 아이템에 사용합니다.

    public class Treasure : MonoBehaviour {
        public InventoryObject inventoryObject = null;
        public uint amount = 1;
    }
  2. 손상을 일으킬 수있는 물체를 지정하는 행동. 이것은 용암, 산, 독성 물질 또는 날아 오는 발사체 일 수 있습니다.

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

이 정도 고려 DamageType하고 InventoryObject열거있다.

이러한 행동은 지상에 있거나 주위를 비행함으로써 스스로 행동하지 않는 방식으로 활성화 되지 않습니다. 그들은 아무것도하지 않습니다. 예를 들어, 빈 공간에서 불 덩어리가 날아 다니면 피해를 입을 준비가되지 않은 것입니다 (아마도 다른 행동은 불 덩어리에서 여행 중에 힘을 소비하고 그 과정에서 피해를 감소시키는 것과 같이 불 덩어리에서 활성화 된 것으로 간주되어야합니다. 잠재적 인 FuelingOut행동이 개발 될 수있을 때 , 그것은 다른 행동이며 여전히 동일한 DamageProducer 행동이 아님), 만약 당신이 지상에 물체를 보게되면, 전체 사용자를 확인하지 않고 그들이 나를 선택할 수 있을까요?

이를 위해서는 적극적인 행동이 필요합니다. 상대방은 다음과 같습니다.

  1. 하나 개의 동작은 (일반적으로 별도의 행동을하는 삶을 낮추고 손상을받은 이후 핸들의 삶과 죽음에 대한 행동을 요구한다, 나는 가능한 한 간단하게이 예제를 유지하기를 원하기 때문에) 피해를 아마도하는 저항 피해를 입 힙니다.

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

    이 코드는 테스트되지 않았으며 개념으로 작동해야합니다 . 목적은 무엇입니까? 손해는 누군가가 느끼는 것으로 , 생각하는 것처럼 손상 되는 물체 (또는 살아있는 형태)와 관련이 있습니다. 예를 들면 다음과 같습니다. 일반 RPG 게임에서는 칼이 피해를 입히는 반면, 다른 RPG에서는이를 소환 (예 : Summon Night Swordcraft Story I and II ) 하면 어려운 부분을 치거나 방어구를 막아 피해를 입을 수 있습니다. 수리 . 따라서 손상 논리는 발신자가 아닌 수신자와 관련이 있으므로 관련 데이터는 발신자에게만, 논리는 수신자에게만 둡니다.

  2. 인벤토리 보유자에게도 동일하게 적용됩니다 (또 다른 필수 동작은 바닥에있는 물체와 관련이있는 동작과 관련이 있습니다). 아마도 이것은 적절한 행동입니다.

    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

다양한 답변에서 볼 수 있듯이, 이러한 결정과 관련된 특정 개인 스타일이 있으며 특정 게임의 요구에 따른 판단이 절대적 최선의 방법이 아닙니다.

필자는 게임을 확장 하고 기능을 추가 및 반복 할 때 접근 방식이 어떻게 확장 될 것인지에 대해 생각하는 것이 좋습니다 . 충돌 처리 로직을 한 곳에두면 나중에 더 복잡한 코드가 생성됩니까? 아니면 더 많은 모듈 식 동작을 지원합니까?

따라서 플레이어를 손상시키는 스파이크가 있습니다. 자신에게 물어:

  • 스파이크에 손상을 줄 수있는 플레이어 이외의 다른 것들이 있습니까?
  • 충돌시 플레이어가 피해를 입히는 스파이크 이외의 물건이 있습니까?
  • 플레이어가있는 모든 레벨에 스파이크가 있습니까? 스파이크가있는 모든 레벨에 플레이어가 있습니까?
  • 다른 일을 해야하는 스파이크에 변형이있을 수 있습니까? (예 : 다른 피해량 / 피해 유형?)

분명히, 우리는이 문제를 해결하고 우리가 필요로하는 하나의 사례 대신에 백만 개의 사례를 처리 할 수있는 과도하게 설계된 시스템을 구축하고 싶지 않습니다. 그러나 그것이 어느 쪽이든 똑같이 효과가 있지만 (예 : 클래스 A 대 클래스 B 에이 4 줄을 넣음) 하나가 더 잘 조정되면 결정을 내리는 데 가장 좋은 규칙입니다.

이 예제에서는 Unity에서 구체적으로, 충돌과 관련하여 충돌 처리에서 구성 요소 의 손상 처리 방법을 호출하는 구성 요소 와 같은 형태로 스파이크에 손상 처리 논리 를 배치하는 데 중점을 둡니다 . 이유는 다음과 같습니다.CollisionHazardDamageable

  • 구별하려는 모든 충돌 참가자마다 태그를 따로 따로 설정할 필요가 없습니다.
  • 더 많은 종류의 충돌 가능한 상호 작용 (예 : 용암, 총알, 스프링, 파워 업 등)을 추가 할 때 플레이어 클래스 (또는 손상 가능한 구성 요소)는 각각을 처리하기 위해 거대한 if / else / switch 케이스를 축적하지 않습니다.
  • 레벨의 절반이 스파이크가없는 경우 플레이어 충돌 처리기에서 스파이크가 관련되어 있는지 확인하는 데 시간을 낭비하지 않습니다. 충돌과 관련이있을 때만 비용이 발생합니다.
  • 나중에 스파이크가 적과 소품을 죽여야한다고 결정한 경우 플레이어 특정 클래스에서 코드를 복제 할 필요가 없으며 Damageable구성 요소를 고정하십시오 (일부 스파이크에만 면역이 필요한 경우 추가 할 수 있음) 손상 유형 및 Damageable구성 요소에 대한 내성 처리)
  • 팀에서 일하는 경우 스파이크 동작을 변경하고 위험 등급 / 자산을 확인해야하는 사람이 플레이어와의 상호 작용을 업데이트해야하는 모든 사람을 차단하지는 않습니다. 또한 스파이크 동작을 변경하기 위해 스크립트를 작성해야하는 새로운 팀원 (특히 레벨 디자이너)에게는 직관적입니다. 스파이크에 부착 된 것입니다!

Entity-Component 시스템은 그러한 IF를 피하기 위해 존재합니다. 이러한 IF는 항상 틀립니다 (만 해당하는 경우 ). 또한 스파이크가 움직이는 경우 (또는 발사체 인 경우) 충돌 오브젝트가 플레이어인지 여부에 관계없이 충돌 처리기가 트리거됩니다.
Luis Masuelli

2

누가 충돌을 처리하는지는 중요하지 않지만 누구의 작업과 일치하는지는 중요합니다.

내 게임에서는 GameObject충돌을 제어하는 ​​대상으로 다른 대상에 "실행"하는 것을 선택하려고합니다 . IE 스파이크가 플레이어를 손상시켜 충돌을 처리합니다. 두 개체가 서로 "일을"수행 할 때 각각 다른 개체에 대한 작업을 처리하도록합니다.

또한 충돌을 적은 물체로 충돌에 반응하는 객체가 처리하도록 충돌을 설계하는 것이 좋습니다. 스파이크는 플레이어와의 충돌에 대해서만 알기 만하면 스파이크 스크립트의 충돌 코드가 훌륭하고 간결 해집니다. 플레이어 오브젝트는 더 많은 것들과 충돌하여 부풀린 충돌 스크립트를 만들 수 있습니다.

마지막으로 충돌 처리기를 더 추상적으로 만들어보십시오. 스파이크는 플레이어와 충돌했다는 것을 알 필요가 없으며 손상을 입힐 필요가있는 것과 충돌했다는 것을 알아야합니다. 스크립트가 있다고 가정 해 봅시다.

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

DamageController플레이어 GameObject에서 서브 클래스 로 손상을 처리 한 다음 Spike의 충돌 코드에서

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

2

처음 시작할 때도 같은 문제가 발생했습니다.이 경우에는 적을 사용하십시오. 일반적으로 액션을 수행하는 오브젝트 (이 경우에는 적이지만 플레이어에 무기가있는 경우 무기가 충돌을 처리해야 함)에서 충돌을 처리하려고합니다. 속성을 조정하려는 경우 (예 : 피해) 플레이어 스크립트가 아닌 적 스크립트에서 변경하려고합니다 (특히 여러 플레이어가있는 경우). 마찬가지로 플레이어가 사용하는 무기의 피해를 변경하려는 경우 게임의 모든 적에게 무기를 변경하고 싶지는 않습니다.


2

두 객체 모두 충돌을 처리합니다.

충돌로 인해 일부 물체가 즉시 변형, 파손 또는 파손될 수 있습니다. (글 머리 기호, 화살표, 물 풍선)

다른 사람들은 그냥 튀어서 옆으로 굴러갔습니다. (바위) 그들이 맞은 것이 충분히 힘들면 여전히 피해를 입을 수 있습니다. 바위는 사람을 때리는 피해를 입지 않지만 썰매 망치에서 나올 수 있습니다.

인간과 같은 뾰족한 물건은 피해를 입을 것입니다.

다른 사람들은 서로 묶여있을 것입니다. (자석)

두 충돌 모두 특정 시간과 장소 (및 속도)에서 객체에 작용하는 액터의 이벤트 처리 대기열에 배치됩니다. 핸들러는 객체에 발생하는 일만 처리해야한다는 것을 기억하십시오. 핸들러가 액터 또는 조치중인 오브젝트의 특성에 따라 충돌의 추가 효과를 처리하기 위해 큐에 다른 핸들러를 배치 할 수도 있습니다. (폭탄이 폭발하여 폭발로 인해 다른 물체와의 충돌을 테스트해야합니다.)

스크립팅 할 때 코드에서 인터페이스 구현으로 변환 할 속성이있는 태그를 사용하십시오. 그런 다음 처리 코드에서 인터페이스를 참조하십시오.

예를 들어, 검에는 Melee에 대한 태그가있을 수 있으며, Melee라는 인터페이스로 변환되어 DamageGiving 인터페이스가 손상 유형 및 고정 된 값 또는 범위 등을 사용하여 정의 된 피해량을 갖는 DamageGiving 인터페이스를 확장 할 수 있습니다. Explosive 인터페이스가 DamageGiving 인터페이스를 확장하여 반지름에 대한 추가 속성이 있고 손상이 얼마나 빨리 확산되는지를 나타내는 Explosive 태그.

그러면 게임 엔진이 특정 객체 유형이 아닌 인터페이스에 대해 코딩합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.