콘텐츠로 이동

5차시: 속도를 코드로 표현하다 - 등속 운동과 경계 조건 구현하기

⏰ 50분 · 등속 운동 · 속도 벡터 · 경계 조건 · 반사 로직 · 난이도 ●●●○○

학습목표: 속도 벡터와 시간 간격(dt)을 활용하여 등속 운동을 구현하고, if 조건문으로 벽 반사 로직을 작성할 수 있다.

오늘의 질문: "게임 속 공이 벽에 부딪혀 튕기는 장면, 코드 몇 줄이면 만들 수 있을까요?"


🧠 등속 운동 패턴

pos += velocity * dt 한 줄로
물체를 일정 속도로 움직이는 핵심 공식

💻 경계 조건 & 반사

if 조건문으로 벽에 닿으면
속도 부호를 뒤집어 튕기기 구현


🔍 도입: 움직임의 비밀 (5분)

일상 속 등속 운동 사례를 살펴보고, "속도"를 코드로 표현하는 핵심 아이디어를 잡습니다.

📖 개념 탐구: 속도 벡터와 dt (10분)

velocity 벡터, dt(시간 간격), 그리고 pos += velocity * dt 패턴의 물리적 의미를 이해합니다.

🛠️ 실습: 등속 운동 → 벽 반사까지 (25분)

v1(정지 공) → v2(등속 운동) → v3(경계 조건 추가) → v4(완성) 순서로 코드를 점진적으로 빌드업합니다.

🧪 실험 & 탐구 (5분)

속도 값과 dt 값을 바꿔보며 운동 결과가 어떻게 달라지는지 직접 확인합니다.

📝 정리 & 평가 (5분)

핵심 패턴을 정리하고, 형성 평가와 자기점검으로 마무리합니다.


🔍 도입: 움직임의 비밀

여러분, 볼링장에서 공을 굴려본 적 있나요? 힘껏 밀어낸 볼링공은 레인 위를 일정한 속도로 쭉 굴러갑니다. 그리고 끝에 있는 핀(벽)에 부딪히면 방향이 바뀌죠.

이번 시간에 만들 것이 바로 이겁니다:

현실 세계 VPython 코드
볼링공이 일정 속도로 굴러감 ball.pos += velocity * dt
벽에 부딪히면 튕겨 나옴 if ball.pos.x > 벽: velocity.x = -velocity.x
시간이 계속 흐름 while True: 루프 + rate()

4차시에서 우리는 벡터로 위치를 표현하는 법을 배웠습니다. 오늘은 거기에 속도를 더해서 물체를 실제로 움직여 보겠습니다! 🎯


📚 핵심 개념 1: 등속 운동 패턴 — pos += velocity * dt

🎯 일상 비유로 시작하기

자동차 내비게이션을 생각해 봅시다. 내비게이션은 이렇게 위치를 업데이트합니다:

"현재 위치 = 이전 위치 + (속도 × 지난 시간)"

예를 들어, 시속 60km로 달리는 자동차가 있다면: - 1시간 후 위치: 0 + 60 × 1 = 60km 지점 - 2시간 후 위치: 60 + 60 × 1 = 120km 지점

컴퓨터 시뮬레이션도 똑같습니다. 다만 "1시간"이 아니라 아주 작은 시간 간격(dt)마다 위치를 갱신하는 것뿐입니다.

📐 정확한 정의

용어 의미 VPython 코드 비유
velocity (속도 벡터) 물체가 이동하는 방향과 빠르기 velocity = vector(2, 0, 0) "오른쪽으로 초속 2m"
dt (시간 간격) 한 프레임에서 다음 프레임까지의 시간 dt = 0.01 스톱워치의 1틱
pos += velocity * dt 매 순간 위치를 속도만큼 갱신 ball.pos += velocity * dt 한 발짝씩 걷기

dt가 필요할까요? 컴퓨터는 연속적인 움직임을 표현할 수 없습니다. 대신 아주 작은 시간 간격마다 위치를 조금씩 바꿔서 부드러운 움직임처럼 보이게 하는 것입니다. 영화가 초당 24장의 사진을 빠르게 보여주는 것과 같은 원리입니다! 🎬

등속 운동의 시뮬레이션 루프 흐름:

flowchart LR A["시작<br/>위치 설정"] --> B["rate로<br/>속도 제어"] B --> C["pos += velocity * dt<br/>위치 갱신"] C --> D{"경계 도달?"} D -- 아니오 --> B D -- 예 --> E["속도 반전"] E --> B

🔬 심화: velocity * dt가 만드는 "한 걸음"의 크기

velocity * dt한 프레임에서 물체가 이동하는 거리입니다.

  • velocity = vector(2, 0, 0), dt = 0.01이면 → 한 프레임에 0.02만큼 이동
  • velocity = vector(2, 0, 0), dt = 0.001이면 → 한 프레임에 0.002만큼 이동

dt가 작을수록 한 걸음이 작아져서 더 정밀한 시뮬레이션이 됩니다. 대신 같은 거리를 이동하려면 더 많은 계산이 필요하겠죠?


📚 핵심 개념 2: 경계 조건 — 벽에 부딪히면 튕기기

🎯 일상 비유

탁구를 떠올려 보세요. 공이 테이블 끝에 닿으면 반대 방향으로 튕겨 나옵니다. 이때 공의 속도 크기(빠르기)는 그대로이지만, 방향(부호)만 반대가 됩니다.

코드로는 딱 한 줄입니다:

velocity.x = -velocity.x → x방향 속도의 부호를 뒤집기!

📐 정확한 정의

경계 조건(Boundary Condition) 이란, 물체가 특정 영역의 경계에 도달했을 때 어떤 일이 일어나는지를 정의하는 규칙입니다.

경계 조건 유형 설명 코드 패턴
반사 (Reflect) 벽에 부딪히면 튕김 velocity.x = -velocity.x
사라짐 (Destroy) 벽 밖으로 나가면 제거 ball.visible = False
순환 (Wrap) 한쪽 끝에서 나가면 반대쪽에서 나타남 ball.pos.x = -wall_x

오늘은 가장 직관적인 반사(Reflect) 를 구현합니다. 나머지는 나중에 프로젝트에서 필요할 때 도전해 볼 수 있습니다.

🔬 반사 로직의 구조

반사 로직은 항상 이 패턴을 따릅니다:

flowchart TD A{"ball.pos.x > 오른쪽 벽?"} A -- 예 --> B["velocity.x = -velocity.x<br/>왼쪽으로 방향 전환"] A -- 아니오 --> C{"ball.pos.x < 왼쪽 벽?"} C -- 예 --> D["velocity.x = -velocity.x<br/>오른쪽으로 방향 전환"] C -- 아니오 --> E["계속 진행"]

핵심은 "경계 확인 → 속도 반전" 이 두 단계입니다!


🛠️ 실습: 등속 운동에서 벽 반사까지 점진적 빌드업

실행 환경: VPython 3.2 (GlowScript 또는 로컬 설치)

🏗️ v1: 정지한 공 — 시작점 만들기 (3줄)

먼저 화면에 공 하나를 띄워 봅시다. 아직 움직이지 않습니다.

from vpython import *

# 씬(무대)을 설정합니다
scene = canvas(width=800, height=400, background=color.white)

# 빨간 공 하나를 원점에 생성합니다
ball = sphere(pos=vector(0, 0, 0), radius=0.5, color=color.red)

예상 결과: 화면 중앙에 빨간 공이 하나 나타납니다. 아무 움직임도 없습니다.

📌 위 코드의 3번째 줄 scene = canvas(...)가 바로 시뮬레이션 무대 설정입니다. 지난 시간에 배운 것과 같은 패턴이죠?


🏗️ v2: 공을 움직여 보자 — 등속 운동 구현

이제 핵심입니다! v1에 속도 벡터애니메이션 루프를 추가합니다.

v1에서 바뀐 점: 🆕 표시된 줄이 새로 추가된 코드입니다.

from vpython import *

scene = canvas(width=800, height=400, background=color.white)

ball = sphere(pos=vector(-5, 0, 0), radius=0.5, color=color.red)  # 🆕 시작 위치를 왼쪽으로

velocity = vector(2, 0, 0)  # 🆕 속도 벡터: 오른쪽으로 초속 2  <- 여기가 [속도 벡터 정의]
dt = 0.01                    # 🆕 시간 간격: 0.01초             <- 여기가 [시간 간격 정의]

while True:                  # 🆕 무한 반복 (애니메이션 루프)
    rate(100)                # 🆕 초당 100번 루프 실행 (부드러운 애니메이션)
    ball.pos += velocity * dt  # 🆕 매 프레임마다 위치 갱신     <- 여기가 [등속 운동 패턴]

예상 결과: 빨간 공이 왼쪽에서 오른쪽으로 일정한 속도로 이동합니다. 그런데... 화면 밖으로 사라져 버립니다! 😱

개념-코드 매핑:

  • 7번째 줄 velocity = vector(2, 0, 0)이 바로 속도 벡터 정의입니다. x방향으로 초속 2의 속력을 의미합니다.
  • 8번째 줄 dt = 0.01이 바로 시간 간격입니다. 한 프레임에 해당하는 시간입니다.
  • 11번째 줄 ball.pos += velocity * dt가 바로 등속 운동의 핵심 패턴입니다. 매 프레임마다 vector(2,0,0) * 0.01 = vector(0.02, 0, 0)만큼 위치가 바뀝니다.
  • 10번째 줄 rate(100)은 1초에 루프를 100번 실행하라는 의미입니다. 100번 × dt(0.01) = 1초에 실시간 1초가 흐르도록 맞춘 것입니다.

💡 rate(100)dt = 0.01의 관계: rate(N)은 1초에 N번 루프를 실행합니다. dt는 한 번의 루프가 "시뮬레이션 세계에서 몇 초에 해당하는지"를 정합니다. rate(100) × dt(0.01) = 1이면 실시간과 시뮬레이션 시간이 1:1로 흘러갑니다.


⚠️ 에러 경험: 공이 사라지는 문제

v2를 실행하면 공이 오른쪽으로 계속 나아가다 화면 밖으로 사라집니다. 이건 에러는 아니지만 원치 않는 동작입니다.

❓ 질문: "공이 화면 밖으로 나가지 않게 하려면 어떤 조건을 추가해야 할까요?"

정답은 경계 조건입니다! "공의 x 위치가 일정 값을 넘으면 방향을 바꿔라"라는 규칙을 추가해야 합니다.


🏗️ v3: 오른쪽 벽에서 튕기기 — 경계 조건 추가

v2에서 바뀐 점: while 루프 안에 🆕 if 조건문이 추가됩니다.

from vpython import *

scene = canvas(width=800, height=400, background=color.white)

ball = sphere(pos=vector(-5, 0, 0), radius=0.5, color=color.red)

velocity = vector(2, 0, 0)
dt = 0.01

wall_right = 6  # 🆕 오른쪽 벽의 x 좌표를 정의

while True:
    rate(100)
    ball.pos += velocity * dt

    # 🆕 경계 조건: 오른쪽 벽에 닿으면 속도를 반전  <- 여기가 [경계 조건]
    if ball.pos.x > wall_right:       # 🆕 공이 오른쪽 벽을 넘었는가?
        velocity.x = -velocity.x      # 🆕 x방향 속도의 부호를 뒤집기  <- 여기가 [반사 로직]

예상 결과: 공이 오른쪽 벽(x = 6)에 닿으면 왼쪽으로 방향을 바꿉니다! 하지만... 왼쪽으로 계속 가다가 또 사라집니다. 😅

개념-코드 매핑:

  • 10번째 줄 wall_right = 6이 바로 경계(벽) 위치 정의입니다. 벽의 위치를 변수로 저장하면 나중에 값을 쉽게 바꿀 수 있습니다.
  • 17번째 줄 if ball.pos.x > wall_right:가 바로 경계 도달 확인입니다.
  • 18번째 줄 velocity.x = -velocity.x가 바로 반사(Reflect) 입니다. 양수(→)가 음수(←)로, 음수가 양수로 바뀝니다.

⚠️ 에러 경험: 흔한 실수 — velocity를 새로 만들기

초보자가 자주 하는 실수를 한번 살펴볼까요?

from vpython import *

scene = canvas(width=800, height=400, background=color.white)
ball = sphere(pos=vector(-5, 0, 0), radius=0.5, color=color.red)

velocity = vector(2, 0, 0)
dt = 0.01
wall_right = 6

while True:
    rate(100)
    ball.pos += velocity * dt

    if ball.pos.x > wall_right:
        velocity = vector(-2, 0, 0)  # ← 이렇게 쓰면 작동은 하지만...

❓ 질문: "이 코드는 에러가 나지는 않지만, 왜 좋지 않은 방법일까요?"

문제 분석: 이 코드는 실행됩니다. 하지만 velocity = vector(-2, 0, 0)처럼 속도 값을 직접 하드코딩하면 문제가 생깁니다:

  1. 속도가 vector(3, 1, 0) 같은 대각선 방향이면? -velocity.x만 바꿔야 하는데 전체를 새로 써야 합니다.
  2. 속도가 프로그램 중간에 바뀌면? 항상 원래 값을 기억하고 있어야 합니다.
  3. 코드 수정이 번거롭고 실수하기 쉽습니다.

올바른 방법:

# 좋은 방법: 부호만 뒤집기 (어떤 속도값이든 작동!)
velocity.x = -velocity.x

이 한 줄은 속도가 2이든 100이든 -3이든 항상 정확하게 반대 방향으로 바꿔줍니다. 이것이 더 범용적(general)인 코드입니다.


🏗️ v4 (최종): 양쪽 벽에서 튕기는 완성 코드

v3에서 바뀐 점: 🆕 왼쪽 벽 조건과 시각적 벽 객체가 추가됩니다.

from vpython import *

# --- 무대 설정 ---
scene = canvas(width=800, height=400, background=color.white)
scene.caption = "🏓 등속 운동 + 벽 반사 시뮬레이션"

# --- 벽 만들기 (시각적 효과) ---
wall_x = 6  # 벽의 x 좌표 (좌우 대칭)
wall_left = box(pos=vector(-wall_x, 0, 0), size=vector(0.2, 4, 4),
                color=color.blue, opacity=0.3)       # 🆕 왼쪽 벽
wall_right_obj = box(pos=vector(wall_x, 0, 0), size=vector(0.2, 4, 4),
                     color=color.blue, opacity=0.3)   # 🆕 오른쪽 벽

# --- 공 생성 ---
ball = sphere(pos=vector(0, 0, 0), radius=0.5, color=color.red,
              make_trail=True, trail_color=color.orange)  # 🆕 궤적 표시

# --- 물리량 설정 ---
velocity = vector(2, 0, 0)  # 속도 벡터: 오른쪽으로 초속 2  <- 여기가 [속도 벡터]
dt = 0.01                    # 시간 간격                      <- 여기가 [dt]

# --- 애니메이션 루프 ---
while True:
    rate(100)  # 초당 100프레임

    ball.pos += velocity * dt  # <- 여기가 [등속 운동 핵심 패턴]

    # 오른쪽 벽 반사
    if ball.pos.x > wall_x:           # <- 여기가 [오른쪽 경계 조건]
        velocity.x = -velocity.x      # <- 여기가 [반사 로직]

    # 왼쪽 벽 반사                      🆕 왼쪽 벽도 추가!
    if ball.pos.x < -wall_x:          # <- 여기가 [왼쪽 경계 조건]
        velocity.x = -velocity.x      # 같은 반사 로직 적용

예상 결과: 빨간 공이 양쪽 파란 벽 사이를 왔다 갔다 하며, 주황색 궤적이 남습니다! 🎉

전체 코드 구조 요약:

flowchart TD A["무대 설정<br/>canvas, 벽, 공"] --> B["물리량 설정<br/>velocity, dt"] B --> C["while True 루프"] C --> D["rate로 속도 제어"] D --> E["pos += velocity * dt<br/>위치 갱신"] E --> F{"오른쪽 벽 초과?"} F -- 예 --> G["velocity.x 반전"] F -- 아니오 --> H{"왼쪽 벽 초과?"} G --> H H -- 예 --> I["velocity.x 반전"] H -- 아니오 --> D I --> D

🧪 실험 & 탐구: 값을 바꿔보면 어떤 일이?

실험 1: 속도 바꾸기

v4 완성 코드에서 velocity 값만 바꿔보세요.