
React 위주의 프로젝트를 진행하다보니, 바닐라 자바스크립트로 프로젝트를 구현한 경험이 매우 적었다. 이를 보완하기 위해서 + 동적인 웹앱을 만들어 보기 위해서 테트리스 만들기를 클론 코딩하게 되었다.
참고영상은 아래와 같고, 단순히 따라만 만들 경우 머리속에 얼마 남지 않는다고 느껴서, TypeScript로 디벨롭하기로 생각했다. (최근 우아한 타입스크립트 책을 보면서 배운 내용을 체득할 겸..)
https://www.youtube.com/watch?v=_xGETajBA98
완성 레포지토리 주소는 아래와 같다.
https://github.com/suhong99/StudyRepo/tree/main/tetrisclone
프로젝트 코드 리뷰
개발환경은 Vite를 사용하였고, Phaser를 npm으로 install하였다.
폴더 구조는 아래와 같다.
┣ 📂public
┃ ┣ 📂image
┃ ┃ ┣ 📜back.png
┃ ┃ ┗ 📜block.png
┃ ┗ 📜vite.svg
┣ 📂src
┃ ┣ 📂config
┃ ┃ ┣ 📜gameconfig.ts
┃ ┃ ┗ 📜type.ts
┃ ┣ 📂manager
┃ ┃ ┗ 📜timermanager.ts
┃ ┣ 📂obj
┃ ┃ ┣ 📂tetrisblock
┃ ┃ ┃ ┣ 📜base.ts
┃ ┃ ┃ ┣ 📜helper.ts
┃ ┃ ┃ ┣ 📜iblock.ts
┃ ┃ ┃ ┣ 📜jblock.ts
┃ ┃ ┃ ┣ 📜lblock.ts
┃ ┃ ┃ ┣ 📜oblock.ts
┃ ┃ ┃ ┣ 📜sblock.ts
┃ ┃ ┃ ┣ 📜shape.ts
┃ ┃ ┃ ┣ 📜tblock.ts
┃ ┃ ┃ ┗ 📜zblock.ts
┃ ┃ ┣ 📜gameBoard.ts
┃ ┃ ┗ 📜tetrisblockfactory.ts
┃ ┣ 📂scene
┃ ┃ ┗ 📜mainScene.ts
┃ ┗ 📜main.ts
┣ 📜.gitignore
┣ 📜index.html
┣ 📜package.json
┣ 📜README.md
┗ 📜tsconfig.json
<body>
<div
id="app"
style="
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
"
></div>
<script type="module" src="/src/main.ts"></script>
script를 통해서 Phaser를 사용할 수 있는데, main.ts에서 config설정을 했다.
//main.ts
import Phaser from 'phaser';
import MainScene from './scene/mainScene';
const config = {
type: Phaser.AUTO,
parent: 'app',
width: 400,
height: 800,
scene: [MainScene],
};
export default new Phaser.Game(config);
이때 공식문서에서는 physic를 통해서 중력도 적용하고 다양한 효과를 줬지만, 테트리스 이기에 기본적인 scene만 사용하였다.
MainScene 클라스에서 메소드를 활용하였다.
import Phaser from 'phaser';
import GameBoard from '../obj/gameBoard';
import GameConfig from '../config/gameconfig';
import TimerManager from '../manager/timermanager';
export default class MainScene extends Phaser.Scene {
private gameBoard: GameBoard;
private timerManager: TimerManager; // timeManager 속성 선언
constructor() {
super('MainScene');
this.gameBoard = new GameBoard(this);
// this.timeManager = new TimerManager(); // timeManager 인스턴스 생성
//private라 인스턴스로 생성
this.timerManager = TimerManager.getInstance();
}
preload() {
this.load.spritesheet(
GameConfig.MainScene.RENDER_TILE_SPRITE_SHEET_KEY,
'image/block.png',
{
//이미지를 64*64으로 쪼갬
frameWidth: 64,
frameHeight: 64,
}
);
this.load.spritesheet(
GameConfig.MainScene.BACKGROUND_TILE_SPRITE_SHEET_KEY,
'image/back.png',
{
//이미지를 64*64으로 쪼갬
frameWidth: GameConfig.MainScene.RENDER_TILE_SPRITE_ORIGIN_SIZE,
frameHeight: GameConfig.MainScene.RENDER_TILE_SPRITE_ORIGIN_SIZE,
}
);
}
create(): void {
this.gameBoard.init();
this.gameBoard.spawnRandomBlock(
GameConfig.MainScene.GAME_BOARD_WIDTH_CNT / 2,
0
);
this.gameBoard.render();
}
update(time: number, delta: number): void {
this.timerManager.update(delta);
this.gameBoard.update(time, delta);
this.gameBoard.render();
}
}
preload : 에셋 로드하기 --> 여러개의 이미지가 합쳐진 하나의 이미지를 64픽셀로 쪼개서 인덱스를 통해 사용함
create : Scene 만들기 -> 초기 줄금있는 보드판 만들기 , 첫 블록 생성
update : 업데이트 하기 ->블록이 자동으로 내려가기, 줄 없애기, 블록 이동, 게임 종료 등등
이때 세부적인 기능은 GameBoard, TimeManager 클라스를 만들어 따로 관리하였다.
gameBoard 에서는 게임과 관련된 함수들 ( 블록
timeManager에서는 시간과 관련된 함수를 넣었다. (블록 떨어진 시간 측정)
구현방식은 움직이는 블록과 보드 상태를 더해서 랜더링되는 보드로 보여주게 설정하였다.
프로젝트 피드백
자바스크립트 코드를 타입스크립트 코드로 바꾸면서 캡슐화에 신경썼다. 코드가 기본적으로 class형으로 사용되었는데, 자바스크립트에는 private 같은 키워드 없이도 class를 사용할 수 있다. 이를 private 키워드를 사용하여 각각의 클라스의 은닉화를 시도하였다.
Timer를 예시로 외부에서 사용할 init, update 등의코드는 public으로 하였고 나머지는 private으로 설정하였다.
import GameConfig from '../config/gameconfig';
export default class TimerManager {
private static instance: TimerManager;
private fallingBlockTimer: number = 0;
// 하나의 Timer만 인스턴스로 생성
private constructor() {
if (!TimerManager.instance) {
TimerManager.instance = this;
}
return TimerManager.instance;
}
public static getInstance(): TimerManager {
return TimerManager.instance || new TimerManager();
}
public init(): void {
this.fallingBlockTimer = 0;
}
public update(delta: number): void {
this.fallingBlockTimer += delta;
}
public checkBlockDropTime(): boolean {
if (
this.fallingBlockTimer > GameConfig.MainScene.TIMER_INTERVAL_BLOCK_DOWN
) {
this.fallingBlockTimer = 0;
return true;
}
return false;
}
}
또한 각각의 타입을 보여하여서 런타임 에러를 예방하고자 하였다
좋았던 점
클래스에 대해서 사실 쓸 일이 많지도 않고, 익숙치도 않다. (자바에서 얕게 배운게 끝..)
이번 프로젝트를 통해서 클래스를 사용해본 경험도 좋았고, 타입스크립트에 조금은 더 익숙해져서 좋았다.
다만 사이즈가 작았고, model이 많지는 않아서 type을 많이 사용할 일은 없었다.
JS를 TS로 마이그레이션 하게 되는 경험을 하는것도 좋았다.
아쉬운점
다만 아직 게임보드의 update의 경우에 사용하지 않는 변수가 존재한다(time,delta, isClear ) 이를 개선하고
public update(time: number, delta: number): void {
if (this.gameEnd) return;
const { isClear, line } = this.checkForClearableLines();
if (line !== undefined) {
this.clearLines(line);
this.lineDown(line);
} else {
if (this.currentTetrisBlock === undefined) {
const block = this.spawnRandomBlock(
GameConfig.MainScene.GAME_BOARD_WIDTH_CNT / 2,
0
);
if (this.canSpawnBlock(block)) {
this.currentTetrisBlock = block;
} else {
this.currentTetrisBlock = this.setLastBlockPos(block);
this.gameEnd = true;
this.scene.cameras.main.shake(500);
}
} else {
if (this.timerManger.checkBlockDropTime()) {
if (this.canMoveBlock(0, 1)) {
this.currentTetrisBlock.move(0, 1);
} else {
this.placeBlock();
}
}
}
}
}
또한 줄삭제가 되는 로직을 업데이트할 여지가 있어 보인다.
해당 부분은
'프로젝트' 카테고리의 다른 글
R3F에서 시바를 키보드 입력을 통해 회전시키기(트러블 슈팅) (0) | 2024.05.30 |
---|---|
자바스크립트로 테트리스 클론 코딩 및 디벨롭(Phaser , Vite)-2 성능개선(Chrome Performane활용) (0) | 2024.03.24 |
windowWidth를 통해서 구한 값을 통해, ui라이브러리에 반응형 크기주기 (리팩토링을 하며) (0) | 2023.10.08 |
ssh를 통해서 배포된 서버 디버깅하기(DOCKER, EC2,윈도우) (0) | 2023.09.27 |
react 숙소 등록 퍼널형식으로 관리하기 (0) | 2023.09.24 |