arena/CONTEXT.md

8.5 KiB

Context: Arena Picker 개발 가이드

1. 모듈별 상세 역할

[Core Engine]

  • src/main.js: Phaser 게임의 전역 설정(Physics, Scale, Canvas Parent)을 담당하며, ArenaScene을 인스턴스화합니다.
    • 앱 로드 시 trackVisitor()를 호출해 방문자 체크 API와 연동합니다.
  • src/constants.js: 게임 내 모든 튜닝 수치를 관리합니다.
    • ATTACK_DAMAGE_MIN, ATTACK_DAMAGE_MAX: 일반 공격 1회 적중 시 적용되는 랜덤 피해량 범위.
    • FIGHTER_HITBOX_*: 100x100 캐릭터 프레임 안에서 실제 충돌 판정이 놓이는 위치와 크기.
    • KILL_HEALTH_RECOVERY_RATIO, KILL_GROWTH_MULTIPLIER: 처치 후 회복량과 크기/공격속도/이동속도 성장 배율.
    • SELECTED_FIGHTER_OUTLINE_GAP, SELECTED_FIGHTER_OUTLINE_WIDTH, SELECTED_FIGHTER_OUTLINE_ALPHA: 선택 실루엣의 캐릭터 이격 거리, 두께, 투명도.
    • SPECTATOR_CAMERA_LERP: 카메라 추적의 부드러움 정도.
    • MINIMAP_VIEWPORT_SIZE: 미니맵의 고정 픽셀 크기.
    • ARENA_SIZE: 경기장 전체 크기 (GRID * TILE).

[Server/API - server/]

  • server/index.js:
    • Fastify 서버 진입점입니다.
    • 개발 모드에서는 @fastify/middie로 Vite 미들웨어를 붙이고, /api/* 요청은 Vite SPA fallback이 가로채지 않도록 Fastify 라우트로 통과시킵니다.
    • 운영 모드(npm start)에서는 @fastify/static으로 dist/를 서빙하고, HTML 요청은 index.html로 fallback합니다.
  • server/config.js:
    • config.json을 읽어 서버 포트, MongoDB host/port/db/user/pass, 쿠키 보안 옵션을 정규화합니다.
    • MONGODB_URI가 직접 있으면 우선 사용하고, 없으면 MONGODB_HOST/MONGODB_PORT 기반으로 URI를 조립합니다.
  • server/db.js:
    • MongoClient를 한 번 생성한 뒤 재사용하여 MongoDB 커넥션 풀을 유지합니다.
    • 종료 시 closeMongoConnection()으로 커넥션을 닫습니다.
  • server/visitors.js:
    • POST /api/visitors/check: arena_visitor_id HttpOnly 쿠키를 확인하고 없으면 UUID를 발급합니다.
    • MongoDB visitors 컬렉션에 _id = visitorId로 upsert해 방문자 1명당 1개 문서를 유지합니다.
    • GET /api/visitors/stats: 전체 유니크 방문자 수를 반환합니다.

[Game Logic - src/game/]

  • ArenaScene.js:
    • update(): 매 프레임 생존 팀을 체크하고 스코어보드를 갱신합니다.
    • observeCombat(): 캐릭터가 공격할 때 카메라가 주목할 "관전 대상"을 설정합니다.
    • selectFighter(), focusSelectedFighter(): 캐릭터 클릭 시 선택 상태를 설정하고 해당 캐릭터의 히트박스 중심으로 카메라를 고정합니다.
    • updateMinimapViewportFrame(): 주 카메라의 이동에 맞춰 미니맵 가이드 사각형을 렌더링합니다.
  • matchSetup.js:
    • 입력된 닉네임을 순회하여 team 객체를 생성하고, 요청된 인원만큼 캐릭터 데이터를 복제 배치합니다.
  • combat.js:
    • updateFighter(): 가장 가까운 적을 찾아 이동하거나 공격하는 유닛 AI의 핵심입니다.
    • applyHit(): 일반 공격 피해량은 ATTACK_DAMAGE_MIN/MAX 범위에서 계산합니다.
    • applyKillReward(): 처치한 캐릭터의 체력 회복, 크기 증가, 공격속도/이동속도 배율 증가를 처리합니다.
    • projectilePathHitsDefender(): 투사체가 대상을 스쳐 지나가지 않도록 궤적 검사를 수행합니다.

[Assets & UI]

  • fighterAssets.js: 원본 캐릭터 스프라이트의 alpha 값을 읽어 선택용 노란 실루엣 spritesheet를 런타임에 생성합니다. 원본 주변 1px은 비워두고 그 바깥 1px만 칠해 선택 윤곽이 캐릭터에 붙어 보이지 않도록 합니다.
  • fighterFactory.js: 캐릭터 히트박스, 이름표, 체력바, 선택 실루엣 sprite를 생성하고 매 프레임 위치/스케일/방향을 동기화합니다. 이름표는 스프라이트 중심이 아니라 실제 히트박스 하단에 고정됩니다.
  • fighterManifest.js: 20여 종의 캐릭터 스킨 정보가 담긴 딕셔너리입니다. type (melee/projectile/instant-spell)에 따라 전투 메커니즘이 결정됩니다.
  • matchForm.js: index.html의 입력을 읽어 ArenaScene에 매치 구성을 전달합니다.
  • visitorCounter.js: POST /api/visitors/check를 호출하고, 응답의 uniqueVisitors 값을 #visitor-count에 표시합니다.

2. 주요 로직 구현 세부 사항

지능형 카메라 추적 (Lerp & Jittering 방지)

카메라가 소수점 단위의 평균 좌표를 즉시 따라가면 화면이 떨려 보일 수 있습니다. 이를 방지하기 위해:

  1. 목표 좌표(targetX, targetY)를 Math.round()로 정수화합니다.
  2. 현재 카메라 위치에서 목표 지점까지 매 프레임 0.1의 배율로 거리를 좁혀나가는 Lerp 연산을 수행합니다.
this.cameras.main.scrollX += (targetX - this.cameras.main.midPoint.x) * SPECTATOR_CAMERA_LERP;

미니맵 가이드라인

미니맵은 전장 전체를 축소하여 보여주는 독립된 카메라입니다. 주 카메라가 비추는 영역을 계산하여 미니맵 위에 사각형(graphics)을 그려줍니다.

  • camera.displayWidth / zoom 등을 이용하여 현재 월드에서 보이는 실제 영역 크기를 계산합니다.

캐릭터 선택 실루엣

선택 표시는 히트박스 사각형이 아니라 캐릭터 모양을 따라가는 별도 spritesheet입니다.

  1. fighterAssets.js가 로드된 원본 스프라이트시트의 alpha 데이터를 캔버스에서 읽습니다.
  2. 원본 alpha 픽셀 주변 SELECTED_FIGHTER_OUTLINE_GAP 범위는 공백 마스크로 남깁니다.
  3. 그 바깥 SELECTED_FIGHTER_OUTLINE_WIDTH 범위에만 노란색 outline을 칠합니다.
  4. fighterFactory.js가 선택된 캐릭터 뒤에 outline sprite를 배치하고, 현재 texture frame, flip 방향, scale, 위치를 원본 캐릭터와 동기화합니다.

이 방식은 별도 에셋 없이도 캐릭터 모양에 맞춘 선택 윤곽을 만들 수 있으며, 캐릭터가 처치 보상으로 커져도 윤곽이 같은 배율로 따라갑니다.

유니크 방문자 체크

브라우저가 직접 MongoDB에 연결하지 않고, Fastify API가 MongoDB 커넥션 풀을 유지합니다.

  1. 프론트엔드가 앱 로드 시 POST /api/visitors/check를 호출합니다.
  2. 서버가 arena_visitor_id 쿠키를 검사합니다.
  3. 쿠키가 없거나 유효하지 않으면 crypto.randomUUID()로 새 방문자 ID를 만들고 HttpOnly 쿠키로 내려줍니다.
  4. MongoDB에는 _id, firstSeenAt, lastSeenAt, visits, firstUserAgent, lastUserAgent를 저장합니다.
  5. countDocuments()로 전체 유니크 방문자 수를 계산해 반환합니다.

방문자 체크는 인증 기능이 아니며, 브라우저/쿠키 단위의 단순 유니크 카운트입니다.

3. 개발 및 유지보수 규칙

  • 신규 캐릭터 추가: public/assets/characters/에 에셋 배치 후 fighterManifest.js에 정의를 추가하면 즉시 게임에 반영됩니다.
  • 물리 수치 조정: 캐릭터의 속도나 사거리 등은 src/constants.js 또는 fighterManifest.js 내 개별 설정을 통해 변경하십시오.
  • 공격력 조정: 기본 피해량은 src/constants.jsATTACK_DAMAGE_MIN, ATTACK_DAMAGE_MAX를 수정합니다. 캐릭터별 특수 공격 방식은 fighterManifest.jscombat 설정을 우선 확인합니다.
  • DOM 접근: 성능을 위해 ArenaScene은 상단 스코어보드 등 필요한 시점에만 최소한으로 DOM에 접근합니다.
  • 서버 설정: .env 대신 config.json을 사용합니다. config.json은 로컬 전용 파일이며, 저장소에는 config.json.sample만 공유합니다.
  • 패키지 락 파일: 이 프로젝트는 package-lock.json을 저장소에서 제외합니다. 의존성 변경 시 package.json을 기준으로 관리합니다.
  • 기본 포트: SERVER_PORT 기본값은 9736입니다.
  • MongoDB 연결: DB 접속 정보는 config.jsonMONGODB_HOST, MONGODB_PORT, MONGODB_DB, 선택적 MONGODB_USER, MONGODB_PASS로 관리합니다.
  • API 변경: /api/* 경로는 Fastify 라우트가 담당합니다. 개발 모드에서 Vite 미들웨어가 API 요청을 SPA HTML로 처리하지 않도록 서버 라우팅 순서를 유지해야 합니다.