Phaser3 학습 및 튜토리얼

송우든·2023년 11월 17일
0

Dev

목록 보기
16/20
post-thumbnail

지금까지는 canvas 요소를 이용해서 간단한 게임을 만들어봤다. 새롭고 재밌는 활동이었지만, 시행착오가 많았던 시간이었다. 이제 본격적으로 phaser를 이용해서 게임을 만드는 과정을 다루어 보려고 한다.

먼저 오늘은 phaser가 무엇인지, 어떻게 동작하고 사용하는지를 정리해보려고 한다.

phaser란,

phasercanvas API를 맵핑한 자바스크립트 기반의 게임 프레임워크다. 데스크탑이나 모바일 브라우저 등 다양한 웹 게임을 만들 수 있게 도와준다. phaser는 자바스크립트로 개발되었기 때문에 자바스크립트 또는 타입스크립트를 사용하여 게임을 개발할 수 있다.

phaser 설치는 Phaser - Download 링크를 통해서 다운로드 해서 직접 설칳하거나 명령어로 간단하게 설치할 수 있다.

npm i phaser // "phaser": "^3.60.0"

프로젝트를 만들어보자

사용법을 익히기 위해 phaser의 튜토리얼을 따라서 진행할 예정이다. phaser 튜토리얼을 진행하기 전에 먼저 다운받아야하는 파일들이 있다. 먼저 필요한 파일을 다운받자

나는 vite룰 이용해서 기본 프로젝트를 생성하였고(typescript를 사용) src/에 있는 모든 내용을 제거하고 index.ts를 파일을 추가하였다. 그리고 아래와 같이 코드를 작성해보았다.

const config = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  backgroundColor: "#B23A3A",
};

const game = new Phaser.Game(config);

먼저, typephaser의 렌더링 방식을 설정할 수 있다. 기본값은 Phaser.AUTO이다.(Phaser.WEBGL이나 Phaser.CANVAS 두 가지 중 선택하여 사용할 수 있다)

여기서 widthheightphaser를 생성하는 canvas요소의 크기이다.(단위: pixel) backgroundColor 속성을 추가하여 게임의 배경을 지정할 수 있고, 일반적으로 hex값을 사용한다.

그럼 아래와 같은 화면이 나타난다.

장면(SCENE)을 만들어보자

phaserscene이라는 개념은 게임의 맵이나 화면 하나를 나타내는 단위이다. 보통 게임의 로직을 작성한다.(캐릭터의 움직임이나 효과음등 게임이 동작하기 위해 필요한 작업)

나는 Scene/GameScene.ts 파일을 추가해줬다. phsser의 장면은 Phaser.Scene을 상속받아 만들어진다.

class GameScene extends Phaser.Scene {
  constructor() {
    super({ key: "GameScene" }); // key는 Phaser에서 Scene을 식별하기 위한 값
  }

 // 외부 파일 혹은 assets을 미리 불러오기 위한 작업 처리
  preload(): void {
    $.load.image("sky", "assets/sky.png");
    this.load.image("ground", "assets/platform.png");
    this.load.image("star", "assets/star.png");
    this.load.image("bomb", "assets/bomb.png");
    this.load.spritesheet("dude", "assets/dude.png", {
      frameWidth: 32,
      frameHeight: 48,
    });
  }

  // 게임 시작시에 필요한 GameObject를 정의
  create(): void {
    this.add.image(400, 300, "sky");
    this.add.image(400, 300, "star");
  }

  // 애니메이션을 정의하거나 게임상에서 상호작용을 해야하는 경우 처리
  update(): void {}
}

export default GameScene;

이렇게 만들어진 장면(Scene)을 index.ts의 다음 코드에 추가해준다.

import Phaser from "phaser";
import GameScene from "./\bScene/GameScene";

const config: Phaser.Types.Core.GameConfig = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  backgroundColor: "#B23A3A",
  scene: GameScene,
};

const game = new Phaser.Game(config);

그럼 아래와 같은 결과 화면을 볼 수 있다. 배경이 나타났다.

땅을 만들자

preload 함수에서 미리 불러온 assets들을 이용해 하나씩 게임 요소를 부착해볼 것이다. 먼저, ground라는 key값을 이용해서 platform.png를 가져올 것이다. 먼저,index.ts에서 phsics를 추가해보자

const config: Phaser.Types.Core.GameConfig = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  backgroundColor: "#B23A3A",
  physics: {
    default: "arcade",
    arcade: {
      gravity: { y: 300 },// y방향 300의 중력을 적용, y가 클수록 빠르게 하강
      debug: false, // 디버그 모드 활성화시, 충돌 영역 및 기타 물리 관련 정보 표시
    },
  },
  scene: [GameScene],
};
const game = new Phaser.Game(config);

physics에 대해 간단하게 설명해보자면 물리 엔진을 설정하기 위해 사용하는 속성이다.

자주 사용하는 물리 엔진 종류는 크게 arcade / matter / impact가 있다.

  • arcade : 가벽고 간단한 2D 스타일의 물리 엔진을 만들 때 사용 / 박스기반 물리엔진
  • matter : 복잡하고 강력한 2D 엔진을 만들 때 사용 / 세부적인 조작이 가능한 물리엔진
    • path간 처리 가능
  • impact : impact.js 라이브러리에서 파생된 2D 물리 엔진으로, HTML5 기반 게임에 최적화

그리고 다시 GameScene.ts로 돌아가서 땅 이미지를 추가하는 코드를 작성한다.

create() : void {
	
	...

	// 땅 이미지 추가하여 맵 생성
	const platforms = this.physics.add.staticGroup();
  platforms.create(400, 568, "ground").setScale(2).refreshBody();
  platforms.create(600, 400, "ground");
  platforms.create(50, 250, "ground");
  platforms.create(750, 220, "ground");
}

캐릭터를 만들어보자

이제 캐릭터를 생성해보자. GameScene.ts에 create함수 안에 아래 내용을 추가한다.

create() : void {

	...

	const player = this.physics.add.sprite(100, 450, "dude").setName("player");

	// player가 바닥에 닿았을 때, 약간 튕기는 느낌을 설정
	player.setBounce(0.2); // y값을 생략하면 x와 y모두 동일한 값을 적용하는 것과 같음

	// player가 게임 화면 밖으로 나가지 않게 설정 
	player.setCollideWorldBounds(true);
}

여기서 사용한 sprite(x, y, imageKey)메서드는 게임에 등장하는 이미지나 애니메이션을 표현하기 위한 스프라이트 객체를 생성해준다. 이렇게 생성된 캐릭터(player)는 중력이나 충돌과 같은 물리적 특징을 부여할 수 있다.

출력된 이미지를 자세히 보면 플레이어가 플랫폼보다 아래에 나타나는 것을 확인할 수 있다. 플레이어와 플랫폼 간의 충돌을 감지하여 충돌시 플레이어가 플랫폼 위로 올라올 수 있도록 중력을 적용해보자

맨 마지막 줄에 아래 코드를 추가하면 된다. colliderphaser에서 충돌을 체크해주는 메서드이다.

this.physics.add.collider(player, platforms);

다시 화면을 출력해보면 플레이어가 땅위로 올라온 것을 확인할 수 있다.

애니메이션을 만들고 플레이어를 움직여보자

GameScene.ts 안에 애니메이션을 생성하는 코드를 아래와 같이 추가한다. 사용할 애니메이션을 먼저 생성해준다.

create() : void {

...

	// 애니메이션 생성 - 나중에 key로 애니메이션 식별해서 참조
    this.anims.create({
      key: "left",
      frames: this.anims.generateFrameNumbers("dude", { start: 0, end: 3 }), 
      frameRate: 10, // 초당 프레임 수를 나타냄(초당 10프레임 설정 의미)
      repeat: -1, // 무한 반복을 의미
    });

    this.anims.create({
      key: "turn",
      frames: [{ key: "dude", frame: 4 }],
      frameRate: 20,
    });

    this.anims.create({
      key: "right",
      frames: this.anims.generateFrameNumbers("dude", { start: 5, end: 8 }),
      frameRate: 10,
      repeat: -1,
    });

}

그리고 GameScene안에 아래와 같이 작성한다. 먼저, create 메서드 안에 createCursorKeys()를 통해 키보드의 화살표 키들에 대한 입력을 처리한다.

cursors = this.input.keyboard?.createCursorKeys();

다음에 update 메서드 안에 해당 키보드가 눌렸을 때 플레이어의 상태를 추가해준다. setVelocityX(value)를 통해 프레임마다 움직일 속도를 지정하고, play(애니메이션키, 반복여부)메서드를 통해서 애니메이션을 실행한다.

// 애니메이션을 정의하거나 게임상에서 상호작용을 해야하는 경우 처리
update(): void {
	const cursors = this.input.keyboard?.createCursorKeys();
  const player = this.children.getByName(
    "player"
  ) as Phaser.Types.Physics.Arcade.SpriteWithDynamicBody;
  if (!cursors || !player) return;
  if (cursors.left.isDown) {
    player.setVelocityX(-160); // 프레임마다 움직일 속도
    player.anims.play("left", true); // 애니메이션 실행 메서드(애니메이션 키, 반복여부)
  } else if (cursors.right.isDown) {
    player.setVelocityX(160);
    player.anims.play("right", true);
  } else {
    player.setVelocity(0);
    player.anims.play("turn");
  }

  if (cursors.up.isDown && player.body.touching.down) {
    player.setVelocityY(-330);
  }
}

그럼 아래와 같이 동작하는 플레이어를 구현할 수 있다.

별을 만들자

이번에는 dude가 모아야 하는 별 그룹을 만들어보자. GameScene.ts안에 create()에 아래 코드를 추가하여 별 그룹을 생성한다. x좌표 70만큼 간격을 두고 11번 반복하여 별을 생성한다.

// 별 생성
stars = this.physics.add.group({
  key: "star",
  repeat: 11,
  setXY: { x: 12, y: 0, stepX: 70 },
});

// 별 이미지 요소 튕김 효과
stars.children.iterate((child) => {
  const image = child as Phaser.Physics.Arcade.Image;
  image.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
  return null;
});

그리고 disableBody(active, visible)을 이용하여 플레이어와 별이 닿았을 때를 별을 수집한 것과 같은 처리를 한다. (게임 객체를 비활성화 시키고 보이지 않게 설정한다)

collectStar(_player: Phaser.Types.Physics.Arcade.GameObjectWithBody | Phaser.Tilemaps.Tile,
	          star: Phaser.Types.Physics.Arcade.GameObjectWithBody | Phaser.Tilemaps.Tile) {
   
   const stars = this.data.get("stars") as Phaser.Physics.Arcade.Group;
	 const target = star as Phaser.Physics.Arcade.Image;
   target.disableBody(true, true);
}

점수를 처리하는 텍스트를 추가하자

이번에는 create()안에 아래와 같이 작성하여 텍스트 객체를 생성한다. 그럼 화면과 같은 텍스트가 게임 화면에 부착된다.

// 점수 텍스트 생성
  this.scoreText = this.add.text(16, 16, "SCORE : 0", {
    fontSize: "32px",
    color: "#333",
  });

그리고 collectStar()안에 별을 모을 때마다 점수를 부여해준다. 이렇게 하면 별을 수집할 때 마다 10씩 점수를 획득하게 된다.

this.score += 10;
this.scoreText?.setText("SCORE : " + this.score);

장애물 설정을 해보자

이제 마지막으로 폭탄을 만들 것이다. 폭탄과 플레이어간 충돌을 처리해보려고 한다. 아래 코드를 create()안에 작성하여 폭탄을 생성한다.

여기서 주요 코드는 collider라는 메서드인데 인자로 받은 두 요소 간의 충돌을 발생시킨다.

// 장애물 생성
    const bombs = this.physics.add.group();
    this.data.set("bombs", bombs);
    this.physics.add.collider(bombs, platforms);
    this.physics.add.collider(player, bombs, this.hitBomb, undefined, this);

그리고 GameScene클래스 안에 부딪혔을 때 함수를 추가해준다.

  // 플레이어 및 폭탄 충돌 처리
  hitBomb(
    _player:
      | Phaser.Types.Physics.Arcade.GameObjectWithBody
      | Phaser.Tilemaps.Tile
  ) {
    this.physics.pause();

    const player = _player as Phaser.Types.Physics.Arcade.SpriteWithDynamicBody;
    player.setTint(0xff0000);
    player.anims.play("turn");
  }

참고

Phaser3 - 튜토리얼

profile
개발 기록💻

0개의 댓글