237 lines
11 KiB
JavaScript
237 lines
11 KiB
JavaScript
// 경기장을 구성하는 격자 칸 수입니다. 값이 커질수록 전장이 넓어집니다.
|
|
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("")}`;
|
|
}
|