Phaser 3의 모듈러 게임 세계 (타일 맵 # 1) - 정적 맵

이것은 Phaser 3 게임 엔진에서 타일 맵을 사용하여 모듈러 월드를 만드는 것에 관한 일련의 블로그 게시물입니다. 이 첫 번째 글에서는 플레이어가 탐색 할 수있는 포켓몬 스타일의 톱 다운 게임 세계를 만들기 위해 0부터 진행할 것입니다.

다음 글에서는 동적 플랫폼 작성자를 만드는 방법에 대해 다룹니다. 그 이후에는 Matter.js를 사용하여 절차 적으로 생성 된 던전과 벽면 점핑 맵을 다룰 것입니다.

우리가 들어가기 전에이 게시물과 관련된 모든 코드가이 저장소에 있습니다. 이 자습서에서는 02/26/19 현재 Phaser (v3.16.2) 및 Tiled (v1.2.2)의 최신 버전을 사용합니다.

대상 독자

이 게시물은 JavaScript, Phaser 및 Tiled 맵 편집기에 대한 경험이있는 사용자에게 가장 적합합니다. 그렇지 않으면 독서를 유지하면서 Google, Phaser 자습서 및 Phaser 예제 및 문서를 손쉽게 작성하여 틈을 메워야합니다.

좋아, 들어 가자!

타일 맵이란?

타일 맵은 모듈러 빌딩 블록으로 게임 세계를 만드는 기술입니다. 세상을 레고와 같은 조각들로 나눌 때, 기억력, 성과 및 창조적 인 승리를 얻게됩니다.

Mario를 처음부터 다시 만들려고한다고 상상해보십시오. 거대한 이미지 파일로 각 레벨을로드하려고 결정했다고합시다. 세계 1-1 위는 3500 픽셀 이상입니다.


NES 게임의 첫 번째 레벨과 다른 31 개의 레벨을 이미지로 저장하려면 많은 픽셀이 필요합니다. 또한 이미지를 로직과 게임에 동기화하는 것은 어려울 것입니다. Mario는 어떤 픽셀을 사용할 수 있습니까? 그가 입력 할 수있는 파이프에 해당하는 픽셀은 무엇입니까?

타일 맵 접근법은 레벨을 빌드하는 데 사용할 수있는 모듈 식, 규칙적인 크기의 타일 세트를 정의합니다. 그렇게하면 우리는 하나의 이미지, 타일셋만을 필요로합니다.


따라서 304px x 192px 이미지는 기존 마리오 게임의 모든 레벨과 상상할 수있는 새로운 레벨을 재현 할 수 있습니다. (물론, 당신은 여전히 콧등이있는 남자와 2 인승 거북이를 놓치고 있습니다.) 각 타일은 단지 16 x 16 픽셀입니다. 이러한 타일을 한 레벨에 배열하는 것을 타일 맵 (tilemap)이라고합니다. 타일 맵 편집 소프트웨어를 사용하여 타일 속성을 쉽게 구성 할 수 있습니다. 예를 들어, 지상 타일과 같은 일부 타일은 마리오가 견딜 수있는 견고한 타일로 표시 할 수 있습니다.

따라서 타일 맵을 사용하면 수준 디자인 (창조적 인 승리)을 쉽게 만들고 반복 할 수있는 작은 이미지 (성능 및 메모리 우승)를 얻을 수 있습니다.

Phaser 3 기본 템플릿

코드에서 타일 맵을로드하기 전에 Phaser 3 게임의 구조를 살펴 보겠습니다. v3에서는 게임이 Scene 객체 주위에 구성됩니다. 이것들은 v2의 상태 객체와 비슷하지만보다 유연합니다.
const config = {
type: Phaser.AUTO, // Which renderer to use
width: 800, // Canvas width in pixels
height: 600, // Canvas height in pixels
parent: "game-container", // ID of the DOM element to add the canvas to
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
function preload() {
// Runs once, loads up assets like images and audio
}
function create() {
// Runs once, after all assets in preload are loaded
}
function update(time, delta) {
// Runs once per frame for the duration of the scene
}
이것은 Phaser 예제 저장소 전체에서 볼 수있는 템플릿입니다. 시작하기 쉬운 방법입니다. 게임을 생성하고 장면을 미리로드, 생성 및 업데이트하는 기능 모음으로 정의합니다.

다음은 백그라운드와 일부 텍스트를로드하고 만드는 방법을 보여주는 약간 더 복잡한 예제입니다.
실행데모)
https://www.mikewesthad.com/phaser-3-tilemap-blog-posts/post-1/00-phaser-3-template/


Phaser를 사용하지 않았다면이 시리즈에는 새로운 개념이 많이있을 수 있으므로 분명한 경우 광범위한 예제문서를 확인하십시오.

첫 번째 단계

Mario 타일셋을 제거한 가장 간단한 설정부터 시작하여 미니 마리오 레벨을 다시 만들어 보겠습니다.



마지막 섹션의 상용구부터 시작하겠습니다. 프리로드 내부에서 타일셋 이미지를로드 할 수 있습니다.
function preload() {
this.load.image("mario-tiles", "../assets/tilesets/super-mario-tiles.png");
}

이것은 현재 장면과 this.load를 참조합니다 .load는 자산 로딩을 처리하는 장면의 로더입니다. create 함수는 미리로드 된 모든 에셋이로드 될 때까지 실행되지 않습니다.
function create() {
// Load a map from a 2D array of tile indices
const level = [
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 1, 2, 3, 0, 0, 0, 1, 2, 3, 0 ],
[ 0, 5, 6, 7, 0, 0, 0, 5, 6, 7, 0 ],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0, 0, 14, 13, 14, 0, 0, 0, 0, 0 ],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
[ 0, 0, 14, 14, 14, 14, 14, 0, 0, 0, 15 ],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 15 ],
[ 35, 36, 37, 0, 0, 0, 0, 0, 15, 15, 15 ],
[ 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, 39 ]
];
// When loading from an array, make sure to specify the tileWidth and tileHeight
const map = this.make.tilemap({ data: level, tileWidth: 16, tileHeight: 16 });
const tiles = map.addTilesetImage("mario-tiles");
const layer = map.createStaticLayer(0, tiles, 0, 0);
}
레벨은 타일 세트의 특정 타일을 가리키는 숫자 또는 인덱스의 2D 배열입니다. 0은 왼쪽 상단 타일, 1은 옆 타일입니다.



참고 : 0보다 작은 인덱스는 빈 타일로 간주됩니다.
실행데모)
https://www.mikewesthad.com/phaser-3-tilemap-blog-posts/post-1/01-array/





이 코드를 세분화하면 세 가지 주요 부분이 있습니다. 타일 맵, 타일 세트 및 정적 타일 맵 레이어입니다. this.make.tilemap (또는 this.add.tilemap)을 통해 Tilemap을 생성합니다. 이 객체는 표시 객체가 아니고 지도에 대한 데이터를 보유하고 있으므로 타일 세트 및 타일 맵 레이어를 추가 할 수 있습니다.

지도에는 하나 이상의 레이어가있을 수 있습니다.이 레이어는 실제로 타일 세트에서 타일을 렌더링하는 표시 객체입니다. StaticTilemapLayer & DynamicTilemapLayer의 두 가지 맛이 있습니다. StaticTilemapLayer는 매우 빠르지 만 해당 레이어의 타일을 수정할 수 없으며 뒤집기 또는 색조와 같은 타일 별 효과를 렌더링 할 수 없습니다. DynamicTilemapLayer는 개별 타일 조작의 유연성과 성능면에서 어느 정도 속도가 비슷합니다. 이 게시물에서는 정적 레이어를 사용 하겠지만 다음에 동적 레이어를 살펴 보겠습니다.

Loading from a File: CSV

2D 배열에서지도를로드하는 것 외에도 CSV에서로드 할 수 있습니다.
function preload() {
this.load.image("tiles", "../assets/catastrophi_tiles_16_blue.png");
this.load.tilemapCSV("map", "../assets/catastrophi_level2.csv");
}
function create() {
// When loading a CSV map, make sure to specify the tileWidth and tileHeight!
const map = this.make.tilemap({ key: "map", tileWidth: 16, tileHeight: 16 });
const tileset = map.addTilesetImage("tiles");
const layer = map.createStaticLayer(0, tileset, 0, 0); // layer index, tileset, x, y
}
참고 :이 예제는 기본적으로 Rich Davey & Ilija Melentijević의 Cat Astro Phi 자산을 특징으로하는 Phaser 예제의 사본입니다.

실행데모)
https://www.mikewesthad.com/phaser-3-tilemap-blog-posts/post-1/02-csv/

우리는 Phaser의 카메라 시스템을 사용하여 플레이어가 전 세계를 돌아 다니도록함으로써 여기에 쉽게 상호 작용을 추가 할 수 있습니다. 이 코드는 사용 된 Phaser의 새로운 부분을 설명하지만 Phaser 카메라 예제를 통해 카메라에 대한 자세한 내용을 확인할 수 있습니다.

실행데모)
https://www.mikewesthad.com/phaser-3-tilemap-blog-posts/post-1/03-csv-camera/


타일로 지도 만들기

2D 배열 또는 CSV에서 로드하는 것은 간단한 것을 테스트하거나 절차 적 세계를 생성하려는 경우에 좋지만 확률은 수평 설계 도구가 필요할 것입니다. 그것이 바로 Tiled가 등장하는 곳입니다. 무료 오픈 소스 타일 맵 편집기로 CSV, JSON 및 다른 형식으로 내보낼 수 있습니다.

우리는 Tiled를 사용하는 방법에 대해 배우지 않을 것입니다 - 그 자체로 광대 한 주제입니다 - Tiled의 문서와 Crash 튜토리얼 시리즈의 Game을 통해 충돌 과정을 확인하십시오. 여기에서 데모에서 타일 맵 (.tmx 파일) 및 타일 세트를 다운로드 할 수도 있습니다.

Phaser의 지도를 생성하기 위해 Tiled로 작업 할 때 수행해야 할 몇 가지 사항이 있습니다.

1. 타일셋을지도에로드 할 때 '지도에 포함'옵션을 선택해야합니다. 이 작업을 잊어 버린 경우에는 화면 하단의 embedding tileset 버튼을 클릭하십시오. 아래의 첫 번째 두 이미지를 참조하십시오.

2. 압축 된 "Tile Layer Format"을 사용하고 있지 않은지도 확인하십시오. 상단 툴바의 "Map → Map Properties"를 눌러 열 수있는지도 속성 사이드 바에서 조정할 수 있습니다. 아래의 세 번째 이미지를 참조하십시오.

3. 지도를 내보낼 때 JSON 파일로 저장하십시오.

타일 세트를 생성 할 때 임베드하기

타일 세트를 작성한 후에 임베드하기

타일 맵 형식 변경하기

중요 사항! 최근 출시 된 Tiled (버전 1.2)에서는 Phaser의지도 가져 오기를 중단하는 방식으로지도 내보내기 형식을 변경했습니다. 지도를로드하는 데 문제가 발생하는 경우 (특히 충돌 정보가로드되지 않는 경우) 적어도 Phaser 3.14.0 이상을 사용하고 있는지 확인하십시오.

바둑판 식으로 된 맵로드하기

tilemapTiledJSON 로더 메소드를 사용하여 Tiled에서 내 보낸 타일 맵을로드하고 표시 할 수 있습니다.
function preload() {
this.load.image("tiles", "../assets/tilesets/tuxmon-sample-32px-extruded.png");
this.load.tilemapTiledJSON("map", "../assets/tilemaps/tuxemon-town.json");
}
function create() {
// Parameters are the name you gave the tileset in Tiled and then the key of the tileset image in
// Phaser's cache (i.e. the name you used in preload)
const tileset = map.addTilesetImage("tuxmon-sample-32px-extruded", "tiles");
// Parameters: layer name (or index) from Tiled, tileset, x, y
const belowLayer = map.createStaticLayer("Below Player", tileset, 0, 0);
const worldLayer = map.createStaticLayer("World", tileset, 0, 0);
const aboveLayer = map.createStaticLayer("Above Player", tileset, 0, 0);
}

이 단계는 주로 데이터를 연결하는 단계입니다. 이름을 약간 더 명확히하기 위해 이름의 출처는 다음과 같습니다.


지도가 서로 위에 놓인 여러 개의 레이어로 구성되어 있음을 알 수 있습니다.


Tiled로 작업 할 때 일반적인 디자인 패턴입니다. 그것은 우리가 게임에서 다른 "깊이"에 배치 될 요소를 분리 할 수있게 해줍니다. 이 레이어를 사용하면 플레이어의 스프라이트 아래에 "Below Player"레이어 (지상 및 경로)가 표시되고 플레이어 스프라이트 상단에 "Above Player"레이어 (지붕 / 동상 / 기호 상단)가 표시되도록 할 수 있습니다. "세계"레이어는 세계의 충돌 / 단단한 것들을 포함하여 나머지 모든 것들을 가지고 있습니다.

카메라 코드를 추가하면 다음과 같이됩니다.

예제데모)
https://www.mikewesthad.com/phaser-3-tilemap-blog-posts/post-1/04-tiled/


물리엔진으로 이동하기

이제 우리는 세계가 생겨 났고, 세계에 적절한 성격을 부여하고 물리학을 가지고 걸어 다닐 수 있습니다. 현재 Phaser에 통합 된 세 가지 물리 엔진이 있습니다 : 아케이드 물리학 (AP), 물질 .js 및 영향. AP는 빠르고 간단하므로 우리가 시작할 곳입니다. 나중에 문제를 해결할 것입니다.

AP에서는 직사각형 또는 원 모양의 물리 구조를 만들 수 있습니다. 사각형 몸체는 축 정렬 된 경계 상자로 대략 회전 할 수 없습니다. AP와 함께로드 된지도에서 충돌하는 타일에는 타일 크기와 일치하는 직사각형 바디가 제공됩니다.

우리가해야 할 4 가지가 있습니다 :

1. worldLayer의 특정 타일을 충돌로 표시하여 AP가 충돌에 사용할 수 있도록합니다.
2. AP 물리 엔진을 사용합니다.
3. 플레이어에 물리 기반 스프라이트를 만듭니다.
4. worldLayer와 충돌하도록 플레이어를 설정합니다.

물리와 함께 타일 맵을 사용하는 첫 번째 단계는 어떤 타일이 단색 ( "충돌")해야하는지 표시해야한다는 것입니다. 이를 수행하는 한 가지 방법은 특정 타일 인덱스를 레이어 내에서 충돌하는 것으로 표시하는 것입니다.
// The 13th tile through and including the 45th tile will be marked as colliding
worldLayer.setCollisionBetween(12, 44);

타일 인덱스로 작업하는 경우 setCollision, setCollisionBetween 및 setCollisionByExclusion이 있습니다. 그러나 인덱스 측면에서 생각하는 것은 어렵습니다. 따라서 더 좋은 방법이 있습니다 : setCollisionByProperty. Tiled를 사용하면 Tileset Editor를 통해 타일 세트에 속성을 추가 할 수 있으므로 어떤 타일이 Tiled에서 직접 충돌하는지 표시 할 수 있습니다.

단계 (또는 아래 GIF 참조) :
1. "타일 세트 편집" 버튼 (화면 오른쪽 하단)을 클릭하여 타일 세트 편집기를 엽니다.
2. 클릭하고 끌어서 (또는 Ctrl + A) 모든 타일을 선택하십시오.
3. 속성 창 (화면 왼쪽)에서 더하기 아이콘을 클릭하고 "충돌"이라는 부울 속성을 추가합니다.
4. 상자를 선택하여 충돌시키고 "충돌"을 true로 설정하려는 타일 만 선택하십시오.
5. 지도를 다시 내 보냅니다.

페이저 내부로 되돌아 가면 다음과 같이 worldLayer 내에 충돌하는 타일을 표시 할 수 있습니다.
worldLayer.setCollisionByProperty({ collides: true });

충돌로 표시된 올바른 타일이 있는지 확인하려면 레이어의 디버그 렌더링을 사용합니다.
const debugGraphics = this.add.graphics().setAlpha(0.75);
worldLayer.renderDebug(debugGraphics, {
tileColor: null, // Color of non-colliding tiles
collidingTileColor: new Phaser.Display.Color(243, 134, 48, 255), // Color of colliding tiles
faceColor: new Phaser.Display.Color(40, 39, 37, 255) // Color of colliding face edges
});

다음과 같이 보일 것입니다 :



충돌로 표시된 타일이 있으면 물리를 추가 할 수 있습니다. 우리 게임의 설정에서 우리는 다음을 수행하여 아케이드 물리 엔진을 켤 수 있습니다 :
var config = {
// ... (rest of the config from earlier)
physics: {
default: "arcade",
arcade: {
gravity: { y: 0 } // Top down game, so no gravity
}
}
};

물리를 사용하여 움직이는 간단한 플레이어 스프라이트를 만들 수 있습니다.
let player;
function create() {
player = this.physics.add.sprite(400, 350, "atlas", "misa-front");
}
function update(time, delta) {
// Stop any previous movement from the last frame
player.body.setVelocity(0);
// Horizontal movement
if (cursors.left.isDown) {
player.body.setVelocityX(-100);
} else if (cursors.right.isDown) {
player.body.setVelocityX(100);
}
// Vertical movement
if (cursors.up.isDown) {
player.body.setVelocityY(-100);
} else if (cursors.down.isDown) {
player.body.setVelocityY(100);
}
// Normalize and scale the velocity so that player can't move faster along a diagonal
player.body.velocity.normalize().scale(speed);
}

참고 : 저는 여기에서 텍스쳐 아틀라스를 사용하고 있습니다. 자세한 내용은이 자습서를 참조하십시오.

마지막 단계는 플레이어와 타일 맵 레이어를 서로 충돌시키는 것입니다. 우리는 collide 또는 addCollider를 사용할 수 있습니다. 우리는 후자와 함께 갈 것입니다 :
function create() {
// ...
// This will watch the player and worldLayer every frame to check for collisions
this.physics.add.collider(player, worldLayer);
}

그리고 플레이어 애니메이션에 추가하는 것과 같은 몇 가지 기능을 추가하여 모든 것을 결합했습니다.
예제데모)
https://www.mikewesthad.com/phaser-3-tilemap-blog-posts/post-1/05-physics/

Tiled와 Phaser로 할 수있는 많은 것들이있어 게임 세계를보다 쉽게 개발할 수있는 창조적 인 과정을 만들 수 있습니다. 예를 들어이 섹션의 코드는 객체 레이어를 사용하여 플레이어의 스폰 지점을지도에 직접 포함합니다.


const spawnPoint = map.findObject("Objects", obj => obj.name === "Spawn Point");
player = this.physics.add.sprite(spawnPoint.x, spawnPoint.y, "atlas", "misa-front");

그러나 그것은 단지 표면을 긁는 것입니다! 다음 글에 주목하여 동적 타일 맵 레이어를 살펴보고 절차 적 던전을 만듭니다.

다음

이것은 단지 표면을 긁는 것입니다. 퍼즐 타일 플랫폼을 생성하기 위해 동적 타일 맵 레이어로 들어가는 다음 글을 확인하십시오.

읽어 주셔서 감사합니다. 향후 게시물에서보고 싶은 내용이 있으면 알려 주시기 바랍니다.

댓글 없음:

댓글 쓰기