3P by GN⁺ | ★ favorite | 댓글 1개
  • 2D 게임 물리의 강체 충돌 해결은 이미 닿거나 겹친 물체가 다음 프레임에서 서로 관통하지 않도록 속도 변화를 계산하는 문제임
  • 게임 루프는 매 프레임 속도와 Δt로 위치를 갱신하므로, 새 위치에서 기하가 겹치면 별도 처리가 없을 때 물체가 서로 통과함
  • 충돌은 단순한 접촉 여부가 아니라, 접촉한 물체들이 현재 속도대로 계속 서로를 향해 움직이는지까지 함께 봐야 함
  • 표면에서 멀어지는지는 법선(normal) 과 속도의 내적 부호로 판단할 수 있으며, 양수면 같은 방향 성분, 음수면 반대 방향 성분이 있음
  • 두 물체의 경우 개별 속도보다 상대 속도와 충돌 법선이 중요하며, 접촉 상태에서 상대 법선 속도가 음수일 때 충돌로 볼 수 있음

강체 물리와 충돌 해결의 범위

  • 대상은 강체 물리(rigid body physics) 로, 힘을 받아도 변형되지 않는 물체를 다룸
    • 현실에서는 모든 물체가 분자 수준에서 변형되므로 완전한 강체는 존재하지 않음
    • 대부분의 물리 시뮬레이션에서 이런 세부 변형까지 계산하기는 매우 어렵거나 비용이 큼
    • 물체가 충분히 현실적으로 보인다면, 강체처럼 단순화하는 방식이 실용적임
  • 게임 엔진의 충돌 처리는 보통 두 단계로 나뉨
    • 충돌 감지(collision detection): 장면에서 어떤 물체들이 충돌 중인지 판단함
    • 충돌 해결(collision resolution): 충돌 중인 물체의 이동 방향, 속도, 재질 등을 바탕으로 이후 상태를 결정함
  • 여기서 다루는 초점은 기하학적 교차 여부를 찾는 단계가 아니라, 충돌 이후의 움직임을 정하는 충돌 해결

게임 루프에서 충돌이 생기는 방식

  • 대부분의 게임은 큰 루프 안에서 장면 속 물체들의 위치를 반복해서 다시 계산함
  • 각 반복에서 물체의 위치는 현재 속도(velocity) 를 바탕으로 갱신됨
    • 속도는 크기와 방향을 모두 가진 벡터량임
    • 화살표의 길이는 속력, 화살표가 가리키는 방향은 이동 방향을 나타냄
  • 일정 시간 간격 Δt 동안의 위치 변화는 변위(displacement) 로 표현됨
    • 변위도 크기와 방향을 가진 벡터량임
    • 게임 루프가 초당 60번 실행되면 Δt는 1/60초가 됨
  • 새 위치는 현재 속도에서 계산한 변위를 기존 위치에 더해 얻음
  • 두 물체의 새 위치가 서로의 기하를 겹치게 만들면, 별도 처리가 없을 때 물체들은 관통한 뒤 통과하게 됨

충돌 해결이 찾으려는 값

  • 충돌 해결의 목표는 시뮬레이션이 진행될 때 물체들이 더 이상 서로 관통하지 않도록 각 물체의 속도 변화를 정하는 것임
  • 충돌 전후 속도는 다음 기호로 표현됨
    • v_a,i, v_b,i: 물체 a, b의 충돌 전 속도
    • v_a,f, v_b,f: 물체 a, b의 충돌 후 속도
    • Δv_a, Δv_b: 충돌 때문에 생긴 각 물체의 속도 변화
  • 결국 충돌 해결은 Δv_aΔv_b 값을 찾는 문제임
  • 현실적으로 보이는 충돌을 만들려면 선택한 속도 변화가 관련 물리 법칙을 만족해야 함

접촉만으로는 충돌을 알 수 없음

  • 두 물체가 닿아 있다고 해서 항상 충돌 중인 것은 아님
  • 충돌은 현재 속도대로 계속 움직였을 때 물체들이 서로 관통하게 되는 상황임
  • 같은 접촉 장면이라도 두 물체의 속도 방향에 따라 충돌일 수도, 아닐 수도 있음
  • 따라서 충돌 조건에는 두 가지가 함께 필요함
    • 물체들의 기하가 닿거나 겹쳐 있어야 함
    • 물체들이 여전히 충돌 방향으로 움직이고 있어야 함

표면 법선과 멀어지는 방향

  • 물체가 어떤 표면에서 멀어지는지는 표면에 대한 법선 방향(normal direction) 으로 판단할 수 있음
  • 법선 방향은 표면에 수직이며, 표면에서 직접 멀어지는 방향을 가리킴
    • 평평한 표면에서는 모든 지점의 법선 방향이 같음
    • 곡면에서는 지점마다 법선 방향이 달라짐
    • 원의 둘레에서는 중심에서 해당 둘레 지점으로 향하는 방향이 법선 방향임
  • 법선 방향은 길이가 1인 정규화 벡터로 표현됨
    • 길이가 1인 벡터는 단위 벡터(unit vector)라고도 함
    • 정규화된 벡터임을 나타내기 위해 변수 위에 ^ 표시를 붙일 수 있음
  • 어떤 점에서의 법선 방향은 그 점에서 표면의 접선(tangent) 에 수직임

내적으로 방향 성분을 판단하기

  • 한 벡터가 다른 벡터와 얼마나 같은 방향을 향하는지 계산할 때 내적(dot product) 을 사용할 수 있음
  • 2차원 벡터에서 내적은 대응하는 성분들의 곱을 더한 값이며, 결과는 벡터가 아니라 스칼라
  • 기하학적으로 내적은 한 벡터를 다른 벡터 방향으로 투영한 스칼라 투영 길이에, 투영 대상 벡터의 길이를 곱한 값으로 볼 수 있음
  • 내적의 부호는 두 벡터의 방향 관계를 알려줌
    • 두 벡터 사이 각도가 90°보다 작으면 내적은 양수이고, 대체로 같은 방향을 가리킴
    • 각도가 90°보다 크면 내적은 음수이고, 대체로 반대 방향을 가리킴
    • 각도가 정확히 90°이면 내적은 0
  • 물체의 속도 벡터와 표면 법선의 내적이 양수이면, 물체는 그 표면에서 멀어지고 있음

두 물체 충돌에 적용하기

  • 두 상자처럼 물체가 둘이면 속도 벡터도 각 물체마다 하나씩 존재함
  • 이때는 개별 속도보다 두 물체의 상대 속도(relative velocity) 를 사용함
    • 상대 속도는 두 물체의 속도 차이임
    • 기하학적으로는 v_b의 끝에서 v_a의 끝으로 향하는 벡터임
    • 예를 들어 두 차가 각각 50km/h로 정면 충돌하는 상황은, 조건이 같다면 한 차가 정지한 차를 100km/h로 들이받는 것과 같음
  • 표면에 해당하는 방향은 충돌 법선(collision normal) 으로 표현함
    • 충돌 법선을 계산하는 방식은 충돌하는 물체의 형태나 기하에 따라 달라짐
    • 여기서의 예시는 한 물체의 점 또는 꼭짓점이 다른 물체의 변과 충돌하는 vertex-edge collision
    • vertex-edge collision에서 충돌 법선은 해당 변에 수직임
  • 관례상 물체를 a, b로 표시하면 충돌 법선은 물체 a를 향함
    • 어떤 물체를 a 또는 b라고 부르는지는 계산 전체에서 일관되기만 하면 됨

상대 법선 속도로 정의하는 충돌

  • 상대 속도 v_ab와 충돌 법선 n^의 내적을 계산하면 두 물체가 충돌 방향으로 움직이는지 판단할 수 있음
  • 이 값은 상대 법선 속도(relative normal velocity) 라고 부름
    • 상대 속도 중 충돌 법선 방향 성분임
    • 여기서는 부호가 중요하지만, 이후 충돌 중 작용하는 힘을 계산할 때도 중요한 역할을 함
  • 상대 법선 속도의 부호는 충돌 상태를 구분함
    • 값이 양수이면 두 물체는 이미 서로 멀어지고 있음
    • 값이 음수이면 두 물체는 아직 서로 들이받고 있음
  • 최종적으로는 한 물체의 점이 다른 물체와 닿아 있고, 상대 법선 속도가 음수일 때 충돌이 발생함

댓글과 토론

Hacker News 의견들
  • 안녕하세요, 글쓴이입니다! 배경을 조금 덧붙이면, 이 글은 제가 쓰려는 강체 물리 블로그 시리즈의 1편일 뿐입니다
    이 글은 저처럼 게임 개발자가 아니고 수학 배경도 강하지 않은 사람을 대상으로 했습니다. 그래서 이 분야 경험자에게는 거의 자명해 보일 개념도 꽤 오래 설명했습니다. 질문이 있으면 기꺼이 답하겠습니다
    • 피드백을 주자면, 도입부의 “Mario가 Goomba 위에서 튕겨 나오는…” 예시는 조금 오해를 부를 수 있어 보입니다. NES와 SNES의 고전 Super Mario류 게임 대부분은 이런 계산 대부분이 필요하지 않았고 쓰지도 않았습니다
      게임 개발 입문자는 충돌 처리를 하려면 강체 충돌 계산이나 Box2D 같은 2D 물리 엔진이 필요하다고 오해하는 경우가 많습니다. 당구 게임이나 Angry Birds처럼 상자가 무너지는 게임을 만들고 싶다면 맞지만, 2D 플랫포머라면 축 정렬 사각형끼리 비교해 충돌을 감지하고, 겹침을 되돌리도록 캐릭터의 X/Y 좌표를 바꾸거나 점프·착지 후 Y 속도를 설정하는 정도면 됩니다. 이렇게 하면 캐릭터 조작감도 더 세밀하게 조정하기 쉽고, 관성도 포함되지만 보통 물리적으로 사실적인 관성은 아닙니다. 초보자가 사실적인 물리를 쓰려 하면 움직임이 둥둥 뜨고 만족스럽지 않게 되기 쉽습니다
      이 단순한 물리 엔진 없는 접근으로 시작할 수 있는 튜토리얼 예시: https://www.love2d.org/wiki/Tutorial:Baseline_2D_Platformer
    • 아주 좋았습니다. “A word about math” 섹션이 정말 중요합니다. 저도 수학을 잘하는 편은 아니지만, 예전에 수학 개념을 극단적으로 단순화해서 아주 기초적인 물리 시뮬레이션을 만든 적이 있습니다
      점, 선 같은 구성 요소를 반복해서 쌓아 올렸고, 작은 단계와 시각적 디버깅 선을 많이 넣다 보니 결과는 매우 삐걱대고 느렸지만 어쨌든 어느 정도 동작했습니다
    • 글이 훌륭하고 읽기 재미있었습니다. 저도 수학 배경이 강하지 않은 사람이라 이런 “자명한” 개념을 설명해줘서 고맙습니다 :)
      앞으로 XPBD(Extended Position Based Dynamics - http://mmacklin.com/xpbd.pdf)도 읽고 설명할 계획이 있나요? 이 개념이 점점 주목받는 것 같고, 저는 Bevy에서 https://github.com/Jondolf/bevy_xpbd를 통해 꽤 성공적으로 써봤습니다. 일반적인 접근보다 더 안정적으로 보입니다
    • 글을 정말 재미있게 읽었습니다 :) 학교에서 비슷한 내용을 어려워했던 입장에서도 이해하기 쉬웠습니다
      계속 따라갈 수 있도록 RSS 피드를 추가해주면 정말 좋겠습니다
    • 설명이 정말 좋습니다!
      호기심인데, 그 페이지를 만드는 데 어떤 도구를 썼나요?
  • 오! 잘 조사했고, 깊이 설명했고, 상호작용까지 되는 글이네요
    솔직히 처음에 도메인 이름을 보고 최상위 도메인이 “.ski”인 걸 알았을 때, Mechanical Watch [1]와 다른 멋진 글을 쓴 사람의 사이트인 줄 알았습니다. 알고 보니 완전히 다른 사람이지만 품질은 비슷하네요. 이 “.ski” 최상위 도메인에는 무슨 비밀 소스가 있는 걸까요 :)
    1. https://news.ycombinator.com/item?id=31261533
    • 이유는 아주 단순합니다. “ski”는 폴란드 성씨에서 가장 흔한 접미사이고, 가장 유명한 예가 Kowalski입니다. 폴란드인이거나 폴란드계인 사람이 꽤 많습니다
      우리가 여기서 좋아하는 https://ciechanow.ski의 글쓴이도 Apple에서 일하는 폴란드 프로그래머입니다
  • 지금 아들과 함께 2D 우주 슈팅 게임을 사이드 프로젝트로 만들고 있습니다. 위에서 내려다보는 시점이고, 각 플레이어가 어떤 함선을 조종해 우주 잔해가 가득한 닫힌 공간을 날아다니며 상대를 쏘는 구상입니다
    이 게임의 중요한 요소는 우주 잔해를 경기장 안에서 움직일 수 있고, 이를 창의적으로 활용해 상대를 가두거나 목표 달성을 막는 식으로 쓰는 것입니다. 프로젝트의 일부로 게임 엔진은 아예 건너뛰려고 했습니다. 아들에게 애플리케이션 구조를 조금 더 가르치고 싶었고, 나중에는 기성 게임 엔진을 쓰더라도 적어도 한 번은 전부 구현해보는 과정을 거치고 싶었습니다. 충돌 감지와 처리에 접근하기 전까지는 괜찮았습니다. 그때부터 상황이 빠르게 나빠졌습니다. 이론 수학 배경이 있는데도 엄청난 양의 경계 사례에 금방 압도됐고, 결국 포기하고 Box2D를 쓰기로 했습니다. 전문 게임 개발자는 아니지만 개발 경력 20년 이상에 수학 배경도 있는데, 그래도 이 문제를 과소평가하는 실수를 했습니다. 말로만 하면 쉬워 보이지만 세부로 들어갈수록 복잡도가 기하급수적으로 커지는 문제 같습니다
    • 그 게임에 사실적인 물리 충돌이 꼭 필요했나요? 아니라면 불필요한 복잡도일 수 있습니다. 2000년 이전의 2D 슈팅 게임은 거의 전부, 이후 게임도 아주 일부만 그런 방식을 씁니다
      아주 단순한 사각형 비교로 슈팅 게임을 만드는 일반적인 방법은 여기 있습니다: https://kidscancode.org/blog/2016/08/pygame_shmup_part_3/
      다만 우주 잔해 객체들이 현실적으로 충돌하고 뭉쳐야 하며, 플레이어 함선이 무거운 물체 무리를 밀어내기 어렵게 만들고 싶다면 물리 라이브러리를 쓰는 것이 합리적입니다
    • Verlet 적분 [1]은 봤나요? 여러 용도에 꽤 설득력 있고 실용적이며, 실제로 꽤 단순합니다. 이 훌륭한 튜토리얼 [2]을 보고 몇 시간 만에 기본 물리 시스템을 만들 수 있어서 스스로 놀랐습니다
      [1]https://m.youtube.com/watch?v=lS_qeBy3aQI&pp=ygUSVmVybGV0IGl...
      [2]https://m.youtube.com/watch?v=3HjO_RGIjCU&pp=ygUSdmVybGV0IGl...
    • 그래도 아들에게 좋은 교훈이 될 것 같습니다. 프로젝트의 모든 부분에서 순수하게 직접 만들겠다는 꿈을 따르는 게 항상 가치 있는 건 아니니까요
    • 지금이 아니더라도 나중에 유용한 참고 자료가 될 겁니다. http://www.jeffreythompson.org/collision-detection/table_of_...는 점, 원, 사각형, 선, 다각형, 삼각형 사이의 충돌 감지를 다룹니다
  • 저는 N 게임의 설명을 늘 좋아했습니다: https://www.metanetsoftware.com/technique/tutorialA.html
    Flash가 어디에나 있던 시절이었죠
  • 이 주제로 튕기고 충돌하는 공이 들어간 TypeScript 데모를 만들어보며 재미있었습니다. 많이 배웠습니다
    코드: https://github.com/vandrieu/canvas-bouncing-ball
    충돌 로직은 src/collision.ts에 있습니다
    결과/데모: https://vandrieu.github.io/canvas-bouncing-ball/
    • 정말 좋은 데모입니다, 잘 만들었습니다! 괜찮다면 이걸 작은 멀티플레이어 게임으로 바꿔보고 싶습니다
      가능하다면 라이선스를 추가해줄 수 있나요?
  • 강체 동역학과 제약 조건까지 더 깊이 가고 싶다면, 이 블로그 글 시리즈가 아주 유용했습니다: https://www.toptal.com/game/video-game-physics-part-i-an-int...
  • 충돌은 물체 사이의 쌍별 비교차 제약을 위반한 것입니다. 충돌 힘은 이 제약의 라그랑주 승수입니다. 충돌 법선은 한 물체의 구성에 대한 제약 함수의 정규화된 편미분입니다
    • 그런 방식은 1kHz 이상으로 물리를 계산하고, 에너지 보존을 존중하는 수치적으로 안정적인 적분 알고리즘을 쓸 때는 잘 맞을 것 같습니다
      하지만 게임에서는 종종 30Hz까지 내려가는 물리 갱신에 임의적인 Euler-Cromer 방식을 쓰기 때문에, 꽤 다른 접근이 필요합니다
    • 흥미롭네요! 이 관점을 더 설명하는 자료가 있나요?
  • 정말 멋집니다! 설명, 상호작용, 특히 글의 친근한 어조가 좋았습니다. 다음 글들도 기대됩니다
  • 2D 강체 물리 엔진을 만드는 건 정말 재미있는 프로젝트입니다. 저는 선형대수를 배우기 전에 JavaScript로 하나 만들어봤고, 동작하게 하려고 수학을 깊이 파고들었습니다
    몇 달을 들였지만 널리 알려진 기본을 조금 넘어서는 수준에서 겨우 표면만 긁은 정도였습니다. 물체가 서로 눌려 들어가거나 떨리지 않는 안정적인 엔진을 만드는 일은 바닥 없는 토끼굴이고, 제가 찾을 수 있었던 수학-heavy한 글들도 거의 다루지 않았습니다. 저는 Christ Hecker의 오래된 글 시리즈로 수학을 이해했습니다
    http://www.chrishecker.com/Rigid_Body_Dynamics
    • 맞습니다! “Part 3: Collision Response”가 사실상 이 글들의 참고 자료로 쓰고 있는 내용입니다
  • JavaScript를 배우려고 canvas로 시작했고, 게임 개발 경험 없이 귀여운 작은 브라우저 게임 몇 개를 만들었습니다. 그중 하나는 Galaga 클론인데, 대체로 잘 동작합니다
    어려운 부분은 투사체 충돌입니다. 총알의 현재 위치와 다음 시간 단계의 위치를 잡고, 적의 히트박스도 같은 방식으로 보면서 교차 여부를 확인해야 했는데, 저는 현재 시간 단계만 검사했습니다. 그래서 총알이 마법처럼 적을 피해 지나갈 수 있습니다! 바보 같은 일이죠. 언젠가 돌아가서 고칠지도 모르겠습니다