HML5는 내가 좋아하기 때문에뿐만 아니라, 기존의 웹 개발 기술을 활용하여 단일 코드 기반으로 다양한 플랫폼을 타겟팅 할 수 있기 때문에 집중적으로 다룹니다.
이 튜토리얼에서는 간단한 사이드 스크롤러 모바일 / 크로스 플랫폼 게임을 처음부터 만들어 Phaser 라이브러리를 사용하여 HTML5 모바일 게임 개발의 기초를 배웁니다.
제공된 코드와 Assets는 여러분의 모바일 프로젝트에 자유롭게 사용셔도 됩니다.
소스 코드 파일
소스 코드와 게임 저작물을 zip 파일로 다운로드 하십시오. Github에서 가져올 수도 있습니다. 여기에서 완성 된 게임을 플레이 할 수 있습니다.
튜토리얼 목표
이 튜토리얼을 따르면, Phaser로 2D 모바일 HTML5 게임을 만들 수있는 기초가 있어야합니다.
1. 2 차원 사이드 스크롤러를 만들어 Phaser의 기초를 배웁니다.
2. 무료 Tiled 레벨 편집기를 사용하여 HTML5 게임용 레벨을 만드는 방법을 배웁니다.
3. 기본적인 2D 게임 메카니즘을 배우십시오.
튜토리얼 요구 사항
- JavaScript 에 대한 기초 지식부터 중간 지식이 필요하며, 코드 편집기 또는 IDE, Sublime Text, Netbeans 또는 Gean와 같은 편집기가 필요합니다.
- GitHub 레포에서 Phaser를 다운로드하십시오. repo를 복제하거나 ZIP 파일을 다운로드 할 수 있습니다.
- 로컬 또는 원격 웹 서버를 사용하여 코드 및 Phaser 예제를 실행해야합니다. 몇 가지 일반적인 옵션은 Apache (Windows의 경우 WAMP, Mac의 경우 MAMP)입니다. 가벼운 대안은 몽구스 웹 서버와 파이썬의 HTTP 서버입니다. 자세한 내용은이 안내서를 참조하십시오.
- Phaser 설명서와 예제 페이지를 준비하십시오. 또한 Phaser의 소스 코드를 보면서 언제나 해답을 찾을 수 있다는 것을 잊지 마십시오.
- 우리는 Windows, Mac 및 Linux와 호환되는 무료 및 Open Source Tiledmap 편집기를 사용할 타일 기반 게임을 제작할 것입니다.
- 터치 컨트롤의 경우 HTML5 Virtual Game Controller라는 라이브러리를 사용합니다. gamecontroller.js 파일은 게임 코드에 포함되어 있지만 최신 버전을 구해서 설명서를 읽어보십시오.
새 프로젝트
새 폴더, 색인 HTML 파일 및 Phaser 파일로 시작하십시오. 다운로드 한 Phaser zip 또는 복제 된 저장소의 "build"디렉토리에서 가져올 수 있습니다.
개발 모드 (전 세계에 최종 목적지로 게임을 배포하는 생산 방식과 달리)에서는 phaser.js 파일 (phaser.min.js와 반대)을 포함하지 않는 phaser.js 파일을 포함하는 것이 좋습니다. 그 이유는 게임 엔진을 블랙 박스로 취급하는 것은 좋은 습관이 아닙니다. 파일의 내용을 탐색하고 올바르게 디버그 할 수 있기를 원합니다.
모든 답변이 Google이나 StackOverflow에있는 것은 아니므로 가장 좋은 방법은 무엇이 진행되고 있는지를 이해하기 위해 원본 소스 코드를 읽는 것입니다. Google의 index.html 파일은 다음과 같습니다. 자습서가 진행됨에 따라 여기에 표시되는 파일을 추가하고 설명합니다.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Learn Game Development at ZENVA.com</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<script type="text/javascript" src="js/gamecontroller.js"></script>
<script type="text/javascript" src="js/phaser.min.js"></script>
<script type="text/javascript" src="js/Boot.js"></script>
<script type="text/javascript" src="js/Preload.js"></script>
<script type="text/javascript" src="js/Game.js"></script>
<style>
body {
padding: 0px;
margin: 0px;
}
</style>
</head>
<body>
<!-- include the main game file -->
<script src="js/main.js"></script>
</body>
</html>
뷰포트 메타 태그를 사용하여 사용자가 화면을 꼬집어 재생하면 확대 / 축소를 방지합니다 (재생시 실수로 할 수 있음). 모바일 웹 앱을 사용할 수 있고 Apple 모바일 웹 앱을 추가하면 휴대 전화 사용자가 휴대 전화의 바로 가기를 webapp에 추가 할 수 있습니다. 아이콘을 추가하고 iOS 및 Android에 대한 자세한 내용을 볼 수도 있습니다).
main.js 파일이 게임을 시작합니다.
States
코드로 진행하기 전에 Phaser의 중요한 개념 인 States에 대해 이야기하고 싶습니다.
페이저에서는 모든 동작이 States에서 발생합니다. 당신은 그것들을 게임의 주요 순간으로 생각할 수 있습니다. 축구 경기를 생각하면 문이 열리고 사람들이 들어 오기 시작할 때가 있습니다. 그러면 게임 전 쇼가 개최되는 States가 있습니다. 그런 다음 경기 전 경기 자료가 현장에서 제거 된 상태입니다. 그런 다음 게임이 시작되는 States 등이 있습니다.
Phaser는 당신이 가질 수있는 States만큼 많은 유연성을 제공하지만, 많은 게임과 튜토리얼에서 사용되는 사실상의 컨벤션이 있습니다. 이름은 조금씩 다를 수 있지만 일반적으로 다음과 같습니다.
Boot States : 일반 게임 설정이 정의되고 사전로드 화면의 자산이 로드됩니다 (예 :로드 바). 사용자에게 아무 것도 표시되지 않습니다.
Preload States : 게임 Assets (이미지, 스프라이트 시트, 오디오, 텍스처 등)이 메모리에 로드됩니다 (디스크에서). PreLoad 화면은 사용자에게 보여지며 일반적으로 진행 상황을 보여주는 로드 바를 포함합니다.
MainMenu State : 게임의 시작 화면. Preload States 후에는 모든 게임 이미지가 이미 메모리에 로드되어 있으므로 빠르게 액세스 할 수 있습니다.
Game State : 게임이 실제로 작동되는 States 입니다.
투토리얼에서는 Boot, Preload 및 Game의 세 가지 States 만 사용합니다.
게임을 시작하다
다시 코딩으로 돌아갑니다! 게임의 진입 점인 main.js를 살펴 보겠습니다.
var SideScroller = SideScroller || {};
SideScroller.game = new Phaser.Game(746, 420, Phaser.AUTO, '');
SideScroller.game.state.add('Boot', SideScroller.Boot);
SideScroller.game.state.add('Preload', SideScroller.Preload);
SideScroller.game.state.add('Game', SideScroller.Game);
SideScroller.game.state.start('Boot');
우리가 할 첫 번째 일은 우리 게임의 모든 데이터를 유지할 객체를 만드는 것입니다. 이 객체를 SideScroller라고합니다.
게임이라는 변수를 만든 다음 각 파일에서 정의 할 다른 객체에 대한 새 변수를 만드는 것이 어떻습니까?
진실은 당신이 그렇게 할 수 있다는 것입니다. 그러나 당신은 그렇게 선호합니다.하지만 모든 것을 하나의 단일 객체로 유지하는 것이 좋습니다. 이를 네임 스페이스 패턴이라고 합니다. 일반적으로 원하지 않는 것은 응용 프로그램의 전역 범위를 오염시키는 것입니다. 내가 "game"이라는 변수를 정의하는 외부 라이브러리를 포함한다고 가정 해보십시오. 전역 범위에서 작성하는 변수가 많을수록 더 많은 변수가 발생할 수 있습니다. 이로 인해 게임이 오작동을 일으킬 수 있습니다. 따라서 우리는 일반적으로 "SideScroller"와 같은 다른 곳에서 사용하기는 거의 불가능한 이름을 선택하고 그 안에 모든 것을 넣습니다.
문장 : var SideScroller = SideScroller || {}; SideScroller가 이전에 정의되어 있지 않으면 빈 객체로 만들어집니다.
SideScroller.game = new Phaser.Game(746, 420, Phaser.AUTO, ”);
우리는 화면 해상도로 Phaser 게임을 시작합니다. 게임을 렌더링하기 위해 Phaser는 CANVAS 요소를 사용하거나 WebGL (대부분의 최신 브라우저에있는 OpenGL과 웹용으로 작동하는 사양)에서 사용할 수 있습니다. 렌더링을 Phaser.AUTO로 설정하여 Phaser가 사용 가능한 것을 따라 무엇을 사용할 지 결정합니다.
States를 정의한 후에 (외부 파일로 생성 된 개체) Phaser가 Boot 상태를 시작하도록 지시합니다.
States 메서드
States는 특정 목적에 부합하는 일부 예약 된 방법을 가지고 있습니다. 이것들은 우리가 이 튜토리얼에서 사용할 것들입니다. 여기에서 전체 목록을 찾을 수 있습니다.
- init : States 초기화시 호출됩니다. States에 매개 변수를 전송해야하는 경우 여기에 액세스 할 수 있습니다 (자세한 내용은 나중에 설명합니다)
-preload : Assets이 로드되는 곳입니다.
-create : Assets 로드가 완료되면 호출됩니다.
-update : 이것은 모든 게임 틱에서 호출됩니다. 기본적으로 "초당 여러 번" 호출 되므로 충돌 감지와 같이 지속적으로 테스트해야하는 항목을 포함시키고 자합니다.
Boot State
Boot State에 대해 이야기하기 전에 이론적으로 모든 게임이 수행되는 상태를 사용할 수는 없지만 States를 사용하면 코드를 더 잘 구성 할 수 있다고 말하고 싶습니다.
Boot State는 어두운 곳입니다. 여기서 우리는 화면 크기와 우리가 사용할 물리 엔진과 같은 일반적인 게임 구성 옵션을 정의합니다 (Phaser는 그 중 세 가지가 있으며 우리는 Arcade라는 가장 간단한 것을 사용합니다). 또한 Boot State에서 Preload State로 표시 할 Assets을 로드합니다.
그래서 Boot State는 Preload State의 Assets을 로드하고 Preload State는 게임 Assets을 로드합니다. 왜 게임 Assets을 거기에서 로드하지 않는 것이 좋을까요? 아무 것도 그 일을 막지는 못하지만, 게임 Assets은 미리로드 된 화면 애셋보다 로드하는 데 시간이 오래 걸릴 가능성이 높습니다. 로딩 바 및 로고 일뿐입니다.
프리 로딩 화면 에셋을 먼저 로드하면 (즉, 가벼워 야 함) 빈 화면이있는 시간을 최소화합니다 (블루 스크린보다 좋지만 사용자는 이를 좋아하지 않습니다). 그런 다음 Preload 상태에서 눈을 바쁘게하는 멋지고 매력적인 프리로드 스크린으로 다른 모든 것을 로드 할 시간이 있습니다.
Boot.js의 내용 :
var SideScroller = SideScroller || {};
SideScroller.Boot = function(){};
//setting game configuration and loading the assets for the loading screen
SideScroller.Boot.prototype = {
preload: function() {
//assets we'll use in the loading screen
this.load.image('preloadbar', 'assets/images/preloader-bar.png');
},
create: function() {
//loading screen will have a white background
this.game.stage.backgroundColor = '#fff';
//scaling options
this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
//have the game centered horizontally
this.scale.pageAlignHorizontally = true;
this.scale.pageAlignVertically = true;
//screen size will be set automatically
this.scale.setScreenSize(true);
//physics system
this.game.physics.startSystem(Phaser.Physics.ARCADE);
this.state.start('Preload');
}
};
main.js와 같은 패턴을 따라 시작합니다. SideScroller가 이미 정의 된 경우이를 사용하고 그렇지 않으면 새 객체를 시작합니다.
SideScroller.Boot는 Boot 상태를 포함 할 객체입니다. 이것은 게임에 상태를 추가 할 때 main.js에서 전달한 것과 같은 객체입니다.
SideScroller.game.state.add('Boot', SideScroller.Boot);
preload 메소드에서 우리는 진행 상태를 위한 스프라이트를 로드하고 있습니다. 이것은 단순히 녹색 사각형입니다. 다음과 같이 이미지를 로딩하면 :
this.load.image('preloadbar', 'assets/images/preloader-bar.png');
우리는 게임의 어디에서든 이미지 자산을 참조 할 수 있습니다 (이 경우 "preloadbar").
(이미지는 HTML5 foriOS 및 Android로 만들어진 레트로 게임 Huungree RPG의 스크린 샷입니다.)
우리는 우리 게임이 수평 및 수직 가운데에 위치하기를 원합니다.
this.scale.pageAlignHorizontally = true;
this.scale.pageAlignVertically = true;
// Now, the game area we defined in main.js:
SideScroller.game = new Phaser.Game(746, 420, Phaser.AUTO, '');
게임의 픽셀을 나타냅니다. 또한 화면의 픽셀을 나타낼 수도 있지만 게임을 전체 화면에 맞추기를 원하는 대부분의 경우에 이 줄을 추가하는 이유는 다음과 같습니다.
this.scale.setScreenSize(true);
// Lastly, we initiate the physics engine and launch the Preload state:
this.game.physics.startSystem(Phaser.Physics.ARCADE);
this.state.start('Preload');
Preload State
Preload 상태는 모든 게임 에셋이 로드되는 곳입니다. 여기에는 게임에서 사용할 수있는 이미지, 오디오, 타일 맵 및 기타 파일이 포함됩니다.
왜 자산을 미리로드해야합니까?
웹 또는 하드 드라이브에서 파일을 로드하는 경우 시간이 오래 걸립니다. 플레이어는 총알을 쏘고 총알 스프라이트가로드 될 때까지 잠시 시간을 내어 게임이 멈추거나 표시해야하는 이미지가 표시되지 않도록합니다. 미리 로드함으로써, 모든 파일을 장치의 RAM에 로드하므로 게임에서 필요에 따라 이미지 / 리소스를 빠르게 액세스 할 수 있습니다. 처음부터 플레이어가 끔찍한 경험을 하는 것보다 기다리는 것이 가장 좋습니다!
var SideScroller = SideScroller || {};
//loading the game assets
SideScroller.Preload = function(){};
SideScroller.Preload.prototype = {
preload: function() {
//show loading screen
this.preloadBar = this.add.sprite(this.game.world.centerX, this.game.world.centerY, 'preloadbar');
this.preloadBar.anchor.setTo(0.5);
this.preloadBar.scale.setTo(3);
this.load.setPreloadSprite(this.preloadBar);
//load game assets
this.load.tilemap('level1', 'assets/tilemaps/level1.json', null, Phaser.Tilemap.TILED_JSON);
this.load.image('gameTiles', 'assets/images/tiles_spritesheet.png');
this.load.image('player', 'assets/images/player.png');
this.load.image('playerDuck', 'assets/images/player_duck.png');
this.load.image('playerDead', 'assets/images/player_dead.png');
this.load.image('goldCoin', 'assets/images/goldCoin.png');
this.load.audio('coin', 'assets/audio/coin.wav');
},
create: function() {
this.state.start('Game');
}
};
첫 번째 부분은 "preloadBar"라는 스프라이트의 생성입니다.이 스프라이트는 게임 영역의 중앙에 배치되며 이전에 Boot 상태로 로드 한 키 "preloadbar"가 있는 이미지를 사용합니다.
그런 다음 이미지의 앵커 포인트를 중간으로 설정합니다. 이미지가 커지고 축소되거나 회전한다고 가정 해보십시오. 고정점이 변형이 발생한 후에 화면의 같은 부분에 남아있는 점입니다.
막대가 조금 작기 때문에 크기를 3 배로 합니다.
this.preloadBar.scale.setTo(3);
Phaser는 우리가 스프라이트를 프리로드 바 (preload bar)로 지정할 수있게 해줍니다. 기본적으로 게임 요소가 로드되면 풀 사이즈가 될 때까지 커집니다.
this.load.setPreloadSprite(this.preloadBar);
그 후에 모든 요소를로드합니다. 로드가 완료되면 게임 상태로 이동합니다.
main.js에서 SideScroller.Game에 대한 참조를 주석 처리하면 이 시점에서 로딩 막대가 보입니다.
Game State
타일 기반 게임과 Tiled 맵 편집기에 대해서도 이야기 할 필요가 있지만 Game.js에 대한 해골을 넣어 콘솔 오류없이 게임을 완벽하게 실행할 수 있게하십시오.
var SideScroller = SideScroller || {};
SideScroller.Game = function(){};
SideScroller.Game.prototype = {
preload: function() {
this.game.time.advancedTiming = true;
},
create: function() {
//create player
this.player = this.game.add.sprite(100, 300, 'player');
},
update: function() {
},
render: function()
{
this.game.debug.text(this.game.time.fps || '--', 20, 70, "#00ff00", "40px Courier");
}
};
우리는 여기에서 두 가지 작업을 수행합니다.
첫번째 우리는 Preload에서 "player"라는 이미지를 사용하여 플레이어에 대한 스프라이트를 생성합니다.
두번째로, 우리는 이니셔티브를 통해 게임의 초당 프레임 수 (fps)를 여기에서 보여줍니다.
this.game.time.advancedTiming = true;
그런 다음 render 메서드에 디버그 텍스트를 표시합니다. Phaser는 60fps로 실행하는 것이 가장 좋습니다. 오늘날 HTML5 게임은 상당히 괜찮은 휴대 전화 및 컴퓨터에서도 이 속도로 실행되는 것이 어렵지만 점점 더 많이 개선되고 있습니다. 나는 성능에 주시하고 그 수가 떨어지는 상황 (나쁜 것)을 감지하기 위해이 숫자를 유지하기를 원합니다.
설명서에서 디버깅에 대해 자세히 읽어보십시오.
더 이상 이동하려면 Tiled에 대해 이야기해야합니다.
Get Tiled Face
Tiled는 Thorbjørn Lindeijer가 만든 정말 멋진 레벨 편집기로, 게임에 타일 기반 레벨을 시각적으로 만들 수 있습니다. 이 레벨에서 사용하는 주요 파일 형식을 TMX 및 XML이라고 합니다. Phaser에서 이러한 레벨을 사용하기 위해 이들을 JSON 파일로 내보낼 것입니다.
"타일"과 "타일 기반 게임"에 관해 이야기 할 때, 레벨이란 개별적인 작은 블록이나 조각으로 구성된 게임을 의미합니다. 이 개념에 100 % 익숙하지 않다면 Wikipedia 정의를 확인하십시오.
타일형식의 새지도를 만들려면 파일 -> 새로 만들기 :
레벨의 너비는 50 타일이고 높이는 6 타일입니다. 이 타일의 크기는 70 x 70 픽셀입니다. 오리엔테이션이 "직교"로 설정되고 레이어 포맷이 "CSV"로 설정되었는지 확인하십시오.
성과에 관한 조언 :지도가 커지고 성능이 최악입니다. 지도가 불필요하게 커지지 않도록하십시오. 또한 타일 크기가 작을수록 좋습니다. 심지어 70도 조금 큽니다. 게임 영역의 크기와 동일합니다. 알아야 할 유일한 방법은 게임을 밖으로 시도하는 것입니다. 느린 경우 게임이 잘 돌아갈 때까지 줄입니다.
다음 단계는 레이어를 만드는 것입니다. 레벨은 서로 위에있는 서로 다른 레이어를 가지게 됩니다. 레이어의 이름은 나중에 코드에서 참조해야하므로 중요합니다.
두 가지 유형의 레이어가 있습니다.
- 타일 레이어 : 타일 / 블록으로 이루어진 레이어.
- 객체 레이어 : 메타 데이터를 포함 할 수있는 벡터 객체를 만드는 레이어입니다.
레이어 아래에있는 "+" 버튼을 사용하여 레이어를 만들 수 있으며 새로 만든 레이어의 이름을 클릭하면 이름을 변경할 수 있습니다. 순서가 중요합니다. 화살표를 사용하여 순서를 재정렬 할 수 있습니다.
이 예에서는 backgroundLayer라는 배경 (플레이어와 충돌하지 않음)과 blockedLayer (바닥, 벽)라는 차단 요소를위한 두 개의 타일 레이어가 있습니다. 또한 게임 요소를 나타내는 하나의 오브젝트 레이어 (objectsLayer) (이 경우 동전)가 있지만 원수, 문, 플레이어의 시작 위치가 될 수도 있습니다.
이것은 모두 하나의 제안이며, 내가 정상적으로 그것을 하는 방법은 결코 관습이나 틀의 규칙이 아닙니다!
타일셋 로드하기
타일셋은 개별 타일 집합으로 이미지에 표시됩니다. 타일 맵에 많은 타일 세트를 사용할 수 있습니다. asset / images / tiles_spritesheet.png 중 하나만 사용합니다. 이 타일셋은 공개 도메인이므로 (자신의 상용 프로젝트에 사용할 수 있음) Kenney가 제작했습니다.
타일 세트를 로드하려면 : Map -> New Tileset
타일의 너비와 높이는 70 픽셀입니다 (가능한 경우 작게하려고하면 성능이 더 좋아집니다.). 타일 간격은 2 픽셀이므로 간격에 대해 2를 입력했습니다.
이름에 대해서는 tiles_spritesheets로 남겨 두었습니다. Phaser 내의 타일 시트를 참조 할 때이 이름을 사용하십시오.
레벨 만들기
이제 레벨을 만들 수 있습니다. 이제 당신에게 달려 있습니다! 내가 한 것은 외계인의 하늘을 표현하기 위해 분홍색 타일로 backgroundLayer를 채운 다음 (양동이 도구 사용) 차단 된 타일로 변경하고 일부 바닥과 장애물을 그렸습니다.
오브젝트 레이어
이 튜토리얼에서는 한 가지 유형의 객체만 추가 할 것입니다. 그러나 비슷한 접근 방식으로 원하는 만큼 객체를 추가 할 수 있습니다.
이 객체들은 타일셋의 스프라이트로 표현 될 것입니다. 그러나 그것은 우리의 단순성을 위한 것입니다. Phaser는 Sprite 객체를 만들 때 Sprite를 표시합니다.
쉽게 설명을 하자면, tiles_spritesheet.png 파일에 나는 동전을 넣었고 이 이미지를 사용하는 물체를 레벨에 배치 할 것입니다. 동전 이미지를 추가하는 대신 문자 "C"가있는 타일을 추가 할 수있었습니다. Phaser는 당신이 말한 것을 로드 할 것이지만 레벨이 실제로 어떻게 보이는지를 시각화 할 수 있다면 더 쉽습니다.
새 오브젝트를 삽입하려면 : objectsLayer 레이어를 선택하고 타일 삽입 버튼 (일몰 사진을 표시하는 버튼)을 클릭 한 다음 표시 할 타일을 클릭 한 다음, 지도에서 오브젝트의 위치를 클릭하십시오.
객체에 속성 추가하기
Phaser는 이러한 요소가 무엇을 나타내며 스프라이트를 사용하여 표시해야하는지 어떻게 알 수 있을까요? 이는 객체에 속성을 추가하면 알수 있습니다.
메뉴에서 "개체 선택"단추를 클릭 한 다음 개체를 두 번 클릭하면 속성을 입력 할 수있는 대화 상자가 나타납니다.
값이 "coin"인 "type"이라는 속성을 입력하십시오. "sprite"라는 또 다른 속성을 설정하고 "coin"을 입력하십시오. 이것은 Phaser에게이 오브젝트에 어떤 이미지를 사용해야하는지 알려줄 것입니다 (Preload에서 동전 이미지를로드하고 "동전"이라고 부르므로 여기에 "coin"을 입력하는 이유입니다).
게임에 따라 더 많은 속성을 입력 할 수 있습니다 (실제로 당신에게 달려 있습니다!). 예를 들어, "value"을 사용하여 다른 동전에 다른 값을 주거나 여기에 다른 "sprite"값을 지정하면 동전 오브젝트에 다른 스프라이트를 사용할 수 있습니다.
여러 요소를 만들려면 생성 한 동전을 마우스 오른쪽 버튼으로 클릭하고 "duplicate"를 클릭하면 해당 속성과 함께 개체를 빠르게 복제 할 수 있습니다. 여러 동전을 복제하여 레벨 주위로 끌 수 있습니다.
더 나아 가기 전에,이 튜토리얼 (및 다른 튜토리얼의 일부)으로 구축하고있는 게임 템플릿으로 동작 하는것이 디폴트의 Phaser 기능이 아닌 것을 분명히 하고 싶습니다. Phaser는 동일한 스프라이트 키를 사용하여 객체로부터 여러 스프라이트를 생성하는 기능 만 제공합니다. Phaser가 이 부분에서 제공하는 것을 배우려면 Tilemap 문서를보십시오.
일단 JSON 파일로 레벨을 내보내십시오. 파일 -> 다른 이름으로 내보내기 -> Json 파일, /assets/tilemaps/level1.json에 저장
타일 맵을 게임에 로드하기
이 프로세스 개요는 다음과 같습니다.
- 타일셋 PNG 파일과 레벨 JSON 파일을 미리로드하십시오 (이 시점에서 Phaser는 이들이 함께 속하는지 알지 못합니다).
- Game 구문에 타일 맵 만들기 추가하기
- 타일셋을 타일 맵에 추가합니다 (기본적으로 JSON 데이터를 실제 이미지와 병합하여 Phaser가 함께 사용한다는 것을 알 수 있습니다)
- 타일 레이어를 타일 맵에 추가합니다 (backgroundLayer 및 blockedLayer).
blockedLayer를 충돌 레이어로 만듭니다.
- 바둑판 식 기반으로 만든 레벨과 같은 크기로 게임 세계 크기를 조정하십시오.
- 레이어 objectsLayer에있는 것을 기반으로 게임 개체를 만듭니다.
타일 맵과 관련하여 프리로드 상태에서 로드 한 내용을 검토해 보겠습니다.
this.load.tilemap('level1', 'assets/tilemaps/level1.json', null, Phaser.Tilemap.TILED_JSON);
this.load.image('gameTiles', 'assets/images/tiles_spritesheet.png');
로더의 문서 페이지에서 더 자세히 살펴보십시오. 머리 속에 "레벨 1"과 "gameTiles"라는 키를 사용하십시오. 이 키는 완전히 임의적이며 나중에이 애셋을 나타 내기 위해 사용할 것입니다.
이제 플레이어 생성 전에 Game.js에 create 메소드의 다음을 추가합니다.
this.map = this.game.add.tilemap('level1');
//the first parameter is the tileset name as specified in Tiled, the second is the key to the asset
this.map.addTilesetImage('tiles_spritesheet', 'gameTiles');
//create layers
this.backgroundlayer = this.map.createLayer('backgroundLayer');
this.blockedLayer = this.map.createLayer('blockedLayer');
//collision on blockedLayer
this.map.setCollisionBetween(1, 100000, true, 'blockedLayer');
//resizes the game world to match the layer dimensions
this.backgroundlayer.resizeWorld();
처음 세 문장은 단계 설명 다음에 설명이 필요합니다. 충돌에 대한 설명 :
this.map.setCollisionBetween(1, 100000, true, 'blockedLayer');
처음 두 매개 변수는 타일 ID의 범위를 지정합니다. 타일을 사용하여 지도를 저장하면 각 타일마다 고유 한 ID가 지정됩니다. level1.json을 열면이 숫자가 표시됩니다. setCollisionBetween을 사용하면 범위 내의 모든 ID에 대해 충돌을 지정할 수 있습니다. 1에서 100000과 같이 큰 숫자를 지정하면 blockedLayer의 모든 타일에 충돌이 발생합니다.
우리는 게임 세계를 배경 레이어의 크기로 조정하고 있습니다. 동일한 크기이므로 차단 된 레이어 일 수도 있습니다. 중요하지 않습니다.
이제는 목록의 8 단계 (객체 계층에서 객체 생성)를 무시합니다.
이것을 실행하면 브라우저에 두 레이어가 모두 표시됩니다. 이제 플레이어에게 생명을 불어 넣을 시간입니다!
플레이어 추가하기
우리 플레이어는 이미 화면에 있지만 많이하지는 않습니다! 우리는 중력을 줌으로써 바보처럼 주위를 떠 다니는 것이 아닙니다.
이를 위해 우리는 물리엔진을 가능하게 해야합니다. 일단 물리엔진이 가능 해지면, 플레이어 스프라이트에는 body라는 속성이 생기고, 모든 물리 속성과 동작을 갖게됩니다.
//create player
this.player = this.game.add.sprite(100, 300, 'player');
//enable physics on the player
this.game.physics.arcade.enable(this.player);
//player gravity
this.player.body.gravity.y = 1000;
//the camera will follow the player in the world
this.game.camera.follow(this.player);
우리는 플레이어에게 육체뿐 아니라 중력도 주었습니다! 이제 플레이어는 땅에 떨어져야하고, 카메라는 플레이어를 따라 레벨을 따라 가야합니다. 플레이어가 지나가는 대신에 땅에 닿도록 해 봅시다.
충돌 감지
충돌 감지는 두 게임 요소가 서로 교차할때 이를 감지하여 이에 따라 조치를 취할 수 있음을 의미합니다.
Phaser는 충돌과 중첩의 두 가지 옵션을 제공합니다. 두 객체 사이에 충돌 메소드를 지정하면 물리적으로 충돌에 도달합니다. 예를 들어 속도가 멈출 것입니다. 반면에 겹치는 동작을 지정하면 서로 영향을주지 않습니다. 마치 홀로그램을 건드린 것과 같습니다.
우리 게임에서는 플레이어와 바닥 사이에 충돌이 발생하기를 원하며, 플레이어와 동전 사이에 겹치는 동작을 원합니다 (그렇지 않으면 동전을 만질 때처럼 벽에 충돌하는 것처럼 될것입니다).
update 메소드는 모든 게임 틱 ( "초당 여러 번")에서 트리거되기 때문에 충돌 감지를 처리 할 장소입니다.
성능이 너무 낮으면 게임에서 충돌 감지가 제 시간에 일어나지 않고 벽과 바닥을 통과 할 가능성이 있습니다. 그러므로 fps를 주시하십시오 !!
update ()에 다음을 추가 할 수 있습니다.
//collision
this.game.physics.arcade.collide(this.player, this.blockedLayer, this.playerHit, null, this);
게임 상태에 playerHit 메서드를 추가합니다. 이 메소드는 플레이어가 blockedLayer와 충돌 할 때 실행됩니다. 지금은 비어있을 것입니다.
playerHit: function(player, blockedLayer) {}
나중에 코드를 추가하면 됩니다.
this.game.physics.arcade.collide(this.player, this.blockedLayer)
우리 플레이어는 이제 타격을 입어야합니다.
기본 사이드 스크롤링
이제 x에서 속도를 주어 오른쪽으로 이동하고 UP 키를 사용하여 점프하는 기능을 추가합니다.
Phaser 및 내가 알고있는 모든 HTML5 게임 프레임 워크에서 X 축은 오른쪽으로 증가하고 왼쪽으로 감소합니다. Y 축은 내려갈 때 증가하고 올라갈 때 감소합니다. 좌표계의 원점은 왼쪽 상단 구석에 있습니다.
create 메소드가 끝나면 커서 키 (화살표 키)를 시작합니다.
[javscript] // 커서 키로 플레이어 이동
this.cursors = this.game.input.keyboard.createCursorKeys (); [/ javascript]
업데이트 메소드에서 다음을 추가하십시오.
this.player.body.velocity.x = 300;
if(this.cursors.up.isDown) {
this.playerJump();
}
우리가 하는 첫 번째 작업은 플레이어에게 x의 속도를 300으로 지정하는 것입니다. 플레이어의 물리 엔진을 활성화 했기 때문에 이 작업을 수행 할 수 있습니다.
위쪽 화살표를 누르면 playerJump라는 메서드가 실행됩니다. 게임 상태에서이 메서드를 추가해 보겠습니다.
playerJump: function() {
if(this.player.body.blocked.down) {
this.player.body.velocity.y -= 700;
}
},
플레이어가 바닥에 닿아 있는지 여부를 확인하고 있습니다. 그렇다면 우리는 뛰어 넘을 수 있습니다. 점프는 Y에 속도를 설정하는 것으로 구성됩니다 (올라갈 때 음수).
이제 캐릭터를 레벨로 이동시키고 장애물을 뛰어 넘을 수 있어야합니다. 우리는 여전히 오리를 먹을 수는 없지만 다음에 그것을 추가 할 것입니다.
Ducking
점핑 외에도 우리는 장애물을 피할 수 있기를 원합니다. 우리는 점프와 비슷한 접근법을 따를 것입니다. 유일한 차이점은 플레이어가 이미지 "player_duck.png"( "Preload"의 key "playerDuck")로 향할 때 스프라이트 이미지를 변경하려는 것입니다.
create 메서드에서 플레이어의 중력을 설정 한 후
//properties when the player is ducked and standing, so we can use in update()
var playerDuckImg = this.game.cache.getImage('playerDuck');
this.player.duckedDimensions = {width: playerDuckImg.width, height: playerDuckImg.height};
this.player.standDimensions = {width: this.player.width, height: this.player.height};
this.player.anchor.setTo(0.5, 1);
각 코드 행을 살펴보기 전에 우리가하는 일을 설명하겠습니다. 플레이어가 오리를 잡을 때 이미지를 "playerDuck"로 변경하고 싶습니다. 이미지를 변경하는 것도 한 가지이지만, 우리는 또한 물리엔진 객체를 나타내는 플레이어의 바디를 업데이트 해야합니다. 이미지를 더 작은 이미지로 변경하더라도 명시 적으로 말하지 않으면 본문이 변경되지 않습니다.
더욱이, 우리가 오리를 나타낸 후에는 플레이어가 다시 일어나기를 원하기 때문에 플레이어의 몸을 한 번 더 업데이트해야합니다.
첫 번째 줄은 플레이어의 이미지를 가져 와서 크기를 읽을 수 있도록하는 것입니다.
var playerDuckImg = this.game.cache.getImage('playerDuck');
그런 다음 이 이미지의 크기와 서있는 플레이어의 크기를 저장하여 나중에 다시 사용할 수 있습니다.
this.player.duckedDimensions = {width: playerDuckImg.width, height: playerDuckImg.height};
this.player.standDimensions = {width: this.player.width, height: this.player.height};
플레이어가 수축 할 때 발이 바닥에 있어야합니다 (오리를 잡을 때 시도하는 방식입니다). 따라서 앵커 포인트를 스프라이트 맨 아래에 설정해야합니다.
this.player.anchor.setTo(0.5, 1);
이제 update 함수에서 DOWN 화살표를 읽고 플레이어가 오리가 되게합니다. 또한 플레이어가 더킹 된 상태에서 더 이상 DOWN 키를 누르고 있지 않은 경우, 그 사람이 다시 일어 서기를 바랍니다.
this.player.body.velocity.x = 300;
if(this.cursors.up.isDown) {
this.playerJump();
}
else if(this.cursors.down.isDown) {
this.playerDuck();
}
if(!this.cursors.down.isDown && this.player.isDucked) {
//change image and update the body size for the physics engine
this.player.loadTexture('player');
this.player.body.setSize(this.player.standDimensions.width, this.player.standDimensions.height);
this.player.isDucked = false;
}
그리고 playerDuck 메소드 :
playerDuck: function() {
//change image and update the body size for the physics engine
this.player.loadTexture('playerDuck');
this.player.body.setSize(this.player.duckedDimensions.width, this.player.duckedDimensions.height);
//we use this to keep track whether it's ducked or not
this.player.isDucked = true;
},
이제 이 방법으로 물체를 피할 수 있습니다!
게임 오버
이 게임에서는 절벽에서 떨어지거나 벽 / 장애물을 피하는 대신 패를 당하면 잃게됩니다. 낙하로 인한 사망을 구현하기 위해 업데이트 방법에 새로운 체크를 추가 할 것입니다. 플레이어의 위치가 게임 세계의 가장자리보다 크거나 같으면 게임을 다시 시작하는 gameOver () 메소드를 실행합니다.
//restart the game if reaching the edge
if(this.player.x >= this.game.world.width) {
this.game.state.start('Game');
}
플레이어가 죽을 수있는 다른 방법은 장애물을 부딪치는 것입니다. 기본적으로 플레이어의 오른쪽에 차단 된 레이어 사이에 충돌이있을 때. 이는 플레이어가 오른쪽으로 이동하기 때문입니다. 이전에 생성 한 playerHit 메소드에서 이 로직을 구현합니다. 다음과 같습니다.
playerHit: function(player, blockedLayer) {
//if hits on the right side, die
if(player.body.blocked.right) {
//set to dead (this doesn't affect rendering)
this.player.alive = false;
//stop moving to the right
this.player.body.velocity.x = 0;
//change sprite image
this.player.loadTexture('playerDead');
//go to gameover after a few miliseconds
this.game.time.events.add(1500, this.gameOver, this);
}
},
우리가 "alive" 속성을 어떻게 확인하는지보십시오. 이 속성은 페이저의 모든 스프라이트에 존재하지만 렌더링에는 영향을 미치지 않지만 게임 요소를 라이브 또는 죽게하는 것이 대부분의 게임에서 공통적이기 때문에 편의상 배치되었습니다.
플레이어가 오른쪽 (player.body.blocked.right)에 도달하면 움직임을 멈추고 플레이어를 "not alive"로 설정하고 이미지를 preloaded에 로드 한 playerDead 이미지로 변경 한 후 게임을 다시 시작합니다. 1.5 초 (바닥에 닿을 시간을 줄 수 있습니다!).
하지만, 만약 플레이어가 죽었다 할지라도 키를 사용한다면 우리는 여전히 뛰어 넘을 수 있습니다. 플레이어가 점프 또는 오리를 만들기 전에 플레이어가 살아 있는지 확인해야합니다. 업데이트를 수정하면 다음과 같이 보입니다.
update: function() {
//collision
this.game.physics.arcade.collide(this.player, this.blockedLayer, this.playerHit, null, this);
this.game.physics.arcade.overlap(this.player, this.coins, this.collect, null, this);
//only respond to keys and keep the speed if the player is alive
if(this.player.alive) {
this.player.body.velocity.x = 300;
if(this.cursors.up.isDown) {
this.playerJump();
}
else if(this.cursors.down.isDown) {
this.playerDuck();
}
if(!this.cursors.down.isDown && this.player.isDucked) {
//change image and update the body size for the physics engine
this.player.loadTexture('player');
this.player.body.setSize(this.player.standDimensions.width, this.player.standDimensions.height);
this.player.isDucked = false;
}
//restart the game if reaching the edge
if(this.player.x >= this.game.world.width) {
this.game.state.start('Game');
}
}
},
타일형식으로 배열 된 레이어에서 객체로드
지금까지 타일링 된 맵에서 타일 레이어 만 로드했습니다. 우리도 객체 레이어를 만들었던 것을 잊지 마십시오. 이러한 객체를 로드하려면 Tilemap 클래스와 함께 제공되는 메서드를 사용할 수 있지만 그렇게 하려면 원하는 각 객체 유형에 대해 객체 레이어를 만들어야합니다 (각 스프라이트 이미지에 대해서도 마찬가지 임) .
내가 선호하는 또 다른 옵션은 게임의 모든 개체에 대해 단일 개체 계층을 사용하고 각 개체의 개체 유형 및 사용할 스프라이트 이미지를 지정하는 것입니다. 우리가 그렇게 할 수 있도록 게임 상태에 추가 할 두 가지 도우미 메서드를 만들었습니다.
첫 번째 메소드 인 findObjectsByType을 사용하면 특정 유형과 일치하는 객체 레이어의 모든 객체가 있는 배열을 얻을 수 있습니다 (객체 계층을 타일로 만들 때 유형을 어떻게 지정했는지 기억하십시오).
//find objects in a Tiled layer that containt a property called "type" equal to a certain value
findObjectsByType: function(type, map, layerName) {
var result = new Array();
map.objects[layerName].forEach(function(element){
if(element.properties.type === type) {
//Phaser uses top left, Tiled bottom left so we have to adjust
//also keep in mind that some images could be of different size as the tile size
//so they might not be placed in the exact position as in Tiled
element.y -= map.tileHeight;
result.push(element);
}
});
return result;
},
두 번째 방법 인 createFromTiledObject는 객체에서 스프라이트를 만드는 것입니다. 예를 들어 첫 번째 방법을 사용하여 모든 "coin"을 가져온 다음 두 번째 방법을 사용하여 실제로 스프라이트를 만듭니다.
//create a sprite from an object
createFromTiledObject: function(element, group) {
var sprite = group.create(element.x, element.y, element.properties.sprite);
//copy all properties to the sprite
Object.keys(element.properties).forEach(function(key){
sprite[key] = element.properties[key];
});
},
Loading Coin
두 가지 도우미 메서드를 사용하여 게임에서 모든 동전을 만듭니다. create 메소드에서 world resizing 문 다음에 다음을 추가하십시오.
this.createCoins();
Let's implement the createCoins method:
//create coins
createCoins: function() {
this.coins = this.game.add.group();
this.coins.enableBody = true;
var result = this.findObjectsByType('coin', this.map, 'objectsLayer');
result.forEach(function(element){
this.createFromTiledObject(element, this.coins);
}, this);
},
여기에 무슨 일이 일어날까요? :
우리는 동전이라는 새로운 스프라이트 그룹을 만듭니다. Phaser에서는 그룹을 사용하여 동일하거나 유사한 속성 및 동작을 가진 요소 집합과 쉽게 작업 할 수 있습니다. 예를 들어, 적, 수집품, 아이템 등에 대한 그룹을 만들 수 있습니다.
우리는 전체 그룹을 대상으로 물리 시스템을 활성화합니다.
우리는 objectsLayer에서 모든 동전 오브젝트를 찾습니다.
우리가 가진 것은 일련의 사물들이었습니다. 이제 우리는 이 객체들 각각에 대해 실제 스프라이트를 만들고 싶습니다.
우리는 플레이어가 동전을 수집하기를 원합니다. 플레이어가 동전에 닿으면 플레이어의 속도가 동전의 영향을 받지 않아야 하므로 충돌 대신 겹침 유형을 사용합니다. 업데이트 방법 :
this.game.physics.arcade.overlap(this.player, this.coins, this.collect, null, this);
다음에 collect 메소드를 구현해 보겠습니다.
소리
Phaser에서 사운드를 재생하는 것은 매우 간단하며 다른 애셋을 로드 / 사용하는 것과 동일한 방식을 따릅니다. Preloader에서 먼저 로드해야합니다.
this.load.audio('coin', 'assets/audio/coin.wav');
그런 다음 게임 상태의 create 메소드에서 오디오 객체를 만듭니다.
//sounds
this.coinSound = this.game.add.audio('coin');
사운드를 재생하려면 사운드 객체의 play () 메서드를 사용하면 됩니다. 우리는 collect 메서드에서 동전 사운드를 재생합니다 (사용자가 동전을 쥐었을 때).
collect: function(player, collectable) {
//play audio
this.coinSound.play();
//remove sprite
collectable.destroy();
},
이상적으로 동전을 추적하고 총계를 화면에 표시하고 싶지만,이 예에서는 그 부분을 생략합니다.
사운드 관련 추가 노트
Phaser는 사용 가능한 경우 WebAudio API를 사용하고, 그렇지 않으면 AUDIO 태그를 사용하여 사운드를 나타냅니다. 이것은 자동으로 처리됩니다.
서로 다른 브라우저는 오디오 형식에 대한 지원이 다릅니다. Phaser에서는 동일한 오디오에 대해 여러 개의 오디오 파일을 전달할 수 있으므로 엔진이 브라우저에 맞는 형식으로 재생됩니다.
가능한 한 오디오 파일을 압축하십시오. 무료 사운드 유틸리티 인 Audacity를 사용하면됩니다. 나는 일반적으로 프로젝트 속도 (HZ)를 가능한 한 많이 내리고 품질은 좋습니다. 그런 다음 파일을 OGG 및 MP3로 내 보냅니다.
터치 컨트롤러
이 섹션에서는 HTML5 Virtual Game Controller라는 외부 라이브러리를 사용하여 터치 컨트롤러를 추가 할 것입니다. 페이지를 체크 아웃하여 다양한 컨트롤러 유형에 대한 스크린 샷과 코드 예제를 확인하십시오.
우리 게임에서 우리는 단지 두 개의 버튼을 원할뿐입니다. 하나는 점핑을 위한 것이고 다른 하나는 더킹을 위한 것입니다. 이 게임을 게시하려면 이 작업을 위해 화면을 스와이프 하거나 터치하는 것을 선호하지만 이 튜토리얼에서는 터치 컨트롤러 사용법을 포함하기를 원했습니다.
우리가 한 첫번째 일은 우리의 인덱스 페이지에 gamecontroller.js (또는 gamecontroller.min.js)를 포함 시켰습니다. GameController라는 전역 개체가 만들어집니다.
이 컨트롤러는 CANVAS에 그려집니다. 우리는 상태를 다시 시작할 때마다 다르게 그려지기를 원합니다. 다시 그려집니다. 게임 상태의 create 메소드에서 add :
//init game controller
this.initGameController();
Lets implement this method:
initGameController: function() {
if(!GameController.hasInitiated) {
var that = this;
GameController.init({
left: {
type: 'none',
},
right: {
type: 'buttons',
buttons: [
false,
{
label: 'J',
touchStart: function() {
if(!that.player.alive) {
return;
}
that.playerJump();
}
},
false,
{
label: 'D',
touchStart: function() {
if(!that.player.alive) {
return;
}
that.pressingDown = true; that.playerDuck();
},
touchEnd: function(){
that.pressingDown = false;
}
}
]
},
});
GameController.hasInitiated = true;
}
},
GameController.hasInitiated는 컨트롤러가 시작되었는지 여부를 추적하여 두 번하지 않도록 추적하는 데 사용할 변수입니다.
우리가 키보드로 했던 것과 비슷한 방식으로 player.alive를 확인하고 어떻게 작동하는지 보십시오.
업데이트에서 변경하고자 하는 한 가지 사항은 다운 키가 눌러지지 않았지만 오리 버튼이 눌려지고있는 경우 플레이어가 일어 서기를 원하지 않기 때문에 다음을 대체하십시오.
//instead of: if(!this.cursors.down.isDown && this.player.isDucked) {
if(!this.cursors.down.isDown && this.player.isDucked && !this.pressingDown) {
이젠 모바일에서도 키보드를 사용하지 않고도 사용할수 잇습니다.
이 튜토리얼의 게임 소스는 링크로 접속하여 다운 받으시면됩니다.
댓글 없음:
댓글 쓰기