์‚ฌ์šฉ์ž ๋„๊ตฌ

์‚ฌ์ดํŠธ ๋„๊ตฌ


wiki:it:dream_of_enc:metaverse:phaser

๋ฌธ์„œ์˜ ์ด์ „ ํŒ์ž…๋‹ˆ๋‹ค!


๐ŸŽฎ Phaser.js ๊ฒŒ์ž„ ์—”์ง„

Phaser Baduk Metaverse ํ”„๋กœ์ ํŠธ์˜ Phaser.js ๊ฒŒ์ž„ ์—”์ง„ ๊ตฌํ˜„์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“‹ ๊ฐœ์š”

Phaser.js๋Š” HTML5 ๊ฒŒ์ž„ ๊ฐœ๋ฐœ์„ ์œ„ํ•œ JavaScript ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, ๋ฐ”๋‘‘ ๊ฒŒ์ž„์˜ ์‹œ๊ฐ์  ์š”์†Œ์™€ ์ธํ„ฐ๋ž™์…˜์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ—๏ธ ๊ธฐ๋ณธ ๊ตฌ์กฐ

๋ฉ”์ธ ๊ฒŒ์ž„ ํŒŒ์ผ (game.js):

import Phaser from 'phaser';
import BadukScene from './scenes/BadukScene';
import MenuScene from './scenes/MenuScene';
 
const config = {
    type: Phaser.AUTO,
    width: 1200,
    height: 800,
    parent: 'game-container',
    backgroundColor: '#2c3e50',
    scene: [MenuScene, BadukScene],
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 0 },
            debug: false
        }
    },
    scale: {
        mode: Phaser.Scale.FIT,
        autoCenter: Phaser.Scale.CENTER_BOTH
    }
};
 
const game = new Phaser.Game(config);
export default game;

๐ŸŽฏ ์”ฌ(Scene) ๊ด€๋ฆฌ

๋ฉ”๋‰ด ์”ฌ (MenuScene.js):

import Phaser from 'phaser';
 
export default class MenuScene extends Phaser.Scene {
    constructor() {
        super({ key: 'MenuScene' });
    }
 
    preload() {
        // ๋ฉ”๋‰ด ๋ฐฐ๊ฒฝ ๋ฐ ๋ฒ„ํŠผ ์ด๋ฏธ์ง€ ๋กœ๋“œ
        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() {
        // ๋ฐฐ๊ฒฝ ์„ค์ •
        this.add.image(600, 400, 'menu-bg');
 
        // ์ œ๋ชฉ ํ…์ŠคํŠธ
        this.add.text(600, 200, '๋ฐ”๋‘‘ ๋ฉ”ํƒ€๋ฒ„์Šค', {
            fontSize: '48px',
            fill: '#ffffff',
            fontFamily: 'Arial'
        }).setOrigin(0.5);
 
        // ํ”Œ๋ ˆ์ด ๋ฒ„ํŠผ
        const playButton = this.add.image(600, 350, 'play-button')
            .setInteractive();
 
        playButton.on('pointerdown', () => {
            this.scene.start('BadukScene');
        });
 
        // ์„ค์ • ๋ฒ„ํŠผ
        const settingsButton = this.add.image(600, 450, 'settings-button')
            .setInteractive();
 
        settingsButton.on('pointerdown', () => {
            // ์„ค์ • ์”ฌ์œผ๋กœ ์ด๋™
            console.log('์„ค์ • ๋ฉ”๋‰ด ์—ด๊ธฐ');
        });
    }
}

๋ฐ”๋‘‘ ๊ฒŒ์ž„ ์”ฌ (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'
    }
 
    preload() {
        // ๋ฐ”๋‘‘ํŒ๊ณผ ๋Œ ์ด๋ฏธ์ง€ ๋กœ๋“œ
        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() {
        // ๋ฐ”๋‘‘ํŒ ์ƒ์„ฑ
        this.board = new BadukBoard(this, 600, 400);
 
        // ๊ทธ๋ฆฌ๋“œ ๋ผ์ธ ์ถ”๊ฐ€
        this.add.image(600, 400, 'grid');
 
        // ํด๋ฆญ ์ด๋ฒคํŠธ ์„ค์ •
        this.input.on('pointerdown', (pointer) => {
            this.handleBoardClick(pointer);
        });
 
        // ํ”Œ๋ ˆ์ด์–ด ํ‘œ์‹œ
        this.createPlayerIndicator();
    }
 
    handleBoardClick(pointer) {
        const boardX = pointer.x - 600;
        const boardY = pointer.y - 400;
 
        // ๋ฐ”๋‘‘ํŒ ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜
        const gridX = Math.round(boardX / 40);
        const gridY = Math.round(boardY / 40);
 
        // ์œ ํšจํ•œ ์œ„์น˜์ธ์ง€ ํ™•์ธ
        if (this.isValidMove(gridX, gridY)) {
            this.placeStone(gridX, gridY);
        }
    }
 
    isValidMove(x, y) {
        // ๋ฐ”๋‘‘ํŒ ๋ฒ”์œ„ ๋‚ด์ธ์ง€ ํ™•์ธ
        if (x < 0 || x > 18 || y < 0 || y > 18) {
            return false;
        }
 
        // ์ด๋ฏธ ๋Œ์ด ๋†“์ธ ์œ„์น˜์ธ์ง€ ํ™•์ธ
        return !this.stones.some(stone => 
            stone.gridX === x && stone.gridY === y
        );
    }
 
    placeStone(x, y) {
        const stoneImage = this.currentPlayer === 'black' ? 'black-stone' : 'white-stone';
        const stone = new BadukStone(this, x, y, this.currentPlayer);
 
        this.stones.push(stone);
 
        // ํ”Œ๋ ˆ์ด์–ด ๋ณ€๊ฒฝ
        this.currentPlayer = this.currentPlayer === 'black' ? 'white' : 'black';
 
        // UI ์—…๋ฐ์ดํŠธ
        this.updatePlayerIndicator();
 
        // ์„œ๋ฒ„์— ์ด๋™ ์ „์†ก
        this.sendMoveToServer(x, y, this.currentPlayer);
    }
 
    createPlayerIndicator() {
        this.playerText = this.add.text(50, 50, 'ํ˜„์žฌ ํ”Œ๋ ˆ์ด์–ด: ํ‘๋Œ', {
            fontSize: '24px',
            fill: '#ffffff'
        });
    }
 
    updatePlayerIndicator() {
        const playerName = this.currentPlayer === 'black' ? 'ํ‘๋Œ' : '๋ฐฑ๋Œ';
        this.playerText.setText(`ํ˜„์žฌ ํ”Œ๋ ˆ์ด์–ด: ${playerName}`);
    }
 
    sendMoveToServer(x, y, player) {
        // Socket.IO๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„์— ์ด๋™ ์ „์†ก
        if (window.socket) {
            window.socket.emit('move', {
                x: x,
                y: y,
                player: player,
                gameId: this.gameId
            });
        }
    }
}

๐ŸŽจ ์Šคํ”„๋ผ์ดํŠธ(Sprite) ๊ตฌํ˜„

๋ฐ”๋‘‘ํŒ ์Šคํ”„๋ผ์ดํŠธ (BadukBoard.js):

import Phaser from 'phaser';
 
export default class BadukBoard extends Phaser.GameObjects.Sprite {
    constructor(scene, x, y) {
        super(scene, x, y, 'board');
 
        this.setOrigin(0.5);
        this.setInteractive();
 
        // ๋ฐ”๋‘‘ํŒ ํฌ๊ธฐ ์„ค์ •
        this.setScale(1.0);
 
        // ๊ทธ๋ฆฌ๋“œ ์ขŒํ‘œ ์‹œ์Šคํ…œ
        this.gridSize = 40;
        this.boardSize = 19; // 19x19 ๋ฐ”๋‘‘ํŒ
    }
 
    // ๊ทธ๋ฆฌ๋“œ ์ขŒํ‘œ๋ฅผ ํ”ฝ์…€ ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜
    gridToPixel(gridX, gridY) {
        const pixelX = this.x + (gridX - 9) * this.gridSize;
        const pixelY = this.y + (gridY - 9) * this.gridSize;
        return { x: pixelX, y: pixelY };
    }
 
    // ํ”ฝ์…€ ์ขŒํ‘œ๋ฅผ ๊ทธ๋ฆฌ๋“œ ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜
    pixelToGrid(pixelX, pixelY) {
        const gridX = Math.round((pixelX - this.x) / this.gridSize) + 9;
        const gridY = Math.round((pixelY - this.y) / this.gridSize) + 9;
        return { x: gridX, y: gridY };
    }
}

๋ฐ”๋‘‘๋Œ ์Šคํ”„๋ผ์ดํŠธ (BadukStone.js):

import Phaser from 'phaser';
 
export default class BadukStone extends Phaser.GameObjects.Sprite {
    constructor(scene, gridX, gridY, color) {
        const imageKey = color === 'black' ? 'black-stone' : 'white-stone';
        const { x, y } = scene.board.gridToPixel(gridX, gridY);
 
        super(scene, x, y, imageKey);
 
        this.gridX = gridX;
        this.gridY = gridY;
        this.color = color;
 
        this.setOrigin(0.5);
        this.setScale(0.8);
 
        // ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ
        this.setScale(0);
        scene.tweens.add({
            targets: this,
            scaleX: 0.8,
            scaleY: 0.8,
            duration: 200,
            ease: 'Back.easeOut'
        });
 
        scene.add.existing(this);
    }
 
    // ๋Œ ์ œ๊ฑฐ (์ฐฉ์ˆ˜)
    remove() {
        this.scene.tweens.add({
            targets: this,
            scaleX: 0,
            scaleY: 0,
            duration: 150,
            ease: 'Back.easeIn',
            onComplete: () => {
                this.destroy();
            }
        });
    }
}

๐Ÿ”Œ Socket.IO ํ†ต์‹ 

์‹ค์‹œ๊ฐ„ ํ†ต์‹  ์„ค์ •:

// game.js์—์„œ Socket.IO ์—ฐ๊ฒฐ
import io from 'socket.io-client';
 
const socket = io('http://localhost:3000');
 
socket.on('connect', () => {
    console.log('์„œ๋ฒ„์— ์—ฐ๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
});
 
socket.on('game-update', (data) => {
    // ๋‹ค๋ฅธ ํ”Œ๋ ˆ์ด์–ด์˜ ์ด๋™ ์ฒ˜๋ฆฌ
    if (data.player !== currentPlayer) {
        scene.placeStoneFromServer(data.x, data.y, data.player);
    }
});
 
socket.on('game-start', (data) => {
    console.log('๊ฒŒ์ž„์ด ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
    scene.startGame(data.gameId);
});
 
// ์ „์—ญ์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค์ •
window.socket = socket;

๐ŸŽจ UI/UX ๊ตฌํ˜„

๊ฒŒ์ž„ UI ์ปดํฌ๋„ŒํŠธ:

export default class GameUI {
    constructor(scene) {
        this.scene = scene;
        this.createUI();
    }
 
    createUI() {
        // ํŒจ์Šค ๋ฒ„ํŠผ
        this.passButton = this.scene.add.text(50, 150, 'ํŒจ์Šค', {
            fontSize: '20px',
            fill: '#ffffff',
            backgroundColor: '#e74c3c',
            padding: { x: 10, y: 5 }
        }).setInteractive();
 
        this.passButton.on('pointerdown', () => {
            this.handlePass();
        });
 
        // ํ•ญ๋ณต ๋ฒ„ํŠผ
        this.surrenderButton = this.scene.add.text(50, 200, 'ํ•ญ๋ณต', {
            fontSize: '20px',
            fill: '#ffffff',
            backgroundColor: '#c0392b',
            padding: { x: 10, y: 5 }
        }).setInteractive();
 
        this.surrenderButton.on('pointerdown', () => {
            this.handleSurrender();
        });
 
        // ์ ์ˆ˜ ํ‘œ์‹œ
        this.scoreText = this.scene.add.text(50, 250, '์ ์ˆ˜: 0 - 0', {
            fontSize: '18px',
            fill: '#ffffff'
        });
    }
 
    handlePass() {
        // ํŒจ์Šค ์ฒ˜๋ฆฌ
        if (window.socket) {
            window.socket.emit('pass', {
                gameId: this.scene.gameId,
                player: this.scene.currentPlayer
            });
        }
    }
 
    handleSurrender() {
        // ํ•ญ๋ณต ์ฒ˜๋ฆฌ
        if (window.socket) {
            window.socket.emit('surrender', {
                gameId: this.scene.gameId,
                player: this.scene.currentPlayer
            });
        }
    }
 
    updateScore(blackScore, whiteScore) {
        this.scoreText.setText(`์ ์ˆ˜: ${blackScore} - ${whiteScore}`);
    }
}

๐ŸŽฎ ๊ฒŒ์ž„ ๋กœ์ง

๋ฐ”๋‘‘ ๊ทœ์น™ ๊ตฌํ˜„:

export default class BadukRules {
    constructor() {
        this.board = Array(19).fill().map(() => Array(19).fill(null));
        this.capturedStones = { black: 0, white: 0 };
    }
 
    // ๋Œ ๋†“๊ธฐ
    placeStone(x, y, color) {
        if (this.isValidMove(x, y, color)) {
            this.board[y][x] = color;
 
            // ์ฐฉ์ˆ˜ ํ™•์ธ
            const captured = this.checkCapture(x, y, color);
            this.capturedStones[color] += captured;
 
            return true;
        }
        return false;
    }
 
    // ์œ ํšจํ•œ ์ด๋™์ธ์ง€ ํ™•์ธ
    isValidMove(x, y, color) {
        if (x < 0 || x >= 19 || y < 0 || y >= 19) {
            return false;
        }
 
        if (this.board[y][x] !== null) {
            return false;
        }
 
        // ์ž์ถฉ ํ™•์ธ
        this.board[y][x] = color;
        const hasLiberties = this.hasLiberties(x, y);
        this.board[y][x] = null;
 
        return hasLiberties;
    }
 
    // ์ž์œ ๋„ ํ™•์ธ
    hasLiberties(x, y) {
        const color = this.board[y][x];
        const visited = new Set();
 
        return this.checkGroupLiberties(x, y, color, visited);
    }
 
    checkGroupLiberties(x, y, color, visited) {
        const key = `${x},${y}`;
        if (visited.has(key)) return false;
        visited.add(key);
 
        // ์ƒํ•˜์ขŒ์šฐ ํ™•์ธ
        const directions = [[0, 1], [0, -1], [1, 0], [-1, 0]];
 
        for (const [dx, dy] of directions) {
            const nx = x + dx;
            const ny = y + dy;
 
            if (nx < 0 || nx >= 19 || ny < 0 || ny >= 19) {
                continue;
            }
 
            if (this.board[ny][nx] === null) {
                return true; // ์ž์œ ๋„ ๋ฐœ๊ฒฌ
            }
 
            if (this.board[ny][nx] === color) {
                if (this.checkGroupLiberties(nx, ny, color, visited)) {
                    return true;
                }
            }
        }
 
        return false;
    }
 
    // ์ฐฉ์ˆ˜ ํ™•์ธ
    checkCapture(x, y, color) {
        const directions = [[0, 1], [0, -1], [1, 0], [-1, 0]];
        let captured = 0;
 
        for (const [dx, dy] of directions) {
            const nx = x + dx;
            const ny = y + dy;
 
            if (nx < 0 || nx >= 19 || ny < 0 || ny >= 19) {
                continue;
            }
 
            const opponentColor = color === 'black' ? 'white' : 'black';
 
            if (this.board[ny][nx] === opponentColor) {
                if (!this.hasLiberties(nx, ny)) {
                    captured += this.removeGroup(nx, ny);
                }
            }
        }
 
        return captured;
    }
 
    // ๊ทธ๋ฃน ์ œ๊ฑฐ
    removeGroup(x, y) {
        const color = this.board[y][x];
        if (!color) return 0;
 
        let count = 0;
        const stack = [[x, y]];
 
        while (stack.length > 0) {
            const [cx, cy] = stack.pop();
 
            if (this.board[cy][cx] === color) {
                this.board[cy][cx] = null;
                count++;
 
                const directions = [[0, 1], [0, -1], [1, 0], [-1, 0]];
                for (const [dx, dy] of directions) {
                    const nx = cx + dx;
                    const ny = cy + dy;
 
                    if (nx >= 0 && nx < 19 && ny >= 0 && ny < 19) {
                        if (this.board[ny][nx] === color) {
                            stack.push([nx, ny]);
                        }
                    }
                }
            }
        }
 
        return count;
    }
}

๐Ÿ“š ๊ด€๋ จ ๋ฌธ์„œ

โ€” ์ด ํŽ˜์ด์ง€๋Š” ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

wiki/it/dream_of_enc/metaverse/phaser.1753682235.txt.gz ยท ๋งˆ์ง€๋ง‰์œผ๋กœ ์ˆ˜์ •๋จ: ์ €์ž 127.0.0.1

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki