2D 플랫 포머 AABB 충돌 문제


51

http://dl.dropbox.com/u/3724424/Programming/Gifs/game6.gif

AABB 충돌 해결에 문제가 있습니다.


X 축을 먼저 확인한 다음 Y 축을 해결하여 AABB 교차를 해결합니다. 이것은이 버그를 방지하기 위해 수행됩니다 : http://i.stack.imgur.com/NLg4j.png


현재 방법은 객체가 플레이어로 이동하고 플레이어를 가로로 밀어야 할 때 잘 작동합니다. .gif에서 볼 수 있듯이 수평 스파이크는 플레이어를 올바르게 밀어냅니다.


그러나 수직 스파이크가 플레이어로 이동할 때 X 축이 여전히 먼저 해결됩니다. 이것은 "스파이크를 리프트로 사용"을 불가능하게합니다.

플레이어가 수직 스파이크로 이동할 때 (중력의 영향을 받아 그 안에 빠짐) X 축에서 겹치지 않기 때문에 Y 축을 눌렀습니다.


내가 시도한 것은이 링크의 첫 번째 답변에 설명 된 방법입니다 : 2D 직사각형 객체 충돌 감지

그러나 스파이크와 움직이는 객체는 속도가 아닌 위치를 변경하여 이동하며 Update () 메서드가 호출 될 때까지 다음 예측 위치를 계산하지 않습니다. 말할 필요도없이이 솔루션은 효과가 없었습니다. :(


위에서 설명한 두 가지 경우가 의도 한대로 작동하도록 AABB 충돌을 해결해야합니다.

이것은 현재 충돌 소스 코드입니다 : http://pastebin.com/MiCi3nA1

이 버그가 처음부터 엔진에 존재했기 때문에 누군가가 이것을 볼 수 있다면 정말 감사 할 것입니다. 나는 성공하지 않고 좋은 해결책을 찾기 위해 고심하고 있습니다. 이것은 심각하게 충돌 코드를 보면서 밤을 보내고 "재미있는 부분"에 도달하고 게임 로직을 코딩하지 못하게합니다.


XNA AppHub 플랫 포머 데모에서와 동일한 충돌 시스템을 구현하려고 시도했습니다 (대부분의 내용을 복사하여 붙여 넣음). 그러나 "점프"버그는 게임에서 발생하지만 AppHub 데모에서는 발생하지 않습니다. [점프 버그 : http://i.stack.imgur.com/NLg4j.png ]

점프하려면 플레이어가 "onGround"인지 확인한 다음 Velocity.Y에 -5를 추가하십시오.

플레이어의 Velocity.X가 Velocity.Y보다 높으므로 (그림의 네 번째 패널 참조) onGround는 사용하지 않아야 할 경우 true로 설정되어 공중에서 점프 할 수 있습니다.

플레이어의 Velocity.X가 Velocity.Y보다 높지 않기 때문에 AppHub 데모에서는 이러한 현상이 발생하지 않는다고 생각하지만 오해 할 수 있습니다.

X 축을 먼저 해결 한 다음 Y 축을 해결하여이 문제를 해결했습니다. 그러나 위에서 언급 한 것처럼 스파이크와의 충돌을 막습니다.


5
실제로 아무 문제가 없습니다. 내가 사용하는 방법은 먼저 X 축을 누른 다음 Y 축을 푸시합니다. 그것이 수직 스파이크에서도 플레이어가 수평으로 밀리는 이유입니다. "점프 문제"를 피하고 플레이어를 얕은 축 (최소 관통력을 가진 축)으로 밀어 넣는 솔루션을 찾아야하지만 찾을 수 없습니다.
Vittorio Romeo

1
플레이어가 어떤 장애물을 만지고 있는지 감지하고이를 해결할 수 있습니다.
Ioachim

4
@Johnathan Hobbs, 질문을 읽으십시오. Vee는 자신의 코드가 수행하는 작업을 정확히 알고 있지만 특정 문제를 해결하는 방법을 모릅니다. 코드를 단계별로 실행해도이 상황에서는 도움이되지 않습니다.
AttackingHobo

4
@Maik @Jonathan : 프로그램에 "잘못되는"것은 없습니다. 그는 자신의 알고리즘이 자신이 원하는 것을 수행하지 않는 위치와 이유를 정확하게 이해합니다. 그는 원하는 것을 수행하기 위해 알고리즘을 변경하는 방법을 모릅니다. 따라서이 경우 디버거는 유용하지 않습니다.
BlueRaja-대니 Pflughoeft

1
@AttackingHobo @BlueRaja 동의하며 내 의견을 삭제했습니다. 그것은 단지 무슨 일이 일어나고 있는지에 대한 결론으로 ​​뛰어 들었습니다. 나는 당신이 그것을 설명하고 적어도 한 사람을 잘못 인도 한 것에 대해 사과드립니다. 솔직히, 당신은 내가 다음에 어떤 반응을 떠나기 전에 질문을 제대로 흡수 할 것을 의지 할 수 있습니다.
doppelgreener

답변:


9

XNA AppHub 플랫 포머 데모 에 "점프"버그가없는 이유를 알아 냈습니다 . 데모는 충돌 타일을 위에서 아래로 테스트합니다 . "벽"에 도달하면 플레이어가 여러 타일과 겹칠 수 있습니다. 한 충돌을 해결하면 다른 충돌도 해결할 수 있기 때문에 해결 순서가 중요합니다 (그러나 다른 방향으로). 이 onGround속성은 플레이어를 y 축에서 위로 밀어 충돌이 해결 된 경우에만 설정됩니다. 이전 해상도가 플레이어를 아래로 및 / 또는 가로로 밀면이 해상도가 발생하지 않습니다.

이 줄을 변경하여 XNA 데모에서 "점프"버그를 재현 할 수있었습니다.

for (int y = topTile; y <= bottomTile; ++y)

이에:

for (int y = bottomTile; y >= topTile; --y)

(또한 물리 관련 상수 중 일부를 조정했지만 이것은 중요하지 않습니다.)

bodiesToCheck게임 충돌을 해결하기 전에 y 축을 정렬 하면 "점프"버그가 해결 될 수 있습니다. XNA 데모와 Trevor가 제안한 것처럼 "얕은"침투 축에서 충돌을 해결하는 것이 좋습니다 . 또한 XNA 데모 플레이어는 충돌 가능한 타일의 두 배 크기이므로 여러 충돌 사례가 발생할 가능성이 높습니다.


흥미로운 것 같습니다 ... 충돌을 감지하기 전에 Y 좌표로 모든 것을 정렬하면 내 모든 문제를 해결할 수 있다고 생각하십니까? 캐치가 있습니까?
Vittorio Romeo

Aaaand 나는 "캐치"를 발견했다. 이 방법을 사용하면 점프 버그가 수정되지만 천장에 닿은 후 Velocity.Y를 0으로 설정하면 다시 발생합니다.
Vittorio Romeo

@Vee : 루프 Position = nextPosition내부에 즉시 설정 합니다 for. 그렇지 않으면 원치 않는 충돌 해결 (설정 onGround)이 계속 발생합니다. 천장에 닿을 때 플레이어를 수직으로 아래로 밀지 말고 위로 onGround올리지 않아야합니다. 이것이 XNA 데모가하는 방식이며 "천장"버그를 재현 할 수 없습니다.
Leftium

pastebin.com/TLrQPeBU- 이것은 내 코드입니다. "nextPosition"변수를 사용하지 않고 실제로 Position 자체에서 작업합니다. XNA 데모에서와 같이 작동해야하지만 Velocity.Y를 0으로 설정하는 줄 (57 줄)을 주석 처리하지 않으면 점프 버그가 발생합니다. 내가 그것을 제거하면, 플레이어는 천장에 뛰어들 때 계속 떠 있습니다. 이 문제를 해결할 수 있습니까?
Vittorio Romeo

@Vee : 코드 onGround에 설정 방법 이 표시되지 않으므로 점프가 잘못 허용되는 이유를 조사 할 수 없습니다. XNA 데모는 isOnGround내부 의 유사한 속성을 업데이트합니다 HandleCollisions(). 또한 Velocity.Y0으로 설정 한 후에 왜 중력이 플레이어를 다시 아래로 움직이지 않습니까? 내 생각 엔 그 onGround부동산이 잘못 설정되었다는 것입니다. 어떻게 XNA 데모 업데이트를 살펴 가지고 previousBottomisOnGround( IsOnGround)를.
Leftium

9

가장 간단한 해결책은 충돌을 해결하기 전에 월드의 모든 객체에 대해 두 충돌 방향을 확인하고 두 개의 "복합 충돌"중 작은 것을 해결하는 것입니다. 이것은 항상 x를 먼저 해결하거나 항상 y를 먼저 해결하는 대신 가능한 적은 양으로 해결한다는 것을 의미합니다.

코드는 다음과 같습니다.

// This loop repeats, until our object has been fully pushed outside of all
// collision objects
while ( StillCollidingWithSomething(object) )
{
  float xDistanceToResolve = XDistanceToMoveToResolveCollisions( object );
  float yDistanceToResolve = YDistanceToMoveToResolveCollisions( object );
  bool xIsColliding = (xDistanceToResolve != 0.f);

  // if we aren't colliding on x (not possible for normal solid collision 
  // shapes, but can happen for unidirectional collision objects, such as 
  // platforms which can be jumped up through, but support the player from 
  // above), or if a correction along y would simply require a smaller move 
  // than one along x, then resolve our collision by moving along y.

  if ( !xIsColliding || fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) )
  {
    object->Move( 0.f, yDistanceToResolve );
  }
  else // otherwise, resolve the collision by moving along x
  {
    object->Move( xDistanceToResolve, 0.f );
  }
}

큰 개정 : 다른 답변에 대한 논평을 읽음으로써, 나는이 추정되지 않은 가정을 마침내 발견했습니다. 사람들은이 접근법으로 보았습니다). 좀 더 자세히 설명하자면, 이전에 참조한 함수가 실제로 수행해야하는 기능을보다 명확하게 보여주는 의사 코드가 더 있습니다.

bool StillCollidingWithSomething( MovingObject object )
{
  // loop over every collision object in the world.  (Implementation detail:
  // don't test 'object' against itself!)
  for( int i = 0; i < collisionObjectCount; i++ )
  {
    // if the moving object overlaps any collision object in the world, then
    // it's colliding
    if ( Overlaps( collisionObject[i], object ) )
      return true;
  }
  return false;
}

float XDistanceToMoveToResolveCollisions( MovingObject object )
{
  // check how far we'd have to move left or right to stop colliding with anything
  // return whichever move is smaller
  float moveOutLeft = FindDistanceToEmptySpaceAlongNegativeX(object->GetPosition());
  float moveOutRight = FindDistanceToEmptySpaceAlongX(object->GetPosition());
  float bestMoveOut = min( fabs(moveOutLeft), fabs(moveOutRight) );

  return minimumMove;
}

float FindDistanceToEmptySpaceAlongX( Vector2D position )
{
  Vector2D cursor = position;
  bool colliding = true;
  // until we stop colliding...
  while ( colliding )
  {
    colliding = false;
    // loop over all collision objects...
    for( int i = 0; i < collisionObjectCount; i++ )
    {
      // and if we hit an object...
      if ( Overlaps( collisionObject[i], cursor ) )
      {
        // move outside of the object, and repeat.
        cursor.x = collisionObject[i].rightSide;
        colliding = true;

        // break back to the 'while' loop, to re-test collisions with
        // our new cursor position
        break;
      }
    }
  }
  // return how far we had to move, to reach empty space
  return cursor.x - position.x;  
}

이것은 "객체 별"테스트가 아닙니다. 월드 맵의 각 타일에 대해 움직이는 객체를 개별적으로 테스트하고 해결하여 작동하지 않습니다 (이 접근 방식은 안정적으로 작동하지 않으며 타일 크기가 감소함에 따라 점점 치명적인 방식으로 실패합니다). 대신, 월드 맵의 모든 오브젝트에 대해 움직이는 오브젝트를 동시에 테스트 한 다음 전체 월드 맵과의 충돌을 기반으로 해결합니다.

이것은 (예를 들어) 벽 내의 개별 벽 타일이 두 개의 인접한 타일 사이에서 플레이어를 위아래로 튕기 지 않도록하여 플레이어가 존재하지 않는 공간 사이에 갇히게하는 방법입니다. 충돌 해상도 거리는 단일 타일의 경계 뿐만 아니라 그 위에 다른 솔리드 타일이있을 수도있는 것이 아니라 항상 세계의 빈 공간까지 계산 됩니다.


1
i.stack.imgur.com/NLg4j.png- 위의 방법을 사용하면 발생합니다.
Vittorio Romeo

1
어쩌면 나는 코드를 올바르게 이해하지 못했지만 다이어그램에서 문제를 설명해 드리겠습니다. 플레이어가 X 축에서 더 빠르기 때문에 X 침투가 Y 침투보다 큽니다. 충돌은 Y 축을 선택하고 (투과도가 작기 때문에) 플레이어를 세로로 밀어 넣습니다. 플레이어의 속도가 느려서 Y 침투가 항상 X 침투보다> 경우에는 발생하지 않습니다.
Vittorio Romeo

1
@Vee이 접근법을 사용하여 잘못된 동작을하는 것으로 다이어그램의 어느 창에서 언급하고 있습니까? 나는 창 5를 가정합니다. 플레이어가 Y보다 X에서 덜 관통하는 것은 X 축을 따라 밀려 나올 것입니다. 이는 올바른 행동입니다. 실제 침투를 기반으로하지 않고 (이미지에서와 같이) 속도를 기반으로 해상도의 축을 선택하는 경우에만 잘못된 동작을 얻습니다 (제안한대로).
트레버 파월

1
@Trevor : 아, 당신이 무슨 말을하는지 봅니다. 이 경우 간단하게 만들 수 있습니다if(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
BlueRaja-Danny Pflughoeft

1
@BlueRaja 좋은 지적. 제안한대로 적은 수의 조건을 사용하도록 답변을 편집했습니다. 감사!
트레버 파월

6

편집하다

나는 그것을 개선했다

내가 변경 한 핵심은 교차점을 분류하는 것 같습니다.

교차로는 다음 중 하나입니다.

  • 천장 범프
  • 지면 타격 (바닥에서 밀려남)
  • 공중 벽 충돌
  • 접지 된 벽 충돌

순서대로 해결합니다

플레이어가 타일에서 최소 1/4 이상인지면 충돌로 정의

이것은 지상 충돌이며 플레이어 ( 파란색 )는 타일 ( 검정색 ) 위에 앉습니다.지상 충돌

그러나 이것은 지상 충돌이 아니며 플레이어는 타일에 착륙 할 때 타일의 오른쪽에서 "슬립"합니다. 지상 충돌 플레이어가 될 수 없습니다

이 방법으로 플레이어는 더 이상 벽 측면에 걸리지 않습니다.


나는 당신의 방법을 시도했지만 (내 구현 : pastebin.com/HarnNnnp 참조 ) 플레이어의 속도를 0으로 설정할 위치를 모르겠습니다. encrY <= 0 인 경우 0으로 설정하려고 시도했지만 플레이어가 공중에서 멈출 수있게했습니다. 벽을 따라 미끄러지면서 반복적으로 벽을 뛰어 넘습니다.
Vittorio Romeo

그러나 이것은 플레이어가 스파이크와 상호 작용할 때 올바르게 작동합니다 (.gif에 표시됨). 벽 점프 (벽에 갇히는 문제) 문제를 해결하도록 도와 주시면 정말 감사하겠습니다. 내 벽은 여러 AABB 타일로 만들어졌습니다.
Vittorio Romeo

다른 의견을 게시 해 죄송하지만 새 프로젝트에서 코드를 복사하여 붙여넣고 sp를 5로 변경하고 타일을 벽 모양으로 생성하면 점프 버그가 발생합니다 (이 다이어그램 참조 : i.stack.imgur.com /NLg4j.png)
비토리오 로미오

좋아, 지금 해봐
bobobobo

작업 코드 샘플의 경우 +1 (하지만 가로로 움직이는 "스파이크"블록 누락 :)
Leftium

3

참고 사항 :
내 엔진은 개체가 이동할 때만 현재 개체가 차지하고있는 격자 사각형에서만 모든 개체와의 충돌을 실시간으로 검사하는 충돌 감지 클래스를 사용합니다.

내 솔루션 :

2d 플랫 포머에서 이와 같은 문제에 부딪쳤을 때 다음과 같은 작업을 수행했습니다.

-(엔진이 가능한 충돌을 신속하게 감지)
-(게임은 엔진에 특정 객체의 정확한 충돌을 확인하도록 알려줍니다) [오브젝트의 벡터를 반환합니다 *]
-(오브젝트는 침투 깊이 및 다른 객체의 이전 위치와 관련된 이전 위치를 보며, 어느 쪽을 밀어 내야할지 결정
)


"(객체는 다른 물체의 이전 위치에 상대적인 이전 위치뿐만 아니라 침투 깊이를보고 어느 쪽을 밀어 내야할지 결정합니다." "내가 관심있는 부분입니다. .gif와 다이어그램으로 표시된 상황에서 성공합니까?
Vittorio Romeo

이 방법이 적용되지 않는 상황 (고속 이외의 상황)을 아직 찾지 못했습니다.
ultifinitus

잘 들리지만 실제로 침투를 어떻게 해결하는지 설명 할 수 있습니까? 항상 가장 작은 축에서 해결합니까? 충돌 측면을 확인합니까?
Vittorio Romeo

사실 작은 축은 좋은 (기본) 방법이 아닙니다. 이모. 나는 일반적으로 어느 쪽이 이전에 다른 물체의 바깥쪽에 있었는지에 따라 결정합니다. 실패하면 속도와 깊이를 기준으로 확인합니다.
ultifinitus

2

비디오 게임에서 나는 접근 방식을 프로그래밍하여 위치가 유효한지 알려주는 기능을 갖도록했습니다. bool ValidPos (int x, int y, int wi, int he);

이 함수는 게임의 모든 지오메트리에 대해 경계 상자 (x, y, wi, he)를 확인하고 교차점이 있으면 false를 반환합니다.

이동하려면 오른쪽으로 말하고 플레이어 위치를 잡고 x에 4를 더한 다음 위치가 유효한지 확인하십시오. 유효하지 않은 경우 +3, +2 등으로 확인하십시오.

중력도 필요하다면지면에 닿지 않는 한 자라는 변수가 필요합니다 (접지 : ValidPos (x, y + 1, wi, he) == true, y는 아래쪽으로 양수입니다). 그 거리를 움직일 수 있다면 (즉, ValidPos (x, y + gravity, wi, he)는 true를 반환합니다) 넘어지는 경우가 있습니다. 넘어 질 때 캐릭터를 제어 할 수 없을 때 유용합니다.

이제, 당신의 문제는 당신이 세상에 물건을 가지고 있다는 것입니다. 먼저 이전 위치가 여전히 유효한지 확인해야합니다!

그렇지 않은 경우 해당 위치를 찾아야합니다. 게임 내 개체가 게임 회 전당 2 픽셀보다 빠르게 이동할 수없는 경우 위치 (x, y-1)이 유효한지 확인한 다음 (x, y-2), (x + 1, y) 등을 확인해야합니다. 등 (x-2, y-2)에서 (x + 2, y + 2) 사이의 전체 공간을 확인해야합니다. 유효한 위치가 없으면 '파쇄'되었음을 의미합니다.

HTH

발 몬드


Game Maker 시절에이 방법을 다시 사용했습니다. 나는 더 나은 검색으로 이동 속도를 높이거나 향상시킬 수있는 방법을 생각할 수 있습니다 (가는 거리와 지오메트리에 따라 이동 한 공간에 대한 이진 검색과 같이 느릴 수도 있음). 그러나이 접근법의 주요 단점은 가능한 모든 충돌에 대해 한 번의 확인을하는 대신 위치 변경이 확인되는 모든 작업을 수행합니다.
Jeff

"당신은 당신의 위치 변화가 수표에 무엇이든하고있다"미안하지만 정확히 무엇을 의미 하는가? 물론 BTW는 공간 분할 기술 (BSP, Octree, quadtree, Tilemap 등)을 사용하여 게임 속도를 높이 지 만 항상 어쨌든 (지도가 큰 경우)해야합니다. 그러나 문제는 아닙니다. 플레이어를 이동시키는 데 사용되는 알고리즘에 대해
Valmond

@Valmond @Jeff는 플레이어가 10 픽셀을 왼쪽으로 움직이면 최대 10 개의 서로 다른 충돌 감지를 할 수 있다고 생각합니다.
조나단 코넬

그것이 그가 의미하는 바라면, 그는 옳고 그 방식이어야합니다 (누가 우리가 +7이 아니라 +6에 멈추었는지 알 수 있습니까?). 컴퓨터가 빠르기 때문에 S40 (~ 200mhz 및 그렇게 빠른 kvm)에서 이것을 사용했으며 매력처럼 작동했습니다. 항상 초기 알고리즘을 최적화 할 수는 있지만 항상 OP와 같은 코너 케이스를 제공합니다.
Valmond

그것이 바로 제가 의미 한 바입니다
Jeff

2

이 질문에 대답하기 전에 몇 가지 질문이 있습니다. 첫째, 벽에 붙어있는 원래 버그에서 왼쪽 개별 타일의 타일이 하나의 큰 타일과 반대입니까? 그리고 만약 그렇다면 플레이어가 그들 사이에 갇히게 되었습니까? 두 가지 질문에 모두 맞다면, 새로운 직책이 유효한지 확인하십시오 . 즉, 플레이어에게 이동을 지시하는 위치에 충돌이 있는지 확인해야합니다. 아래에 설명 된대로 따라서 최소 변위를 해결하고 그 기반으로 플레이어를 이동에만 그 경우 저기로 움직여. 코 아래에서도 거의 : P 이것은 실제로 "코너 케이스"라고하는 또 다른 버그를 소개합니다. 본질적으로 모서리 측면에서 (.gif에서 수평 스파이크가 나오는 왼쪽 하단과 같이 스파이크가없는 경우) 충돌을 해결하지 못합니다. 왜냐하면 생성 한 해상도 중 어느 것도 유효한 위치로 이어지지 않는다고 생각하기 때문입니다. . 이 문제를 해결하려면 충돌이 해결되었는지 여부와 모든 최소 침투 해상도 목록을 유지하십시오. 그런 다음 충돌이 해결되지 않은 경우 생성 된 모든 해상도를 반복하고 최대 X 및 최대 Y 해상도를 추적하십시오 (최대 값이 동일한 해상도에서 나올 필요는 없음). 그런 다음 해당 최대 값의 충돌을 해결하십시오. 이것은 내가 만난 문제뿐만 아니라 모든 문제를 해결하는 것으로 보입니다.

    List<Vector2> collisions = new List<Vector2>();
        bool resolved = false;
        foreach (Platform p in testPlat)
        {
            Vector2 dif = p.resolveCollision(player.getCollisionMask());

            RectangleF newPos = player.getCollisionMask();

            newPos.X -= dif.X;
            newPos.Y -= dif.Y;


            if (!PlatformCollision(newPos)) //This checks if there's a collision (ie if we're entering an invalid space)
            {
                if (dif.X != 0)
                    player.velocity.X = 0; //Do whatever you want here, I like to stop my player on collisions
                if (dif.Y != 0)
                    player.velocity.Y = 0;

                player.MoveY(-dif.Y);
                player.MoveX(-dif.X);
                resolved = true;
            }
            collisions.Add(dif);
        }

        if (!resolved)
        {
            Vector2 max = Vector2.Zero;

            foreach (Vector2 v in collisions)
            {
                if (Math.Abs(v.X) > Math.Abs(max.X))
                {
                    max.X = v.X;
                }
                if (Math.Abs(v.Y) > Math.Abs(max.Y))
                {
                    max.Y = v.Y;
                }
            }

            player.MoveY(-max.Y);

            if (max.Y != 0)
                player.velocity.Y = 0;
            player.MoveX(-max.X);

            if (max.X != 0)
                player.velocity.X = 0;
        }

또 다른 질문은 하나의 타일 또는 개별 타일을 표시하는 스파이크입니까? 그것들이 개별적인 얇은 타일이라면, 수평 타일과 수직 타일에 대해 아래에서 설명하는 것과 다른 접근법을 사용해야 할 수도 있습니다. 그러나 그들이 전체 타일이라면 이것이 작동해야합니다.


기본적으로 이것이 @Trevor Powell이 묘사 한 것입니다. AABB 만 사용하기 때문에 하나의 사각형이 다른 사각형을 얼마나 많이 관통하는지 확인하면됩니다. 그러면 X 축과 Y에 수량이 표시됩니다. 둘 중 최소값을 선택하고 충돌하는 객체를 해당 축을 따라 해당 양만큼 이동합니다. 이것이 AABB 충돌을 해결하는 데 필요한 전부입니다. 이러한 충돌에서 둘 이상의 축을 따라 이동할 필요가 없으므로 최소 이동 만하므로 먼저 이동할 축에 대해 혼동해서는 안됩니다.

메타넷 소프트웨어에는 접근 방식에 대한 고전적인 자습서가 있습니다 . 그것은 또한 다른 형태로 들어갑니다.

다음은 두 사각형의 오버랩 벡터를 찾기 위해 만든 XNA 함수입니다.

    public Point resolveCollision(Rectangle otherRect)
    {
        if (!isCollision(otherRect))
        {
            return Point.Zero;
        }

        int minOtherX = otherRect.X;
        int maxOtherX = otherRect.X + otherRect.Width;

        int minMyX = collisionMask.X;
        int maxMyX = collisionMask.X + collisionMask.Width;

        int minOtherY = otherRect.Y;
        int maxOtherY = otherRect.Y + otherRect.Height;

        int minMyY = collisionMask.Y;
        int maxMyY = collisionMask.Y + collisionMask.Height;

        int dx, dy;

        if (maxOtherX - minMyX < maxMyX - minOtherX)
        {
            dx = (maxOtherX - minMyX);
        }
        else
        {
            dx = -(maxMyX - minOtherX);
        }

        if (maxOtherY - minMyY < maxMyY - minOtherY)
        {
            dy = (maxOtherY - minMyY);
        }
        else
        {
            dy = -(maxMyY - minOtherY);
        }

        if (Math.Abs(dx) < Math.Abs(dy))
            return new Point(dx, 0);
        else
            return new Point(0, dy);
    }

(나는 그것을 구현하는 더 좋은 방법이 있다고 확신하기 때문에 따르기가 간단하기를 바랍니다 ...)

isCollision (Rectangle)은 말 그대로 XNA의 Rectangle.Intersects (Rectangle)에 대한 호출입니다.

나는 이것을 움직이는 플랫폼으로 테스트했으며 정상적으로 작동하는 것 같습니다. .gif와 유사한 테스트를 더 수행하여 작동하지 않는지 확인하고 다시 알려 드리겠습니다.



나는 얇은 플랫폼에서 작동하지 않는다는 의견에 회의적입니다. 나는 그렇지 않은 이유를 생각할 수 없다. 또한 소스를 원한다면 XNA 프로젝트를 업로드 할 수 있습니다
Jeff

그래, 나는 이것을 조금 더 시험해 보았고 벽에 갇히는 같은 문제로 되돌아 갔다. 두 타일이 서로 나란히 위치하면 아래쪽 타일 때문에 위쪽으로 이동 한 다음 위쪽 타일에서 오른쪽으로 나가도록 지시하므로 y 속도가 충분히 느리면 중력 이동을 효과적으로 무시할 수 있기 때문입니다 . 나는 이것을 위해 해결책을 조사해야 할 것이다. 나는이 문제를 전에 기억했던 것처럼 보인다.
Jeff

좋아, 나는 첫 번째 문제를 해결할 수있는 매우 해킹 된 방법을 생각해 냈습니다. 이 솔루션에는 각 AABB에 대한 최소 침투를 찾아서 X가 가장 큰 것만 해결 한 다음 가장 큰 Y에만 해결하는 두 번째 통과를 해결하는 것이 포함됩니다. 수평으로, 원래의 고착 문제가 사라졌습니다. 그것은 해키이며, 그것이 다른 모양으로 어떻게 변환 될지 전혀 모른다. 또 다른 가능성은 접촉하는 형상을 용접하는 것이지만, 시도조차하지 않았습니다
Jeff

벽은 16x16 타일로 만들어집니다. 스파이크는 16x16 타일입니다. 최소 축에서 해결하면 점프 버그가 발생합니다 (다이어그램 참조). "매우 해킹 된"솔루션이 .gif에 표시된 상황에서 작동합니까?
Vittorio Romeo

그렇습니다. 캐릭터의 크기는 얼마입니까?
Jeff

1

간단 해.

스파이크를 다시 작성하십시오. 스파이크의 잘못이야

충돌은 별개의 유형 단위로 발생해야합니다. 내가 볼 수있는 한 문제는 다음과 같습니다.

1. Player is outside spikes
2. Spikes move into intersection position with the player
3. Player resolves incorrectly

문제는 3 단계가 아닌 2 단계입니다.

물건을 견고하게 느끼려고한다면, 아이템이 서로 미끄러 져 들어 가지 않도록해야합니다. 교차로에 있으면 자리를 잃으면 문제를 해결하기가 더 어려워집니다.

스파이크는 플레이어의 존재를 이상적으로 확인해야하며 움직일 때 필요에 따라 그를 밀어야 합니다.

플레이어가 가지고 이것을 달성하는 쉬운 방법입니다 moveXmoveY풍경을 이해하는 기능을하고 특정 델타에 의해 플레이어를 밀어 것 장애물을 타격하지 않고 지금까지 그들이 할 수 또는 .

일반적으로 이벤트 루프에 의해 호출됩니다. 그러나 플레이어를 밀기 위해 객체로 호출 할 수도 있습니다.

function spikes going up:

    move spikes up

    if intersecting player:
        d = player bottom edge + spikes top edge

        player.moveY(-d)
    end if

end function

물론 플레이어는 여전히 경우 스파이크에 반응 할 필요가 그가 로가는 그들 .

가장 중요한 규칙은 객체가 이동 한 후 교차하지 않는 위치에서 끝나야한다는 것입니다. 이런 식으로, 당신이보고있는 결함을 얻는 것은 불가능합니다.


moveY교차로를 제거하지 못하는 극단적 인 경우 (다른 벽이 방해가되기 때문에) 교차로를 다시 확인하고 moveX를 시도하거나 그냥 죽일 수 있습니다.
Chris Burt-Brown
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.