๋ชฉ์ฐจ
๐ฎ Phaser.js ๊ฒ์ ์์ง
Phaser Baduk Metaverse ํ๋ก์ ํธ์์ ์ฌ์ฉ๋๋ Phaser.js ๊ฒ์ ์์ง์ ๊ตฌํ ๋ด์ฉ์ ๋ํด ์์ธํ ์ค๋ช ํฉ๋๋ค.
๐ ๊ฐ์
Phaser.js๋ HTML5 ๊ฒ์ ๊ฐ๋ฐ์ ํนํ๋ ๊ฐ๋ ฅํ JavaScript ํ๋ ์์ํฌ์ ๋๋ค. ์ด ํ๋ ์์ํฌ๋ ๋ฐ๋ ๊ฒ์์ ์๊ฐ์ ์์๋ฅผ ๊ตฌํํ๊ณ , ์ฌ์ฉ์ ์ธํฐ๋์ ๋ฐ ์ค์๊ฐ ํต์ ๊ธฐ๋ฅ์ ํตํฉํ์ฌ ๋ชฐ์ ๊ฐ ์๋ ๊ฒ์ ๊ฒฝํ์ ์ ๊ณตํ๋ ๋ฐ ํต์ฌ์ ์ธ ์ญํ ์ ํฉ๋๋ค.
HTML5 ๊ธฐ๋ฐ
: ์น ๋ธ๋ผ์ฐ์ ์์ ์ง์ ์คํ๋์ด ๋ณ๋์ ์ค์น ์์ด ์ ๊ทผ์ฑ์ด ๋์ต๋๋ค.๊ฒ์ ๊ฐ๋ฐ ํนํ
: ์คํ๋ผ์ดํธ, ์ ๋๋ฉ์ด์ , ๋ฌผ๋ฆฌ ์์ง ๋ฑ ๊ฒ์ ๊ฐ๋ฐ์ ํ์ํ ๋ค์ํ ๊ธฐ๋ฅ์ ๋ด์ฅํ๊ณ ์์ต๋๋ค.์ ์ฐํ ๊ตฌ์กฐ
: ์ฌ(Scene) ๊ธฐ๋ฐ์ ์ํคํ ์ฒ๋ฅผ ํตํด ๊ฒ์์ ๊ฐ ๋ถ๋ถ์ ๋ชจ๋ํํ์ฌ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
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;
์ฝ๋ ์ค๋ช
:
์ํฌํธ (Import)
: Phaser ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ฒ์์์ ์ฌ์ฉํMenuScene
,BadukScene
์ฌ๋ค์ ๋ถ๋ฌ์ต๋๋ค.config ๊ฐ์ฒด
: ๊ฒ์์ ์ ๋ฐ์ ์ธ ์ค์ ์ ๋ด๊ณ ์์ต๋๋ค. ์ฃผ์ ์์ฑ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.type
: ๊ฒ์ ๋ ๋๋ง ๋ฐฉ์์ ๊ฒฐ์ ํฉ๋๋ค.Phaser.AUTO
๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์ง์ํ๋ ์ต์ ์ ๋ ๋๋ง ๋ฐฉ์(WebGL ๋๋ Canvas)์ ์๋์ผ๋ก ์ ํํฉ๋๋ค.width
,height
: ๊ฒ์ ํ๋ฉด์ ํด์๋๋ฅผ ํฝ์ ๋จ์๋ก ์ง์ ํฉ๋๋ค.parent
: ๊ฒ์ ์บ๋ฒ์ค๊ฐ ์ฝ์ ๋ HTML ๋ฌธ์ ๋ด์ ํน์ DOM ์์ ID๋ฅผ ์ง์ ํฉ๋๋ค. ์ด ๊ฒฝ์ฐ,id=โgame-containerโ
๋ฅผ ๊ฐ์ง ์์์ ๊ฒ์์ด ํ์๋ฉ๋๋ค.backgroundColor
: ๊ฒ์์ ๊ธฐ๋ณธ ๋ฐฐ๊ฒฝ์์ ์ง์ ํฉ๋๋ค.scene
: ๊ฒ์์์ ์ฌ์ฉํ ๋ชจ๋ ์ฌ(Scene)๋ค์ ๋ฐฐ์ด์ ๋๋ค. ๋ฐฐ์ด์ ์์์ ๋ฐ๋ผ ์ฌ๋ค์ด ๋ก๋๋์ง๋ง, ๊ฒ์ ์์ ์์๋ ์ฒซ ๋ฒ์งธ ์ฌ์ด ๊ธฐ๋ณธ์ผ๋ก ์์๋ฉ๋๋ค.physics
: ๋ฌผ๋ฆฌ ์์ง ์ค์ ์ ํฌํจํฉ๋๋ค. ์ฌ๊ธฐ์๋ ๋ฐ๋ ๊ฒ์์ ํ์ํ์ง ์์ ์ค๋ ฅ์ ๋นํ์ฑํํ๊ณ ๋๋ฒ๊ทธ ๋ชจ๋๋ฅผ ๊บผ๋์์ต๋๋ค.scale
: ๊ฒ์์ด ๋ค์ํ ํ๋ฉด ํฌ๊ธฐ์ ๋ง์ถฐ ์ด๋ป๊ฒ ์กฐ์ ๋ ์ง ์ ์ํฉ๋๋ค.FIT
๋ชจ๋๋ ํ๋ฉด ๋น์จ์ ์ ์งํ๋ฉฐ ๊ฐ๋ฅํ ํ ํฌ๊ฒ ํ์ํ๊ณ ,CENTER_BOTH
๋ ๊ฒ์์ ํ๋ฉด ์ค์์ ๋ฐฐ์นํฉ๋๋ค.
Phaser.Game ์ธ์คํด์ค ์์ฑ
: ์์์ ์ ์ํconfig
๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ์๋ก์ด Phaser ๊ฒ์ ์ธ์คํด์ค๋ฅผ ์์ฑํฉ๋๋ค. ์ด ์ธ์คํด์ค๊ฐ ์ค์ ๊ฒ์์ ์คํํ๋ ์ฃผ์ฒด์ ๋๋ค.export default game
: ์ด ๊ฒ์ ์ธ์คํด์ค๋ฅผ ๋ค๋ฅธ JavaScript ํ์ผ์์ ๋ถ๋ฌ์ ์ฌ์ฉํ ์ ์๋๋ก ๋ด๋ณด๋ ๋๋ค.
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('์ค์ ๋ฉ๋ด ์ด๊ธฐ'); // ์ค์ ๊ธฐ๋ฅ์ ์ฝ์ ๋ก๊ทธ๋ก ๋์ฒด }); } }
์ฝ๋ ์ค๋ช
:
constructor
: ์ฌ์ ๊ณ ์ ํ ํค(์ฌ๊ธฐ์๋ 'MenuScene')๋ฅผ ์ ์ํฉ๋๋ค. ์ด๋ ๋ค๋ฅธ ์ฌ์์ ์ด ์ฌ์ ์ฐธ์กฐํ ๋ ์ฌ์ฉ๋ฉ๋๋ค.preload()
: ์ด ์ฌ์ด ์์๋๊ธฐ ์ ์ ํ์ํ ๋ชจ๋ ์ด๋ฏธ์ง, ์ค๋์ค ๋ฑ์ ์์ ์ ๋ฏธ๋ฆฌ ๋ก๋ํ๋ ํจ์์ ๋๋ค. ๊ฒ์ ๋ก๋ฉ ์๊ฐ์ ๋จ์ถํ๊ณ , ์ฌ์ด ์ค๋น๋์์ ๋ ์ฆ์ ์ฌ์ฉํ ์ ์๋๋ก ํฉ๋๋ค.create()
: ์ฌ์ ์์ ๋ก๋ฉ์ด ์๋ฃ๋ ํ, ๊ฒ์ ์ค๋ธ์ ํธ(๋ฐฐ๊ฒฝ, ๋ฒํผ, ํ ์คํธ ๋ฑ)๋ฅผ ์์ฑํ๊ณ ๋ฐฐ์นํ๋ ํจ์์ ๋๋ค.this.add.image()
: ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง์ ๋ฒํผ ์ด๋ฏธ์ง๋ฅผ ํ๋ฉด์ ์ถ๊ฐํฉ๋๋ค.this.add.text()
: ๊ฒ์ ์ ๋ชฉ ํ ์คํธ๋ฅผ ์ถ๊ฐํ๊ณ ์คํ์ผ์ ์ ์ฉํฉ๋๋ค.setOrigin(0.5)
๋ ํ ์คํธ์ ์ค์์ ๊ธฐ์ค์ผ๋ก ์์น๋ฅผ ์ก๋๋ก ํฉ๋๋ค.setInteractive()
: ๋ฒํผ ์ด๋ฏธ์ง๋ฅผ ํด๋ฆญ ๊ฐ๋ฅํ ์ํธ์์ฉ ๊ฐ์ฒด๋ก ๋ง๋ญ๋๋ค.on('pointerdown', โฆ)
: ๋ฒํผ์ด ํด๋ฆญ(๋๋ ํฐ์น)๋์์ ๋ ์คํ๋ ์ฝ๋ฐฑ ํจ์๋ฅผ ์ ์ํฉ๋๋ค.this.scene.start('BadukScene')
์ ํตํด ์ค์ ๋ฐ๋ ๊ฒ์ ์ฌ์ผ๋ก ์ ํํฉ๋๋ค.
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 ํฌํจ }); } } }
์ฝ๋ ์ค๋ช
:
constructor
: ์ฌ์ ๊ณ ์ ํค๋ฅผ ์ ์ํ๊ณ , ๋ฐ๋ํ ๊ฐ์ฒด, ๋์ธ ๋๋ค์ ์ ์ฅํ ๋ฐฐ์ด, ํ์ฌ ํด ํ๋ ์ด์ด, ๊ฒ์ ID์ ๊ฐ์ ๊ฒ์ ์ํ ๋ณ์๋ค์ ์ด๊ธฐํํฉ๋๋ค.preload()
: ๋ฐ๋ํ, ํ๋, ๋ฐฑ๋ ์ด๋ฏธ์ง ๋ฑ ๊ฒ์ ํ๋ ์ด์ ํ์ํ ๋ชจ๋ ์์ ์ ๋ก๋ํฉ๋๋ค.create()
:this.board = new BadukBoard(โฆ)
: ์ปค์คํ BadukBoard
์คํ๋ผ์ดํธ๋ฅผ ์์ฑํ์ฌ ์๊ฐ์ ์ธ ๋ฐ๋ํ์ ํ๋ฉด์ ์ถ๊ฐํฉ๋๋ค.this.input.on('pointerdown', โฆ)
: ๋ง์ฐ์ค ํด๋ฆญ(๋๋ ํฐ์น) ์ด๋ฒคํธ๋ฅผ ๊ฐ์งํ์ฌhandleBoardClick
ํจ์๋ฅผ ํธ์ถํ๋๋ก ์ค์ ํฉ๋๋ค.createPlayerIndicator()
: ํ์ฌ ํด์ ํ์ํ๋ UI ํ ์คํธ๋ฅผ ์์ฑํฉ๋๋ค.window.socket.on(โฆ)
: Socket.IO๋ฅผ ํตํด ์๋ฒ๋ก๋ถํฐ ๊ฒ์ ์์ ๋ฐ ๋ค๋ฅธ ํ๋ ์ด์ด์ ์ฐฉ์ ์ ๋ณด๋ฅผ ์ค์๊ฐ์ผ๋ก ์์ ํ์ฌ ์ฒ๋ฆฌํฉ๋๋ค.
handleBoardClick(pointer)
: ์ฌ์ฉ์๊ฐ ๋ฐ๋ํ์ ํด๋ฆญํ์ ๋ ํธ์ถ๋ฉ๋๋ค. ํด๋ฆญ๋ ํ๋ฉด ํฝ์ ์ขํ๋ฅผ ๋ฐ๋ํ์ ๊ทธ๋ฆฌ๋ ์ขํ(์: (0,0)์์ (18,18)๊น์ง)๋ก ๋ณํํ๊ณ ,isValidMove
๋ฅผ ํตํด ์ ํจ์ฑ ๊ฒ์ฌ ํplaceStone
์ ํธ์ถํฉ๋๋ค.isValidMove(x, y)
: ์ฃผ์ด์ง ๊ทธ๋ฆฌ๋ ์ขํ๊ฐ ๋ฐ๋ํ ๋ฒ์ ๋ด์ ์๋์ง, ๊ทธ๋ฆฌ๊ณ ํด๋น ์์น์ ์ด๋ฏธ ๋์ด ๋์ฌ์๋์ง ํ์ธํ์ฌ ์ฐฉ์ ๊ฐ๋ฅ ์ฌ๋ถ๋ฅผ ๋ฐํํฉ๋๋ค. (TODO
์ ์ธ๊ธ๋ ๊ฒ์ฒ๋ผ ์ค์ ๋ฐ๋ ๊ท์น์ ๋ ๋ณต์กํฉ๋๋ค.)placeStone(x, y, playerColor)
:new BadukStone(โฆ)
:BadukStone
์คํ๋ผ์ดํธ๋ฅผ ์์ฑํ์ฌ ํด๋น ์์น์ ์๊ฐ์ ์ผ๋ก ๋์ ๋์ต๋๋ค. ์ด ๊ณผ์ ์์ ๋์ด ๋ํ๋๋ ์ ๋๋ฉ์ด์ ์ด ํฌํจ๋ฉ๋๋ค.this.stones.push(stone)
: ๋์ฌ์ง ๋์stones
๋ฐฐ์ด์ ์ถ๊ฐํ์ฌ ๊ฒ์ ์ํ๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.this.currentPlayer = โฆ
: ํ์ฌ ํ๋ ์ด์ด์ ํด์ ํ๋์์ ๋ฐฑ๋๋ก, ๋๋ ๋ฐฑ๋์์ ํ๋๋ก ์ ํํฉ๋๋ค.updatePlayerIndicator()
: UI๋ฅผ ์ ๋ฐ์ดํธํ์ฌ ํ์ฌ ํด ํ๋ ์ด์ด๋ฅผ ํ์ํฉ๋๋ค.sendMoveToServer()
: ๋์ธ ๋์ ์ ๋ณด๋ฅผ ์๋ฒ๋ก ์ ์กํ์ฌ ๋ค๋ฅธ ํ๋ ์ด์ด์ ๊ฒ์ ์ํ๋ฅผ ๋๊ธฐํํฉ๋๋ค.
createPlayerIndicator()
,updatePlayerIndicator()
: ํ์ฌ ํด ํ๋ ์ด์ด๋ฅผ ํ๋ฉด์ ํ ์คํธ๋ก ํ์ํ๊ณ ์ ๋ฐ์ดํธํ๋ ํจ์์ ๋๋ค.sendMoveToServer(x, y, player)
: Socket.IO๋ฅผ ์ฌ์ฉํ์ฌ ์ฐฉ์ ์ ๋ณด๋ฅผ ์๋ฒ์ 'move' ์ด๋ฒคํธ๋ก ์ ์กํฉ๋๋ค. ๊ฒ์ 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 }; } }
์ฝ๋ ์ค๋ช
:
constructor(scene, x, y)
:super(scene, x, y, 'board')
: ๋ถ๋ชจ ํด๋์ค์ธPhaser.GameObjects.Sprite
์ ์์ฑ์๋ฅผ ํธ์ถํ์ฌ, ์ด ๊ฐ์ฒด๊ฐ 'board'๋ผ๋ ํค๋ก ๋ก๋๋ ์ด๋ฏธ์ง๋ฅผ ์ฌ์ฉํ๋๋ก ์ค์ ํฉ๋๋ค.x
์y
๋ ๋ฐ๋ํ ์คํ๋ผ์ดํธ์ ํ๋ฉด์ ์์น์ ๋๋ค.this.setOrigin(0.5)
: ์คํ๋ผ์ดํธ์ ์์ (๊ธฐ์ค์ )์ ์ค์์ผ๋ก ์ค์ ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉดx
,y
์ขํ๊ฐ ์คํ๋ผ์ดํธ์ ์ค์์ ๊ฐ๋ฆฌํค๊ฒ ๋์ด ์์น ๊ณ์ฐ์ด ํธ๋ฆฌํด์ง๋๋ค.this.setInteractive()
: ์ด ์คํ๋ผ์ดํธ๊ฐ ๋ง์ฐ์ค ํด๋ฆญ ๊ฐ์ ์ฌ์ฉ์ ์ ๋ ฅ ์ด๋ฒคํธ๋ฅผ ๊ฐ์งํ ์ ์๋๋ก ์ค์ ํฉ๋๋ค.this.gridSize
,this.boardSize
: ๋ฐ๋ํ์ ํ ์นธ(์ ๊ณผ ์ ์ฌ์ด)์ ํฝ์ ํฌ๊ธฐ์ ๋ฐ๋ํ์ ๊ฐ๋ก/์ธ๋ก ์นธ ์๋ฅผ ์ ์ํฉ๋๋ค. ์ด๋ ํฝ์ ์ขํ์ ๊ทธ๋ฆฌ๋ ์ขํ ๊ฐ ๋ณํ์ ์ฌ์ฉ๋ฉ๋๋ค.
gridToPixel(gridX, gridY)
: ๋ฐ๋ํ์ ๊ทธ๋ฆฌ๋ ์ขํ(์: (0,0)๋ถํฐ (18,18)๊น์ง)๋ฅผ ๊ฒ์ ํ๋ฉด ์์ ์ค์ ํฝ์ ์ขํ๋ก ๋ณํํฉ๋๋ค. ์ด๋ ๋ฐ๋๋์ ์ ํํ ์์น์ ๋์ ๋ ์ฌ์ฉ๋ฉ๋๋ค.pixelToGrid(pixelX, pixelY)
: ๊ฒ์ ํ๋ฉด ์์ ํฝ์ ์ขํ๋ฅผ ๋ฐ๋ํ์ ๊ทธ๋ฆฌ๋ ์ขํ๋ก ๋ณํํฉ๋๋ค. ์ด๋ ์ฌ์ฉ์๊ฐ ๋ง์ฐ์ค๋ฅผ ํด๋ฆญํ์ ๋ ์ด๋ ์นธ์ ํด๋ฆญํ๋์ง ์์๋ผ ๋ ์ฌ์ฉ๋ฉ๋๋ค.
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