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