-
231003 PhysX Collision Checking기록 2023. 10. 3. 18:45
PhysX 컴포넌트에는 FilterGroup을 나누는 PxFilterData를 설정할 수 있다.
현재 충돌체크와 충돌 확인 시 이벤트 처리 방식이 다음과 같다.
void CustomSimulationEventCallback::onTrigger(physx::PxTriggerPair* pairs, physx::PxU32 count) { //트리거에 충돌이 일어났을때 어떤 걸 할 것인지 while (count--) { physx::PxTriggerPair& current = *pairs++; physx::PxShape* TriggerShape = current.triggerShape; physx::PxShape* OtherShape = current.otherShape; //충돌한 본인 혹은 상대의 액터가 null이면 continue if (TriggerShape->userData == nullptr || OtherShape->userData == nullptr) { continue; } physx::PxFilterData TriggerFilterdata = TriggerShape->getSimulationFilterData(); physx::PxFilterData OtherFilterdata = OtherShape->getSimulationFilterData(); //둘 중 하나라도 충돌 필터 없으면 continue if (TriggerFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::None) || OtherFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::None)) { continue; } //C26813 : 비트플래그로 사용된 enum끼리의 비교는 == 이 아닌 bitwise and(&)로 비교하는 것이 좋음 //WARNING : resultFd.word0 == static_cast<physx::PxU32>(PhysXFilterGroup::Ground //Obstacle와 PlayerDynamic 충돌체크 if (TriggerFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::Obstacle) && // 트리거의 필터그룹이 장애물일때 OtherFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::PlayerDynamic))// 충돌한놈의 필터그룹이 플레이어일때 { if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) // 첫 충돌 했을 때 { PhysXTrigger* TestTrigger = reinterpret_cast<PhysXTrigger*>(TriggerShape->userData); PhysXTestActor* TestActor = reinterpret_cast<PhysXTestActor*>(OtherShape->userData); } if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_PERSISTS) // 충돌 유지시 계속 들어옴 { PhysXTrigger* TestTrigger = reinterpret_cast<PhysXTrigger*>(TriggerShape->userData); PhysXTestActor* TestActor = reinterpret_cast<PhysXTestActor*>(OtherShape->userData); } if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) // 충돌이 끝날 때 { PhysXTrigger* TestTrigger = reinterpret_cast<PhysXTrigger*>(TriggerShape->userData); PhysXTestActor* TestActor = reinterpret_cast<PhysXTestActor*>(OtherShape->userData); int a = 0; } } //LeverTrigger와 PlayerDynamic 충돌체크 if (TriggerFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::LeverTrigger) && // 트리거 필터 그룹 레버 OtherFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::PlayerDynamic)) // 충돌한놈의 필터그룹이 플레이어일때 { if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) // 첫 충돌 했을 때 { PhysXTrigger* TestTrigger = reinterpret_cast<PhysXTrigger*>(TriggerShape->userData); PhysXTestActor* TestActor = reinterpret_cast<PhysXTestActor*>(OtherShape->userData); } if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_PERSISTS) // 충돌 유지시 계속 들어옴 { PhysXTrigger* TestTrigger = reinterpret_cast<PhysXTrigger*>(TriggerShape->userData); PhysXTestActor* TestActor = reinterpret_cast<PhysXTestActor*>(OtherShape->userData); } if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) // 충돌이 끝날 때 { PhysXTrigger* TestTrigger = reinterpret_cast<PhysXTrigger*>(TriggerShape->userData); PhysXTestActor* TestActor = reinterpret_cast<PhysXTestActor*>(OtherShape->userData); } } } }
어떤 FilterGroup과 충돌을 체크 할 것인지 그 추가할 때마다 지정을 해줘야 하고
충돌 시 이벤트도 여기서 지정해줘야 한다는 문제가 있다.
충돌이 되었는지 해당하는 Actor에서 바로 확인하기에 위 작업이 thread로 실행되기 때문에 위 피직스 충돌 내용을 컨텐츠쪽으로 가져올 수 없다.
충돌 유무 만을 확인할 수 있도록 체크할 콜리전 필터타입을 set에 보관하고
class GlobalValue { public: static std::set<std::pair<UINT, UINT>> PhysXCollision; }; std::set<std::pair<UINT, UINT>> GlobalValue::PhysXCollision = { std::make_pair(static_cast<UINT>(PhysXFilterGroup::PlayerDynamic), static_cast<UINT>(PhysXFilterGroup::MonsterDynamic)), };
vertex 충돌 시 thread가 부여되어 원하는 필터타입의 충돌인지 체크 하는 것이다.
충돌하였을 경우 GameEngineActor의 atomic_bool맴버변수를 true로 변경하여 충돌 됨을 확인하고
충돌이 끝난경우 false로 변경해주는 방법을 사용하였다.
void CustomSimulationEventCallback::onTrigger(physx::PxTriggerPair* pairs, physx::PxU32 count) { //트리거에 충돌이 일어났을때 어떤 걸 할 것인지 while (count--) { physx::PxTriggerPair& current = *pairs++; physx::PxShape* TriggerShape = current.triggerShape; // 트리거의 모양 physx::PxShape* OtherShape = current.otherShape; //충돌한 본인 혹은 상대의 액터가 null이면 continue if (TriggerShape->userData == nullptr || OtherShape->userData == nullptr) { continue; } physx::PxFilterData TriggerFilterdata = TriggerShape->getSimulationFilterData(); physx::PxFilterData OtherFilterdata = OtherShape->getSimulationFilterData(); //둘 중 하나라도 충돌 필터 없으면 continue if (TriggerFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::None) || OtherFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::None)) { continue; } // 실제 데이터가 있는 경우 // 필터 두개를 make_pair if (GlobalValue::PhysXCollision.end() == GlobalValue::PhysXCollision.find(std::make_pair(static_cast<UINT>(TriggerFilterdata.word0), static_cast<UINT>(OtherFilterdata.word0)))|| GlobalValue::PhysXCollision.end() == GlobalValue::PhysXCollision.find(std::make_pair(static_cast<UINT>(OtherFilterdata.word0), static_cast<UINT>(TriggerFilterdata.word0))) ) // 두개의 충돌을 체크한다면 { if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) // 첫 충돌 했을 때 { GameEngineActor* TestTrigger = reinterpret_cast<GameEngineActor*>(TriggerShape->userData); GameEngineActor* TestActor = reinterpret_cast<GameEngineActor*>(OtherShape->userData); TestTrigger->isPhysXCollision = true; TestActor->isPhysXCollision = true; } if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) // 충돌이 끝날 때 { GameEngineActor* TestTrigger = reinterpret_cast<GameEngineActor*>(TriggerShape->userData); GameEngineActor* TestActor = reinterpret_cast<GameEngineActor*>(OtherShape->userData); TestTrigger->isPhysXCollision = false; TestActor->isPhysXCollision = false; } } } }
해당하는 액터에서 체크를 하였을 때 정상적으로 디버깅이 되어 잘 작동한다고 생각했지만..
여러 문제가 있었는데
문제1)
//둘 중 하나라도 충돌 필터 없으면 continue if (TriggerFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::None) || OtherFilterdata.word0 & static_cast<physx::PxU32>(PhysXFilterGroup::None)) { continue; }
PhysXFilterGroup::None = 0 으로 위의 if문에 절대 들어가지 않는 식이었던 것이다
//둘 중 하나라도 충돌 필터 없으면 continue if (0 == TriggerFilterdata.word0 || 0 == OtherFilterdata.word0) { continue; }
위와 같이 변경하여 필터가 없는 피직스 컴포넌트의 충돌은continue하도록 변경하였다.
문제2)
// 필터 두개를 make_pair if (GlobalValue::PhysXCollision.end() == GlobalValue::PhysXCollision.find(std::make_pair(static_cast<UINT>(TriggerFilterdata.word0), static_cast<UINT>(OtherFilterdata.word0)))|| GlobalValue::PhysXCollision.end() == GlobalValue::PhysXCollision.find(std::make_pair(static_cast<UINT>(OtherFilterdata.word0), static_cast<UINT>(TriggerFilterdata.word0))) ) // 두개의 충돌을 체크한다면 {
체크할 필터 조합인지 확인하는 if문인데 end체크로 조합이 존재하지 않는 경우를 체크하여 제대로 된 충돌 체크를 하지 않았다.
if (GlobalValue::PhysXCollision.end() != GlobalValue::PhysXCollision.find(std::make_pair(static_cast<UINT>(TriggerFilterdata.word0), static_cast<UINT>(OtherFilterdata.word0)))|| GlobalValue::PhysXCollision.end() != GlobalValue::PhysXCollision.find(std::make_pair(static_cast<UINT>(OtherFilterdata.word0), static_cast<UINT>(TriggerFilterdata.word0))) ) // 두개의 충돌을 체크한다면 {
다음과 같이 변경하였다.
문제3)
if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) // 첫 충돌 했을 때 { GameEngineActor* TestTrigger = reinterpret_cast<GameEngineActor*>(TriggerShape->userData); GameEngineActor* TestActor = reinterpret_cast<GameEngineActor*>(OtherShape->userData); TestTrigger->isPhysXCollision = true; TestActor->isPhysXCollision = true; } if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) // 충돌이 끝날 때 { GameEngineActor* TestTrigger = reinterpret_cast<GameEngineActor*>(TriggerShape->userData); GameEngineActor* TestActor = reinterpret_cast<GameEngineActor*>(OtherShape->userData); TestTrigger->isPhysXCollision = false; TestActor->isPhysXCollision = false; }
위 식은 충돌여부는 확인할 수 있으나 무슨 컴포넌트와 충돌했는지 알 수 없다.
std::atomic_uint filterbit = (static_cast<UINT>(TriggerFilterdata.word0) | static_cast<UINT>(OtherFilterdata.word0)); if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_FOUND) // 첫 충돌 했을 때 { GameEngineActor* TestTrigger = reinterpret_cast<GameEngineActor*>(TriggerShape->userData); GameEngineActor* TestActor = reinterpret_cast<GameEngineActor*>(OtherShape->userData); TestTrigger->isPhysXCollision |= filterbit; TestActor->isPhysXCollision |= filterbit; } if (current.status & physx::PxPairFlag::eNOTIFY_TOUCH_LOST) // 충돌이 끝날 때 { GameEngineActor* TestTrigger = reinterpret_cast<GameEngineActor*>(TriggerShape->userData); GameEngineActor* TestActor = reinterpret_cast<GameEngineActor*>(OtherShape->userData); TestTrigger->isPhysXCollision = ~(~TestTrigger->isPhysXCollision | filterbit); TestActor->isPhysXCollision = ~(~TestActor->isPhysXCollision | filterbit); }
atomic_bool이 아닌 atomic_uint를 이용하여 충돌을 체크하였는데,
enum class PhysXFilterGroup //PhysX 충돌 용 그룹 { None = 0, PlayerDynamic = (1 << 0), // 플레이어 충돌체 Ground = (1 << 1), // 바닥 충돌체 Obstacle = (1 << 2), // 장애물 충돌체 GroundTrigger = (1 << 3), // 트리거 LeverTrigger = (1 << 4), // Frog_Lever PlayerSkill = (1 << 5), MonsterDynamic = (1 << 6), };
필터 그룹을 비트 값으로 가지고 있기 때문에 가능하였다.
예를 들어 PhysXFilterGroup::MonsterDynamic 를 필터로 가지고 있는 몬스터 기준으로 isPhysXCollision가 0000 0000 0000 0000일때
이 몬스터가 PhysXFilterGroup ::PlayerSkill 과 충돌한 경우
filterbit = 0000 0000 0010 0000 | 0000 0000 0100 0000가 되어
isPhysXCollision == 0000 0000 0110 0000가 된다.
업데이트 충돌 event를 실행할 때는
if ( 0 < (HitFromPlayer & static_cast<UINT>(PhysXFilterGroup::PlayerSkill))) { //PlayerSkill과 충돌한 경우 }
다음과 같이 충돌 체크를 하고 업데이트 할 수 있다.
다음 그림과 같이
플레이어 어택범위에 있으면 FirePlant Enemy는 Hit모션으로 state를 변경하도록 하였다.
'기록' 카테고리의 다른 글
231010 플레이어 문제해결, 화살 매쉬 생성, 2D방향을 3D에 적용, (0) 2023.10.10 231005 Attack 원거리 공격 (1) 2023.10.05 231002 Batch파일 경로 문제 오류 , Animation의 Start Event Function (0) 2023.10.02 20231001 PhysX Component를 가지는 액터 Position체크 (0) 2023.10.01 20230928 플레이어 버그와 최적화 (0) 2023.09.28