HTML5GAME :: HTML5GAME

달력

92025  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

레벨 디자인

이전 문서에서 나는 Gravity Quest의 비주얼과 사운드를 어떻게 만들 었는지 설명했습니다. 게임 플레이, 영상 및 사운드를 제작했으면 다음 단계는 레벨을 제작하는 것입니다. Gravity Quest의 레벨 생성은 도전적이었습니다. 단일 레벨 디자이너는 반복적 인 레벨로 인해 특정 스타일을 고수 할 위험이 있으므로 플레이어가 빠르게 지루해집니다. 아직도 게임을 합리적으로 판매하기 위해 적어도 20 레벨 (완전히 주관적으로 선택한 숫자)을 특징으로했습니다. 따라서 레벨을 만들고 편집하고 표현할 수있는 효율적인 방법을 찾아야 했습니다. 그리고 궁극적으로 이것이 내가 레벨을 설계하는데 적 접근 방식을 시도하기로 결정한 이유입니다.



레벨 표현


Gravity Quest의 세계를 만들기 시작하기 전에 한 가지 결정을 내려 레벨 데이터를 표현하는 방법을 결정했습니다. 플랫폼 게임에서 타일 맵은 Phaser가 지원하는 일반적인 접근 방식입니다. 타일 맵에서 레벨은 거친 타일 (예 : 16x16, 32x32 또는 64x64 픽셀 크기의 사각형)로 나뉩니다. 모든 타일에 대해 렌더링 할 텍스처와 타일이 플레이어 또는 적과 충돌하는지 여부가 지정됩니다. Tile 맵은 Phaser에서의 지원과 Tiled 맵 에디터와 같은 툴을 생성 할 수있는 Gravity Quest의 유일한 접근 방법이었습니다.


그러나 Gravity Quest의 요구 사항은 타일 맵을 사용하여 제외되었습니다. 레벨의 요소는 다양한 크기로 되어 있으며 거친 격자에 꼭 맞을 필요는 없습니다. 게다가, 그들의 위치는 세밀하게 아주 정교하게 정의 될 필요가 있습니다. 예를 들어, 레벨 13에서 우주 비행사는 노바와 충돌하기 전에 마지막 순간에 소행성으로 가속하기 위해 중력총을 사용해야합니다. 이 레벨을 생성하기 위해 소행성과 왜소 노바가 픽셀 단위로 정확하게 배치되었습니다.


결과적으로, Gravity Quest에있는 레벨 데이터를 JavaScript 객체에 저장하기로 결정했습니다 (목록 1 참조). 레벨의 모든 요소는 최소한 x 및 y 좌표를 나타냅니다. 게임 플레이 상태의 create 함수에서 레벨을 설정할 때 JavaScript 객체가 반복되고 모든 요소가이 좌표에 따라 배치됩니다. 일부 요소는 사용중인 스프라이트의 이름을 지정하는 키와 충돌 감지에 사용되는 반경을 나타냅니다 (게임 플레이 구현 : 충돌 감지 관련 도움말 참조). 예를 들어, 소행성이나 nova가 다른 크기를 가질 수 있기 때문에이 정보가 필요합니다. maxDistance 값은 우주 비행사가 손실되기 전에 가장 가까운 소행성에서 얼마나 멀어 질 수 있는지를 결정합니다. 

Game.Level_02 = {
 player: {
  x: 431,
  y: 607,
 },
 asteroids: [
  {
   x: 680,
   y: 410,
   key: "asteroid_32",
   radius: 16
  },
  {
   x: 465,
   y: 548,
   key: "asteroid_32",
   radius: 16
  }
 ],
 target: {
  x: 606,
  y: 307,
  key: "target",
  radius: 18
 },
 goodies: [
  {
   x: 687,
   y: 514
  },
  {
   x: 776,
   y: 361
  },
  {
   x: 517,
   y: 460
  }
 ],
 maxDistance: 250
}

목록 1 : Gravity Quest의 레벨 2를 정의하는 JavaScript 객체.



레벨 편집기 구현하기

Gravity Quest의 레벨 빌드를 시작할 때 처음에는 텍스트 편집기를 기본 도구로 사용했습니다. 즉, 코드를 작성하여 레벨 요소와 위치를 정의했습니다. 이것은 쉽게 문제가 되는 것으로 판명되었습니다 : 레벨이 많은 요소로 구성되어있을 때 코드의 위치를 ​​추적하기가 어려워지며 코드를 이동하는 것이 번거로워집니다. 생산성을 높이기 위해 필자는 시각화 및 최종 편집 작업을 시작했습니다. 그래서 레벨 편집기를 개발하기 시작했습니다. 필자는 HTML5 캔버스 요소의 드래그 앤 드롭 기능을 지원하는 Fabric.js를 사용하여 레벨을 표시하고 요소를 편집했습니다. 나는 다음과 같은 기능 세트에 도달 할 때까지 편집기를 끊임없이 수정했습니다.

- 편집기는 blueprint로 기존 레벨 중 하나를 로드하여 새로운 레벨을 생성 할 수 있습니다.

- 팔레트에서 요소를 추가 및 선택하고 휴지통 버튼을 눌러 제거 할 수 있습니다.

- 드래그 앤 드롭을 사용하여 요소를 이동할 수 있습니다.

- 설계된 레벨은 편집기에서 테스트 할 수 있습니다. 그렇게 하기 위해, Gravity Quest는 팝업 창에서 시작되고 편집기의 레벨은 해당 팝업 창으로 전달되어 즉시 시작됩니다. 이 기능을 사용하면 레벨을 신속하게 테스트하고 설계를 반복 할 수 있습니다.

- 디자인 수준의 양방향 데이터 바인딩 JSON 표현이 지속적으로 표시됩니다. a)는 레벨 요소 (예 : 좌표)를 프로그래밍 방식으로 조정하고 b) 복사 및 붙여 넣기 레벨을 허용합니다.

- 브라우저의 로컬 저장소는 변경 사항을 지속적으로 저장하여 작업을 일시 중지하고 나중에 계속할 수 있습니다.


비디오 1은 Gravity Quest의 레벨 편집기에 대한 간단한 데모를 보여줍니다.

 
비디오 1 : Gravity Quest의 레벨 편집기 데모 

참여 수준 디자인

처음에는 레벨을 생성하기 위해 내 노트북에서만 실행되는 편집기 (임시 버전)를 사용했습니다. 편집기는 디자인하는 과정을 가속화했습니다. 그러나 20 레벨 이상을 만드는 것은 지루하고 반복적 인 레벨을 만들 수 있다는 것을 깨달았습니다. 따라서 편집자가 친구와 가족이 게임에 자신의 레벨을 제공 할 수 있도록 허용하기로 결정했습니다.


편집기로 참여 레벨 디자인을 사용하려고 약간 확장했습니다. 완성 된 레벨의 이름을 지정하고 편집자로부터 검토하여 최종 게임에 포함시킬 수 있습니다. 그런 다음 Heroku에 편집기를 배치하여 웹에서 액세스 할 수 있도록했습니다. 편집자가 (대부분) 안정 버전으로 배포되면 선택한 친구에게 전자 메일을 보내서 게임에 레벨을 기여하도록 요청했습니다. 처음에 16 명에게 요청을 보냈습니다. 일부 업데이트 및 알리미에서는 반복적으로이 숫자를 늘려 궁극적으로 25 명이 해결되었습니다.


Gravity Quest의 친구들이 레벨을 만들도록 허용하는 것은 여러모로 긍정적인 효과가 있었습니다. 첫째, 최종 게임을 완성 할 수있는 수준으로 발전했습니다. 대체로 5 명의 친구로부터 10 레벨을 받았습니다. 게임 레벨의 40 %만이 아니라, 모두 아주 즐겁고 재미있게 플레이 할 수 있습니다! 둘째, 참여 방식은 게임을 베타 테스트 할 수있는 견고한 수단임을 입증했습니다. 레벨 디자이너가 레벨을 테스트 할 수있게되면서 게임 플레이, 영상 및 사운드를 자연스럽게 검토했습니다. 레벨 디자이너의 피드백은 게임을 개선하는 데 매우 중요했습니다. 예를 들어, 우주 비행사가 움직이는 속도에 대한 힌트, 중력 총에 의해 목표 된 경우 우주 비행사에게 소행성을 이동하라는 제안, 시차 스크롤을 구현하기위한 제안, 또는 버전 1.1에서 3 가지 보너스 레벨을 추가하라는 제안이 포함됩니다. 게임. 셋째, 레벨 디자인에 대한이 참여 방식은 조기 마케팅 조치로 작용하여 진행중인 게임에 대한 단어를 전파했습니다.

물론 모든 공헌자는 Gravity Quest의 출품작에 대해 감사를 표합니다.



결론

게임을 개발할 때 많은 단계를 생각해내는 것이 어려운 작업입니다. Gravity Quest의 경우, 레벨 편집기는 레벨 설계에 대한 시각적 피드백을 제공하고 레벨 편집 속도를 높이며 아이디어와 디자인을 신속하게 테스트 할수 있어 작업을 쉽게 수행 할수 있었습니다.


웹을 통해 배포되고 액세스 할 수있는 편집기는 가족과 친구들을 레벨 생성에 추가 할수 있었습니다. 이것은 훌륭한 수준일뿐 아니라 귀중한 피드백과 게임에 대한 토론에도 귀결되었습니다. 그리고 궁극적으로 개발 과정을 보다 사회적이고 재미있게 만들어주는 것으로 입증되었습니다. 게임에 수준을 수동으로 만들어야하는 경우이 방법을 적극 권장합니다.


이 시리즈의 다음 기사에서는 Apple App Store에서 Gravity Quest의 배포에 대해 설명합니다.



Posted by 마스터킹
|

비주얼 및 사운드

이전 문서에서, Gravity Quest에서 사용 된 충돌 감지에 대해 논의 했으므로 (게임의 게임 플레이에 대한 부분을 마무리 함) 이 문서에서는 Gravity Quest의 비주얼과 사운드가 어떻게 생겨 났는지 설명합니다.


Gravity Quest의 경우 영상과 사운드에 관한 저의 목표는 매우 낮았습니다. 나는 결코 예술가가 아니 었습니다. 솔직히 뛰어난 시각 효과와 사운드를 만드는 데 드는 노력을 두려워했습니다. 따라서 내 목표는 일관된 경험을 창조하는 것 - Gravity Quest에 뚜렷한 느낌을 주기위한 것입니다.



스프라이트 페인팅


여러 가지 이유로 픽셀 아트 스타일을 추구하기로 결정했습니다. 첫째, 픽셀 아트는 제약을 유도하며 응집력있는 스타일을 만드는 데 도움이됩니다. 둘째, 픽셀 아트는 많은 주목을 받고있는 이 글의 일부이며 결국에는 게임의 미래로 여겨지기도합니다. 셋째, 제한된 양의 픽셀로 인해 비주얼을 만드는 데 필요한 노력이 줄어들 것이라고 추론했습니다 (이는 매우 순진한 생각이었습니다). 이 훌륭한 기사는 픽셀 아트와 어떤 관계가 있는지 설명합니다.


게임의 스프라이트를 만들기 위해 필자는 주로 Mac OS 용 Pixen 응용 프로그램을 사용했습니다. Apple App Store에서 구하거나 GitHub에서 무료로 구할 수있는 xCode 프로젝트에서 컴파일 할 수 있습니다. Pixen은 잘 선택된 드로잉 도구 세트, 작업 할 레이어 및 애니메이션 제작을 지원하는 픽셀 아트를 제작하도록 설계되었습니다.


아트 워크 만들기는 연속적인 반복으로 표시되었습니다. 예를 들어, 이미지 1은 우주 비행사의 일부 버전을 보여줍니다. 

이미지 1 : 우주 비행사의 일부 버전


게임의 스프라이트에 사용할 모양과 색상에 영감을 얻기 위해 Google의 이미지 검색을 사용하여 소행성, 폭발물 또는 우주선과 같은 이러한 객체의 사진을 얻었습니다.



애니메이션


애니메이션에 많은 노력을 기울였습니다. 확실히 제한적 임에도 불구하고 게임에 애니메이션이 있습니다. Novae는 이미지 2에서 보여지는 것처럼 깜박임 현상이 나타나며, 이미지가 항상 활성화 되도록 합니다. 반 중력력 필드는 이미지 3 에서 처럼 중력파 확대를 지속적으로 방출하며 Pixen의 원 도구로 구속력이 필요합니다. 확장된 광선의 투명도가 높아져서 사라지는 것을 시뮬레이트하기 때문에 마지막 두 프레임이 흐리게 보입니다. 미네랄은 이미지 4에서 볼 수 있듯이 자주 번쩍 거립니다. 외계인은 이미지 5에서 볼 수있는 것처럼 살아 있다는 것을 암시하기 위해 조명을 돌립니다. UFO가 우주 비행사를 따라 가면이 표시등이 빨간색으로 바뀝니다. 마지막으로, 우주 비행사는 이미 이미지 1에 표시된 몇 가지 최소 애니메이션을 특징으로합니다.

이미지 2 : 노바 애니메이션


이미지 3 : 반 중력 영역 애니메이션



이미지 4 : 광물 애니메이션


이미지 5 : 외계인 애니메이션


애니메이션은 목록 1의 nova 및 우주 비행사에 표시된 것처럼 preload 함수에서 애니메이션 프레임을 포함하는 스프라이트 시트를 먼저 로드하여 Phaser에서 사용할 수 있습니다. 모든 프레임의 너비와 높이가 32 픽셀임을 추가로 정의합니다. 이 코드와 다음 코드는 문서에서 제시 한 게임 플레이 구현, 특히 충돌 감지에 대한 내용을 확장합니다.

...
// instead of: game.load.image('nova', 'assets/nova.png');
game.load.spritesheet('nova', 'assets/nova_animation.png', 32, 32);
// instead of: game.load.image('astronaut', 'assets/astronaut.png');
game.load.image('astronaut', 'assets/astronaut_animation.png', 20, 32);
...

목록 1 : 우주 비행사와 우주 비행사를위한 애니메이션 프레임을 포함하는 스프라이트 시트 로딩.


그런 다음 create 함수 내에서 목록 2와 같이 해당 스프라이트를 만들 때 애니메이션이 정의됩니다. 애니메이션을 만들 때 프레임을 재생해야하는 순서를 정의 할 수 있습니다. nova의 경우, 정의 된 애니메이션이 즉시 시작됩니다. 애니메이션을 시작할 때 프레임 속도 (여기 : 5)와 애니메이션을 반복할지 여부 (여기 : true)를 지정할 수 있습니다.

...
// (randomly) place novae and add to group:
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);

 // define and start animation:
 nova.animations.add('flicker', [0,1,2,3,2,1]);
 nova.animations.play('flicker', 5, true);

 this.novae.add(nova);
};
...
// create astronaut at the center of the world:
this.astronaut = game.add.sprite(game.world.width * 0.5, game.world.height * 0.5, 'astronaut');
this.astronaut.animations.add('idle', [0]);
this.astronaut.animations.add('firing', [1]);
...

목록 2 : 애니메이션 정의 및 시작.


우주 비행사의 경우 update 함수에서 중력총이 발사 될 때 애니메이션이 재생됩니다. 따라서 코드는 목록 3과 같이 중력총이 발사되었는지 여부를 확인하는 데 사용되는 if-else 문에 배치됩니다.

...
if(game.input.activePointer.isDown){
 ...
 // animate astronaut:
 this.astronaut.animations.play('firing', 1, false);
} else {
 ...
 // animate astronaut:
 this.astronaut.animations.play('idle', 1, false);
}
...

목록 3 : 중력 총 발사시 우주 비행사 애니메이션 재생


전반적으로 나는 픽셀 아트에 대한 나의 순진한 아이디어가 틀렸다고 Gravity Quest의 비주얼을 제작함으로써 배웠습니다. 픽셀 수가 제한되어 있어도 반드시 노력이 줄어들지는 않습니다. 오히려 유도 된 구속 조건은 디자이너가 원하는 모양을 만들고, 관련된 세부 사항에 초점을 맞추고, 적절한 비율로 못 박는 것을 더욱 어렵게 만듭니다.



타이포그래피


타이포그래피의 경우, 나는 pixelart-esque 모양을 위해 무료 (상업용으로도 사용 가능) 글꼴 실크 스크린을 선택했습니다. Silkscreen은 TrueType 형식으로 제공되고 Phaser는 비트 맵 글꼴이 필요합니다. 트루 타입 글꼴에서 비트 맵 글꼴을 만들려면 littera 비트 맵 글꼴 생성기를 사용했습니다. 주어진 글꼴 파일에 대해 비트 맵 글꼴 스프라이트 시트를 구성 가능한 크기 및 색상으로 출력합니다. 색상 (흰색 또는 검은 색)과 크기가 다른 여러 가지 변형 된 실크 스크린 비트 맵 글꼴을 만들었습니다.



시각 효과


시각적 효과가 제대로 적용되면 덤덤한 게임을 진정으로 흥미로운 것으로 만들 수 있습니다. 여기에 Gravity Quest에서 사용 된 시각 효과에 대한 설명이 있습니다.



파티클 이미터


일부 효과는 Phaser의 파티클 이미터에 달려 있습니다. 파티클 이미터는 개발자 정의 방식으로 여러 개의 스프라이트 인스턴스를 생성합니다. 비, 폭발, 연기, 스파크 링 등과 같은 효과를 구현하는 데 사용할 수 있습니다. Gravity Quest에서는 중력 총의 사용을 나타내는 데 하나의 입자 방사체가 사용됩니다. 다음 코드는이 시리즈의 두 번째 기사에서 Gravity Quest의 게임 플레이 구현을 논의 할 때 제시되는 코드를 확장합니다. Listing 4는 preload 함수에서 필요한 스프라이트를 로드하는 방법을 보여준다. 두 입자는 이미지 6에 나와 있습니다.



이미지 6 : 중력총 이미터의 입자.

game.load.image('gun_particle_1', 'assets/gun_particle_1.png');
game.load.image('gun_particle_2', 'assets/gun_particle_2.png');

목록 4 : 중력총 이미터용 입자로드.


Listing 5는 gunEmitter가 create 함수에서 정의된 방법을 보여준다. 처음 두 매개 변수는 이미터 위치를 지정합니다. 이미터는 위치가 지속적으로 업데이트되므로 처음에는 게임에 위치합니다. thrid 매개 변수는 이미 터에서 최대 입자 수를 정의합니다. 다음 줄에서는 이미로드 된 입자 스프라이트가 이미터에 사용되도록 정의됩니다.

...
this.gunEmitter = game.add.emitter(0, 0, 25);
this.gunEmitter.makeParticles(['gun_particle_1', 'gun_particle_2']);
...

목록 5 : create 함수에서 중력총용 파티클 이미터 정의하기


이미터를 정의하면 우주 비행사가 중력총을 발사하는 데 사용할 수 있습니다 (Listing 6 참조). 제시된 코드는 중력총이 발사되었는지 여부를 결정하는 데 사용되는 if-else 문으로 update 함수내에서 실행됩니다. 우선 이미터의 위치는 우주 비행사에게 가장 가까운 소행성의 가장자리 (중력총에 의해 목표 된 것)로 업데이트됩니다. 다음으로 이미터가 이미 실행 중이 아니면 시작됩니다. 마지막으로 이미터의 알파와 모든 입자의 알파는 우주 비행사가 가장 가까운 소행성까지의 거리에 따라 설정됩니다. 이렇게하면 중력총이 멀리에서 발사되는 경우 빛이 반짝 일 수 있습니다.

...
if(game.input.activePointer.isDown){
 ...
 // emit particles:
 this.gunEmitter.x = closestAsteroid.x - Math.cos(angle) * closestAsteroid.width * 0.5;
 this.gunEmitter.y = closestAsteroid.y - Math.sin(angle) * closestAsteroid.width * 0.5;
 if(!this.gunEmitter.on){
  this.gunEmitter.start(false, 100, 15);
 }
 this.gunEmitter.alpha = 1 - distance / 250; // make emitter visible
 ...
} ...

목록 6 : 우주 비행사가 중력총을 발사 할 때 이미터 시작하기.


이 효과를 완료하기 위해 이미터는 중력총이 더 이상 발사되지 않으면 중지됩니다 (7). 다시 이 코드는 중력 총 발사 여부를 결정하는 데 사용되는 if-else 문이update 함수에서 사용됩니다 .

...
if(game.input.activePointer.isDown){
 ...
} else {
 ...
 // stop particle emitter:
 this.gunEmitter.on = false;
}
...

목록 7 : 중력총이 더 이상 발사되지 않을 때 이미터를 멈춤.


Gravity Quest의 파티클 이미터는 외계인이 충돌 할 때 우주 비행사가 노벨, 열 활성 소행성 또는 외계인에 의해 살해되거나 우주선의 연기를 만들때 사용됩니다. 전반적으로, 그들은 Phaser로 만든 게임에 생명을 불어 넣는 다목적 도구입니다.



시차 스크롤링


2D 게임에서 일반적으로 사용되는 또 다른 효과는 시차 스크롤입니다. 즉, 카메라가 움직일 때 배경이 전경의 대상과 다른 속도로 움직여 심도있는 효과를 만들어냅니다. Phaser는 수평 시차 스크롤링 (종종 platformer 스타일의 게임에서 사용됨)을 구현하는 기능을 제공합니다. 그러나 Gravity Quest에서는 우주 비행사가 수평 또는 수직으로 움직일 수 있으므로 내 자신의 솔루션을 구현하게되었습니다.


작동하려면 먼저 카메라 동작을 활성화해야 합니다. 그렇게 하기 위해서, 게임 세계의 범위는 목록 8에있는 것처럼 create 함수에서 실제 게임보다 크게 만들어야한다. (참고 : 이것은 무작위 적으로 데모에서 훨씬 더 분산 된 소행성과 nova로 이어질 것입니다).

...
// make the world larger than the actual canvas (for camera to be able to follow):
game.world.setBounds(0, 0, 1280, 960);
...

목록 8 : 게임월드 경계 설정.


다음으로, create 함수에서 게임의 카메라는 목록 9와 같이 우주 비행사를 따라 가도록 설정 했으며, Phaser에는 여러 가지 FOLLOW 모드가 있습니다. FOLLOW_TOPDOWN_TIGHT는 우주 비행사의 움직임이 카메라에 의해 수평 및 수직으로 밀접하게 따르는 것을 의미합니다.

...
// make camera follow player:
game.camera.follow(this.astronaut, Phaser.Camera.FOLLOW_TOPDOWN_TIGHT);
...

목록 9 : 우주 비행사를 따라가는 카메라 설정.


시차 스크롤링이 작동하려면 배경 스프라이트를 preload 함수에서 로드하고 목록 10에 표시된 것처럼 작성 함수에 타일 스프라이트로 배치해야합니다. 타일 스프라이트에서 정의 된 스프라이트는 수평 및 수직으로 반복됩니다.

...
// load background sprite in the preload function:
game.load.image('background', 'assets/background.png');
...
// place background in the create function:
this.background = game.add.tileSprite(0, 0, game.world.width, game.world.height, 'background');
...

목록 10 : 배경로드 및 배치


시차 스크롤링을 구현하기 위해, 먼저, create 함수에서 카메라 위치는 목록 11에 표시된대로 저장됩니다. 이 마지막 위치는 시차 스크롤을 구현하기 위해 다음 update 루프에서 사용되기 때문에 cameraLastX 및 cameraLastY로 표시됩니다.

...
// store cameras position:
this.cameraLastX = this.camera.x;
this.cameraLastY = this.camera.y;
...

목록 11 : create 함수에 카메라 위치를 저장.


update 함수에서 카메라의 현재 위치는 목록 12에 표시된 마지막 저장된 위치와 비교할 수 있습니다. 위치가 변경되면 배경이 이동되고 새 위치가 저장됩니다.

...
// compare current camera position with last one and shift background if it differs:
if(game.camera.x !== this.cameraLastX){
 this.background.x -= 0.2 * (this.cameraLastX - game.camera.x);
 this.cameraLastX = game.camera.x;
}
if(game.camera.y !== this.cameraLastY){
 this.background.y -= 0.2 * (this.cameraLastY - game.camera.y);
 this.cameraLastY = game.camera.y;
}
...

목록 12 : 현재 카메라 위치를 마지막 저장된 위치와 비교하여 배경을 이동.



소리

소리 때문에 나는 주로 몇 달 동안 Gravity Quest에서 수집한 품질 원천에 의존했습니다. 내 Assets은 다음과 같습니다.


UniversalSoundFX는 (상용) 프로젝트 / 게임에서 로열티 없이 사용할 수있는 3000 가지 이상의 사운드 라이브러리입니다. Richard Davey (Phaser의 제작자)가 한 번 그것에 대해 트윗하기 때문에 나는 그것에 대해 알게되었습니다. 나는 다양한 게임에 맞는 다양한 사운드를 고려하여 20$를 좋은 투자로 생각합니다.

Sfrx와 그 기반의 Bfrx는 임의로 특정 스타일의 사운드를 만들고 다양한 속성과 관련하여 미세 조정할 수있는 도구입니다. 나는 이 도구들을 익히지 못했지만 그들이 생성 한 무작위 소리 중 2 개가 최종 게임으로 만들어졌습니다.

CC 믹서는 크리에이티브 커먼즈 라이선스에 따라 음악을 저장하는 곳입니다. 이 사이트에서는 제목이나 제작자에 관한 노래를 검색하고 노래를 올바르게 사용하는 데 필요한 것이 무엇인지 명확하게 명시합니다 (상업적으로). Gravity 퀘스트는 Javolenus의 Nickleus가 게임 메뉴를 제공하는 'C95 - 일상 유지 임무'와 소개를위한 Karsten Holy Moly의 'Space Intro'를 제공합니다. 두 곡 모두 엔딩 점수에 나열됩니다.

Nosoapradio는 Deceased Superior Technician (DST)의 음악 보관소입니다. 노래는 (상업용) 게임에서 사용할 수 있으며 웹 사이트에는 적합한 노래를 선택하기위한 매우 훌륭한 필터링 인터페이스가 있습니다. Gravity 퀘스트는 Outro에서 'ALightIntro'를 특징으로하며,이 곡은 엔딩 스코어에 표시됩니다.


확실히 게임에서 사용할 수있는 (무료) 오디오 파일의 훨씬 좋은 소스가 있습니다. 


플레이 가능한 데모



결론

게임을 위한 훌륭한 비주얼과 사운드를 만드는 일은 마찬가지로 어렵습니다. Gravity Quest를 만들기 퀘스트의 비주얼과 사운드는 완벽하지는 않지만, 게임의 논리를 코딩하는 것보다 시간이 더 걸렸습니다. 이 문서가 시작하기에 흥미로운 자료를 제공하기 바랍니다. 이 기사에서 링크 된 데모의 완전한 주석 소스 코드는 GitHub에서 일반적으로 제공되는 것과 같습니다. 다음 문서에서는 참여 방식을 사용하여 Gravity Quest의 레벨을 만드는 방법에 대해 설명하겠습니다.



Posted by 마스터킹
|

게임 플레이 구현 : 충돌 감지

이 시리즈의 이전 문서에서는 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의 비주얼과 사운드를 어떻게 만들 었는지 설명 할 것입니다.


Posted by 마스터킹
|