← DEVLOG
물리 시뮬레이션2025.11.208 min read

슬링샷 — 중력 보조 비행을 게임으로

탐사선이 행성의 중력을 이용해 속도를 얻는 중력 슬링샷 — 궤도 역학 시뮬레이션을 레벨 게임으로 만든 기록

canvasphysicsorbital-mechanicsgravity-assist

시작 — 보이저 1호가 한 일

보이저 1호는 1977년 발사되어 지금도 태양계 밖을 날고 있습니다. 그 원동력은 로켓 연료만이 아닙니다. 목성과 토성의 중력을 이용한 슬링샷 기동 덕분에 로켓 단독으로는 도달할 수 없는 속도를 얻었습니다.

중력 슬링샷(gravity assist)은 탐사선이 행성 옆을 지나가면서 행성의 운동량 일부를 가져오는 물리 현상입니다. 행성 입장에서는 미세하게 느려지지만, 탐사선 입장에서는 막대한 속도 변화를 경험합니다. 이것을 게임으로 만들면 어떨까라는 생각에서 출발했습니다.


물리 엔진: N체 중력 계산

게임의 물리 핵심은 중력 시뮬레이션입니다. 탐사선과 행성들 사이에 매 프레임 만유인력 법칙을 적용합니다.

for (const body of planets) {
  const dx = body.x - probe.x;
  const dy = body.y - probe.y;
  const distSq = Math.max(dx * dx + dy * dy, MIN_DIST_SQ);
  const dist = Math.sqrt(distSq);
  const force = (G * body.mass) / distSq;
  probe.vx += (dx / dist) * force;
  probe.vy += (dy / dist) * force;
}
probe.x += probe.vx;
probe.y += probe.vy;

행성들은 고정 위치(fixed body)로 처리합니다. 행성끼리의 상호 중력은 무시하고, 탐사선에 가해지는 힘만 계산합니다. 이렇게 하면 레벨 설계가 예측 가능해지고, 각 레벨에서 의도한 슬링샷 경로가 매번 동일하게 재현됩니다.


목표 궤도와 원형 궤도 조건

각 레벨의 목표는 탐사선을 특정 행성 주변의 원형 궤도에 진입시키는 것입니다. 플레이어가 성공했는지 판정하려면 탐사선의 현재 속도가 목표 궤도의 원형 속도와 얼마나 가까운지 계산해야 합니다.

// 목표 행성으로부터의 거리
const r = Math.hypot(probe.x - target.x, probe.y - target.y);
// 원형 궤도 조건: v² = G * M / r
const vCircular = Math.sqrt((G * target.adaptedMass) / r);
// 현재 속도
const vProbe = Math.hypot(probe.vx - target.vx, probe.vy - target.vy);
// 속도 오차가 허용 범위 내이면 궤도 진입 성공
if (Math.abs(vProbe - vCircular) / vCircular < ORBIT_TOLERANCE) {
  st.phase = 'success';
}

레벨이 진행될수록 ORBIT_TOLERANCE 허용 오차가 줄어들어 더 정밀한 조준이 필요합니다.


레벨별 행성 배치와 질량 보정

레벨 1은 행성 1개, 레벨 5는 행성 5개입니다. 행성이 늘어날수록 중력 교란이 복잡해져 난이도가 자연스럽게 증가합니다.

초기 레벨 설계에서 한 가지 문제가 있었습니다. 화면 크기가 달라지면 행성 간 거리가 바뀌고, 물리적으로 동일한 질량이어도 궤도 조건이 달라집니다. 이를 해결하기 위해 행성 질량을 화면 스케일에 맞게 보정하는 getCachedOrSolve 함수를 만들었습니다.

// 목표 반경을 화면 크기 기준으로 환산
const adaptedTargetR = targetR * (canvasW / BASE_W);
// 해당 반경의 원형 궤도 속도
const v = Math.sqrt((G * adaptedMass) / adaptedTargetR);
// 행성 크기도 level 진행에 따라 k3 배율 조정
adaptedMass = baseMass * Math.pow(k3, level - 1);

이 보정 덕분에 모바일 세로 화면에서도 데스크톱과 동일한 난이도 곡선을 유지합니다.


발사 인터페이스: 마우스·터치 조준

탐사선 발사는 마우스 드래그(데스크톱)와 터치 드래그(모바일)로 조준합니다. 드래그 시작 지점에서 끝 지점까지의 벡터를 반전시켜 발사 방향과 속도를 결정합니다. 활과 화살처럼 당길수록 강하게 날아갑니다.

const onPointerUp = (e: PointerEvent) => {
  if (!dragStart) return;
  const dx = dragStart.x - e.clientX; // 반전
  const dy = dragStart.y - e.clientY;
  const power = Math.hypot(dx, dy);
  probe.vx = (dx / power) * Math.min(power, MAX_POWER) * POWER_SCALE;
  probe.vy = (dy / power) * Math.min(power, MAX_POWER) * POWER_SCALE;
  st.phase = 'flying';
};

드래그 궤적은 점선으로 표시하고, 예상 초기 궤도를 반투명 곡선으로 미리 보여줍니다. 이 예측선 덕분에 물리 지식 없이도 직관적으로 조준할 수 있습니다.


탐사선 궤적(Trail) 렌더링

탐사선이 지나온 경로를 표시하면 슬링샷 효과가 눈에 보입니다. 행성 옆을 지나며 속도가 바뀌고, 궤적이 포물선에서 쌍곡선으로 꺾이는 순간이 시각적으로 명확하게 드러납니다.

// 매 프레임 현재 위치를 trail 배열에 추가
trail.push({ x: probe.x, y: probe.y });
if (trail.length > TRAIL_MAX) trail.shift();

// 렌더링: 알파를 시간에 따라 감쇄
trail.forEach((pt, i) => {
  const alpha = (i / trail.length) * 0.6;
  ctx.fillStyle = `rgba(103, 232, 249, ${alpha})`;
  ctx.beginPath();
  ctx.arc(pt.x, pt.y, 1.5, 0, Math.PI * 2);
  ctx.fill();
});

기록 저장: slingshot:best

레벨별 최소 시도 횟수를 slingshot:best 키로 저장합니다. 이전 기록보다 적은 시도로 성공하면 기록이 갱신됩니다.

const prev = storageGet<{ level: number; attempts: number } | null>('slingshot:best', null);
if (!prev || level > prev.level || (level === prev.level && attempts < prev.attempts)) {
  storageSet('slingshot:best', { level, attempts });
}

storageGet/storageSet은 프로젝트 전체에서 localStorage 직접 접근을 대신하는 유틸리티입니다. 구 키 마이그레이션도 자동으로 처리합니다.


캔버스 성능 최적화

4K 해상도 캡

슬링샷 시뮬레이션은 매 프레임 행성, 탐사선, 궤적 Trail, 예측선을 모두 그립니다. 4K 디스플레이에서 Canvas 버퍼가 과도하게 커지는 것을 방지하기 위해, 총 픽셀 수를 1920 × 1080 기준으로 제한했습니다.

const MAX_AREA = 1920 * 1080;
const dpr = Math.min(window.devicePixelRatio, Math.sqrt(MAX_AREA / (w * h)));

시각적 품질 저하 없이 4K 환경에서의 프레임 안정성을 확보할 수 있었습니다.

resize debounce

브라우저 창 크기를 조절할 때 resize 이벤트가 연속적으로 발생하면서 Canvas가 매번 재초기화되는 문제가 있었습니다. 행성 질량을 화면 스케일에 맞게 재계산하는 getCachedOrSolve 호출이 포함되어 있어 비용이 적지 않았습니다.

150ms debounce를 적용해, 사용자가 창 크기 조절을 마친 후에만 한 번 재계산하도록 개선했습니다. 체감 지연은 없으면서 불필요한 재계산을 확실히 줄였습니다.


마치며

슬링샷 기동을 게임으로 구현하고 처음 성공 판정이 뜨는 것을 보았을 때, 보이저 1호 팀이 궤도를 계산하던 엔지니어들의 기분이 조금은 상상이 됐습니다. 중력을 내 편으로 만들어 목적지에 도달하는 퍼즐입니다.

물리 법칙을 직접 조작하는 게임을 만들 때 가장 어려운 점은 '얼마나 현실적으로 만드냐'가 아니라 '얼마나 직관적으로 재미있게 만드냐'임을 다시 한번 확인했습니다. 보이저호가 한 일이 사실은 이렇게 우아한 물리 퍼즐이었다는 것을, 이 게임 하나로 조금이나마 전할 수 있으면 좋겠습니다.

이 포스트와 연결된 콘텐츠

직접 체험하기