Physics Lab
1주차 · 움직이는 공 진행 중

Part 1. 물리 시뮬레이션 기초

1주차 · 움직이는 공

예상 시간 120분 핵심 개념: Canvas, 좌표계, 게임 루프, deltaTime 결과물: 움직이는 공
  1. 15분관찰·예측
  2. 25분원리·코드
  3. 50분실험·AI 협업·직접 구현
  4. 20분자유 실험
  5. 10분정리

오늘의 학습 목표

  • Canvas 좌표계에서 (0, 0)의 위치와 x·y 값이 커지는 방향을 말할 수 있다.
  • 게임 루프가 계산 → 그리기 → 다음 프레임 요청을 반복한다는 것을 설명할 수 있다.
  • deltaTime(dt)을 초 단위로 구해 x += speed * dt로 공을 움직일 수 있다.
  • 프레임 방식과 deltaTime 방식의 차이를 실험 결과로 설명할 수 있다.

관찰과 예측

게임 속 캐릭터는 컴퓨터마다 같은 빠르기로 움직여야 합니다. 그런데 어떤 컴퓨터는 1초에 화면을 60번, 어떤 컴퓨터는 144번, 느린 컴퓨터는 30번만 다시 그립니다. 그래도 공이 똑같은 빠르기로 움직이게 하려면 어떻게 해야 할까요?

아래 실험실에는 공 두 개가 있습니다. 파란 공은 “흐른 시간만큼” 움직이고, 주황 공은 “화면을 한 번 그릴 때마다 같은 거리만큼” 움직입니다. 실험실을 실행하기 전에 먼저 예측해 보세요.

예측 질문

  1. 가상 프레임률을 30회/s로 낮추면 두 공 중 어느 공이 뒤처질까요? 그 이유는 무엇일까요?
  2. 속도가 120 px/s일 때 5초가 지나면 파란 공은 몇 px 이동했을까요?

핵심 물리 개념

① Canvas 좌표계

(0, 0) +x +y (150, 95)

왼쪽 위가 (0, 0)입니다. x는 오른쪽으로, y는 아래쪽으로 커집니다.

수학 시간의 그래프는 y가 위로 커지므로 방향이 반대입니다.

② 게임 루프

① 시간 재기 (dt) ② 계산 (update) ③ 그리기 (render) requestAnimationFrame 이 다음 프레임을 예약

requestAnimationFrame은 “다음에 화면을 그릴 때 이 함수를 불러 줘”라는 예약입니다. 함수 안에서 다시 예약하면 루프가 계속 돕니다.

③ deltaTime (dt)

지난 프레임 이후 흐른 시간입니다. 브라우저는 시각을 밀리초(ms)로 알려 주므로 1000으로 나누어 초(s)로 바꿉니다.

dt = (지금 시각 − 이전 시각) ÷ 1000

1초에 60번 그리면 dt ≈ 0.0167초, 30번 그리면 dt ≈ 0.0333초입니다.

속도와 위치의 관계

속도는 “1초 동안 위치가 얼마나 바뀌는가”입니다. 그러므로 dt초 동안에는 속도 × dt만큼 움직입니다.

새 위치 = 현재 위치 + 속도 × dt   →   x += speed * dt

예) 속도 120 px/s, dt = 0.0167 s → 한 프레임에 약 2 px 이동 → 1초(60프레임) 동안 약 120 px 이동. 프레임이 30번뿐이면 dt가 두 배(0.0333 s)라서 한 번에 약 4 px씩 움직이고, 1초 뒤 위치는 같습니다.

이번 주에 쓰는 양과 단위
양코드 이름단위뜻
위치xpx왼쪽 끝에서부터의 거리
속도speedpx/s1초 동안 움직이는 거리
시간 간격dts (초)지난 프레임 이후 흐른 시간
프레임률—회/s1초 동안 화면을 다시 그린 횟수

이 실험의 가정과 단순화

  • px는 화면 단위입니다. 실제 미터(m)와 같지 않습니다. 실험실의 화면은 항상 가로 800 px × 세로 450 px로 계산합니다.
  • dt는 최대 0.05초로 제한합니다. 탭을 바꿨다 돌아오면 그동안의 시간은 더하지 않습니다.
  • 공은 오른쪽 끝을 벗어나면 왼쪽에서 다시 나타납니다(벽 충돌은 4주차에 배웁니다).

인터랙티브 실험실

캔버스 위에 마우스를 올리면 그 지점의 좌표가 보입니다. 값을 바꾸며 예측이 맞는지 확인하세요.

시작 전

조절하기

120 px/s

실행 중에도 바로 반영됩니다.

가상 프레임률 (회/s) — 느린 컴퓨터 흉내

측정값

경과 시간
0.00 s
이번 프레임 dt
—
실제 프레임률
—
파란 공 이동
0 px
주황 공 이동
0 px

버튼의 차이

일시정지/재개는 멈춘 자리에서 이어 갑니다. 재시작은 지금 설정 그대로 처음부터 다시 실행합니다. 초기화는 모든 값을 기본값으로 되돌리고 시작 전 상태로 만듭니다.

핵심 코드 분석

아래 코드는 위 실험실에서 실제로 실행되는 코드를 파일에서 그대로 불러온 것입니다.

① 한 프레임의 움직임 계산

simulations/week01.js
코드를 불러오는 중…

중요한 줄

  • speed * dt: “1초에 speed px”를 “dt초 동안의 거리”로 바꿉니다.
  • speed / 60: 화면을 1초에 60번 그린다고 가정합니다. 가정이 틀리면 빠르기가 달라집니다.
  • 오른쪽 끝을 벗어나는지 검사할 때 반지름 RADIUS를 빼서, 공이 완전히 사라진 뒤에 옮깁니다.

값을 바꾸면?

  • speed를 2배로 → 같은 시간에 2배 멀리 갑니다(두 공 모두).
  • 프레임률 30회/s → 파란 공은 그대로, 주황 공은 절반 빠르기가 됩니다.
  • * dt를 지우면 → 한 프레임에 120 px씩 움직여 공이 순식간에 사라집니다. 단위가 맞지 않기 때문입니다.

② 게임 루프와 dt 계산

js/sim-kit.js
코드를 불러오는 중…

중요한 줄

  • (now - loop.lastTime) / 1000: ms를 초로 바꾼 dt입니다.
  • Math.min(dt, MAX_DT): 0.05초보다 긴 간격은 잘라서 공이 순간 이동하지 않게 합니다.
  • requestAnimationFrame(frame): 마지막에 다음 프레임을 다시 예약해서 루프가 이어집니다.

생각해 보기

  • 계산(update)을 그리기(render)보다 먼저 하는 이유는 무엇일까요?
  • 일시정지 중에는 왜 requestAnimationFrame을 다시 예약하지 않을까요?

직접 구현 미션

출발 코드 starter/week01/의 main.js를 수정합니다. 수정한 뒤 출발 코드 실행 화면 열기 ↗에서 새로고침해 확인하세요. 원본은 복사해 두고 작업하면 언제든 처음으로 돌아갈 수 있습니다.

기초 미션

dt를 구해서 공을 움직이기

main.js의 TODO 1, TODO 2를 완성해 공이 오른쪽으로 일정한 빠르기로 움직이게 합니다.

성공 조건
  • 공이 끊기지 않고 오른쪽으로 움직인다.
  • 화면 왼쪽 위의 dt 표시가 0.01~0.02초 근처의 값이다(모니터에 따라 다름).
  • 속도를 120 px/s로 두었을 때 약 5초 뒤 공의 x가 처음보다 약 600 px 커진다.
힌트 1 — 시각은 어디서 얻나요?

requestAnimationFrame이 부르는 함수는 첫 번째 매개변수로 현재 시각(ms)을 받습니다.

힌트 2 — “이전 시각”은 어떻게 기억하나요?

함수 바깥에 이전 시각을 담는 변수를 하나 두고, 매 프레임 계산이 끝나면 지금 시각으로 바꿔 둡니다.

응용 미션

화면을 건너는 시간 예측하고 재기

TODO 3을 완성해 공이 오른쪽 끝을 완전히 벗어나면 왼쪽에서 다시 나타나게 하세요. 그다음 속도를 200 px/s로 바꾸고, 공이 화면 폭 800 px을 건너는 시간을 먼저 계산으로 예측한 뒤 스톱워치로 재어 비교합니다.

성공 조건
  • 공이 반쯤 걸친 채 갑자기 사라지지 않는다(반지름을 고려한다).
  • 예측한 시간과 잰 시간의 차이를 학습 기록에 적었다.
힌트 — 공이 “완전히” 벗어난 순간은?

공의 x는 공의 중심입니다. 공의 왼쪽 가장자리가 화면 오른쪽 끝(800)을 넘은 때를 생각해 보세요.

도전 미션

아래로 내려가는 두 번째 공

두 번째 공을 만들어 아래쪽으로 80 px/s로 움직이고, 화면 아래를 벗어나면 위에서 다시 나타나게 하세요. 변수 이름은 뜻이 드러나게 정합니다.

성공 조건
  • 두 공이 각자의 속도로 동시에 움직인다.
  • 두 번째 공은 y 값이 커지는 방향으로 움직인다.
  • 왜 “아래로” 가려면 y를 더해야 하는지 학습 기록에 설명했다.
힌트 — 무엇을 하나 더 만들어야 하나요?

첫 번째 공을 위해 만든 변수(위치·속도)를 두 번째 공용으로 하나씩 더 만들고, 계산과 그리기를 각각 추가합니다.

AI 협업 미션

1~8주의 AI 협업 목표는 코드 설명 듣기와 오류 원인 찾기입니다. AI에게 코드를 대신 써 달라고 하지 말고, 설명을 듣고 직접 실행해서 확인하세요. 이름·학교 같은 개인정보는 프롬프트에 넣지 않습니다.

프롬프트 예시 1 — 설명 요청

아래는 Canvas에서 공을 움직이는 내 게임 루프 코드야. dt가 무엇인지, 왜 1000으로 나누는지 중학생이 이해할 수 있게 설명해 줘. 고친 코드는 쓰지 말고 설명만 해 줘. (여기에 내 코드를 붙여 넣기)

프롬프트 예시 2 — 오류 원인 찾기

내 Canvas 공이 화면에 나타나자마자 오른쪽으로 사라져 버려. 예상: 1초에 120 px씩 움직인다. 실제: (내가 본 현상을 적기) 원인 후보를 3가지 알려 주고, 각각을 확인하려면 어떤 값을 console.log로 찍어 보면 되는지 알려 줘. 코드를 직접 고쳐 주지는 마. (여기에 내 코드를 붙여 넣기)

AI 답변 검증 질문

  1. AI의 설명을 내 말로 한두 문장으로 바꿔 적을 수 있나요?
  2. AI가 말한 대로 값을 바꿔 실행했을 때 결과가 설명과 같았나요? 다르다면 무엇이 달랐나요?
  3. AI 설명 중 실험실의 실제 코드와 맞지 않는 부분이 있었나요?

실험 결과 및 학습 기록

입력하면 자동으로 이 브라우저에 저장됩니다. 이름·연락처는 적지 마세요.

수업 마무리

이해 확인 질문

  1. Canvas에서 (0, 0)은 어디이고, 공의 y 값이 커지면 공은 어느 쪽으로 움직이나요?
  2. 가상 프레임률 30회/s에서 주황 공이 뒤처진 이유를 “dt”라는 말을 넣어 설명해 보세요.
  3. 속도 150 px/s인 공은 2초 동안 몇 px 움직이나요? 프레임률이 달라지면 이 값이 바뀌나요?

핵심 요약

  • Canvas 좌표는 왼쪽 위가 (0, 0)이고, y는 아래로 커집니다.
  • 게임 루프는 시간 재기 → 계산 → 그리기를 반복합니다.
  • x += speed * dt를 쓰면 화면을 몇 번 그리든 같은 시간에 같은 거리를 움직입니다.

다음 주 예고 — 2주차 “자유 이동하는 공”: 이번에는 x 방향으로만 움직였습니다. 다음 주에는 x와 y 방향 속도를 함께 써서 공을 원하는 방향으로 움직이고, 방향과 빠르기를 화살표(벡터)로 나타냅니다.

목표를 확인하고 기록을 저장한 뒤 직접 눌러 주세요.