// 경기장을 구성하는 격자 칸 수입니다. 값이 커질수록 전장이 넓어집니다. export const GRID_SIZE = 50; // 격자 한 칸의 픽셀 크기입니다. 경기장 크기와 좌표 간격에 영향을 줍니다. export const TILE_SIZE = 64; // 실제 전장 전체 픽셀 크기입니다. GRID_SIZE와 TILE_SIZE를 기반으로 계산합니다. export const ARENA_SIZE = GRID_SIZE * TILE_SIZE; // 근접 캐릭터가 공격을 시작할 수 있는 기본 거리입니다. export const ATTACK_RANGE = 84; // 기본 공격 쿨다운(ms)입니다. 낮을수록 공격 빈도가 높아집니다. export const ATTACK_COOLDOWN = 840; // 공격이 한 번 적중했을 때 적용되는 최소 피해량입니다. export const ATTACK_DAMAGE_MIN = 14; // 공격이 한 번 적중했을 때 적용되는 최대 피해량입니다. export const ATTACK_DAMAGE_MAX = 24; // 새 매치가 시작될 때 기본 팀당 캐릭터 수입니다. export const DEFAULT_TEAM_SIZE = 5; // 전투 시작 시 전투원을 배치하는 기본 방식입니다. export const DEFAULT_SPAWN_PLACEMENT = "random"; // 전투 설정 UI와 매치 생성 로직이 공유하는 스폰 배치 모드입니다. export const SPAWN_PLACEMENTS = { RANDOM: DEFAULT_SPAWN_PLACEMENT, STARTING_ZONES: "starting-zones", }; // 최초 접속 대기 전투에서 고정으로 보여줄 팀 수입니다. export const PRESENTATION_TEAM_COUNT = 10; // 최초 접속 대기 전투에서 팀마다 배치할 전투원 수입니다. export const PRESENTATION_TEAM_SIZE = 5; // 캐릭터 스프라이트의 기본 화면 배율입니다. export const FIGHTER_SCALE = 3; export const FIGHTER_DEPTH = 2; export const DEAD_FIGHTER_DEPTH = 1; export const DEAD_FIGHTER_ALPHA = 0.42; // 캐릭터 스프라이트시트에서 한 프레임이 차지하는 원본 너비입니다. export const FIGHTER_FRAME_WIDTH = 100; // 캐릭터 스프라이트시트에서 한 프레임이 차지하는 원본 높이입니다. export const FIGHTER_FRAME_HEIGHT = 100; // 캐릭터 히트박스의 원본 프레임 기준 너비입니다. export const FIGHTER_HITBOX_WIDTH = 22; // 캐릭터 히트박스의 원본 프레임 기준 높이입니다. export const FIGHTER_HITBOX_HEIGHT = 20; // 100x100 프레임 안에서 히트박스가 시작되는 X 좌표입니다. export const FIGHTER_HITBOX_OFFSET_X = 39; // 100x100 프레임 안에서 히트박스가 시작되는 Y 좌표입니다. 실제 캐릭터 픽셀 하단은 대체로 y=59입니다. export const FIGHTER_HITBOX_OFFSET_Y = 40; // 캐릭터의 기본 최대 체력입니다. export const FIGHTER_MAX_HP = 100; // 적 처치 시 현재 체력 기준으로 회복되는 비율입니다. export const KILL_HEALTH_RECOVERY_RATIO = 0.3; // 처치 회복 이펙트 스프라이트시트의 프레임 수입니다. export const KILL_HEAL_EFFECT_FRAMES = 4; // 처치 회복 이펙트 애니메이션의 초당 프레임 수입니다. export const KILL_HEAL_EFFECT_FRAME_RATE = 12; // 적 처치 시 크기, 공격속도, 이동속도에 누적 적용되는 배율입니다. export const KILL_GROWTH_MULTIPLIER = 1.25; // 처치 보상으로 누적 적용되는 최대 배율입니다. 기본 scale에 곱해지는 상한이기도 합니다. export const KILL_GROWTH_MAX_MULTIPLIER = 5; // 처치 성장 연출 tween 지속 시간(ms)입니다. export const KILL_GROWTH_TWEEN_DURATION = 180; // 입력 UI에서 허용하는 팀당 최대 캐릭터 수입니다. export const MAX_TEAM_SIZE = 100; // 근접 캐릭터의 기본 치명타 확률입니다. 치명타는 즉시 처치로 처리됩니다. export const MELEE_CRITICAL_CHANCE = 0.05; // 캐릭터 기본 이동 속도입니다. 처치 보상과 전역 이동 배율이 곱해집니다. export const MOVE_SPEED = 148; // 투사체가 자동으로 사라지기까지의 시간(ms)입니다. export const PROJECTILE_LIFETIME = 1800; // 투사체 기본 이동 속도입니다. 처치 보상과 전역 공격 배율이 곱해집니다. export const PROJECTILE_SPEED = 420; // 원거리 캐릭터의 기본 치명타 확률입니다. export const RANGED_CRITICAL_CHANCE = 0; // 원거리 캐릭터가 공격을 시작할 수 있는 기본 거리입니다. export const RANGED_ATTACK_RANGE = TILE_SIZE * 5; // 근접 공격 애니메이션 시작 후 실제 피해가 들어가기까지의 지연(ms)입니다. export const MELEE_HIT_DELAY = 260; // 원거리 공격 애니메이션 시작 후 투사체가 발사되기까지의 지연(ms)입니다. export const PROJECTILE_FIRE_DELAY = 360; // 투사체 충돌 원형 바디가 이미지 안에서 시작되는 오프셋입니다. export const PROJECTILE_BODY_OFFSET = 4; // 투사체 궤적 충돌 검사 시 대상 히트박스에 더하는 여유 픽셀입니다. export const PROJECTILE_HIT_PADDING = 20; // 투사체 충돌 원형 바디의 반지름입니다. export const PROJECTILE_HIT_RADIUS = 12; // 투사체가 공격자 위치에서 얼마나 떨어져 생성되는지 정하는 거리입니다. export const PROJECTILE_SPAWN_DISTANCE = 1; // 즉발 마법 캐스팅 후 이펙트가 생성되기까지의 지연(ms)입니다. export const SPELL_CAST_DELAY = 340; // 마법 이펙트 생성 후 실제 피해가 들어가기까지의 지연(ms)입니다. export const SPELL_HIT_DELAY = 160; // 카메라 최소 줌입니다. 전장 전체를 보는 기본 배율입니다. export const CAMERA_MIN_ZOOM = 1; // 카메라 최대 줌입니다. 후반 관전 및 휠 확대의 상한입니다. export const CAMERA_MAX_ZOOM = 3; // 마우스 휠 한 번당 카메라 줌 변화량입니다. export const CAMERA_ZOOM_STEP = 0.1; // 미니맵 카메라가 보일 때의 투명도입니다. export const MINIMAP_ALPHA = 0.8; // 미니맵이 화면 가장자리에서 떨어지는 거리입니다. export const MINIMAP_MARGIN = Math.round(ARENA_SIZE * 0.016); // 미니맵의 고정 픽셀 크기입니다. export const MINIMAP_VIEWPORT_SIZE = Math.round(ARENA_SIZE * 0.22); // 미니맵 현재 뷰포트 표시용 선 두께입니다. export const MINIMAP_VIEW_FRAME_STROKE = 10; // 관전 카메라가 목표 전투 지점으로 따라가는 부드러움입니다. export const SPECTATOR_CAMERA_LERP = 0.1; // 생존자가 이 수보다 적으면 최종 전투 줌을 적용합니다. export const SPECTATOR_FINAL_FIGHTER_THRESHOLD = 5; // 최종 전투 구간에서 강제로 적용되는 카메라 줌입니다. export const SPECTATOR_FINAL_FIGHT_ZOOM = 3; export const SPECTATOR_FINAL_TEAM_COUNT = 2; export const SPECTATOR_FINAL_TEAM_TOTAL_THRESHOLD = 8; export const SPECTATOR_RANDOM_FOCUS_INTERVAL = 2400; // 최종교전 슬로우모션 연출을 켜고 끕니다. export const FINAL_COMBAT_SLOW_MOTION_ENABLED = false; // 최종교전 공격 시작에서 슬로우 배율로 내려가는 속도 램프 시간(ms)입니다. export const FINAL_COMBAT_SLOW_MOTION_ENTER_DURATION = 14000; // 최종교전 공격을 슬로우 배율로 붙잡아 두는 시간(ms)입니다. export const FINAL_COMBAT_SLOW_MOTION_HOLD_DURATION = 14000; // 최종교전 슬로우에서 기본 속도로 복귀하는 속도 램프 시간(ms)입니다. export const FINAL_COMBAT_SLOW_MOTION_EXIT_DURATION = 14000; export const FINAL_COMBAT_SLOW_MOTION_SCALE = 0.28; // 생존자가 이 수보다 적으면 후반 전투 줌을 적용합니다. export const SPECTATOR_LATE_FIGHTER_THRESHOLD = 30; // 후반 전투 구간에서 강제로 적용되는 카메라 줌입니다. export const SPECTATOR_LATE_FIGHT_ZOOM = 2; // 캐릭터를 선택했을 때 최소로 확보하는 카메라 줌입니다. export const SELECTED_FIGHTER_CAMERA_ZOOM = 2; // 선택 실루엣과 원본 캐릭터 사이에 비워두는 픽셀 간격입니다. export const SELECTED_FIGHTER_OUTLINE_GAP = 1; // 선택 실루엣 자체가 차지하는 픽셀 두께입니다. export const SELECTED_FIGHTER_OUTLINE_WIDTH = 1; // 선택 실루엣의 빨간색 채널 값입니다. export const SELECTED_FIGHTER_OUTLINE_RED = 255; // 선택 실루엣의 초록색 채널 값입니다. export const SELECTED_FIGHTER_OUTLINE_GREEN = 228; // 선택 실루엣의 파란색 채널 값입니다. export const SELECTED_FIGHTER_OUTLINE_BLUE = 64; // 선택 실루엣의 전체 투명도입니다. 0.65는 윤곽을 또렷하게 보이면서 원본 캐릭터를 덮지 않습니다. export const SELECTED_FIGHTER_OUTLINE_ALPHA = 0.65; // 참가자 닉네임을 잘라낼 최대 글자 수입니다. export const NICKNAME_LENGTH = 18; // 캐릭터 액션별 애니메이션 프레임 속도와 반복 횟수입니다. export const FIGHTER_ANIMATION_OPTIONS = { // 기본 공격 애니메이션 속도입니다. attack: { frameRate: 15, repeat: 0 }, // 보조 공격 애니메이션 속도입니다. attack02: { frameRate: 15, repeat: 0 }, // 강공격/치명타용 공격 애니메이션 속도입니다. attack03: { frameRate: 15, repeat: 0 }, // 방어 애니메이션 속도입니다. block: { frameRate: 13, repeat: 0 }, // 사망 애니메이션 속도입니다. death: { frameRate: 11, repeat: 0 }, // 회복 애니메이션 속도입니다. heal: { frameRate: 13, repeat: 0 }, // 피격 애니메이션 속도입니다. hurt: { frameRate: 13, repeat: 0 }, // 대기 애니메이션 속도입니다. repeat -1은 무한 반복입니다. idle: { frameRate: 7, repeat: -1 }, // 이동 애니메이션 속도입니다. repeat -1은 무한 반복입니다. walk: { frameRate: 10, repeat: -1 }, // 대체 이동 애니메이션 속도입니다. repeat -1은 무한 반복입니다. walk02: { frameRate: 10, repeat: -1 }, }; // 팀 배정에 순서대로 사용되는 기본 색상 팔레트입니다. export const TEAM_COLORS = [ "#da6a48", "#5fb4d9", "#9bd15a", "#d6a94a", "#d477b8", "#7f90e8", "#63c5a6", "#d98755", ]; const TEAM_COLOR_GOLDEN_ANGLE = 137.508; const TEAM_COLOR_HUE_OFFSET = 12; const TEAM_COLOR_SATURATIONS = [72, 62, 78, 68]; const TEAM_COLOR_LIGHTNESSES = [57, 63, 51, 69]; export function getTeamColor(index, totalTeams = TEAM_COLORS.length) { const safeIndex = Math.max(0, Math.floor(Number(index) || 0)); const safeTeamCount = Math.max(1, Math.floor(Number(totalTeams) || 1)); if (safeTeamCount <= TEAM_COLORS.length) { return TEAM_COLORS[safeIndex % TEAM_COLORS.length]; } const hue = (TEAM_COLOR_HUE_OFFSET + safeIndex * TEAM_COLOR_GOLDEN_ANGLE) % 360; const saturation = TEAM_COLOR_SATURATIONS[safeIndex % TEAM_COLOR_SATURATIONS.length]; const lightness = TEAM_COLOR_LIGHTNESSES[ Math.floor(safeIndex / TEAM_COLOR_SATURATIONS.length) % TEAM_COLOR_LIGHTNESSES.length ]; return hslToHex(hue, saturation, lightness); } function hslToHex(hue, saturation, lightness) { const normalizedSaturation = saturation / 100; const normalizedLightness = lightness / 100; const chroma = (1 - Math.abs(2 * normalizedLightness - 1)) * normalizedSaturation; const huePrime = hue / 60; const x = chroma * (1 - Math.abs((huePrime % 2) - 1)); const match = normalizedLightness - chroma / 2; const [red, green, blue] = huePrime < 1 ? [chroma, x, 0] : huePrime < 2 ? [x, chroma, 0] : huePrime < 3 ? [0, chroma, x] : huePrime < 4 ? [0, x, chroma] : huePrime < 5 ? [x, 0, chroma] : [chroma, 0, x]; return `#${[red, green, blue] .map((channel) => Math.round((channel + match) * 255) .toString(16) .padStart(2, "0"), ) .join("")}`; }