1.파이썬 물리엔진 시각화를 위한 라이브러리들
vpython vs pymunk 핵심 비교
항목 | vpython | pymunk |
차원 | 3D | 2D |
주 용도 | 물리 개념 시각화 (교육용) | 실제 물리 엔진 시뮬레이션 (게임/연구) |
물리 기능 | 단순 운동 (속도, 위치, 중력) | 충돌, 회전, 마찰, 반발력 등 고급 물리 |
시각화 | 자체 3D 렌더링 내장 | 별도 필요 (예: pygame) |
학습 난이도 | 쉬움 (직관적) | 중간 ~ 어려움 (구조적 접근) |
설치 | pip install vpython | pip install pymunk |
사용 권장 상황
•
vpython → 중고등학생, 대학 기초 물리 교육 시각화
•
pymunk → 2D 게임 물리 구현, 충돌/역학 기반 시뮬레이션
1-1. 낙하 운동 시뮬레이션(vpython)
[주의]
해당 코드는 .ipynb이 아닌 파이썬 스크립트 형태로 실행해야 함
# vpython 자유 낙하 시뮬레이션
from vpython import sphere, vector, rate, color, scene, box
g = 9.8 # 중력 가속도
dt = 0.01 # 시간 간격
v = 0 # 초기 속도
y = 5 # 초기 높이
# 바닥 추가 - 위치를 더 아래로 조정 (-0.1 → -2.0)
floor = box(pos=vector(0, -2.0, 0), size=vector(10, 0.2, 10), color=color.green)
ball = sphere(pos=vector(0, y, 0), radius=0.2, color=color.red, make_trail=True)
# 공이 실제 바닥에 닿을 때까지 계속
# 바닥 위치 + 바닥 높이의 절반 + 공의 반지름 = 공이 바닥에 닿는 위치
floor_top = floor.pos.y + floor.size.y/2
# 바닥 윗면의 y좌표
while ball.pos.y > floor_top + ball.radius:
rate(100) # 1초에 100프레임
v = v - g * dt
y = y + v * dt
ball.pos.y = y
# 시뮬레이션이 끝난 후 사용자 클릭을 기다림
scene.append_to_caption("\n시뮬레이션 완료. 클릭하면 종료됩니다.")
scene.waitfor('click')
Python
복사
1-2. 낙하 운동 시뮬레이션(pymunk활용 2D)
import pygame, pymunk, pymunk.pygame_util
# pip install pygame으로 pygame 설치, pip install pymunk로 pymunk 설치# 1. 게임 초기화 (game initialize)
pygame.init()
# 2. 게임창 옵션 설정 (window option)
size = (400, 400) # 게임창 크기 (window size)
screen = pygame.display.set_mode(size)
title = "Physics Simulator" # 창 제목 (window title)
pygame.display.set_caption(title)
Python
복사
2. pymunk라이브러리
1.
’munk’이름의 유래: Chipmunk라는 2D 물리 엔진에서 유래
2.
Chipmunk2D :C 언어로 작성된 경량 2D 물리 엔진
2-1. 물리엔진
1.
물리 엔진이란?
•
게임 개발, 시뮬레이션 등 다양한 분야에서 현실적인 물리 현상을 구현하는 데 필수
•
정확히는 ‘물리 법칙을 컴퓨터 시뮬레이션으로 구현한 프로그램’
•
예를 들어, 공이 바닥에 닿아 튕겨 오르는 효과를 자연스럽게 표현가능
•
만약, 물리 엔진 없이 구현하려면 충돌 후 속도와 방향 등을 직접 계산해야 하는 어려움 존재재
[참고 사이트]
https://blog.naver.com/jsk6824/223031525485
2-2. pymunk의 특징
1.
pymunk는 물리 문제를 해결하기 위해 각 물체의 위치를 시간 별로 계산함
2.
pygame은 pymunk에서 계산된 물체 위치를 화면에 표시하는 역할을 하며, 따라서 물체는 실시간으로 위치가 변하게 됨
•
주로 파이게임과 같이 불러와서 사용
•
pygame창에서는 왼쪽 상단이 (0,0)임
1.
pymknk 활용을 위한 기초
•
space: 시뮬레이션 공간을 의미
•
body: 물체를 의미
◦
물체의의 질량, 속도, 위치, 특성을 포함
•
shape: 물체의 형태를 나타내고 주로 원, 삼각형, 사각형 등
◦
충돌 검사 기능을 가짐
3.기초
1.
초기화
•
화면 구성
2.
공간 만들기
•
space 생성
•
중력 설정
3.
물리객체 생성
•
바닥생성: 마찰력, 탄성 등을 설정
>바닥 (static body + segment)
◦
설정 후 space에 등록
•
공생성: 모양, 마찰력, 탄성 등을 설정
>(dynamic body + circle)
◦
설정 후 space에 등록
4.
루프처리
•
FPS 조절
•
시뮬레이션 업데이트
•
이벤트 감지 (QUIT)
•
화면 렌더링 및 업데이트
#바닥까지 만들어보기
import pygame, pymunk, pymunk.pygame_util
# 1. 게임 초기화 (game initialize)
pygame.init()
# 2. 게임창 옵션 설정 (window option)
size = (800, 800) # 게임창 크기 (window size)
screen = pygame.display.set_mode(size)
title = "Physics Simulator" # 창 제목 (window title)
pygame.display.set_caption(title)
# 3. 게임 내 필요한 설정 (option for game)
clock = pygame.time.Clock() # 시계 (clock)
# 공간 만들기 (space)
space = pymunk.Space()
# 중력의 방향과 세기를 설정
space.gravity = (0, 980)
# pygame의 DrawOptions를 사용하여 화면에 그리기 위한 옵션 설정
draw_options = pymunk.pygame_util.DrawOptions(screen)
# 바닥 만들기(고정된 type이므로 STATIC으로 설정)
floor = pymunk.Body(body_type = pymunk.Body.STATIC)
floor.position = (0, size[1]-50)
floor_shape = pymunk.Segment(floor, (0,0), (size[0],-100), 1)
floor_shape.elasticity = 1 # 탄성 계수 (elasticity) 설정
floor_shape.friction = 0.2 # 마찰 계수 (friction) 설정
# space에 바닥을 추가
space.add(floor, floor_shape)
# 4. 메인 이벤트 (main event)
running = True
while running:
# 4-1. FPS 설정 (frame per second)
clock.tick(60) # 메인 이벤트 반복이 1초에 60회 (60 frames per 1 second)
space.step(1/60) # 시뮬레이션 주기
# 4-2. 각종 입력 감지
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 4-3. 입력, 시간에 따른 변화 (change with event or time)
# 4-4. 그리기 (drawing)
screen.fill((0,0,0))
space.debug_draw(draw_options)
# 4-5. 업데이트 (update)
pygame.display.flip()
# 5. 게임 종료 (quit)
pygame.quit()
Python
복사
3-1.추가
먼저 공을 만들어 테스트
그 이후 공에 마찰계수와, 충돌계수를 넣었을때 비교해보기기
# 공, 바닥까지 만드는 코드
import pygame, pymunk, pymunk.pygame_util
# 1. 게임 초기화 (game initialize)
pygame.init()
# 2. 게임창 옵션 설정 (window option)
size = (1600, 1200) # 게임창 크기 (window size)
screen = pygame.display.set_mode(size)
title = "Physics Simulator" # 창 제목 (window title)
pygame.display.set_caption(title)
# 3. 게임 내 필요한 설정 (option for game)
clock = pygame.time.Clock() # 시계 (clock)
# 공간 만들기 (space)
space = pymunk.Space()
# 중력의 방향과 세기를 설정
space.gravity = (0, 980)
# pygame의 DrawOptions를 사용하여 화면에 그리기 위한 옵션 설정
draw_options = pymunk.pygame_util.DrawOptions(screen)
# 바닥 만들기(고정된 type이므로 STATIC으로 설정)
floor = pymunk.Body(body_type = pymunk.Body.STATIC)
floor.position = (0, size[1]-50)
floor_shape = pymunk.Segment(floor, (0,0), (size[0],-100), 1)
floor_shape.elasticity = 1 # 탄성 계수 (elasticity) 설정
floor_shape.friction = 0.2 # 마찰 계수 (friction) 설정
# space에 바닥을 추가
space.add(floor, floor_shape)
# 공 만들기 (움직이는 type이므로 별도 설정필요없음)
ball = pymunk.Body(1, 1)
ball.position = (int(size[0]/2)+500, 50)
ball_shape = pymunk.Circle(ball, 20)
space.add(ball, ball_shape)
# 4. 메인 이벤트 (main event)
running = True
while running:
# 4-1. FPS 설정 (frame per second)
clock.tick(60) # 메인 이벤트 반복이 1초에 60회 (60 frames per 1 second)
space.step(1/60) # 시뮬레이션 주기
# 4-2. 각종 입력 감지
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 4-3. 입력, 시간에 따른 변화 (change with event or time)
# 4-4. 그리기 (drawing)
screen.fill((0,0,0))
space.debug_draw(draw_options)
# 4-5. 업데이트 (update)
pygame.display.flip()
# 5. 게임 종료 (quit)
pygame.quit()
Python
복사
# 충돌 계수 추가
import pygame, pymunk, pymunk.pygame_util
# 1. 게임 초기화 (game initialize)
pygame.init()
# 2. 게임창 옵션 설정 (window option)
size = (1600, 1200) # 게임창 크기 (window size)
screen = pygame.display.set_mode(size)
title = "Physics Simulator" # 창 제목 (window title)
pygame.display.set_caption(title)
# 3. 게임 내 필요한 설정 (option for game)
clock = pygame.time.Clock() # 시계 (clock)
# 공간 만들기 (space)
space = pymunk.Space()
# 중력의 방향과 세기를 설정
space.gravity = (0, 980)
# pygame의 DrawOptions를 사용하여 화면에 그리기 위한 옵션 설정
draw_options = pymunk.pygame_util.DrawOptions(screen)
# 바닥 만들기(고정된 type이므로 STATIC으로 설정)
floor = pymunk.Body(body_type = pymunk.Body.STATIC)
floor.position = (0, size[1]-50)
floor_shape = pymunk.Segment(floor, (0,0), (size[0],-100), 1)
floor_shape.elasticity = 1 # 탄성 계수 (elasticity) 설정
floor_shape.friction = 0.2 # 마찰 계수 (friction) 설정
# space에 바닥을 추가
space.add(floor, floor_shape)
# 공 만들기 (움직이는 type이므로 별도 설정필요없음)
ball = pymunk.Body(1, 1)
ball.position = (int(size[0]/2)+500, 50)
ball_shape = pymunk.Circle(ball, 20)
ball_shape.elasticity = 0.5 # 탄성 계수 (elasticity) 설정
ball_shape.friction = 0.2 # 마찰 계수 (friction) 설정
space.add(ball, ball_shape)
# 4. 메인 이벤트 (main event)
running = True
while running:
# 4-1. FPS 설정 (frame per second)
clock.tick(60) # 메인 이벤트 반복이 1초에 60회 (60 frames per 1 second)
space.step(1/60) # 시뮬레이션 주기
# 4-2. 각종 입력 감지
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 4-3. 입력, 시간에 따른 변화 (change with event or time)
# 4-4. 그리기 (drawing)
screen.fill((0,0,0))
space.debug_draw(draw_options)
# 4-5. 업데이트 (update)
pygame.display.flip()
# 5. 게임 종료 (quit)
pygame.quit()
Python
복사
1. 실제 현상과 가장 가깝게 시뮬레이션 하기 위해서 추가할 것들은?
- 실제 중력가속도에 가깝게 조정
- 탄성계수를 실제와 비슷하게
- 공기저항 반영?
2. 공의 갯수를 늘리고
3. 각각의 공 자유낙하 시간을 측정하여 시각화!!

취한다!!
# 공기저항만 추가
import pygame, pymunk, pymunk.pygame_util
# 1. 게임 초기화 (game initialize)
pygame.init()
# 2. 게임창 옵션 설정 (window option)
size = (1600, 1200) # 게임창 크기 (window size)
screen = pygame.display.set_mode(size)
title = "Physics Simulator" # 창 제목 (window title)
pygame.display.set_caption(title)
# 3. 게임 내 필요한 설정 (option for game)
clock = pygame.time.Clock() # 시계 (clock)
# 공간 만들기 (space)
space = pymunk.Space()
# 중력의 방향과 세기를 설정
space.gravity = (0, 980)
# pygame의 DrawOptions를 사용하여 화면에 그리기 위한 옵션 설정
draw_options = pymunk.pygame_util.DrawOptions(screen)
# 바닥 만들기(고정된 type이므로 STATIC으로 설정)
floor = pymunk.Body(body_type = pymunk.Body.STATIC)
floor.position = (0, size[1]-50)
floor_shape = pymunk.Segment(floor, (0,0), (size[0],-100), 1)
floor_shape.elasticity = 0.7 # 탄성 계수 (elasticity) 설정
floor_shape.friction = 0.4 # 마찰 계수 (friction) 설정
# space에 바닥을 추가
space.add(floor, floor_shape)
# 공 만들기 (움직이는 type이므로 별도 설정필요없음)
ball = pymunk.Body(1, 1)
ball.position = (int(size[0]/2)+500, 50)
ball.damping = 0.99 # ✅ 공기저항 추가: 속도가 점점 느려짐
ball_shape = pymunk.Circle(ball, 20)
ball_shape.elasticity = 0.5 # 탄성 계수 (elasticity) 설정
ball_shape.friction = 0.2 # 마찰 계수 (friction) 설정
space.add(ball, ball_shape)
# 4. 메인 이벤트 (main event)
running = True
while running:
# 4-1. FPS 설정 (frame per second)
clock.tick(60) # 메인 이벤트 반복이 1초에 60회 (60 frames per 1 second)
space.step(1/60) # 시뮬레이션 주기
# 4-2. 각종 입력 감지
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# 4-3. 입력, 시간에 따른 변화 (change with event or time)
# 4-4. 그리기 (drawing)
screen.fill((0,0,0))
space.debug_draw(draw_options)
# 4-5. 업데이트 (update)
pygame.display.flip()
# 5. 게임 종료 (quit)
pygame.quit()
Python
복사
여러개의 공을 한번에 떨어뜨린다면?
import pygame, pymunk, pymunk.pygame_util
import random
# 1. 게임 초기화
pygame.init()
size = (1600, 1200)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("🌍 공기저항 + 튀는 공 10개")
clock = pygame.time.Clock()
# 2. 물리 공간 설정
space = pymunk.Space()
space.gravity = (0, 980)
draw_options = pymunk.pygame_util.DrawOptions(screen)
# 3. 바닥 생성
floor = pymunk.Body(body_type=pymunk.Body.STATIC)
floor.position = (0, size[1] - 50)
floor_shape = pymunk.Segment(floor, (0, 0), (size[0], -100), 1)
floor_shape.elasticity = 0.7 # 바닥도 튀도록 설정
floor_shape.friction = 0.4
space.add(floor, floor_shape)
# 4. 공 여러 개 생성
balls = []
for i in range(10):
mass = 1.0
radius = 20
moment = pymunk.moment_for_circle(mass, 0, radius)
body = pymunk.Body(mass, moment)
# 무작위 위치 설정 (X: 좌우, Y: 상단에서 일정 범위)
x = random.randint(100, size[0] - 100)
y = random.randint(50, 300)
body.position = (x, y)
body.damping = 0.99 # 공기저항
shape = pymunk.Circle(body, radius)
shape.elasticity = 0.7 # 반발력 충분히 높게 (0.8 이상이면 잘 튐)
shape.friction = 0.2
space.add(body, shape)
balls.append((body, shape))
# 5. 메인 루프
running = True
while running:
clock.tick(60)
space.step(1 / 60.0)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((0, 0, 0))
space.debug_draw(draw_options)
pygame.display.flip()
pygame.quit()
Python
복사
5.공의 개별 낙하시간 측정
[방법]
1.
10개의 공이 각각 바닥에 도달하는 데 걸린 낙하 시간(fall time)을 시각화
2.
따라서 개별공에 대한 추적(tracking)이 필요
•
공 생성시 초기 시간 기록 필요
•
공이 바닥에 충돌하는 순간 기록 필요
1.
핵심
•
pymunk의 collision handler 사용
space.add_collision_handler(ball_type, floor_type)
import pygame
import pymunk
import pymunk.pygame_util
import random
import time
import csv
from collections import defaultdict
Python
복사
# 1. Pygame 초기화 및 화면 설정
pygame.init()
size = (1600, 1200)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("🌍 공기저항 + 튀는 공 10개")
clock = pygame.time.Clock()
# 2. Pymunk 물리 공간 생성 및 중력 설정
space = pymunk.Space()
space.gravity = (0, 980)
draw_options = pymunk.pygame_util.DrawOptions(screen)
# 3. 바닥 생성
floor = pymunk.Body(body_type=pymunk.Body.STATIC)
floor.position = (0, size[1] - 50)
floor_shape = pymunk.Segment(floor, (0, 0), (size[0], -100), 1)
floor_shape.elasticity = 0.7 # 바닥 반발력 (공과의 충돌 시 반응)
floor_shape.friction = 0.4 # 마찰 계수
floor_shape.collision_type = 2 # 바닥의 충돌 타입 ID
space.add(floor, floor_shape)
# 4. 공 생성
balls = []
spawn_times = {} # 공 생성 시간 기록용
fall_times = defaultdict(float) # 공의 낙하 시간 저장용
ball_type = 1 # 공의 충돌 타입 ID
for i in range(10):
mass = 1.0
radius = 20
moment = pymunk.moment_for_circle(mass, 0, radius)
body = pymunk.Body(mass, moment)
# 공 위치 무작위 지정
x = random.randint(100, size[0] - 100)
y = random.randint(50, 300)
body.position = (x, y)
body.damping = 0.99 # 공기 저항 효과
shape = pymunk.Circle(body, radius)
shape.elasticity = 0.7
shape.friction = 0.2
shape.collision_type = ball_type
space.add(body, shape)
balls.append((body, shape))
spawn_times[id(shape)] = time.time() # 생성 시각 저장
# 5. 충돌 감지 핸들러: 공이 바닥에 처음 닿은 순간 기록
def on_collision(arbiter, space, data):
shape = arbiter.shapes[0] # 공 shape
sid = id(shape)
if fall_times[sid] == 0:
fall_times[sid] = time.time() - spawn_times[sid]
return True
# 충돌 이벤트 등록
handler = space.add_collision_handler(ball_type, 2)
handler.begin = on_collision
# 6. 메인 루프
running = True
done = False # 창을 닫을 때까지 기다리는 상태
while running:
clock.tick(60)
space.step(1 / 60.0)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
done = True
screen.fill((0, 0, 0))
space.debug_draw(draw_options)
pygame.display.flip()
# 모든 공의 낙하 시간이 기록되었는지 확인
if len(fall_times) == len(balls) and not done:
print("✅ 모든 공이 바닥에 닿음. 결과 기록 준비 완료.")
print("🔔 창을 닫아 결과를 저장하세요.")
done = True # 측정 완료 후 창 유지
pygame.quit()
# 7. 결과 CSV 저장
with open("fall_times.csv", "w", newline="") as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["Ball", "FallTime"])
for i, (body, shape) in enumerate(balls):
sid = id(shape)
writer.writerow([f"Ball {i+1}", round(fall_times[sid], 4)])
print("✅ 낙하 시간 측정 완료. 결과 저장: fall_times.csv")
Python
복사
✅ 모든 공이 바닥에 닿음. 결과 기록 준비 완료.
🔔 창을 닫아 결과를 저장하세요.
✅ 낙하 시간 측정 완료. 결과 저장: fall_times.csv
Plain Text
복사
# 시각화하기
import matplotlib.pyplot as plt
import csv
ball_ids = []
fall_times = []
# CSV 파일에서 데이터 읽기
with open("fall_times.csv", "r") as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
ball_ids.append(row["Ball"])
fall_times.append(float(row["FallTime"]))
# 시각화
plt.figure(figsize=(10, 5))
plt.bar(ball_ids, fall_times, color='orange', edgecolor='black')
plt.xlabel("Ball ID")
plt.ylabel("Fall Time (seconds)")
plt.title("Measured Fall Time for Each Ball")
plt.grid(axis='y', linestyle='--', alpha=0.6)
plt.tight_layout()
plt.show()
Python
복사
[자유낙하시간 시뮬레이션 시각화 결과]