Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions shared/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export const GAME_CONSTANTS = {
dt: 1 / tickRate, // 각 틱의 시간 (초 단위)

// Worm
HEAD_SPEED: 350, // 머리가 마우스를 쫓는 속도(px/s)
HEAD_SPRINT_SPEED: 550, // 달리기 속도
HEAD_SPEED: 500, // 머리가 마우스를 쫓는 속도(px/s)
HEAD_SPRINT_SPEED: 750, // 달리기 속도
SPRINT_FOOD_DROP_INTERVAL: 500, // 달리기 중 먹이 방출 간격 (ms)
TURN_RATE: 5.0, // 지렁이 회전 속도 (라디안/초)

Expand Down
Binary file removed web-io-game/public/background.jpeg
Binary file not shown.
192 changes: 148 additions & 44 deletions web-io-game/public/loginform.html
Original file line number Diff line number Diff line change
@@ -1,49 +1,153 @@
<div
style="
background-color: rgba(0, 0, 0, 0.5);
padding: 40px;
border-radius: 10px;
<style>
.lobby-shell {
width: min(92vw, 520px);
padding: 34px 32px 30px;
border-radius: 24px;
border: 1px solid rgba(78, 127, 198, 0.35);
background: rgba(0, 15, 40, 0.88);
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.55),
0 0 30px rgba(0, 255, 136, 0.1);
backdrop-filter: blur(8px);
font-family: "Trebuchet MS", Arial, sans-serif;
color: #d8e9ff;
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
"
>
<h1 style="color: white; font-size: 32px; margin: 0 0 10px 0">BeyondWorm</h1>
<input
type="text"
id="username-input"
placeholder="Enter your ID"
style="padding: 10px; font-size: 16px; width: 250px; border-radius: 5px; border: none"
/>

<div id="server-list-container" style="width: 300px; color: white">
<h2 style="font-size: 20px; margin-bottom: 10px">Server List</h2>
<ul
id="server-list"
style="
list-style: none;
padding: 0;
height: 150px;
overflow-y: auto;
border: 1px solid #555;
border-radius: 5px;
"
></ul>
gap: 18px;
}

.lobby-title {
margin: 0;
font-size: clamp(34px, 6vw, 54px);
font-weight: 800;
letter-spacing: 0.06em;
text-align: center;
color: #00ff88;
text-shadow: 0 0 26px rgba(0, 255, 136, 0.38);
}

.lobby-subtitle {
margin: -6px 0 8px;
text-align: center;
font-size: 12px;
letter-spacing: 0.22em;
color: rgba(132, 180, 255, 0.8);
}

.lobby-label {
margin: 0 0 8px;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.16em;
color: rgba(132, 180, 255, 0.85);
}

#username-input {
width: 100%;
box-sizing: border-box;
padding: 12px 14px;
border: 1px solid rgba(78, 127, 198, 0.45);
border-radius: 12px;
outline: none;
font-size: 16px;
color: #e8f3ff;
background: rgba(7, 21, 42, 0.95);
}

#username-input:focus {
border-color: rgba(0, 255, 136, 0.75);
box-shadow: 0 0 0 2px rgba(0, 255, 136, 0.16);
}

#server-list-container {
margin-top: 2px;
}

#server-list {
margin: 0;
padding: 8px;
list-style: none;
max-height: 182px;
overflow-y: auto;
border-radius: 14px;
border: 1px solid rgba(78, 127, 198, 0.4);
background: rgba(7, 21, 42, 0.72);
}

.server-item {
padding: 11px 12px;
border-radius: 10px;
border: 1px solid transparent;
color: #c7dcff;
font-size: 14px;
cursor: pointer;
transition: 120ms ease;
}

.server-item + .server-item {
margin-top: 6px;
}

.server-item:hover {
background: rgba(78, 127, 198, 0.18);
border-color: rgba(78, 127, 198, 0.42);
}

.server-item.selected {
background: rgba(0, 255, 136, 0.16);
border-color: rgba(0, 255, 136, 0.62);
color: #00ff88;
}

.server-empty {
padding: 10px 12px;
color: #8ea8cf;
font-size: 14px;
}

.server-error {
color: #ff7f93;
}

#start-button {
margin-top: 8px;
width: 100%;
padding: 14px 16px;
border-radius: 14px;
border: 2px solid rgba(0, 255, 136, 0.58);
background: rgba(0, 255, 136, 0.14);
color: #00ff88;
font-size: 17px;
font-weight: 800;
letter-spacing: 0.1em;
cursor: pointer;
transition: 130ms ease;
}

#start-button:hover {
transform: translateY(-1px);
background: rgba(0, 255, 136, 0.22);
box-shadow: 0 10px 26px rgba(0, 255, 136, 0.16);
}

#start-button:active {
transform: translateY(0);
}
</style>

<div class="lobby-shell">
<h1 class="lobby-title">BEYOND WORM</h1>
<p class="lobby-subtitle">EAT . GROW . SURVIVE</p>

<div>
<p class="lobby-label">플레이어 닉네임</p>
<input type="text" id="username-input" placeholder="아이디를 입력하세요" />
</div>

<div id="server-list-container">
<p class="lobby-label">서버 목록</p>
<ul id="server-list"></ul>
</div>

<button
id="start-button"
style="
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
"
>
Start Game
</button>
<button id="start-button">PLAY</button>
</div>
161 changes: 123 additions & 38 deletions web-io-game/src/DeathScene.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,141 @@
import Phaser from "phaser";

export default class DeathScene extends Phaser.Scene {
private rootContainer?: Phaser.GameObjects.Container;

constructor() {
super({ key: "DeathScene" });
}

create() {
// 반투명 배경 오버레이
const overlay = this.add.rectangle(0, 0, this.cameras.main.width, this.cameras.main.height, 0x000000, 0.7);
overlay.setOrigin(0, 0);
overlay.setScrollFactor(0); // 카메라 이동에 영향받지 않음

// 중앙에 위치할 컨테이너
const centerX = this.cameras.main.width / 2;
const centerY = this.cameras.main.height / 2;

// 죽음 메시지
const deathText = this.add.text(centerX, centerY - 50, "You Died!", {
fontSize: "48px",
color: "#ff0000",
fontStyle: "bold",
});
deathText.setOrigin(0.5);
deathText.setScrollFactor(0);

// 로비로 돌아가기 버튼
const backButton = this.add.text(centerX, centerY + 20, "Back to Lobby", {
fontSize: "24px",
color: "#ffffff",
backgroundColor: "#333333",
padding: { x: 20, y: 10 },
create(data: { score?: number; bestScore?: number }) {
this.cameras.main.once(Phaser.Cameras.Scene2D.Events.FADE_OUT_COMPLETE, () => {
location.reload();
});
backButton.setOrigin(0.5);
backButton.setScrollFactor(0);
backButton.setInteractive({ useHandCursor: true });

// 버튼 호버 효과
backButton.on("pointerover", () => {
backButton.setStyle({ backgroundColor: "#555555" });
this.renderUI(data);

this.scale.on("resize", (gameSize: Phaser.Structs.Size) => {
this.rootContainer?.destroy(true);
this.renderUI(data, gameSize.width, gameSize.height);
});
}

private renderUI(
data: { score?: number; bestScore?: number },
width: number = this.cameras.main.width,
height: number = this.cameras.main.height,
) {
const centerX = width / 2;
const centerY = height / 2;

const overlay = this.add.rectangle(0, 0, width, height, 0x000913, 0.78).setOrigin(0, 0).setScrollFactor(0);

const panelGfx = this.add.graphics();
panelGfx.fillStyle(0x000f28, 0.9);
panelGfx.lineStyle(2, 0xff3350, 0.35);
panelGfx.fillRoundedRect(-220, -190, 440, 380, 26);
panelGfx.strokeRoundedRect(-220, -190, 440, 380, 26);

const gameOverText = this.add
.text(0, -130, "GAME OVER", {
fontFamily: "Trebuchet MS, Arial, sans-serif",
fontSize: "52px",
color: "#ff3350",
fontStyle: "bold",
})
.setOrigin(0.5)
.setStroke("#2a0f18", 6);

backButton.on("pointerout", () => {
backButton.setStyle({ backgroundColor: "#333333" });
const scoreLabel = this.add
.text(0, -54, "FINAL SCORE", {
fontFamily: "Trebuchet MS, Arial, sans-serif",
fontSize: "16px",
color: "#84b4ff",
fontStyle: "bold",
})
.setOrigin(0.5);

const scoreValue = this.add
.text(0, -10, `${data.score ?? 0}`, {
fontFamily: "Trebuchet MS, Arial, sans-serif",
fontSize: "64px",
color: "#00ff88",
fontStyle: "bold",
})
.setOrigin(0.5)
.setStroke("#0a1324", 6);

const bestLabel = this.add
.text(0, 46, "BEST", {
fontFamily: "Trebuchet MS, Arial, sans-serif",
fontSize: "14px",
color: "#ffd166",
fontStyle: "bold",
})
.setOrigin(0.5);

const bestValue = this.add
.text(0, 72, `${data.bestScore ?? data.score ?? 0}`, {
fontFamily: "Trebuchet MS, Arial, sans-serif",
fontSize: "30px",
color: "#ffd166",
fontStyle: "bold",
})
.setOrigin(0.5)
.setStroke("#2d220f", 4);

const buttonGfx = this.add.graphics();
buttonGfx.fillStyle(0x00ff88, 0.17);
buttonGfx.lineStyle(2, 0x00ff88, 0.55);
buttonGfx.fillRoundedRect(-110, 108, 220, 58, 18);
buttonGfx.strokeRoundedRect(-110, 108, 220, 58, 18);

const playAgainText = this.add
.text(0, 136, "PLAY AGAIN", {
fontFamily: "Trebuchet MS, Arial, sans-serif",
fontSize: "24px",
color: "#00ff88",
fontStyle: "bold",
})
.setOrigin(0.5);

const buttonHitArea = this.add
.rectangle(0, 136, 220, 58, 0x000000, 0.001)
.setInteractive({ useHandCursor: true });

buttonHitArea.on("pointerover", () => {
buttonGfx.clear();
buttonGfx.fillStyle(0x00ff88, 0.24);
buttonGfx.lineStyle(2, 0x00ff88, 0.8);
buttonGfx.fillRoundedRect(-110, 108, 220, 58, 18);
buttonGfx.strokeRoundedRect(-110, 108, 220, 58, 18);
});

// 버튼 클릭 시 로비로 이동
backButton.on("pointerdown", () => {
this.cameras.main.fadeOut(300, 0, 0, 0);
buttonHitArea.on("pointerout", () => {
buttonGfx.clear();
buttonGfx.fillStyle(0x00ff88, 0.17);
buttonGfx.lineStyle(2, 0x00ff88, 0.55);
buttonGfx.fillRoundedRect(-110, 108, 220, 58, 18);
buttonGfx.strokeRoundedRect(-110, 108, 220, 58, 18);
});

this.cameras.main.on(Phaser.Cameras.Scene2D.Events.FADE_OUT_COMPLETE, () => {
location.reload();
buttonHitArea.on("pointerdown", () => {
this.cameras.main.fadeOut(220, 0, 0, 0);
});

this.rootContainer = this.add.container(centerX, centerY, [
panelGfx,
gameOverText,
scoreLabel,
scoreValue,
bestLabel,
bestValue,
buttonGfx,
playAgainText,
buttonHitArea,
]);
this.rootContainer.setScrollFactor(0);
this.rootContainer.setDepth(11000);
overlay.setDepth(10999);
}
}
Loading