====== ๐จ UI/UX ๊ตฌํ ======
Phaser Baduk Metaverse ํ๋ก์ ํธ์ ์ฌ์ฉ์ ์ธํฐํ์ด์ค์ ์ฌ์ฉ์ ๊ฒฝํ ๊ตฌํ์ ๋ํด ์ค๋ช
ํฉ๋๋ค.
----
===== 1. UI/UX๋ ๋ฌด์์ธ๊ฐ์? =====
''UI''(''User Interface'')๋ ์ฌ์ฉ์๊ฐ ํ๋ก๊ทธ๋จ๊ณผ ์ํธ์์ฉํ๋ ํ๋ฉด์ ์๋ฏธํ๊ณ , ''UX''(''User Experience'')๋ ์ฌ์ฉ์๊ฐ ํ๋ก๊ทธ๋จ์ ์ฌ์ฉํ ๋ ๋๋ผ๋ ์ ์ฒด์ ์ธ ๊ฒฝํ์ ์๋ฏธํฉ๋๋ค.
**UI/UX์ ์ค์์ฑ:**
* ์ฌ์ฉ์๊ฐ ์ฝ๊ฒ ์ดํดํ ์ ์๋ ์ธํฐํ์ด์ค.
* ์ง๊ด์ ์ธ ์กฐ์ ๋ฐฉ๋ฒ.
* ์ผ๊ด๋ ๋์์ธ ์ธ์ด.
* ์ ๊ทผ์ฑ๊ณผ ์ฌ์ฉ์ฑ ๊ณ ๋ ค.
**๋ฐ๋ ๊ฒ์์์์ UI/UX:**
* ๋ช
ํํ ๋ฐ๋ํ ํ์.
* ๋์ ๋๋ ๋ฐฉ๋ฒ์ด ์ง๊ด์ .
* ๊ฒ์ ์ํ ์ ๋ณด๊ฐ ๋ช
ํ.
* ๋ชจ๋ฐ์ผ๊ณผ ๋ฐ์คํฌํฑ ๋ชจ๋ ์ง์.
----
===== 2. ๋์์ธ ์์น =====
==== 1) ์ง๊ด์ฑ ====
* ๋ฐ๋ํ์ ์ ํต์ ์ธ 19x19 ๊ฒฉ์ ๋ ์ด์์.
* ๋ช
ํํ ์์ ๊ตฌ๋ถ (ํ๋ฐฑ ๋).
* ์ง๊ด์ ์ธ ๋ฒํผ ๋ฐฐ์น์ ์์ด์ฝ.
* ์ฌ์ฉ์๊ฐ ํ๋์ ์ดํดํ ์ ์๋ ์ธํฐํ์ด์ค.
==== 2) ๋ฐ์ํ ====
* ๋ค์ํ ํ๋ฉด ํฌ๊ธฐ์ ๋์.
* ํฐ์น ์ธํฐํ์ด์ค ์ต์ ํ.
* ํค๋ณด๋ ๋จ์ถํค ์ง์.
* ๋ชจ๋ฐ์ผ๊ณผ ๋ฐ์คํฌํฑ ๋ชจ๋ ์ต์ ํ.
==== 3) ์ ๊ทผ์ฑ ====
* ๊ณ ๋๋น ์์ ๋ชจ๋.
* ํค๋ณด๋ ๋ค๋น๊ฒ์ด์
.
* ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ง์.
* ๋ชจ๋ ์ฌ์ฉ์๊ฐ ์ฌ์ฉํ ์ ์๋๋ก ์ค๊ณ.
----
===== 3. UI ๊ตฌ์กฐ ์ดํดํ๊ธฐ =====
==== 1) ๊ธฐ๋ณธ UI ํด๋์ค ๊ตฌ์กฐ ====
class BadukUI {
constructor(scene) {
this.scene = scene;
this.board = null;
this.stoneLayer = null;
this.uiLayer = null;
this.infoPanel = null;
this.controlPanel = null;
this.initUI();
}
initUI() {
// ๋ฐ๋ํ ์์ฑ
this.createBoard();
// UI ๋ ์ด์ด ์์ฑ
this.createUILayer();
// ์ ๋ณด ํจ๋ ์์ฑ
this.createInfoPanel();
// ์ปจํธ๋กค ํจ๋ ์์ฑ
this.createControlPanel();
}
}
**์ค๋ช
:**
* ''scene'' - ''Phaser.js''์ ๊ฒ์ ์ฌ ๊ฐ์ฒด
* ''board'' - ๋ฐ๋ํ ๊ฐ์ฒด
* ''stoneLayer'' - ๋๋ค์ ํ์ํ๋ ๋ ์ด์ด
* ''uiLayer'' - UI ์์๋ค์ ํ์ํ๋ ๋ ์ด์ด
* ''infoPanel'' - ๊ฒ์ ์ ๋ณด๋ฅผ ํ์ํ๋ ํจ๋
* ''controlPanel'' - ๋ฒํผ๋ค์ ํ์ํ๋ ํจ๋
==== 2) ๋ ์ด์ด ๊ตฌ์กฐ ====
**Phaser.js์ ๋ ์ด์ด ์์คํ
:**
* **๋ฐฐ๊ฒฝ ๋ ์ด์ด** - ๋ฐ๋ํ ๊ฒฉ์
* **๋ ๋ ์ด์ด** - ๋ฐ๋๋๋ค
* **UI ๋ ์ด์ด** - ๋ฒํผ, ํ
์คํธ, ํจ๋
* **ํจ๊ณผ ๋ ์ด์ด** - ์ ๋๋ฉ์ด์
, ํจ๊ณผ
----
===== 4. ๋ฐ๋ํ ๋ ๋๋ง =====
==== 1) ๊ธฐ๋ณธ ๊ฒฉ์ ์์ฑ ====
createBoard() {
// 19x19 ๊ฒฉ์ ์์ฑ
const boardSize = 18 * 40; // 40px ๊ฐ๊ฒฉ
const startX = (800 - boardSize) / 2;
const startY = (600 - boardSize) / 2;
// ๊ฒฉ์ ๊ทธ๋ฆฌ๊ธฐ
const graphics = this.scene.add.graphics();
graphics.lineStyle(1, 0x8B4513, 1);
for (let i = 0; i < 19; i++) {
// ์ธ๋ก์
graphics.moveTo(startX + i * 40, startY);
graphics.lineTo(startX + i * 40, startY + boardSize);
// ๊ฐ๋ก์
graphics.moveTo(startX, startY + i * 40);
graphics.lineTo(startX + boardSize, startY + i * 40);
}
graphics.strokePaths();
}
**์ค๋ช
:**
* ''boardSize'' - ๋ฐ๋ํ์ ์ ์ฒด ํฌ๊ธฐ (18 * 40 = 720px)
* ''startX, startY'' - ๋ฐ๋ํ์ ์์ ์์น (ํ๋ฉด ์ค์)
* ''graphics.lineStyle()'' - ์ ์ ์คํ์ผ ์ค์ (๋๊ป, ์์)
* ''graphics.moveTo()'' - ์ ์ ์์์
* ''graphics.lineTo()'' - ์ ์ ๋์
* ''graphics.strokePaths()'' - ๊ทธ๋ฆฐ ์ ๋ค์ ์ค์ ๋ก ํ์
==== 2) ๋ณ์ ์์น ํ์ ====
drawStarPoints(graphics, startX, startY) {
const starPoints = [
[3, 3], [3, 9], [3, 15],
[9, 3], [9, 9], [9, 15],
[15, 3], [15, 9], [15, 15]
];
graphics.fillStyle(0x8B4513, 1);
starPoints.forEach(([x, y]) => {
graphics.fillCircle(
startX + x * 40,
startY + y * 40,
3
);
});
}
**์ค๋ช
:**
* ''starPoints'' - ๋ฐ๋ํ์ ๋ณ์ ์์น๋ค (3,3,9,9,15,15 ๋ฑ)
* ''graphics.fillStyle()'' - ์์ ์์ ์ค์
* ''graphics.fillCircle()'' - ์ ๊ทธ๋ฆฌ๊ธฐ (x, y, ๋ฐ์ง๋ฆ)
==== 3) ๋ฐ๋ํ ๋ฐฐ๊ฒฝ ====
createBoardBackground() {
const boardSize = 18 * 40;
const startX = (800 - boardSize) / 2;
const startY = (600 - boardSize) / 2;
// ๋ฐ๋ํ ๋ฐฐ๊ฒฝ (๋๋ฌด ์์)
const background = this.scene.add.rectangle(
startX + boardSize / 2,
startY + boardSize / 2,
boardSize + 20,
boardSize + 20,
0xD2B48C
);
background.setDepth(0); // ๊ฐ์ฅ ๋ค์ชฝ์ ๋ฐฐ์น
}
**์ค๋ช
:**
* ''rectangle'' - ์ฌ๊ฐํ ์์ฑ (x, y, width, height, color)
* ''setDepth(0)'' - ๋ ์ด์ด ์์ ์ค์ (์ซ์๊ฐ ์์์๋ก ๋ค์ชฝ)
----
===== 5. ๋ ๋ ๋๋ง =====
==== 1) ๋ ์์ฑ ํจ์ ====
createStone(x, y, color) {
const stoneSize = 18;
const startX = (800 - 18 * 40) / 2;
const startY = (600 - 18 * 40) / 2;
const stone = this.scene.add.circle(
startX + x * 40,
startY + y * 40,
stoneSize,
color === 'black' ? 0x000000 : 0xFFFFFF
);
// ํฐ ๋์ ํ
๋๋ฆฌ ์ถ๊ฐ
if (color === 'white') {
stone.setStrokeStyle(1, 0x000000);
}
stone.setDepth(1); // ๊ฒฉ์๋ณด๋ค ์์ชฝ์ ๋ฐฐ์น
return stone;
}
**์ค๋ช
:**
* ''circle'' - ์ ์์ฑ (x, y, ๋ฐ์ง๋ฆ, ์์)
* ''setStrokeStyle()'' - ํ
๋๋ฆฌ ์คํ์ผ ์ค์
* ''setDepth(1)'' - ๊ฒฉ์๋ณด๋ค ์์ชฝ์ ๋ฐฐ์น
==== 2) ๋ ํด๋ฆญ ์ด๋ฒคํธ ====
addStoneClickHandler(stone, x, y) {
stone.setInteractive();
stone.on('pointerdown', () => {
// ๋ ํด๋ฆญ ์ ์ด๋ฒคํธ ์ฒ๋ฆฌ
this.onStoneClick(x, y);
});
// ํธ๋ฒ ํจ๊ณผ
stone.on('pointerover', () => {
stone.setScale(1.1);
});
stone.on('pointerout', () => {
stone.setScale(1.0);
});
}
**์ค๋ช
:**
* ''setInteractive()'' - ํด๋ฆญ ๊ฐ๋ฅํ๊ฒ ์ค์
* ''on('pointerdown')'' - ๋ง์ฐ์ค ํด๋ฆญ ์ด๋ฒคํธ
* ''on('pointerover')'' - ๋ง์ฐ์ค ํธ๋ฒ ์ด๋ฒคํธ
* ''setScale()'' - ํฌ๊ธฐ ์กฐ์ (1.1 = 10% ํ๋)
----
===== 6. UI ํจ๋ ์์ฑ =====
==== 1) ์ ๋ณด ํจ๋ ====
createInfoPanel() {
const panel = this.scene.add.rectangle(
650, 100, 120, 200, 0xF5F5F5
);
panel.setStrokeStyle(2, 0xCCCCCC);
// ๊ฒ์ ์ ๋ณด ํ
์คํธ
this.gameInfoText = this.scene.add.text(
590, 20, '๊ฒ์ ์ ๋ณด',
{ fontSize: '16px', fill: '#333' }
);
// ํ์ฌ ํ๋ ์ด์ด ํ์
this.currentPlayerText = this.scene.add.text(
590, 50, 'ํ์ฌ: ํ',
{ fontSize: '14px', fill: '#000' }
);
// ์บก์ฒ๋ ๋ ์ ํ์
this.capturedText = this.scene.add.text(
590, 80, 'ํ: 0๊ฐ, ๋ฐฑ: 0๊ฐ',
{ fontSize: '12px', fill: '#666' }
);
}
**์ค๋ช
:**
* ''rectangle'' - ํจ๋ ๋ฐฐ๊ฒฝ ์์ฑ
* ''setStrokeStyle()'' - ํ
๋๋ฆฌ ์ค์
* ''add.text()'' - ํ
์คํธ ์ถ๊ฐ (x, y, ๋ด์ฉ, ์คํ์ผ)
==== 2) ์ปจํธ๋กค ํจ๋ ====
createControlPanel() {
// ํจ์ค ๋ฒํผ
this.passButton = this.scene.add.text(
590, 250, 'ํจ์ค',
{ fontSize: '16px', fill: '#333', backgroundColor: '#DDD' }
);
this.passButton.setPadding(10, 5);
this.passButton.setInteractive();
this.passButton.on('pointerdown', () => {
this.onPass();
});
// ํญ๋ณต ๋ฒํผ
this.surrenderButton = this.scene.add.text(
590, 300, 'ํญ๋ณต',
{ fontSize: '16px', fill: '#FFF', backgroundColor: '#D32F2F' }
);
this.surrenderButton.setPadding(10, 5);
this.surrenderButton.setInteractive();
this.surrenderButton.on('pointerdown', () => {
this.onSurrender();
});
}
**์ค๋ช
:**
* ''setPadding()'' - ํ
์คํธ ์ฃผ๋ณ ์ฌ๋ฐฑ ์ค์
* ''setInteractive()'' - ํด๋ฆญ ๊ฐ๋ฅํ๊ฒ ์ค์
* ''on('pointerdown')'' - ํด๋ฆญ ์ด๋ฒคํธ ์ฒ๋ฆฌ
----
===== 7. ๋ฐ์ํ ๋์์ธ =====
==== 1) ํ๋ฉด ํฌ๊ธฐ ๊ฐ์ง ====
handleResize() {
const width = this.scene.game.config.width;
const height = this.scene.game.config.height;
// ํ๋ฉด ํฌ๊ธฐ์ ๋ฐ๋ผ UI ์กฐ์
if (width < 768) {
// ๋ชจ๋ฐ์ผ ๋ ์ด์์
this.adjustForMobile();
} else {
// ๋ฐ์คํฌํฑ ๋ ์ด์์
this.adjustForDesktop();
}
}
adjustForMobile() {
// ๋ฐ๋ํ ํฌ๊ธฐ ์ถ์
this.board.setScale(0.8);
// UI ํจ๋ ์์น ์กฐ์
this.infoPanel.setPosition(300, 50);
this.controlPanel.setPosition(300, 200);
}
**์ค๋ช
:**
* ''game.config.width/height'' - ๊ฒ์ ํ๋ฉด ํฌ๊ธฐ
* ''setScale()'' - ํฌ๊ธฐ ์กฐ์ (0.8 = 80% ํฌ๊ธฐ)
* ''setPosition()'' - ์์น ๋ณ๊ฒฝ
==== 2) ํฐ์น ์ธํฐํ์ด์ค ====
setupTouchInterface() {
// ํฐ์น ์ด๋ฒคํธ ์ค์
this.scene.input.on('pointerdown', (pointer) => {
const x = Math.floor((pointer.x - this.boardStartX) / 40);
const y = Math.floor((pointer.y - this.boardStartY) / 40);
if (x >= 0 && x < 19 && y >= 0 && y < 19) {
this.onBoardClick(x, y);
}
});
}
**์ค๋ช
:**
* ''input.on('pointerdown')'' - ํฐ์น/ํด๋ฆญ ์ด๋ฒคํธ
* ''pointer.x/y'' - ํฐ์นํ ์์น
* ์ขํ ๊ณ์ฐ์ผ๋ก ๋ฐ๋ํ ์์น ํ์ธ
----
===== 8. ์ ๋๋ฉ์ด์
ํจ๊ณผ =====
==== 1) ๋ ๋๊ธฐ ์ ๋๋ฉ์ด์
====
animateStonePlacement(x, y, color) {
const stone = this.createStone(x, y, color);
// ์ฒ์์๋ ์๊ฒ ์์
stone.setScale(0.1);
// ์ ๋๋ฉ์ด์
์ผ๋ก ํฌ๊ธฐ ์กฐ์
this.scene.tweens.add({
targets: stone,
scaleX: 1,
scaleY: 1,
duration: 300,
ease: 'Back.easeOut'
});
}
**์ค๋ช
:**
* ''setScale(0.1)'' - ์ฒ์์๋ 10% ํฌ๊ธฐ
* ''tweens.add()'' - ์ ๋๋ฉ์ด์
์ถ๊ฐ
* ''duration: 300'' - 300ms ๋์ ์ ๋๋ฉ์ด์
* ''ease: 'Back.easeOut''' - ํ์ฑ ํจ๊ณผ
==== 2) ๋ ์ ๊ฑฐ ์ ๋๋ฉ์ด์
====
animateStoneRemoval(stone) {
this.scene.tweens.add({
targets: stone,
scaleX: 0,
scaleY: 0,
alpha: 0,
duration: 200,
onComplete: () => {
stone.destroy();
}
});
}
**์ค๋ช
:**
* ''alpha: 0'' - ํฌ๋ช
๋ 0์ผ๋ก ์ค์
* ''onComplete'' - ์ ๋๋ฉ์ด์
์๋ฃ ํ ์คํ
* ''stone.destroy()'' - ๋ ๊ฐ์ฒด ์ ๊ฑฐ
----
===== 9. ์ ๊ทผ์ฑ ๊ธฐ๋ฅ =====
==== 1) ๊ณ ๋๋น ๋ชจ๋ ====
enableHighContrast() {
// ๊ณ ๋๋น ์์ ์ค์
this.boardBackground.setFillStyle(0x000000);
this.gridLines.setStrokeStyle(1, 0xFFFFFF);
// ํ
์คํธ ์์ ์กฐ์
this.gameInfoText.setColor('#FFFFFF');
this.currentPlayerText.setColor('#FFFFFF');
}
**์ค๋ช
:**
* ''setFillStyle()'' - ๋ฐฐ๊ฒฝ์ ์ค์
* ''setStrokeStyle()'' - ์ ์์ ์ค์
* ''setColor()'' - ํ
์คํธ ์์ ์ค์
==== 2) ํค๋ณด๋ ๋ค๋น๊ฒ์ด์
====
setupKeyboardNavigation() {
this.scene.input.keyboard.on('keydown', (event) => {
switch(event.key) {
case 'ArrowUp':
this.moveSelection(0, -1);
break;
case 'ArrowDown':
this.moveSelection(0, 1);
break;
case 'ArrowLeft':
this.moveSelection(-1, 0);
break;
case 'ArrowRight':
this.moveSelection(1, 0);
break;
case 'Enter':
this.placeStone();
break;
}
});
}
**์ค๋ช
:**
* ''input.keyboard.on('keydown')'' - ํค๋ณด๋ ์ด๋ฒคํธ
* ๋ฐฉํฅํค๋ก ์ ํ ์์น ์ด๋
* Enter ํค๋ก ๋ ๋๊ธฐ
----
===== 10. ์ค์ต ์์ =====
==== 1) ๊ฐ๋จํ ๋ฐ๋ํ UI ====
**HTML ํ์ผ:**
๋ฐ๋ ๊ฒ์
**JavaScript ํ์ผ (''game.js''):**
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'game',
scene: {
create: create
}
};
const game = new Phaser.Game(config);
function create() {
// ๋ฐ๋ํ ๋ฐฐ๊ฒฝ
const background = this.add.rectangle(400, 300, 720, 720, 0xD2B48C);
// ๊ฒฉ์ ๊ทธ๋ฆฌ๊ธฐ
const graphics = this.add.graphics();
graphics.lineStyle(1, 0x8B4513, 1);
for (let i = 0; i < 19; i++) {
const x = 40 + i * 40;
const y = 40 + i * 40;
// ์ธ๋ก์
graphics.moveTo(x, 40);
graphics.lineTo(x, 760);
// ๊ฐ๋ก์
graphics.moveTo(40, y);
graphics.lineTo(760, y);
}
graphics.strokePaths();
// ๋ณ์ ๊ทธ๋ฆฌ๊ธฐ
const starPoints = [[3,3], [3,9], [3,15], [9,3], [9,9], [9,15], [15,3], [15,9], [15,15]];
graphics.fillStyle(0x8B4513, 1);
starPoints.forEach(([x, y]) => {
graphics.fillCircle(40 + x * 40, 40 + y * 40, 3);
});
}
==== 2) ์คํ ๋ฐฉ๋ฒ ====
# ๋ก์ปฌ ์๋ฒ ์คํ
python -m http.server 8000
# ๋๋
npx http-server
**ํ
์คํธ:**
* ๋ธ๋ผ์ฐ์ ์์ ''http://localhost:8000'' ์ ์
* ๋ฐ๋ํ์ด ํ์๋๋์ง ํ์ธ
----
===== 11. ๋ค์ ๋จ๊ณ =====
''UI/UX'' ๊ธฐ๋ณธ์ ๋ฐฐ์ ๋ค๋ฉด ๋ค์์ ํ์ตํด๋ณด์ธ์:
* **๊ณ ๊ธ ์ ๋๋ฉ์ด์
** - ๋ณต์กํ ์ ๋๋ฉ์ด์
ํจ๊ณผ
* **์ฌ์ด๋ ๋์์ธ** - ํจ๊ณผ์๊ณผ ๋ฐฐ๊ฒฝ์์
* **๊ฒ์ ์ํ ๊ด๋ฆฌ** - UI ์ํ ๊ด๋ฆฌ
* **์ฌ์ฉ์ ํ
์คํธ** - ์ค์ ์ฌ์ฉ์ ํผ๋๋ฐฑ
* **์ฑ๋ฅ ์ต์ ํ** - UI ๋ ๋๋ง ์ต์ ํ
**์ถ์ฒ ํ์ต ์์:**
- [[wiki:it:dream_of_enc:metaverse:phaser|๐ฎ Phaser.js ๊ฒ์ ์์ง]]
- [[wiki:it:dream_of_enc:metaverse:game_logic|๐ง ๋ฐ๋ ๊ฒ์ ๋ก์ง]]
- [[wiki:it:dream_of_enc:metaverse:socketio|๐ Socket.IO ์ค์๊ฐ ํต์ ]]
---
**์นดํ
๊ณ ๋ฆฌ:** [[wiki:it:dream_of_enc:metaverse:start|๋ฉํ๋ฒ์ค]] | **๊ด๋ จ ๊ธฐ์ :** Phaser.js, UI/UX, ๋ฐ์ํ ๋์์ธ