물리 엔진의 모멘텀 및 업데이트 문제 순서 . 이전 질문을 읽고 싶지 않은 경우

여기에 이미지 설명을 입력하십시오

이 질문은 충돌 감지 및 해결에 관한 이전 질문의 “추적”질문이며 여기에서 찾을 수 있습니다 .


이전 질문을 읽고 싶지 않은 경우 물리 엔진 작동 방식에 대한 간단한 설명은 다음과 같습니다.

모든 물리적 개체는 SSSPBody라는 클래스에 저장됩니다.

AABB 만 지원됩니다.

모든 SSSPBody는 모든 신체를 업데이트하고 중력을 처리하는 SSSPWorld라는 클래스에 저장됩니다.

모든 프레임, SSSPWorld는 모든 바디를 업데이트합니다.

모든 업데이트 된 바디는 공간 해시에서 주변 바디를 찾고 바디와 충돌을 감지해야하는지 확인합니다. 그렇다면 “collision”이벤트를 호출하고 충돌을 해결해야하는지 확인하십시오. 그렇다면 침투 벡터와 방향성 겹침을 계산 한 다음 침투를 해결하기 위해 위치를 변경하십시오.

바디가 다른 바디와 충돌 할 때 바디의 속도를 자체 속도로 설정하여 다른 바디로 속도를 전달합니다.

바디는 마지막 프레임에서 위치를 변경하지 않은 경우 속도가 0으로 설정됩니다. 또한 이동체 (예 : 리프트 또는 이동 플랫폼)와 충돌하면 리프트의 이동 차이를 계산하여 바디가 마지막 위치에서 이동하지 않았는지 확인합니다.

또한 본문의 모든 AABB 모서리가 프레임의 무언가와 겹칠 때 “분쇄”이벤트가 발생합니다.

이것은 내 게임의 전체 소스 코드입니다. 세 가지 프로젝트로 나뉩니다. SFMLStart는 엔터티의 입력, 그리기 및 업데이트를 처리하는 간단한 라이브러리입니다. SFMLStartPhysics는 SSSPBody 및 SSSPWorld 클래스가있는 가장 중요한 것입니다. PlatformerPhysicsTest는 모든 게임 로직을 포함하는 게임 프로젝트입니다.

그리고 이것은 SSSPBody 클래스의 “update”메소드이며 주석 처리 및 단순화되었습니다. 전체 SFMLStartSimplePhysics 프로젝트를 보지 않으려는 경우에만 이것을 볼 수 있습니다. (그렇더라도 주석 처리되었으므로 여전히 살펴 봐야합니다.)


.gif는 두 가지 문제를 보여줍니다.

  1. 바디가 다른 순서로 배치되면 다른 결과가 발생합니다. 왼쪽의 상자는 오른쪽 상자와 동일하며 역순으로 만 배치됩니다 (편집기에서).
  2. 두 상자는 화면 상단을 향해 추진해야합니다. 왼쪽 상황에서는 상자가 추진되지 않습니다. 오른쪽에는 그중 하나만 있습니다. 두 상황 모두 의도하지 않았습니다.

첫 번째 문제 : 업데이트 순서

이것은 이해하기 매우 쉽습니다. 왼쪽 상황에서는 맨 위 상자가 다른 상자보다 먼저 업데이트됩니다. 바닥의 ​​상자가 다른 속도로 속도를 “전달”하더라도 다음 프레임이 움직일 때까지 기다려야합니다. 움직이지 않았기 때문에 바닥 상자의 속도는 0으로 설정됩니다.

이 문제를 해결하는 방법을 모르겠습니다. 나는 전체 물리 엔진 설계에서 무언가 잘못하고 있다고 느끼기 때문에 업데이트 목록을 “정렬”하는 것에 의존하지 않는 솔루션을 선호합니다.

주요 물리 엔진 (Box2D, Bullet, Chipmunk)은 업데이트 순서를 어떻게 처리합니까?


두 번째 문제 : 한 상자 만 천장쪽으로 추진됩니다

나는 왜 이런 일이 일어나는지 이해하지 못한다. “스프링”엔티티가하는 것은 몸의 속도를 -4000으로 설정하고 스프링 자체 위에 재배치하는 것입니다. 재배치 코드를 비활성화해도 문제가 계속 발생합니다.

내 생각은 하단 상자가 상단 상자와 충돌 할 때 속도가 0으로 설정되어 있다는 것입니다. 왜 이런 일이 발생하는지 잘 모르겠습니다.


첫 번째 문제를 포기한 사람처럼 보일 수도 있지만 위의 전체 프로젝트 소스 코드를 게시했습니다. 나는 그것을 입증 할 것이 없지만 나를 믿습니다.이 문제를 해결하기 위해 열심히 노력했지만 해결책을 찾을 수 없었으며 물리와 충돌에 대한 이전 경험이 없었습니다. 나는이 두 가지 문제를 일주일 이상 해결하려고 노력했지만 지금 필사적입니다.

게임에서 많은 기능을 제거하지 않고 스스로 솔루션을 찾을 수 있다고 생각하지 않습니다 (예 : 속도 전송 및 스프링).

이 질문을 읽는 데 시간을 내 주셔서 감사합니다. 솔루션이나 제안을 생각해 보시면 더 감사드립니다.



답변

사실, 업데이트 순서 문제는 일반적인 임펄스 물리 엔진에서 매우 흔합니다. Vigil이 제안한 것처럼 힘을 가하는 것을 지연시킬 수는 없습니다. 물체가 다른 두 물체와 동시에 충돌 할 때 에너지 보존이 중단 될 수 있습니다. 일반적으로 업데이트 순서가 다르면 결과가 크게 달라 지더라도 실제로는 실제처럼 보일 수 있습니다.

어쨌든, 당신의 목적을 위해 임펄스 시스템에 충분한 딸꾹질이있어서 대신 질량 스프링 모델을 만들 것을 제안합니다.

기본 아이디어는 충돌하는 오브젝트에 힘을 가하는 한 단계에서 충돌을 해결하는 대신이 힘이 오브젝트 간의 겹침의 양과 같아야한다는 것입니다. 이것은 충돌하는 동안 실제 오브젝트가 움직임 에너지를 변형으로, 그리고 다시 움직임으로 되돌릴 때,이 시스템의 가장 큰 장점은 물체가 앞뒤로 튀어 나오지 않고 물체를 통해 힘을 이동할 수있게하며, 완전히 독립적 인 업데이트 순서를 수행 할 수 있다는 것입니다.

물체가 무한정 튀기지 않고 멈추게하려면 어떤 형태의 감쇠를 적용해야하므로 게임 방식에 따라 게임의 스타일과 느낌에 크게 영향을 줄 수 있지만 기본적인 접근 방식은 다음과 같습니다. 내부 움직임과 동등한 두 개의 접촉하는 물체에 힘을 가하고, 서로를 향해 움직일 때만 또는 다른 물체가 서로 멀어 질 때만 힘을 가할 수 있습니다. 후자는 물체가 튀는 것을 완전히 방지하는 데 사용될 수 있습니다 그들이 땅에 닿았을 때 약간 끈적 거리게 만들 것입니다.

충돌의 수직 방향으로 물체를 제동하여 마찰 효과를 줄 수도 있습니다. 제 동량은 중첩 량과 같아야합니다.

모든 물체의 질량을 동일하게함으로써 질량 개념을 쉽게 이해할 수 있으며, 단순히 가속하는 것을 무시하면 무한한 질량을 갖는 것처럼 움직일 수없는 물체가 작동합니다.

위의 내용이 명확하지 않은 경우를 대비하여 일부 의사 코드 :

//Presuming that you have done collision checks between two objects and now have  
//numbers for how much they overlap in each direction.
overlapX
overlapY
if(overlapX<overlapY){ //Do collision in direction X
    if(obj1.X>obj2.X){
        swap(obj1,obj2)
    }
    //Spring effect:
    obj1.addXvelocity-=overlapX*0.1 //Constant, the lower this is set the softer the  
                                    //collision will be.
    obj2.addXvelocity+=overlapX*0.1
    //Dampener effect:
    velocityDifference=obj2.Xvelocity-obj1.Xvelocity
    //velocityDifference=min(velocityDifference,0) //Uncomment to only dampen when  
                                                   //objects move towards each other.
    obj1.addXvelocity+=velocityDifference*0.1 //Constant, higher for more dampening.
    obj2.addXvelocity-=velocityDifference*0.1
    //Friction effect:
    if(obj1.Yvelocity>obj2.Yvelocity){
        swap(obj1,obj2)
    }
    friction=overlapX*0.01
    if(2*friction>obj2.Yvelocity-obj1.Yvelocity){
        obj1.addYvelocity+=(obj2.Yvelocity-obj1.Yvelocity)/2
        obj2.addYvelocity-=(obj2.Yvelocity-obj1.Yvelocity)/2
    }
    else{
        obj1.addYvelocity+=friction
        obj2.addYvelocity-=friction
    }
}
else{ //Do collision in direction Y

}

addXvelocity 및 addYvelocity 속성의 요점은 모든 충돌 처리가 완료된 후 객체의 속도에 추가된다는 것입니다.

편집 :
다음 순서대로 작업을 수행 할 수 있습니다. 다음 요소를 수행하기 전에 모든 요소에서 각 글 머리 기호를 수행해야합니다.

  • 충돌을 감지하면 감지되는 즉시 해결 될 수 있습니다.
  • addVelocity 값을 속도 값에 추가하고, 중력 Yvelocity를 추가하고, addVelocity 값을 0으로 재설정하고, 속도에 따라 객체를 이동하십시오.
  • 장면을 렌더링합니다.

또한 초기 게시물에서 다음 사항이 완전히 명확하지 않을 수 있음을 알고 있습니다. 중력의 영향으로 서로 겹쳐있을 때 객체가 겹칠 것입니다. 시각적으로. 물리가 더 높은 업데이트 속도로 실행되면이 문제는 줄어 듭니다. CPU 시간과 물리 정확도 사이의 합리적인 절충을 위해 120Hz로 실행하는 것이 좋습니다.

Edit2 :
매우 기본적인 물리 엔진 흐름 :

  • 충돌과 중력은 힘 / 가속을 생성합니다. acceleration = [Complicated formulas]
  • 속도에 힘 / 가속이 추가됩니다. velocity += acceleration
  • 위치에 속도가 추가됩니다. position += velocity

답변

글쎄, 당신은 분명히 쉽게 포기하는 사람이 아니며, 당신은 철분의 진짜 사람입니다.

우선, 위치와 속도는 모든 곳에서 설정됩니다. 물리 하위 시스템의 관점에서 볼 때 그것은 재난의 요리법입니다. 또한 다양한 서브 시스템에 의해 필수 항목을 변경하는 경우 “ChangeVelocityByPhysicsEngine”, “ChangeVelocityBySpring”, “LimitVelocity”, “TransferVelocity”또는 이와 유사한 것과 같은 전용 메소드를 작성하십시오. 로직의 특정 부분에 의한 변경 사항을 확인하는 기능을 추가하고 이러한 속도 변경에 추가 의미를 제공합니다. 그렇게하면 디버깅이 쉬워집니다.

첫 번째 문제

질문 자체에. 이제는 모양과 게임 로직 순서에 따라 위치와 속도 수정을 “그대로”적용합니다. 각 복잡한 것의 물리를 신중하게 하드 코딩하지 않으면 복잡한 상호 작용에는 효과가 없습니다. 별도의 물리 엔진이 필요하지 않습니다.

해킹없이 복잡한 상호 작용을 수행하려면 초기 속도로 변경된 위치를 기반으로하는 충돌 감지와 “속도 이후”를 기반으로 한 위치의 최종 변경 사이에 추가 단계를 추가해야합니다. 나는 이것이 다음과 같이 될 것이라고 상상한다.

  • 몸에 작용하는 모든 힘을 사용하여 속도를 통합하십시오 (지금 속도 수정을 적용하고 물리 엔진에 속도 계산을 남기고 대신 힘을 사용하여 물체를 이동) 새로운 속도를 사용하여 위치를 통합하십시오.
  • 충돌을 감지 한 다음 속도와 위치를 복원하고
  • 그런 다음 충돌을 처리합니다 (즉시 위치 업데이트없이 임펄스 사용, ofc, 최종 단계까지 속도 만 변경됨)
  • 새로운 속도를 다시 통합하고 임펄스를 사용하여 모든 충돌을 다시 처리합니다. 충돌은 비 탄력적입니다.
  • 결과 속도를 사용하여 위치를 최종 통합합니다.

저크 처리, FPS가 작을 때 쌓이기를 거부하는 등의 추가 사항이 나타날 수 있습니다.

두 번째 문제

이 “데드 웨이트”상자의 수직 속도는 절대 0에서 변하지 않습니다. 이상하게도 PhysSpring의 업데이트 루프에서는 속도를 지정하지만 PhysCrate의 업데이트 루프에서는 이미 0입니다. 속도가 잘못되는 라인을 찾을 수는 있지만 “Sap What Sew”상황이므로 여기서 디버깅을 중단했습니다. 디버깅이 어려워 질 때 코딩을 중단하고 모든 것을 다시 생각할 시간입니다. 그러나 코드 작성자가 코드에서 무슨 일이 일어나고 있는지 이해할 수없는 지점에 도달하면 코드베이스는 이미 깨닫지 않고 죽었습니다. 🙂

세번째 문제

간단한 타일 기반 플랫 포머를 만들기 위해 Farseer의 일부를 다시 만들어야 할 때 무언가 잘못되었다고 생각합니다. 개인적으로, 나는 당신의 현재 엔진을 엄청난 경험으로 생각하고, 더 간단하고 간단한 타일 기반 물리학을 위해 그것을 완전히 버립니다. 그렇게하는 동안 Debug.Assert와 같은 것들을 선택하는 것이 현명 할 것입니다.


답변

바디가 다른 바디와 충돌 할 때 바디의 속도를 자체 속도로 설정하여 다른 바디로 속도를 전달합니다.

문제는 이것들이 모션에 대한 근본적으로 잘못된 가정이므로, 익숙한 모션과 유사하지 않다는 것입니다.

신체가 다른 신체와 충돌하면 운동량이 보존됩니다. 이것을 “A hits B”대 “B hits A”라고 생각하는 것은 전 이적 동사를 비수 사적 상황에 적용하는 것입니다. A와 B가 충돌합니다. 결과 운동량은 초기 운동량과 같아야합니다. 즉, A와 B가 같은 질량이라면, 둘 다 원래 속도의 평균으로 이동합니다.

충돌 슬로프와 반복 솔버가 필요할 수도 있으며 안정성 문제가 발생할 수도 있습니다. Erin Catto의 GDC 프레젠테이션을 읽어보아야 할 것입니다.


답변

정말 고귀한 노력을 기울 였지만 코드 구성 방법에 근본적인 문제가있는 것 같습니다. 다른 사람들이 제안했듯이 작업을 신중한 부분으로 분리하는 데 도움이 될 수 있습니다.

  1. 광의의 단계 : 모든 물체를 반복합니다-어떤 물체가 충돌하는지 확인하기 위해 빠른 테스트 (예 : AABB)를 수행하십시오. 그렇지 않은 물체는 폐기하십시오.
  2. 좁은 단계 : 충돌하는 모든 개체를 반복합니다-충돌에 대한 침투 벡터를 계산합니다 (예 : SAT 사용).
  3. 충돌 응답 : 충돌 벡터 목록을 반복합니다. 질량을 기준으로 힘 벡터를 계산 한 다음이를 사용하여 가속 벡터를 계산합니다.
  4. 통합 : 모든 가속도 벡터를 반복하고 위치를 통합합니다 (필요한 경우 회전).
  5. 표현 : 계산 된 모든 위치를 반복하고 각 오브젝트를 렌더링합니다.

단계를 분리하면 모든 오브젝트가 점진적으로 동기화되어 현재 어려움을 겪고있는 순서 종속성이 없습니다. 코드는 일반적으로 더 간단하고 변경하기 쉽다는 것이 밝혀졌습니다. 이러한 각 단계는 상당히 일반적이며 작업 시스템을 구축 한 후에 더 나은 알고리즘을 대체 할 수 있습니다.

즉, 이러한 각 부분은 과학 자체이므로 최적의 솔루션을 찾으려고 많은 시간을 할애 할 수 있습니다. 가장 일반적으로 사용되는 일부 알고리즘으로 시작하는 것이 좋습니다.

  • 광범위한 위상 충돌 탐지 : 공간 해싱 .
  • 좁은 위상 충돌 감지 : 간단한 타일 물리학의 경우 AABB (Axis Aligned Bounding Box) 교차 테스트를 적용하면됩니다. 더 복잡한 모양을 원하면 Separating Axis Theorem을 사용할 수 있습니다 . 어떤 알고리즘을 사용하든 두 객체 사이의 교차 방향과 깊이 (침투 벡터라고 함)를 반환해야합니다.
  • 충돌 응답 : 투영 을 사용 하여 상호 침투를 해결합니다.
  • 통합 : 통합 기는 엔진의 안정성과 속도를 결정하는 가장 큰 요소입니다. 널리 사용되는 두 가지 옵션은 Verlet (빠르지 만 단순) 또는 RK4 (정확하지만 느리게) 통합입니다. Verlet 통합을 사용하면 대부분의 물리적 동작 (바운스, 회전)이 너무 많은 노력없이 작동하므로 매우 단순한 디자인으로 이어질 수 있습니다. RK4 통합을 배우기 위해 내가 본 가장 좋은 참고 문헌 중 하나는 게임 물리학에 관한 Glen Fiedler의 시리즈입니다 .

시작하기에 좋은 (그리고 명백한) 장소는 뉴턴의 운동 법칙입니다 .


답변