====== ๐ฎ 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''๋ ๊ฒ์ ๋ด์์ ํ๋๊ณผ ๋ฐฑ๋์ ๋ํ๋ด๋ ์คํ๋ผ์ดํธ์
๋๋ค. ์ด ํด๋์ค๋ ๋์ ์๊ฐ์ ์ธ ํํ๋ฟ๋ง ์๋๋ผ, ์ฐฉ์ ์ ์ ๋๋ฉ์ด์
ํจ๊ณผ๋ฅผ ๋ถ์ฌํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํต๋๋ค.
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