import Phaser from "phaser"; import { drawArena } from "./arenaRenderer.js"; import { clearCombatObjects, updateFighter } from "./combat.js"; import { ARENA_SIZE } from "./config.js"; import { createFighterAnimations, preloadFighterSheets } from "./fighterAssets.js"; import { createFighter, syncFighterHud } from "./fighterFactory.js"; import { fighterManifest } from "./fighterManifest.js"; import { pickFighters } from "./fighterSelection.js"; import { createMatchSetup, matchStatusText } from "./matchSetup.js"; export class ArenaScene extends Phaser.Scene { constructor({ getInitialMatchConfig, setStatus }) { super("arena"); this.fighters = []; this.getInitialMatchConfig = getInitialMatchConfig; this.matchId = 0; this.matchOver = false; this.ready = false; this.setStatus = (message) => { // 기존 배너 제거 const oldBanner = document.querySelector(".victory-banner"); if (oldBanner) oldBanner.remove(); // 승리 또는 무승부 메시지인 경우 전용 배너 생성 if (message.includes("승리") || message.includes("무승부")) { const banner = document.createElement("div"); banner.className = "victory-banner"; banner.textContent = message; document.querySelector(".arena-shell").appendChild(banner); } }; this.teams = []; } preload() { preloadFighterSheets(this, fighterManifest); } create() { this.physics.world.setBounds(0, 0, ARENA_SIZE, ARENA_SIZE); this.cameras.main.setBounds(0, 0, ARENA_SIZE, ARENA_SIZE); this.cameras.main.setBackgroundColor("#282819"); drawArena(this); createFighterAnimations(this, fighterManifest); // 미니맵 카메라 설정 this.minimapCamera = this.cameras.add(10, 10, 150, 150).setZoom(150 / ARENA_SIZE).setName('minimap'); this.minimapCamera.setBackgroundColor(0x000000); this.minimapCamera.scrollX = 0; this.minimapCamera.scrollY = 0; this.minimapCamera.setAlpha(0); // 기본적으로는 숨김 // 마우스 휠로 줌 조절 this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => { const zoomStep = 0.1; const newZoom = Phaser.Math.Clamp(this.cameras.main.zoom + (deltaY > 0 ? -zoomStep : zoomStep), 1, 3); this.cameras.main.setZoom(newZoom); // 확대 시 미니맵 표시 this.minimapCamera.setAlpha(newZoom > 1 ? 0.8 : 0); }); this.ready = true; this.startMatch(this.getInitialMatchConfig()); } startMatch({ names = [], teamSize } = {}) { if (!this.ready) { return; } if (names.length < 2) { this.setStatus("참가자 닉네임을 2명 이상 입력하세요."); return; } const matchSetup = createMatchSetup(names, teamSize); const matchSkins = pickFighters(fighterManifest, matchSetup.fighters.length); this.matchId += 1; this.matchOver = false; clearCombatObjects(this); this.fighters.forEach((fighter) => fighter.destroy()); this.teams = matchSetup.teams; this.fighters = matchSetup.fighters.map((fighterSetup, index) => createFighter(this, { ...fighterSetup, skin: matchSkins[index], }), ); this.setStatus(matchStatusText(this.teams)); this.updateScoreboard(); } update(time) { this.fighters.forEach(syncFighterHud); if (this.matchOver) { return; } // 확대 상태일 때 생존 캐릭터들의 중앙으로 카메라 이동 if (this.cameras.main.zoom > 1) { const aliveFighters = this.fighters.filter(f => !f.isDead); if (aliveFighters.length > 0) { const avgX = aliveFighters.reduce((sum, f) => sum + f.x, 0) / aliveFighters.length; const avgY = aliveFighters.reduce((sum, f) => sum + f.y, 0) / aliveFighters.length; // 소수점 단위 변동으로 인한 지터링 방지를 위해 반올림 처리 및 부드러운 이동(Lerp) 적용 const targetX = Math.round(avgX); const targetY = Math.round(avgY); // 현재 카메라 위치에서 목표 위치로 서서히 이동 (0.1은 따라가는 속도) this.cameras.main.scrollX += (targetX - this.cameras.main.centerX) * 0.1; this.cameras.main.scrollY += (targetY - this.cameras.main.centerY) * 0.1; } } else { // 줌이 1일 때는 경기장 중앙에 고정 this.cameras.main.centerOn(ARENA_SIZE / 2, ARENA_SIZE / 2); } this.fighters.forEach((fighter) => { updateFighter(this, fighter, time, () => { this.updateScoreboard(); this.finishMatch(); }); }); } updateScoreboard() { const scoreLeft = document.getElementById("score-left"); const scoreRight = document.getElementById("score-right"); if (!scoreLeft || !scoreRight) return; scoreLeft.innerHTML = ""; scoreRight.innerHTML = ""; this.teams.forEach((team, index) => { const aliveCount = this.fighters.filter( (f) => f.team.id === team.id && !f.isDead ).length; const teamEl = document.createElement("div"); teamEl.className = "team-score"; teamEl.style.backgroundColor = `${team.color}44`; // 44 is alpha for 26% teamEl.style.borderLeft = `4px solid ${team.color}`; teamEl.innerHTML = `${team.label} ${aliveCount}`; if (index % 2 === 0) { scoreLeft.appendChild(teamEl); } else { scoreRight.appendChild(teamEl); } }); } finishMatch() { const livingFighters = this.fighters.filter((fighter) => !fighter.isDead); const livingTeams = new Set(livingFighters.map((fighter) => fighter.team.id)); if (livingTeams.size > 1) { return; } this.matchOver = true; clearCombatObjects(this); this.fighters.forEach((fighter) => { if (fighter.body) { fighter.body.setVelocity(0, 0); } }); if (livingTeams.size === 1) { const winningTeamId = Array.from(livingTeams)[0]; const winningTeam = this.teams.find((team) => team.id === winningTeamId); this.setStatus(`${winningTeam?.label ?? "Unknown"} 승리!`); } else { this.setStatus("무승부!"); } } }