React
[Phaser3] TypeScript + React에서이동 구현하기
BGK97
2024. 9. 24. 10:06
Phaser3란
- canvas API를 매핑한 JavaScript 기반의 게임 프레임워크
- 유니티, 언리얼 엔진을 제외하고, 2D게임을 만드는데 유용하다!
개발에 앞서 알아야할 사실들
- Phaser의 경우 JavaScript로 구현하기 때문에, React + TypeScript환경에서 적절하게 코드를 수정해야한다.
- Phaser는 Scene이라는 단위로 맵, 화면을 구성한다. 게임 로직을 구성할 때 사용한다. (효과음 또는 동작 등...)
개발 진행
1. Phaser 설치
- VScode에서 다음과 같은 명령어를 입력하여 간단하게 설치가 가능하다.
npm i phaser
2. 프로젝트 생성
- Vite를 이용해서 기본 프로젝트를 생성한다!
npx create vite@latest [프로젝트명] -- --template react-ts
- npx create vite@latest [프로젝트명]
- vite를 최신버전으로 설치한다. 프로젝트명으로 이름을 정하면 된다.
- --
- vite의 추가옵션 전달 시 사용
- --template react-ts
- react + typescript를 사용해 템플릿을 생성하는 옵션
>> 다 받은 이후에는, 본인의 프로젝트 성격에 맞게 src폴더와 vite.config.ts를 알아서 수정하면 된다!
*** 게임 생성에 앞서, 알아야할 사실 ***
Phaser는 자바스크립트 기반이므로, React에 맞게 변경해주어야한다!!
3. 게임 Config 생성
- 게임 config 생성하기
const config: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
gravity: { x: 0, y: 0 },
fps: 60, // 물리 엔진의 프레임 속도 설정
tileBias: 16,
debug: true,
}
},
scene: {
preload,
create,
update
}
};
// Phaser 게임 생성
const game = new Phaser.Game(config);
// 컴포넌트 언마운트 시 Phaser 게임 종료
return () => {
game.destroy(true);
};
- Phaser의 경우 config 설정을 통해 게임 크기및 물리 객체를 선언한다.
** Phaser3 config의 주요 옵션들
- type
- Phaser의 랜더링 방식을 결정한다.
- WEBGL, CANVAS 모드가 있으며 기본 값으로는 Phaser.AUTO를 사용
- width, height
- phaser를 생성하는 캔버스 크기를 조절, 단위는 픽셀 단위이다.
- background
- phaser 배경의 색을 지정, 여기서는 배경에 사진을 삽입할 것이기 때문에 굳이 필요없음!
- physics
- 물리엔진 설정에 사용하는 속성
- arcade, matter, impact가 존재
- arcade - 가벼운 스타일의 2D게임의 물리엔진을 만드는데 사용!, 박스 기반
- matter - 복잡한 스타일의 2D게임의 물리엔진을 만드는데 사용
- impact - impact.js 라이브러리에서 파생, HTML 기반 게임에 최적화 되어있음
- 이후 내부에서 gravity(중력), tileBias(타일 편향) 등을 설정하면 됨
- scene
- phaser에서 사용할 함수(메서드)를 선언하는 곳
- 이후에 new Phaser.Game(config) 명령어를 통해 게임을 생성하면된다.
- ** game.destroy(true)설정을 해주어야, 언마운트 시 게임이 종료되므로 꼭 해줄 것!!
4. 게임 배경 및 플레이어 생성 (create & preload & update)
let player: Phaser.Physics.Arcade.Sprite;
let cursors: Phaser.Types.Input.Keyboard.CursorKeys;
function preload(this: Phaser.Scene) {
// 'player'라는 키로 스프라이트 시트를 로드함
this.load.image('bg', './sky.png');
this.load.spritesheet('player', './ninja_skeleton.png', { frameWidth: 16, frameHeight: 19});
}
function create(this: Phaser.Scene) {
this.physics.world.setBounds(0, 0, 1600, 1200);
const bg = this.add.image(400, 300, 'bg');
bg.setDisplaySize(2600, 2000);
// 'player' 스프라이트 시트를 사용해 캐릭터를 생성
player = this.physics.add.sprite(400, 300, 'player');
player.setCollideWorldBounds(true);
player.setScale(2);
this.cameras.main.setBounds(0, 0, 1600, 1200);
this.cameras.main.startFollow(player, true, 0.1, 0.1); // 부드러운 카메라 이동
// 'player' 키를 사용하여 애니메이션 생성
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('player', { start: 0, end: 7 }),
frameRate: 10,
repeat: -1
});
cursors = this.input.keyboard!.createCursorKeys();
}
function update(this: Phaser.Scene) {
player.setVelocity(0);
let moving = false;
const speed = 160;
if (cursors.left.isDown) {
player.setVelocityX(-speed);
player.flipX = true;
moving = true;
} if (cursors.right.isDown) {
player.setVelocityX(speed);
player.flipX = false;
moving = true;
} if (cursors.up.isDown) {
player.setVelocityY(-speed);
moving = true;
} if (cursors.down.isDown) {
player.setVelocityY(speed);
moving = true;
}
if (moving) {
if (!player.anims.isPlaying || player.anims.currentAnim?.key !== 'walk') {
player.anims.play('walk', true);
}
} else {
player.anims.stop();
player.setFrame(0);
}
}
return <div id="phaser-game-container" />;
};
- React의 경우 preload() 가 지원이 안되더라... 그래서 function으로 선언해주고, 이 안에 Phaser 코드를 작성한다!
- player와 이동에 사용할 cursor를 선언해준다.
- preload
- 플레이어나 배경을 load하는 부분이다.
- 이 때, animation을 사용할 것이라면 image가 아닌 spritesheet로 선언을 해줘야 한다!
- create
- preload에서 생성한 플레이어나 애니메이션 등을 불러오는 구간
config에서 설정한 크기와 physic에서 설정한 크기가 다른데요???
- config에서 사용하는 값은 게임화면(사용자가 볼 수 있는 화면)의 크기를 결정하는 곳
- physics에서 설정한 Bound 값은 물리객체가 이동할수 있는 최대 크기를 설정!!!
-> 요약하자면, config는 사용자의 가시 화면 크기, physics에서는 전체 크기를 설정한다.
- setDisplaySize
- 사용한 배경의 크기를 조절하는 부분! 이미지의 크기가, 작더라도 이 옵션을 통해 확장할 수 있다!
- physics.add.sprite와 setCollideWorldBounds
- 함수 이름 그대로 물리객체로 추가를 하고, 물리적인 세계(게임 전체 크기)내에서만 움직이게 한다.
- anims.create
- spritesheet에 대한 애니메이션을 생성한다.
- 위 사진과 같이, 여러 프레임으로 구성되어있어야 한다!
- key값을 설정하여 나중에 animation을 호출할 때, key값을 통해 호출
- frames을 통해 애니메이션을 적용할 부분을 설정
- frameRate는 프레임의 부드러운 정도를 설정하는 부분으로, 10~15가 적당
- repeat로 반복을 설정, -1로 설정시 무한 반복
- update
- 게임 진행중, 변경사항이 생겼을 때 실행되는 부분
- 움직임 혹은 물리 객체 충돌 등 부분을 설정
- 게임 진행중, 변경사항이 생겼을 때 실행되는 부분
5. 결과 화면!
- 다음과 같이 잘 움직이고, 배경도 잘 표현 된다.
6. 새로 안 사실
- 페이저에서 이동을 구현할 때, if - else if문으로 구현하면 대각선 이동이 불가하다. 각각을 분기점으로 처리해서 그런가보다. 그래서, if만 사용하여 동시 적용이 가능하도록 하였더니 대각선 이동이 되더라!
- player에서 애니메이션 설정할 때, frameWidth, frameHeight를 이미지 픽셀 크기에 맞추어 설정을 해야한다. 안그러면 프레임 이미지가 깨져 보여서, 이동하는데 매우매우매우매우매우매우 불편함이 느껴진다.