목차

🎮 Phaser.js 게임 엔진

Phaser Baduk Metaverse 프로젝트에서 사용되는 Phaser.js 게임 엔진의 구현 내용에 대해 자세히 설명합니다.


📋 개요

Phaser.js는 HTML5 게임 개발에 특화된 강력한 JavaScript 프레임워크입니다. 이 프레임워크는 바둑 게임의 시각적 요소를 구현하고, 사용자 인터랙션 및 실시간 통신 기능을 통합하여 몰입감 있는 게임 경험을 제공하는 데 핵심적인 역할을 합니다.


1. 게임 엔진 기본 구조 및 설정

Phaser.js 게임 엔진의 핵심 진입점인 game.js 파일은 게임의 전반적인 설정과 초기화를 담당합니다. 이 파일은 게임의 캔버스 크기, 배경색, 사용할 씬 목록 등 필수적인 구성 요소를 정의합니다.

1) 메인 게임 파일 (game.js)

game.js는 Phaser 게임 인스턴스를 생성하고, 게임의 전역 설정을 정의하는 파일입니다. 이곳에서 게임의 해상도, 렌더링 방식, 포함될 씬 목록 등을 지정합니다.

import Phaser from 'phaser';
import BadukScene from './scenes/BadukScene';
import MenuScene from './scenes/MenuScene';
 
// 1. 게임 설정 객체 정의
const config = {
    type: Phaser.AUTO, // 렌더링 방식 (WebGL 또는 Canvas 자동 선택)
    width: 1200,      // 게임 화면 너비 (픽셀)
    height: 800,       // 게임 화면 높이 (픽셀)
    parent: 'game-container', // 게임 캔버스가 삽입될 HTML 요소의 ID
    backgroundColor: '#2c3e50', // 게임 배경색
    scene: [MenuScene, BadukScene], // 게임에 포함될 씬 목록 (순서 중요)
    physics: {         // 물리 엔진 설정
        default: 'arcade', // 기본 물리 엔진으로 Arcade 물리 엔진 사용
        arcade: {
            gravity: { y: 0 }, // y축 중력 없음
            debug: false       // 물리 객체 디버그 정보 비활성화
        }
    },
    scale: {           // 스케일링 설정
        mode: Phaser.Scale.FIT, // 화면에 맞게 게임 크기 조절
        autoCenter: Phaser.Scale.CENTER_BOTH // 가로/세로 중앙 정렬
    }
};
 
// 2. Phaser 게임 인스턴스 생성
const game = new Phaser.Game(config);
 
// 3. 게임 인스턴스 내보내기 (다른 파일에서 사용 가능하도록)
export default game;

코드 설명:


2. 씬(Scene) 관리 및 전환

Phaser.js에서 씬(Scene)은 게임의 특정 상태나 화면을 나타내는 독립적인 단위입니다. 예를 들어, 게임의 메뉴 화면, 실제 게임 플레이 화면, 게임 오버 화면 등이 각각의 씬으로 구현될 수 있습니다. 씬은 게임의 로드, 생성, 업데이트, 종료 등 생명주기를 가집니다.

2) 메뉴 씬 (MenuScene.js)

MenuScene.js는 게임이 시작될 때 가장 먼저 사용자에게 보여지는 메뉴 화면을 담당합니다. 여기서는 게임 시작 버튼, 설정 버튼 등을 배치하고, 사용자의 입력에 따라 다른 씬으로 전환하는 기능을 구현합니다.

import Phaser from 'phaser';
 
export default class MenuScene extends Phaser.Scene {
    constructor() {
        super({ key: 'MenuScene' }); // 씬의 고유 키 정의
    }
 
    preload() {
        // 1. 필요한 이미지 에셋 로드
        this.load.image('menu-bg', 'assets/menu-background.png');
        this.load.image('play-button', 'assets/play-button.png');
        this.load.image('settings-button', 'assets/settings-button.png');
    }
 
    create() {
        // 2. 배경 이미지 추가 (화면 중앙에 배치)
        this.add.image(600, 400, 'menu-bg');
 
        // 3. 게임 제목 텍스트 추가
        this.add.text(600, 200, '바둑 메타버스', { // x, y, 텍스트, 스타일
            fontSize: '48px',
            fill: '#ffffff', // 흰색 글자
            fontFamily: 'Arial'
        }).setOrigin(0.5); // 텍스트의 기준점을 중앙으로 설정
 
        // 4. 플레이 버튼 생성 및 상호작용 설정
        const playButton = this.add.image(600, 350, 'play-button')
            .setInteractive(); // 클릭 가능하도록 설정
 
        playButton.on('pointerdown', () => { // 버튼 클릭 시 이벤트
            this.scene.start('BadukScene'); // 'BadukScene'으로 전환
        });
 
        // 5. 설정 버튼 생성 및 상호작용 설정
        const settingsButton = this.add.image(600, 450, 'settings-button')
            .setInteractive();
 
        settingsButton.on('pointerdown', () => {
            console.log('설정 메뉴 열기'); // 설정 기능은 콘솔 로그로 대체
        });
    }
}

코드 설명:


2) 바둑 게임 씬 (BadukScene.js)

BadukScene.js는 바둑 게임의 핵심 로직과 사용자 인터랙션이 구현되는 씬입니다. 바둑판, 바둑돌의 배치, 클릭 이벤트 처리, 턴 관리, 그리고 서버와의 통신 등을 담당합니다.

import Phaser from 'phaser';
import BadukBoard from '../sprites/BadukBoard'; // 바둑판 스프라이트 임포트
import BadukStone from '../sprites/BadukStone'; // 바둑돌 스프라이트 임포트
 
export default class BadukScene extends Phaser.Scene {
    constructor() {
        super({ key: 'BadukScene' });
        this.board = null; // 바둑판 객체
        this.stones = [];  // 놓여진 바둑돌 배열
        this.currentPlayer = 'black'; // 현재 턴 플레이어 ('black' 또는 'white')
        this.gameId = null; // 현재 게임 ID
    }
 
    preload() {
        // 1. 바둑판 및 돌 이미지 로드
        this.load.image('board', 'assets/baduk-board.png');
        this.load.image('black-stone', 'assets/black-stone.png');
        this.load.image('white-stone', 'assets/white-stone.png');
        this.load.image('grid', 'assets/grid-lines.png'); // 바둑판 그리드 이미지
    }
 
    create() {
        // 2. 바둑판 생성 및 그리드 라인 추가
        this.board = new BadukBoard(this, 600, 400); // BadukBoard 스프라이트 생성
        this.add.existing(this.board); // 씬에 바둑판 추가
        this.add.image(600, 400, 'grid'); // 그리드 라인 이미지 추가
 
        // 3. 클릭 이벤트 설정: 바둑판 클릭 시 착수 처리
        this.input.on('pointerdown', (pointer) => {
            this.handleBoardClick(pointer);
        });
 
        // 4. 현재 플레이어 표시 UI 생성
        this.createPlayerIndicator();
 
        // 5. (옵션) 게임 시작 시 서버로부터 게임 ID 수신
        if (window.socket) {
            window.socket.on('game-start', (data) => {
                console.log('게임이 시작되었습니다. 게임 ID:', data.gameId);
                this.gameId = data.gameId;
                // 게임 상태 동기화 및 시작 로직 추가 가능
            });
            window.socket.on('game-update', (data) => {
                // 다른 플레이어의 착수 정보 수신 및 처리
                if (data.player !== this.currentPlayer) { // 자신의 턴이 아닌 경우에만 처리
                    this.placeStone(data.x, data.y, data.player);
                }
            });
        }
    }
 
    // 6. 바둑판 클릭 처리 함수
    handleBoardClick(pointer) {
        // 클릭된 픽셀 좌표를 바둑판의 상대 좌표로 변환
        const boardX = pointer.x - this.board.x;
        const boardY = pointer.y - this.board.y;
 
        // 상대 좌표를 그리드(바둑판 칸) 좌표로 변환
        const gridX = Math.round(boardX / this.board.gridSize) + 9; // 0~18 범위로 조정
        const gridY = Math.round(boardY / this.board.gridSize) + 9;
 
        // 유효한 착수 위치인지 확인 후 돌 놓기
        if (this.isValidMove(gridX, gridY)) {
            this.placeStone(gridX, gridY, this.currentPlayer);
        }
    }
 
    // 7. 착수 위치 유효성 검사
    isValidMove(x, y) {
        // 바둑판 범위 (0~18) 내인지 확인
        if (x < 0 || x > 18 || y < 0 || y > 18) {
            return false;
        }
 
        // 이미 돌이 놓인 위치인지 확인
        return !this.stones.some(stone => 
            stone.gridX === x && stone.gridY === y
        );
        // TODO: 자충, 코 규칙 등 추가적인 바둑 규칙 검사 필요
    }
 
    // 8. 바둑돌 놓기 (시각적 처리 및 상태 업데이트)
    placeStone(x, y, playerColor) {
        // 현재 턴 플레이어의 돌 이미지 선택
        const stoneImage = playerColor === 'black' ? 'black-stone' : 'white-stone';
        // BadukStone 스프라이트 생성 (애니메이션 포함)
        const stone = new BadukStone(this, x, y, playerColor);
 
        this.stones.push(stone); // 놓여진 돌 목록에 추가
 
        // 플레이어 턴 변경 (다음 턴)
        this.currentPlayer = this.currentPlayer === 'black' ? 'white' : 'black';
 
        // UI 업데이트 (현재 플레이어 표시)
        this.updatePlayerIndicator();
 
        // 서버에 착수 정보 전송
        this.sendMoveToServer(x, y, playerColor);
    }
 
    // 9. 현재 플레이어 표시 UI 생성
    createPlayerIndicator() {
        this.playerText = this.add.text(50, 50, '현재 플레이어: 흑돌', {
            fontSize: '24px',
            fill: '#ffffff'
        });
    }
 
    // 10. 현재 플레이어 표시 UI 업데이트
    updatePlayerIndicator() {
        const playerName = this.currentPlayer === 'black' ? '흑돌' : '백돌';
        this.playerText.setText(`현재 플레이어: ${playerName}`);
    }
 
    // 11. 서버에 착수 정보 전송
    sendMoveToServer(x, y, player) {
        if (window.socket && this.gameId) {
            window.socket.emit('move', { // 'move' 이벤트로 서버에 데이터 전송
                x: x,
                y: y,
                player: player,
                gameId: this.gameId // 현재 게임 ID 포함
            });
        }
    }
}

코드 설명:


3. 핵심 스프라이트(Sprite) 구현

Phaser.js에서 스프라이트(Sprite)는 게임 내에서 움직이거나 상호작용할 수 있는 시각적인 객체를 의미합니다. 바둑 게임에서는 바둑판과 바둑돌이 각각의 스프라이트로 구현되어 게임 세계 내에서 독립적으로 존재하며 특정 기능을 수행합니다.

3) 바둑판 스프라이트 (BadukBoard.js)

BadukBoard.js는 바둑판 이미지를 나타내는 스프라이트입니다. 단순히 이미지를 표시하는 것을 넘어, 바둑판의 그리드 시스템을 관리하고 픽셀 좌표와 그리드 좌표 간의 변환을 처리하는 중요한 역할을 합니다.

import Phaser from 'phaser';
 
export default class BadukBoard extends Phaser.GameObjects.Sprite {
    constructor(scene, x, y) {
        super(scene, x, y, 'board'); // Phaser.GameObjects.Sprite 생성자 호출
 
        this.setOrigin(0.5); // 스프라이트의 기준점을 중앙으로 설정
        this.setInteractive(); // 상호작용 가능하도록 설정 (클릭 등)
 
        this.setScale(1.0); // 바둑판 크기 (스케일) 설정
 
        // 바둑판 그리드 좌표 시스템 설정
        this.gridSize = 40; // 한 칸의 픽셀 크기
        this.boardSize = 19; // 19x19 바둑판
    }
 
    // 1. 그리드 좌표(0~18)를 게임 화면의 픽셀 좌표로 변환
    gridToPixel(gridX, gridY) {
        // 바둑판의 중앙이 (0,0)이라고 가정하고, 그리드 좌표를 픽셀로 변환
        // 예: gridX 9는 바둑판의 중앙 x축에 해당
        const pixelX = this.x + (gridX - (this.boardSize - 1) / 2) * this.gridSize;
        const pixelY = this.y + (gridY - (this.boardSize - 1) / 2) * this.gridSize;
        return { x: pixelX, y: pixelY };
    }
 
    // 2. 게임 화면의 픽셀 좌표를 그리드 좌표(0~18)로 변환
    pixelToGrid(pixelX, pixelY) {
        // 픽셀 좌표를 바둑판 중앙을 기준으로 한 상대 좌표로 변환 후 그리드 크기로 나누어 그리드 좌표 얻기
        const gridX = Math.round((pixelX - this.x) / this.gridSize) + (this.boardSize - 1) / 2;
        const gridY = Math.round((pixelY - this.y) / this.gridSize) + (this.boardSize - 1) / 2;
        return { x: gridX, y: gridY };
    }
}

코드 설명:


3) 바둑돌 스프라이트 (BadukStone.js)

BadukStone.js는 게임 내에서 흑돌과 백돌을 나타내는 스프라이트입니다. 이 클래스는 돌의 시각적인 표현뿐만 아니라, 착수 시 애니메이션 효과를 부여하여 사용자 경험을 향상시킵니다.

<file javascript> import Phaser from 'phaser';

export default class BadukStone extends Phaser.GameObjects.Sprite {

  constructor(scene, gridX, gridY, color) {
      // 돌 이미지 키 선택 ('black-stone' 또는 'white-stone')
      const imageKey = color === 'black' ? 'black-stone' : 'white-stone';
      // 바둑판 객체의 gridToPixel 함수를 사용하여 그리드 좌표를 픽셀 좌표로 변환
      const { x, y } = scene.board.gridToPixel(gridX, gridY);
      
      super(scene, x, y, imageKey); // Phaser.GameObjects.Sprite 생성자 호출
      
      this.gridX = gridX; // 돌의 그리드 X 좌표 저장
      this.gridY = gridY; // 돌의 그리드 Y 좌표 저장
      this.color = color; // 돌의 색상 저장
      
      this.setOrigin(0.5); // 돌의 기준점을 중앙으로 설정
      this.setScale(0.8); // 돌의 크기(스케일) 설정
      
      // 1. 착수 시 돌이 커지는 애니메이션 효과 (스케일 0 -> 0.8)
      this.setScale(0); // 처음에는 크기를 0으로 설정
      scene.tweens.add({ // 트윈(애니메이션) 추가
          targets: this, // 애니메이션 적용 대상
          scaleX: 0.8,   // X축 스케일을 0.8로
          scaleY: 0.8,   // Y축 스케일을 0.8