초기 JavaScript는 웹브라우저에 인터렉티브 요소를 추가하기 위해 개발된 경량의 스크립팅 언어였습니다. 그러나 현재는 서버, 모바일, 게임 등의 개발에도 사용되며 그 위상을 넓히고 있습니다. 예를 들어 “뱀파이어 서바이벌”이라는 1인 개발게임은 일반적인 게임 개발엔진이 아닌 JavaScript로 개발되었습니다. 이를 가능하게 해준 것이 바로 Phaser.js입니다. Phaser.js는 Canvas API를 랩핑한 JavaScript 기반 게임 개발 프레임워크입니다. HTML5에서 게임을 개발할 수 있도록 도와주며 데스크탑과 모바일에서 모두 사용 가능합니다. Phaser.js의 기본적인 config는 다음과 같습니다.
const config: {
width: number; // canvas 너비 (반응형의 경우 window.innerWidth)
height: number; // canvas 높이 (반응형의 경우 window.innerHeight)
backgroundColor: number;
scene: typeof Scene[];
pixelArt: boolean;
physics: {
default: string;
arcade: {
debug: boolean;
};
};
}
Phaser.js는 크게 Scene과 Sprite라는 객체와 preload, create, upload라는 메서드를 중심으로 구성되어 있습니다. 각각의 역할은 다음과 같습니다.
게임 중, 로딩, 게임 종료 등의 상황에 맞춰 Scene을 정의하고 config에 추가해야 합니다. Scene은 다음과 같이 Phaser.Scene을 extend하여 class를 정의하고 config에 추가하는 방식으로 생성할 수 있습니다.
src/scenes/GameScene.js
export default class GameScene extends Phaser.Scene {
constructor() {
super();
}
preload() {}
create() {}
update() {}
}
src/config.js
import GameScene from "./scenes/GameScene";
export const config = {
type: Phaser.AUTO,
parent: "phaser-example",
width: 800,
height: 600,
scene: [GameScene],
};
Sprite를 도형을 이용해 만들 수도 있지만 대부분의 게임은 이미지 asset을 활용할 것입니다. 이미지를 이용하여 Sprite를 만들기 위해서는 이미지 preload → create의 과정을 거쳐야 합니다. 먼저 scene의 preload 메서드에서 이미지를 로드합니다.
export default class GameScene extends Phaser.Scene {
constructor() {
super();
}
preload() {
this.load.image("arrow", arrowImg);
}
// ...
}
이후 scene의 create 메서드에서 해당 이미지를 add 해줍니다.
export default class GameScene extends Phaser.Scene {
create() {
this.add.image(200, 450, "arrow");
this.arrow.setScale(0.15); // 사이즈 배수 조절
}
}
활이 포물선을 그리며 이동하도록 구현하려 합니다. 이는 이동할 경로를 그려주고 update함수에서 해당 경로의 x, y값을 따라 이동시키는 방식으로 구현 가능합니다. 먼저 이동 경로를 그려주도록 하겠습니다.
export default class GameScene extends Phaser.Scene {
create() {
this.graphics = this.add.graphics();
this.path = { t: 0, vec: new Phaser.Math.Vector2() };
const startPoint = new Phaser.Math.Vector2(100, 500);
const controlPoint1 = new Phaser.Math.Vector2(100, 200);
const endPoint = new Phaser.Math.Vector2(700, 500);
this.curve = new Phaser.Curves.QuadraticBezier(
startPoint,
controlPoint1,
endPoint
);
this.tweens.add({
targets: this.path,
t: 1,
ease: "Sine.easeInOut",
duration: 1000,
yoyo: false,
repeat: 0,
});
}
update() {
this.graphics.clear();
this.graphics.lineStyle(1, 0x00ff00, 1);
this.curve.draw(this.graphics);
}
}
다음과 같은 curve를 확인할 수 있습니다.
이어서 arrow를 해당 경로에 맞게 이동시켜줍니다.
import { config } from "../config";
import arrowImg from "../assets/arrow.png";
export default class GameScene extends Phaser.Scene {
constructor() {
super();
this.arrow;
}
preload() {
this.load.image("arrow", arrowImg);
}
create() {
this.arrow = this.add.image(200, 450, "arrow");
this.arrow.setScale(0.15);
this.graphics = this.add.graphics();
this.path = { t: 0, vec: new Phaser.Math.Vector2() };
const startPoint = new Phaser.Math.Vector2(100, 500);
const controlPoint1 = new Phaser.Math.Vector2(100, 200);
const endPoint = new Phaser.Math.Vector2(700, 500);
this.curve = new Phaser.Curves.QuadraticBezier(
startPoint,
controlPoint1,
endPoint
);
this.tweens.add({
targets: this.path,
t: 1,
ease: "Sine.easeInOut",
duration: 1000,
yoyo: false,
repeat: 0,
});
}
update() {
this.graphics.clear();
this.curve.getPoint(this.path.t, this.path.vec);
this.arrow.setPosition(this.path.vec.x, this.path.vec.y);
}
}
하지만 위의 코드로는 화살이 어색하게 이동하기만 할뿐이어서 각도의 변경이 필요합니다. 이 또한 update 함수에 매 프레임마다 각도를 바꾸도록 작성하여 구현할 수 있습니다.
update() {
// ...
// 각도 변경
this.arrow.setAngle(this.arrow.angle + 1.5);
// 도착 지점오면 arrow 삭제
if (this.arrow.x === this.endPoint[0] && this.arrow.y === this.endPoint[1])
this.arrow.destroy();
}
위의 코드까지 더한 결과는 다음과 같습니다.
이와 같은 방법으로 Phaser.js를 활용하면 단순히 canvas API를 활용하는 경우보다 편리하고 폭넓은 게임 구현이 가능합니다. 관련 예시 등을 아래에서 확인해볼 수 있습니다.