콘텐츠로 이동

9차시: 부딪히면 반응하라! - 충돌 감지와 에러 디버깅 실전

⏰ 50분 · 충돌 감지 · mag() 거리 계산 · 에러 디버깅 · 반응 구현 · 난이도 ●●●○○

학습목표: 두 객체 사이의 거리를 계산하여 충돌을 감지하고, 다양한 반응을 구현하며, 자주 발생하는 에러를 스스로 해결할 수 있다.

오늘의 질문: "자동차 게임에서 내 차가 장애물에 '부딪혔다'는 걸, 컴퓨터는 어떻게 알까요?"


🎯 핵심 주제 카드

🧠 충돌 감지 원리

두 객체 사이 거리를 mag()로 계산하여
반지름의 합과 비교하는 조건식

💻 충돌 반응 구현

색상 변경, 속도 반전, 객체 소멸 등
충돌 시 다양한 시각적 반응 코딩

🐛 에러 디버깅 실전

AttributeError, 무한루프, rate 누락 등
대표 에러 5가지 원인 파악과 해결

💬 트러블슈팅 훈련

에러 메시지를 읽고 원인을 추론하는
디버깅 습관 기르기


⏱️ 수업 흐름

1단계: 도입 — 충돌이란 무엇인가? (5분)

일상 속 '부딪힘'의 원리를 떠올리고, 컴퓨터가 충돌을 판단하는 방식을 비유로 이해합니다.

2단계: 핵심 개념 — mag()와 거리 계산 (10분)

두 객체 사이 거리를 mag() 함수로 구하고, 반지름의 합과 비교하는 충돌 조건식을 학습합니다.

3단계: 따라하기 실습 — 충돌 감지 & 반응 구현 (20분)

v1(기본 충돌 감지) → v2(색상 변경 반응) → v3(다중 객체 충돌) → 최종(복합 반응)까지 점진적으로 코드를 완성합니다.

4단계: 에러 디버깅 실전 훈련 (10분)

VPython에서 자주 발생하는 대표 에러 5가지를 직접 만나보고, 원인을 분석하여 수정하는 훈련을 합니다.

5단계: 마무리 — 정리 & 성찰 (5분)

배운 내용을 정리하고, 형성 평가와 자기점검으로 학습을 마무리합니다.


📚 핵심 개념 설명

🔑 개념 1: 충돌 감지 — "두 물체 사이의 거리"로 판단한다

🎳 일상 비유: 볼링공과 핀

볼링을 칠 때, 공이 핀에 닿는 순간 핀이 쓰러집니다. 사람은 눈으로 "아, 부딪혔네!" 하고 알 수 있지만, 컴퓨터에게는 눈이 없습니다. 그래서 컴퓨터는 이렇게 판단합니다:

"두 물체의 중심 사이 거리가, 두 물체의 크기(반지름)의 합보다 작아지면 → 부딪힌 것이다!"

이것은 마치 두 사람이 양팔을 벌리고 걸어가다가, 팔이 닿으면 "부딪혔다!"고 판정하는 것과 같습니다.

📐 정확한 정의

충돌 감지(Collision Detection) 란 프로그램에서 두 객체가 겹치거나 접촉했는지를 계산으로 판별하는 것입니다. 구(sphere) 형태의 충돌 감지 공식은 다음과 같습니다:

항목 의미 VPython 코드
두 객체 사이 거리 중심점과 중심점 사이의 직선 거리 mag(ball1.pos - ball2.pos)
반지름의 합 두 구의 반지름을 더한 값 ball1.radius + ball2.radius
충돌 조건 거리 < 반지름의 합 if distance < r_sum:

충돌 판정 다이어그램:

flowchart TD A["매 프레임마다<br/>반복 실행"] --> B["두 객체 사이<br/>거리 계산"] B --> C{"거리 < 반지름의 합?"} C -->|"Yes"| D["🔴 충돌! 반응 실행"] C -->|"No"| E["✅ 충돌 아님, 계속 이동"] D --> A E --> A

🧮 mag() 함수란?

VPython의 mag() 함수는 벡터의 크기(magnitude)를 구합니다. 쉽게 말하면, 3차원 공간에서 두 점 사이의 거리를 한 번에 계산해주는 함수입니다.

수학적으로는 피타고라스 정리의 3D 확장입니다:

수학 공식 VPython 코드 의미
√((x₂-x₁)² + (y₂-y₁)² + (z₂-z₁)²) mag(ball1.pos - ball2.pos) 두 위치 벡터의 차이의 크기

ball1.pos - ball2.pos는 두 위치의 차이 벡터를 만들고, mag()가 그 벡터의 길이(= 거리)를 숫자로 반환합니다. 복잡한 수학을 한 줄로 해결해주는 고마운 함수입니다!

💡 왜 직접 거리를 계산하지 않나요? ((x2-x1)**2 + (y2-y1)**2 + (z2-z1)**2)**0.5 처럼 직접 쓸 수도 있지만, 코드가 길고 실수하기 쉽습니다. mag()는 VPython이 제공하는 편리한 도구이고, 실무에서도 이런 유틸리티 함수를 적극 활용합니다.


🔑 개념 2: 에러 디버깅 — "에러 메시지는 컴퓨터가 보내는 도움 요청"

📬 일상 비유: 택배 배송 실패 문자

택배가 배달 실패했을 때, 문자가 옵니다: "주소 불명으로 반송되었습니다." 이 문자를 무시하면 택배를 영영 못 받지만, 읽고 주소를 수정하면 다시 받을 수 있습니다.

에러 메시지도 마찬가지입니다. 컴퓨터가 "이런 문제가 있어서 실행을 못 하겠어요"라고 알려주는 것인데, 대부분의 초보자는 빨간 글씨만 보고 당황합니다. 하지만 에러 메시지를 차분히 읽으면, 어디서 무엇이 잘못됐는지 거의 다 알려줍니다.

🔍 에러 메시지 읽는 법

에러 메시지는 보통 세 부분으로 이루어져 있습니다:

부분 의미 예시
위치 (Traceback) 에러가 발생한 파일과 줄 번호 line 5, in <module>
종류 (Error Type) 어떤 유형의 에러인지 AttributeError
설명 (Message) 구체적으로 무엇이 문제인지 'sphere' object has no attribute 'colour'
flowchart LR A["에러 발생!<br/>빨간 글씨 😱"] --> B["1. 에러 종류<br/>확인"] B --> C["2. 에러 메시지<br/>읽기"] C --> D["3. 줄 번호<br/>확인"] D --> E["4. 해당 줄<br/>코드 점검"] E --> F["5. 수정 후<br/>다시 실행 ✅"]

💻 따라하기 실습: 충돌 감지 & 반응 구현

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

🏗️ v1: 가장 기본 — 두 공의 거리 확인하기

먼저 두 개의 공을 만들고, 하나를 움직여서 거리를 측정하는 것부터 시작합니다. 아직 충돌 판정은 하지 않습니다. "일단 거리를 재보자!"가 목표입니다.

from vpython import *

# 두 개의 공 생성 — 서로 떨어져 있는 상태
ball1 = sphere(pos=vector(-3, 0, 0), radius=0.5, color=color.red)
ball2 = sphere(pos=vector(3, 0, 0), radius=0.5, color=color.blue)

# ball1이 오른쪽으로 이동하는 속도
ball1.velocity = vector(0.05, 0, 0)

while True:
    rate(60)  # <- 초당 60프레임으로 애니메이션 제어
    ball1.pos = ball1.pos + ball1.velocity

    # 두 공 사이의 거리를 계산하여 출력
    distance = mag(ball1.pos - ball2.pos)  # <- 여기가 mag() 거리 계산
    print(f"거리: {distance:.2f}")

예상 실행 결과 (콘솔에 거리가 계속 출력됩니다):

거리: 5.95
거리: 5.90
거리: 5.85
...
거리: 1.05
거리: 1.00
거리: 0.95
...

개념-코드 매핑:

  • 위 코드의 14번째 줄 distance = mag(ball1.pos - ball2.pos)가 바로 두 객체 사이 거리 계산입니다. ball1.pos - ball2.pos로 차이 벡터를 구하고, mag()로 그 크기를 숫자로 변환합니다.
  • 10번째 줄 rate(60)은 애니메이션 속도를 제어합니다. 이 줄이 없으면 컴퓨터가 최대 속도로 돌려서 화면이 멈추는 것처럼 보입니다.

거리 숫자가 점점 줄어드는 것이 보이시나요? 공이 다가가고 있다는 증거입니다! 하지만 아직 공이 겹쳐도 아무 일도 일어나지 않습니다.


🏗️ v2: 충돌 감지 추가 — "부딪히면 색이 바뀐다!"

v1에서 달라진 점: if 조건문으로 충돌을 감지하고, 충돌 시 두 공의 색상을 변경합니다.

from vpython import *

ball1 = sphere(pos=vector(-3, 0, 0), radius=0.5, color=color.red)
ball2 = sphere(pos=vector(3, 0, 0), radius=0.5, color=color.blue)

ball1.velocity = vector(0.05, 0, 0)

while True:
    rate(60)
    ball1.pos = ball1.pos + ball1.velocity

    distance = mag(ball1.pos - ball2.pos)

    # ▼▼▼ [v2에서 추가된 부분] 충돌 판정 ▼▼▼
    r_sum = ball1.radius + ball2.radius  # <- 반지름의 합 계산
    if distance < r_sum:                  # <- 여기가 충돌 조건식
        ball1.color = color.yellow        # 충돌 시 색상 변경
        ball2.color = color.yellow
        print("💥 충돌 발생!")
    # ▲▲▲ [v2에서 추가된 부분] 끝 ▲▲▲

예상 실행 결과:

3D 화면에서 빨간 공이 오른쪽으로 이동하다가, 파란 공에 닿는 순간 두 공 모두 노란색으로 변합니다! 콘솔에는:

💥 충돌 발생!
💥 충돌 발생!
💥 충돌 발생!
(충돌 상태가 유지되는 동안 계속 출력)

개념-코드 매핑:

  • 15번째 줄 r_sum = ball1.radius + ball2.radius — 두 공의 반지름을 더합니다. 반지름이 각각 0.5이므로 r_sum = 1.0입니다.
  • 16번째 줄 if distance < r_sum: — 핵심 충돌 조건입니다! 두 공의 중심 거리가 1.0보다 작아지면, 두 공의 표면이 겹치는 것이므로 "충돌"로 판정합니다.

🤔 잠깐, "충돌 발생!"이 계속 출력되는 이유가 뭘까요? 공이 충돌해도 멈추지 않고 계속 이동하기 때문입니다. 겹쳐 있는 동안 매 프레임마다 충돌 조건이 참이 됩니다. 다음 버전에서 이 문제를 해결해봅시다!


🏗️ v3: 충돌 반응 개선 — 방향 반전(튕겨나가기)

v2에서 달라진 점: 충돌 시 공이 반대 방향으로 튕겨나가게 속도를 반전시킵니다. 두 번째 공도 움직이도록 합니다.

from vpython import *

ball1 = sphere(pos=vector(-3, 0, 0), radius=0.5, color=color.red)
ball2 = sphere(pos=vector(3, 0, 0), radius=0.5, color=color.blue)

# ▼▼▼ [v3에서 변경] 두 공 모두 서로를 향해 이동 ▼▼▼
ball1.velocity = vector(0.05, 0, 0)    # 오른쪽으로
ball2.velocity = vector(-0.03, 0, 0)   # 왼쪽으로
# ▲▲▲ [v3에서 변경] 끝 ▲▲▲

while True:
    rate(60)

    # 두 공 모두 이동
    ball1.pos = ball1.pos + ball1.velocity
    ball2.pos = ball2.pos + ball2.velocity  # <- v3 추가: ball2도 이동

    distance = mag(ball1.pos - ball2.pos)
    r_sum = ball1.radius + ball2.radius

    if distance < r_sum:
        # ▼▼▼ [v3에서 변경] 속도 반전으로 튕겨나가기 ▼▼▼
        ball1.velocity = -ball1.velocity  # <- 여기가 방향 반전
        ball2.velocity = -ball2.velocity  # <- 여기가 방향 반전
        ball1.color = color.yellow
        ball2.color = color.yellow
        # ▲▲▲ [v3에서 변경] 끝 ▲▲▲

예상 실행 결과:

두 공이 서로를 향해 다가가다가, 만나는 순간 노란색으로 바뀌면서 반대 방향으로 튕겨나갑니다! 마치 당구공이 부딪히는 것 같은 느낌입니다.

개념-코드 매핑:

  • 23~24번째 줄 ball1.velocity = -ball1.velocity — 속도 벡터에 마이너스(-)를 붙이면 방향이 정반대로 바뀝니다. vector(0.05, 0, 0)vector(-0.05, 0, 0)이 되는 것입니다. 이것이 방향 반전입니다.

⚠️ 에러 경험: 이 코드를 실행하면 어떤 에러가 날까요?

v3 코드를 조금 수정해봅시다. 아래 코드에는 일부러 넣은 실수가 있습니다. 찾아보세요!

from vpython import *

ball1 = sphere(pos=vector(-3, 0, 0), radius=0.5, color=color.red)
ball2 = sphere(pos=vector(3, 0, 0), radius=0.5, color=color.blue)

ball1.velocity = vector(0.05, 0, 0)

while True:
    rate(60)
    ball1.pos = ball1.pos + ball1.velocity

    # 🐛 버그가 숨어있는 줄!
    distance = mag(ball1.position - ball2.pos)

    r_sum = ball1.radius + ball2.radius
    if distance < r_sum:
        ball1.color = color.yellow

에러 메시지:

AttributeError: 'sphere' object has no attribute 'position'

원인 분석:

VPython의 sphere 객체에서 위치를 나타내는 속성은 position이 아니라 pos입니다. ball1.position이라고 적었기 때문에, "sphere 객체에는 position이라는 속성이 없다"는 AttributeError가 발생합니다.

잘못된 코드 올바른 코드 설명
ball1.position ball1.pos VPython은 pos가 위치 속성
ball1.colour ball1.color 미국식 철자 color 사용
ball1.rad ball1.radius 반지름은 radius

수정 코드:

    # 수정: position → pos
    distance = mag(ball1.pos - ball2.pos)

💡 AttributeError를 만나면? "이 객체에 그런 이름의 속성은 없다"는 뜻입니다. 속성 이름의 오타를 의심하세요! VPython에서는 pos, color, radius, velocity 등의 정확한 이름을 사용해야 합니다.


🏗️ 최종 완성: 다중 공 충돌 + 복합 반응

이제 리스트를 활용하여 여러 개의 공을 만들고, 모든 쌍의 충돌을 검사하는 최종 코드를 완성합니다. 8차시에서 배운 리스트 활용이 빛을 발하는 순간입니다!

v3에서 달라진 점: 2개가 아닌 여러 개의 공을 리스트로 관리하고, 이중 for 루프로 모든 쌍을 검사합니다. 충돌 시 색상 변경 + 방향 반전을 동시에 적용합니다.