이 시리즈의 이전 문서에서는 Gravity Quest의 게임 플레이의 기본 디자인과 구현에 대해 설명했습니다. 이 문서에서는 충돌 감지에 초점을 맞춰 논의를 하겠습니다. 충돌 감지는 Gravity Quest 개발의 기본 요소로 입증되었으며 다른 많은 게임에서도 필요합니다.
Gravity Quest에서는 다양한 충돌이 발생할 수 있습니다. 우주 비행사는 중력 총에 의해 표적화 될 수있는 열 활동성 소행성과 충돌 할 수 있으며, 중력 총에 의해 표적이 될 수없는 난장이 또는 우주 비행사를 추적하는 외계인과 충돌 할 수 있습니다. 이 모든 경우에 우주 비행사는 사망할수 있으니 충돌 감지가 필요합니다.
Phaser의 물리 엔진
Phaser에는 충돌 감지가 포함 된 세 가지 물리 엔진이 있습니다.
Phaser의 자체 Arcade 엔진은 일반적으로 빠른 성능을 위해 최적화되어 있습니다. 이 기능은 게임 개발자가 가지고 있는 많은 기본 요구 사항을 다루지만 더 복잡한 요구가 발생할 때는 충분하지는 않습니다.
Phaser는 P2 물리엔진 (P2 물리엔진의 GitHub 저장소)을 추가로 패키지합니다. 충돌 감지, 구속 조건 또는 스프링을 위한 다각형 몸체와 같은 다양한 고급 기능을 제공합니다. 이 기능에 대한 데모는 P2 홈페이지에서 찾을 수 있습니다.
마지막으로 Phaser에는 Ninja 물리 엔진이 포함되어 있습니다. 경사면이나 둥근 모서리가 있는 가상의 풍경을 만들 때 빛납니다. 닌자 엔진에서 이익을 얻는 전형적인 게임 플랫폼 입니다 (슈퍼 마리오 또는 소닉의 구 버전을 생각해보십시오).
P2를 초기 선택으로
Gravity Quest의 목적을 위해, 처음에는 P2가 다른 것들을 배제하여 선택한 엔진이었습니다. Ninja 엔진은 다른 스타일의 게임을 목표로하기 때문에 배제되었습니다. Arcade 엔진은 Gravity Quest에 필요한 많은 기능을 제공합니다.
예를 들어, 서로 가속하는 기능, 그리고 Gravity Quest에서는 충돌 감지 기능입니다.
...
accelerateToObject: function (obj1, obj2, speed) {
if (typeof speed === 'undefined') { speed = 60; }
var angle = Math.atan2(obj2.y - obj1.y, obj2.x - obj1.x);
obj1.body.force.x = Math.cos(angle) * speed;
obj1.body.force.y = Math.sin(angle) * speed;
}
...
목록 1 : P2 바디에 대한 accelerateToObject 함수.Gravity Quest의 개발 과정 전반에 걸쳐 나는 P2의 선택에 행복하게 매달 렸습니다. 나는 테스트 할 장치가 비교적 적기 때문에 처음에는 항상 iOS 용 게임을 게시하려고 했습니다. 게임을 개발하는 동안, 나는 대부분 내 iPhone 5에서 테스트를 해 보았습니다. 그러나 게임이 거의 완료되면 iPhone 4 및 iPhone 4와 같은 일부 내 친구의 Apple 장치에서도 게임을 테스트하기 시작했습니다. 그리고 불행히도 성능이 떨어지는 것을 발견했습니다. 프레임 속도가 30 이하로 내려갔습니다 (테스트 한 수준의 복잡성에 따라). 그래 Gravity Quest가 느리게 느껴지도록 만들었습니다. 초기에 업데이트 기능에서 빈번한 거리 계산을 의심하면서 성능 병목 현상을 확인하기 위해 몇 가지 테스트를 수행했습니다 (이전 기사에서 설명한 구현 참조). 그러나 이러한 테스트 결과에 따르면 P2는 이전 장치에 비해 너무 무거웠습니다. (참고 사항 : 나는 이 내용이 일반화 되기를 결코 바라지 않는다 - 이것은 단지 나의 경험이었고 다른 경우에는 적용이 되서는 안되며 확실히 P2의 높은 품질에 반대하지 않는다.)
Gravity Quest가 물리 엔진을 필요로 할 때, 예를 들어 물체를 가속화 할 때 Phaser의보다 빠른 Arcade 엔진을 사용하기로 했습니다. 그러나 직사각형과 원 또는 원과 원 사이에 충돌 감지 기능을 제공하지 않으므로 필자 만의 솔루션을 구현했습니다. 이에 대해서는 다음에서 설명합니다.
원과 원 간의 충돌
Gravity Quest의 가능한 충돌 유형은 둘 다 외계인의 충돌, 둘 다 원형, 그리고 폭발이 발생합니다.
목록 2는 두 원 간의 충돌 감지를 구현하는 방법을 보여줍니다. collidesCircleCircle과 getPowDistance의 두 함수는 필요한 객체의 메소드로 정의 할 수 있습니다. collidesCircleCircle 함수는 body1과 body2라는 두 개의 아케이드 물리엔진 입력으로 사용합니다.
참고 :이 몸체는 직사각형이며 아케이드 물리 엔진이 지원하는 유일한 유형의 몸체입니다. 그러나 이 몸체는 원형 스프라이트에 속해 있으며, 몸체가 사각형 (높이가 같음)임을 암시합니다. 또한이 몸체의 앵커는 수직 및 수평 모두 가운데에 위치한다고 가정합니다.
...
/*
* Determines whether two circles collide or not.
* Input: 2 square Arcade physics bodies with centered anchors
* Output:
* - true, if collision is detected
* - false, if no collision is detected
*/
collidesCircleCircle: function(body1, body2){
var radius1 = body1.width * 0.5;
var radius2 = body2.width * 0.5;
if(Math.abs(body1.x - body2.x) < radius1 + radius2 &&
Math.abs(body1.y - body2.y) < radius1 + radius2){
var distance = this.getPowDistance(body1.x, body1.y, body2.x, body2.y);
if (distance < (radius1 + radius2) * (radius1 + radius2)){
return true;
}
}
return false;
},
/*
* Helper function to determine the distance between
* two points using Pythagorean theorem
*/
getPowDistance: function(fromX, fromY, toX, toY){
var a = Math.abs(fromX - toX);
var b = Math.abs(fromY - toY);
return (a * a) + (b * b);
}
...
목록 2 : 두 개의 원형 몸체 사이의 충돌 감지.처음에는 목록 2에 표시된 것처럼 이 몸체의 반경은 폭에서 파생됩니다. 첫 번째 if 문에서 충돌이 가능한지, 즉 두 몸체 사이의 수평 거리와 수직 거리가 반경의 합보다 작거나 같은지 여부를 확인하기 위해 빠른 검사가 수행됩니다. 이 조건이 성립되지 않으면 두 개의 원이 충돌 할 수 없습니다 (예 : 이미지 1의 사례 1에 표시됨). 조건이 성립하면 충돌이 발생할 수 있습니다. 예를 들어 두 개의 몸체가 이미지 1의 사례 2에서와 같이 수직 또는 수평으로 정렬 된 경우 그러나 이미지 1의 경우 3에서 보여지는 것처럼해서는 안됩니다. 따라서 두 번째 조건이 필요합니다. 그것은 피타고라스의 정리를 사용하여 두 몸체의 닻 사이의 거리를 결정합니다. 결정된 거리가 두 반지름의 합보다 작 으면 충돌 감지는 참을 반환합니다. 이 제 2 조건에 기초하여, 이미지 1의 경우 3의 충돌은 배제되고 이미지 1의 경우 4의 충돌이 검출됩니다.
이미지 1 : 원형 충돌의 예.
회전 된 직사각형과 원 사이의 충돌
Gravity Quest의 다른 유형의 충돌은 회전 된 직사각형 물체와 원형 물체입니다. 예를 들어, 우주 비행사는 직사각형이며 원형의 열 활동성 소행성, 난쟁이 노벨 또는 외계인과 충돌 할 수 있습니다.
목록 3은 회전 된 사각형과 원 간의 충돌 감지가 어떻게 결정되는지 보여줍니다. 함수 collidesRectCircle은 그것이 필요한 곳에서 상태 객체의 메소드로 정의 될 수 있습니다.
...
/*
* Determines whether two circles collide or not.
* Input:
* - rect: an Arcade physics body with centered anchor
* - circle: a square Arcade physics bodies with centered anchor
* Output:
* - true, if collision is detected
* - false, if no collision is detected
*/
collidesRectCircle: function(rect, circle){
var radius = circle.width * 0.5;
var upperRectRadius = Math.max(rect.width, rect.height) * 0.75;
// quick check, whether collision is actually possible:
if(Math.abs(circle.x - rect.x) < radius + upperRectRadius &&
Math.abs(circle.y - rect.y) < radius + upperRectRadius){
// adjust radians:
var rotation = rect.rotation > 0 ? -1 * rect.rotation : -1 * rect.rotation + Math.PI;
// rotate circle around origin of the rectangle:
var rotatedCircleX = Math.cos(rotation) * (circle.x - rect.x) -
Math.sin(rotation) * (circle.y - rect.y) + rect.x;
var rotatedCircleY = Math.sin(rotation) * (circle.x - rect.x) +
Math.cos(rotation) * (circle.y - rect.y) + rect.y;
// get upper left position of the rectangle:
var rectX = rect.x - (rect.width * 0.5);
var rectY = rect.y - (rect.height * 0.5);
// find closest point in the rectangle to the rotated circle's center:
var closestX, closestY;
if (rotatedCircleX < rectX){
closestX = rectX;
} else if (rotatedCircleX > rectX + rect.width){
closestX = rectX + rect.width;
} else {
closestX = rotatedCircleX;
}
if (rotatedCircleY < rectY){
closestY = rectY;
} else if (rotatedCircleY > rectY + rect.height) {
closestY = rectY + rect.height;
} else {
closestY = rotatedCircleY;
}
// check distance between closest point and rotated circle's center:
var distance = this.getPowDistance(rotatedCircleX, rotatedCircleY, closestX, closestY);
if (distance < radius * radius){
return true; // Collision
}
}
return false;
}
...
목록 3 : 회전 된 직사각형과 원형 몸체 사이의 충돌 감지.처음에 목록 3에 표시된 것처럼 주어진 원의 반경이 결정됩니다. 또한, 주어진 사각형을 에워싸는 원의 반경의 대략적인 상한 upperRectRadius가 결정됩니다. 이 반지름을 사용하여 두 원 사이의 충돌 감지와 유사하게 빠른 점검이 수행되어 충돌이 가능한지 여부를 결정합니다.
다음과 같이 비용이 많이 드는 충돌 감지는 Circle 및 Rotated Rectangle Collision Detection에 대한 게시물에 설명 된 방법의 JavaScript 번역입니다. 기본 아이디어는 사각형의 중심을 중심으로 원의 중심을 동일한 양만큼 이동하여 사각형의 회전을 보정하는 것입니다.
이렇게하려면 Brad Greens가 Circle and Rotated Rectangle Collision Detection에서 언급 한 라디안 회전을 조정 한 후 원의 앵커를 사각형의 앵커 주위로 회전시켜 기본 대수를 사용하여 사각형의 회전을 보정합니다. 이 프로시저를 수행하면 rotateCircleX 및 rotatedCircleY로 표시된 회전 된 원의 중심 좌표가 됩니다.
다음으로 회전 된 원의 중심에 대한 직사각형 내의 가장 가까운 점의 X 및 Y 좌표가 if-else 문에서 결정됩니다. 마지막으로 피타고라스 정리를 다시 사용하여 직사각형의 결정된 가장 가까운 점과 회전 된 원의 중심 사이의 거리를 결정합니다. 이 거리가 원의 반경보다 작으면 충돌이 감지됩니다.
게임에서 맞춤 충돌 감지 사용
기본 메커니즘을 제자리에 두고 충돌 감지에 사용할 수 있습니다. 이를 보완하기 위해 이전 기사의 소스 코드가 확장되어 직사각형 우주 비행사와 일부 비명 (novae) 간의 충돌 감지를 지원합니다. 충돌시 게임의 단일 상태가 다시 시작됩니다.
목록 4에서 볼 수 있듯이 해당 Asset이 프리로드됩니다.
...
game.load.image('nova', 'assets/nova.png');
...
다음으로, 작성 함수에서, 목록 5에 나타난 바와 같이, 세 개의 비 순례가 게임에 무작위로 배치되고 비 순응 그룹에 추가됩니다. 이전 기사에서와 같이, 100 픽셀의 경계선은 노베의 위치 지정을 위해 생략되었습니다.
...
this.novae = game.add.group();
for (var i = 0; i < 3; i++) {
var nova = game.add.sprite(
game.rnd.integerInRange(100, game.world.width - 100),
game.rnd.integerInRange(100, game.world.height - 100),
'nova');
nova.anchor.setTo(0.5, 0.5);
this.novae.add(nova);
};
...
목록 5 : novae를 임의의 위치에 배치.마지막으로, 갱신 함수에서, 충돌은 목록 6에 도시 된 바와 같이 체크됩니다. 노벨 그룹이 반복되고, 모든 노바는 상술 한 collidesRectCircle 함수에서 우주 비행사와의 충돌에 대해 체크됩니다. 충돌이 감지되면 main 상태가 다시 시작됩니다.
...
this.novae.forEach(function(nova){
if(this.collidesRectCircle(this.astronaut, nova)){
game.state.start('main');
}
}.bind(this));
...
목록 6 : 업데이트 기능에서 충돌 확인.데모
우주 비행사가 노벨 중 하나와 충돌하면 게임이 다시 시작됩니다.
결론
충돌 탐지는 많은 게임 개발 프레임 워크의 표준 기능이며, 물론 Phaser도 마찬가지입니다. 그러나 Gravity Quest의 경우 경량 물리 시스템에 대한 요구 사항과 원형 체 사이의 충돌 감지가 조합되어 사용자 정의 솔루션을 만들었습니다. 여기 제시된 구현은 완전히 최적화되지는 않았지만 원하는 결과를 생성합니다. 게임은 구형 iOS 장치에서도 잘 수행되고 충돌 감지가 제대로 작동합니다. 충돌 감지 데모의 소스 코드는이 GitHub 저장소에서 사용할 수 있습니다. 다음 기사에서는 Gravity Quest의 비주얼과 사운드를 어떻게 만들 었는지 설명 할 것입니다.
댓글 없음:
댓글 쓰기