If you need to create a 2D physics environment quickly and without bothering about C++ compilation issues, it's better to choose Pymunk over Box2D. The only issue with Pymunk however, is that there's currently only one person (the Pymunk author) answering questions about Pymunk on StackOverflow.
To get started, install Pymunk and Pygame:
pip install pygame
pip install pymunk
You can also install using pip3 if you use Python 3. Pymunk is used only for doing the 2D physics simulations. It is Pygame that sets up the environment to display the objects created in Pymunk. Instead of Pygame, you can also use Pyglet. Pyglet has features like multiple window support which Pygame doesn't have (yet), but the documentation is in my opinion, in need of improvement.
Do note that although Pymunk has a debug_draw() function to display Pymunk objects, the function is just there for the sake of being there. You are actually supposed to use Pygame's drawing functions to draw Pymunk objects. It's more efficient and supports culling of objects (not needing to draw objects outside screen bounds).
Simple program:
import sys
import pygame
from pygame.locals import * #for key codes etc
from pygame.color import *
import pymunk
import pymunk.pygame_util
fps = 60.0 #frames per second
worldWidth = 1200
worldHeight = 300
#---initializations and setup
pygame.init()
screen = pygame.display.set_mode((worldWidth, worldHeight))
clock = pygame.time.Clock()
space = pymunk.Space()
space.gravity = 0, 900
space.sleep_time_threshold = 1 #The time a group of bodies must remain idle in order to fall asleep
draw_options = pymunk.pygame_util.DrawOptions(screen)
pymunk.pygame_util.positive_y_is_up = False
#---setup line segments in space
x = 10; y = 150; thickness = 2; friction = 1.0
points = [(-100,0),(100,0),(112,12),(125,0),(137,12),(150,0),(162,12),(175,0),(200,0),(250,25),(250,0),(315,0),(320,70),(375,70),(385,0),(425,0),(425,12),(448,12),(448,0),(475,0),(475,12),(485,12),(485,0),(512,0),(512,12),(527,12),(527,0),(590,0),(590,10),(610,10),(610,15),(625,15),(625,25),(646,25),(646,35),(660,35),(660,45),(680,48),(710,-10),(755,5),(755,20),(780,25),(850,25),(825,-20),(865,-20),(895,25),(925,25),(935,0),(980,-75),(1100,-75)];
for i in range(1, len(points)):
floor = pymunk.Segment(space.static_body, (x+points[i-1][0], y+points[i-1][1]), (x+points[i][0], y+points[i][1]), thickness)
floor.friction = friction
space.add(floor)
while True:
for event in pygame.event.get():
if event.type == QUIT or event.type == KEYDOWN and (event.key in [K_ESCAPE, K_q]):
sys.exit(0)
#---updations
space.step(1./fps)#Update the space for the given time step.
screen.fill(pygame.color.THECOLORS["black"])#clear screen
space.debug_draw(draw_options)#try using Pygame's drawing functions instead
pygame.display.flip()#flips the draw buffer to display the objects in current frame
dt = clock.tick(fps)#creates a delay
Adding ten cars to the space:
You'll notice the cars collide with each other. That's fixed in the next code example below.
import sys
import pygame
from pygame.locals import * #for key codes etc
from pygame.color import *
import pymunk
import pymunk.pygame_util
from pymunk import Vec2d
import random
class Car:
space = None
physics = None
x = 0
y = 0
friction = 1.5
#---range of values possible for properties of car
speed_range = range(10, 30, 5)#15
chWd_range = range(5, 70, 5)#50#chassis width
chHt_range = range(5, 50, 5)#10#chassis height
wheel1Radius_range = range(6, 25, 5)#15
wheel2Radius_range = range(6, 25, 5)#15
chassisMass_range = range(10, 500, 50)#100
wheel1Mass_range = range(10, 500, 50)#100
wheel2Mass_range = range(10, 500, 50)#100
#---properties of car
speed = 0
chWd = 0#chassis width
chHt = 0#chassis height
wheel1Radius = 0
wheel2Radius = 0
chassisMass = 0
wheel1Mass = 0
wheel2Mass = 0
#---parts to add or remove from space
chassis_b = None
chassis_s = None
wheel1_b = None
wheel1_s = None
wheel2_b = None
wheel2_s = None
pin1 = None
pin2 = None
pin3 = None
pin4 = None
motorJoint1 = None
motorJoint2 = None
def __init__(self, space, pymunk, xStartPos, yStartPos):
self.space = space
self.pymunk = pymunk
self.x = xStartPos
self.y = yStartPos
self.reinitializeWithRandomValues()
def reinitializeWithRandomValues(self):
self.speed = random.choice(self.speed_range)
self.chWd = random.choice(self.chWd_range)
self.chHt = random.choice(self.chHt_range)
self.wheel1Radius = random.choice(self.wheel1Radius_range)
self.wheel2Radius = random.choice(self.wheel2Radius_range)
self.chassisMass = random.choice(self.chassisMass_range)
self.wheel1Mass = random.choice(self.wheel1Mass_range)
self.wheel2Mass = random.choice(self.wheel2Mass_range)
def setValues(self, v):
i = 0
self.speed = v[i]; i = i + 1
if self.speed < self.speed_range[0] or self.speed > self.speed_range[-1]:
self.speed = random.choice(self.speed_range)
self.chWd = v[i]; i = i + 1
if self.chWd < self.chWd_range[0] or self.chWd > self.chWd_range[-1]:
self.chWd = random.choice(self.chWd_range)
self.chHt = v[i]; i = i + 1
if self.chHt < self.chHt_range[0] or self.chHt > self.chHt_range[-1]:
self.chHt = random.choice(self.chHt_range)
self.wheel1Radius = v[i]; i = i + 1
if self.wheel1Radius < self.wheel1Radius_range[0] or self.wheel1Radius > self.wheel1Radius_range[-1]:
self.wheel1Radius = random.choice(self.wheel1Radius_range)
self.wheel2Radius = v[i]; i = i + 1
if self.wheel2Radius < self.wheel2Radius_range[0] or self.wheel2Radius > self.wheel2Radius_range[-1]:
self.wheel2Radius = random.choice(self.wheel2Radius_range)
self.chassisMass = v[i]; i = i + 1
if self.chassisMass < self.chassisMass_range[0] or self.chassisMass > self.chassisMass_range[-1]:
self.chassisMass = random.choice(self.chassisMass_range)
self.wheel1Mass = v[i]; i = i + 1
if self.wheel1Mass < self.wheel1Mass_range[0] or self.wheel1Mass > self.wheel1Mass_range[-1]:
self.wheel1Mass = random.choice(self.wheel1Mass_range)
self.wheel2Mass = v[i]; i = i + 1
if self.wheel2Mass < self.wheel2Mass_range[0] or self.wheel2Mass > self.wheel2Mass_range[-1]:
self.wheel2Mass = random.choice(self.wheel2Mass_range)
def createCar(self):
chassisXY = Vec2d(self.x, self.y)
self.fitness = 0
moment = self.pymunk.moment_for_box(self.chassisMass, (self.chWd, self.chHt))
self.chassis_b = self.pymunk.Body(self.chassisMass, moment)
self.chassis_s = self.pymunk.Poly.create_box(self.chassis_b, (self.chWd, self.chHt))
self.chassis_s.color = 10,150,40
self.chassis_b.position = chassisXY + (0, 0)
self.space.add(self.chassis_b, self.chassis_s)
#---wheel1 (left side wheel)
#w1 = self.addChassis(chassisXY, -chWd, 0, mass, 5, 30) #special rectangular wheel
moment = self.pymunk.moment_for_circle(self.wheel1Mass, 0, self.wheel1Radius)
self.wheel1_b = self.pymunk.Body(self.wheel1Mass, moment)
self.wheel1_s = self.pymunk.Circle(self.wheel1_b, self.wheel1Radius)
self.wheel1_s.friction = self.friction
self.wheel1_s.color = 180,180,180
self.wheel1_b.position = chassisXY - (self.chWd, 0)
self.space.add(self.wheel1_b, self.wheel1_s)
#---wheel2 (right side wheel)
#w1 = self.addChassis(chassisXY, -chWd, 0, mass, 5, 30) #special rectangular wheel
moment = self.pymunk.moment_for_circle(self.wheel2Mass, 0, self.wheel2Radius)
self.wheel2_b = self.pymunk.Body(self.wheel2Mass, moment)
self.wheel2_s = self.pymunk.Circle(self.wheel2_b, self.wheel2Radius)
self.wheel2_s.friction = self.friction
self.wheel2_s.color = 180,180,180
self.wheel2_b.position = chassisXY - (-self.chWd, 0)
self.space.add(self.wheel2_b, self.wheel2_s)
self.pin1 = self.pymunk.PinJoint(self.wheel1_b, self.chassis_b, (0,0), (-self.chWd/2,0))
self.pin2 = self.pymunk.PinJoint(self.wheel2_b, self.chassis_b, (0,0), (self.chWd/2,0))
self.pin3 = self.pymunk.PinJoint(self.wheel1_b, self.chassis_b, (0,0), (0,-self.chHt/2))
self.pin4 = self.pymunk.PinJoint(self.wheel2_b, self.chassis_b, (0,0), (0,-self.chHt/2))
self.space.add(self.pin1, self.pin2, self.pin3, self.pin4)
self.motorJoint1 = self.pymunk.SimpleMotor(self.wheel1_b, self.chassis_b, self.speed); self.motorJoint1.max_force = 10000000
self.motorJoint2 = self.pymunk.SimpleMotor(self.wheel2_b, self.chassis_b, self.speed); self.motorJoint2.max_force = 10000000
self.space.add(self.motorJoint1, self.motorJoint2)
def stop(self):
self.motorJoint1.rate(0.0)
self.motorJoint2.rate(0.0)
def removeCar(self):
self.space.remove(self.chassis_b)
self.space.remove(self.chassis_s)
self.space.remove(self.wheel1_b)
self.space.remove(self.wheel1_s)
self.space.remove(self.wheel2_b)
self.space.remove(self.wheel2_s)
self.space.remove(self.pin1)
self.space.remove(self.pin2)
self.space.remove(self.pin3)
self.space.remove(self.pin4)
self.space.remove(self.motorJoint1)
self.space.remove(self.motorJoint2)
fps = 60.0 #frames per second
worldWidth = 1200
worldHeight = 300
#---initializations and setup
pygame.init()
screen = pygame.display.set_mode((worldWidth, worldHeight))
clock = pygame.time.Clock()
space = pymunk.Space()
space.gravity = 0, 900
space.sleep_time_threshold = 1 #The time a group of bodies must remain idle in order to fall asleep
draw_options = pymunk.pygame_util.DrawOptions(screen)
pymunk.pygame_util.positive_y_is_up = False
#---setup line segments in space
x = 10; y = 200; thickness = 2; friction = 1.0
points = [(-100,0),(100,0),(112,12),(125,0),(137,12),(150,0),(162,12),(175,0),(200,0),(250,25),(250,0),(315,0),(320,70),(375,70),(385,0),(425,0),(425,12),(448,12),(448,0),(475,0),(475,12),(485,12),(485,0),(512,0),(512,12),(527,12),(527,0),(590,0),(590,10),(610,10),(610,15),(625,15),(625,25),(646,25),(646,35),(660,35),(660,45),(680,48),(710,-10),(755,5),(755,20),(780,25),(850,25),(825,-20),(865,-20),(895,25),(925,25),(935,0),(980,-75),(1100,-75)];
for i in range(1, len(points)):
floor = pymunk.Segment(space.static_body, (x+points[i-1][0], y+points[i-1][1]), (x+points[i][0], y+points[i][1]), thickness)
floor.friction = friction
space.add(floor)
#---create cars
xStartPos = 20; yStartPos = 0; numCars = 10; cars = []
for i in range(0, numCars):
cars.append(Car(space, pymunk, xStartPos, yStartPos))
cars[i].createCar()
while True:
for event in pygame.event.get():
if event.type == QUIT or event.type == KEYDOWN and (event.key in [K_ESCAPE, K_q]):
sys.exit(0)
#---updations
space.step(1./fps)#Update the space for the given time step.
screen.fill(pygame.color.THECOLORS["black"])#clear screen
space.debug_draw(draw_options)#try using Pygame's drawing functions instead
pygame.display.flip()#flips the draw buffer to display the objects in current frame
dt = clock.tick(fps)#creates a delay
You'll notice that the cars collide with each other and the wheels of the cars collide with the car's own body too. There's a way to avoid that using ShapeFilters. The shape filter helps categorize objects into "player", "enemy" etc. to help decide which objects should collide and which shouldn't. This is very helpful because if the parts of the body of players collide, it produces very jittery motion.
Cars with ShapeFilters and text display:
import sys
import time
import pygame
from pygame.locals import * #for key codes etc
from pygame.color import *
import pymunk
import pymunk.pygame_util
from pymunk import Vec2d
import random
class Car:
space = None
physics = None
x = 0
y = 0
friction = 1.5
#---range of values possible for properties of car
speed_range = range(10, 30, 5)#15
chWd_range = range(5, 70, 5)#50#chassis width
chHt_range = range(5, 50, 5)#10#chassis height
wheel1Radius_range = range(6, 25, 5)#15
wheel2Radius_range = range(6, 25, 5)#15
chassisMass_range = range(10, 500, 50)#100
wheel1Mass_range = range(10, 500, 50)#100
wheel2Mass_range = range(10, 500, 50)#100
#---properties of car
speed = 0
chWd = 0#chassis width
chHt = 0#chassis height
wheel1Radius = 0
wheel2Radius = 0
chassisMass = 0
wheel1Mass = 0
wheel2Mass = 0
#---parts to add or remove from space
chassis_b = None
chassis_s = None
wheel1_b = None
wheel1_s = None
wheel2_b = None
wheel2_s = None
pin1 = None
pin2 = None
pin3 = None
pin4 = None
motorJoint1 = None
motorJoint2 = None
def __init__(self, space, pymunk, xStartPos, yStartPos):
self.space = space
self.pymunk = pymunk
self.x = xStartPos
self.y = yStartPos
self.reinitializeWithRandomValues()
def reinitializeWithRandomValues(self):
self.speed = random.choice(self.speed_range)
self.chWd = random.choice(self.chWd_range)
self.chHt = random.choice(self.chHt_range)
self.wheel1Radius = random.choice(self.wheel1Radius_range)
self.wheel2Radius = random.choice(self.wheel2Radius_range)
self.chassisMass = random.choice(self.chassisMass_range)
self.wheel1Mass = random.choice(self.wheel1Mass_range)
self.wheel2Mass = random.choice(self.wheel2Mass_range)
def setValues(self, v):
i = 0
self.speed = v[i]; i = i + 1
if self.speed < self.speed_range[0] or self.speed > self.speed_range[-1]:
self.speed = random.choice(self.speed_range)
self.chWd = v[i]; i = i + 1
if self.chWd < self.chWd_range[0] or self.chWd > self.chWd_range[-1]:
self.chWd = random.choice(self.chWd_range)
self.chHt = v[i]; i = i + 1
if self.chHt < self.chHt_range[0] or self.chHt > self.chHt_range[-1]:
self.chHt = random.choice(self.chHt_range)
self.wheel1Radius = v[i]; i = i + 1
if self.wheel1Radius < self.wheel1Radius_range[0] or self.wheel1Radius > self.wheel1Radius_range[-1]:
self.wheel1Radius = random.choice(self.wheel1Radius_range)
self.wheel2Radius = v[i]; i = i + 1
if self.wheel2Radius < self.wheel2Radius_range[0] or self.wheel2Radius > self.wheel2Radius_range[-1]:
self.wheel2Radius = random.choice(self.wheel2Radius_range)
self.chassisMass = v[i]; i = i + 1
if self.chassisMass < self.chassisMass_range[0] or self.chassisMass > self.chassisMass_range[-1]:
self.chassisMass = random.choice(self.chassisMass_range)
self.wheel1Mass = v[i]; i = i + 1
if self.wheel1Mass < self.wheel1Mass_range[0] or self.wheel1Mass > self.wheel1Mass_range[-1]:
self.wheel1Mass = random.choice(self.wheel1Mass_range)
self.wheel2Mass = v[i]; i = i + 1
if self.wheel2Mass < self.wheel2Mass_range[0] or self.wheel2Mass > self.wheel2Mass_range[-1]:
self.wheel2Mass = random.choice(self.wheel2Mass_range)
def createCar(self, shapeFilter):
chassisXY = Vec2d(self.x, self.y)
self.fitness = 0
moment = self.pymunk.moment_for_box(self.chassisMass, (self.chWd, self.chHt))
self.chassis_b = self.pymunk.Body(self.chassisMass, moment)
self.chassis_s = self.pymunk.Poly.create_box(self.chassis_b, (self.chWd, self.chHt))
self.chassis_s.color = 10,150,40
self.chassis_b.position = chassisXY + (0, 0)
self.chassis_s.filter = shapeFilter
self.space.add(self.chassis_b, self.chassis_s)
#---wheel1 (left side wheel)
moment = self.pymunk.moment_for_circle(self.wheel1Mass, 0, self.wheel1Radius)
self.wheel1_b = self.pymunk.Body(self.wheel1Mass, moment)
self.wheel1_s = self.pymunk.Circle(self.wheel1_b, self.wheel1Radius)
self.wheel1_s.friction = self.friction
self.wheel1_s.color = 180,180,180
self.wheel1_b.position = chassisXY - (self.chWd, 0)
self.wheel1_s.filter = shapeFilter
self.space.add(self.wheel1_b, self.wheel1_s)
#---wheel2 (right side wheel)
moment = self.pymunk.moment_for_circle(self.wheel2Mass, 0, self.wheel2Radius)
self.wheel2_b = self.pymunk.Body(self.wheel2Mass, moment)
self.wheel2_s = self.pymunk.Circle(self.wheel2_b, self.wheel2Radius)
self.wheel2_s.friction = self.friction
self.wheel2_s.color = 180,180,180
self.wheel2_b.position = chassisXY - (-self.chWd, 0)
self.wheel2_s.filter = shapeFilter
self.space.add(self.wheel2_b, self.wheel2_s)
self.pin1 = self.pymunk.PinJoint(self.wheel1_b, self.chassis_b, (0,0), (-self.chWd/2,0))
self.pin2 = self.pymunk.PinJoint(self.wheel2_b, self.chassis_b, (0,0), (self.chWd/2,0))
self.pin3 = self.pymunk.PinJoint(self.wheel1_b, self.chassis_b, (0,0), (0,-self.chHt/2))
self.pin4 = self.pymunk.PinJoint(self.wheel2_b, self.chassis_b, (0,0), (0,-self.chHt/2))
self.space.add(self.pin1, self.pin2, self.pin3, self.pin4)
self.motorJoint1 = self.pymunk.SimpleMotor(self.wheel1_b, self.chassis_b, self.speed); self.motorJoint1.max_force = 10000000
self.motorJoint2 = self.pymunk.SimpleMotor(self.wheel2_b, self.chassis_b, self.speed); self.motorJoint2.max_force = 10000000
self.space.add(self.motorJoint1, self.motorJoint2)
def stop(self):
self.motorJoint1.rate(0.0)
self.motorJoint2.rate(0.0)
def removeCar(self):
self.space.remove(self.chassis_b)
self.space.remove(self.chassis_s)
self.space.remove(self.wheel1_b)
self.space.remove(self.wheel1_s)
self.space.remove(self.wheel2_b)
self.space.remove(self.wheel2_s)
self.space.remove(self.pin1)
self.space.remove(self.pin2)
self.space.remove(self.pin3)
self.space.remove(self.pin4)
self.space.remove(self.motorJoint1)
self.space.remove(self.motorJoint2)
fps = 60.0 #frames per second
worldWidth = 1200
worldHeight = 300
#---initializations and setup
pygame.init()
screen = pygame.display.set_mode((worldWidth, worldHeight))
clock = pygame.time.Clock()
space = pymunk.Space()
space.gravity = 0, 900
space.sleep_time_threshold = 1 #The time a group of bodies must remain idle in order to fall asleep
draw_options = pymunk.pygame_util.DrawOptions(screen)
pymunk.pygame_util.positive_y_is_up = False
font = pygame.font.SysFont("Arial", 20)
#---setup line segments in space
x = 10; y = 200; thickness = 2; friction = 1.0
points = [(-100,0),(100,0),(112,12),(125,0),(137,12),(150,0),(162,12),(175,0),(200,0),(250,25),(250,0),(315,0),(320,70),(375,70),(385,0),(425,0),(425,12),(448,12),(448,0),(475,0),(475,12),(485,12),(485,0),(512,0),(512,12),(527,12),(527,0),(590,0),(590,10),(610,10),(610,15),(625,15),(625,25),(646,25),(646,35),(660,35),(660,45),(680,48),(710,-10),(755,5),(755,20),(780,25),(850,25),(825,-20),(865,-20),(895,25),(925,25),(935,0),(980,-75),(1100,-75)];
for i in range(1, len(points)):
floor = pymunk.Segment(space.static_body, (x+points[i-1][0], y+points[i-1][1]), (x+points[i][0], y+points[i][1]), thickness)
floor.friction = friction
space.add(floor)
#---create cars
xStartPos = 20; yStartPos = 0; numCars = 10; cars = []
sf = pymunk.ShapeFilter(group=1)
for i in range(0, numCars):
cars.append(Car(space, pymunk, xStartPos, yStartPos))
cars[i].createCar(sf)
while True:
for event in pygame.event.get():
if event.type == QUIT or event.type == KEYDOWN and (event.key in [K_ESCAPE, K_q]):
sys.exit(0)
#---updations
space.step(1./fps)#Update the space for the given time step.
screen.fill(pygame.color.THECOLORS["black"])#clear screen
space.debug_draw(draw_options)#try using Pygame's drawing functions instead
screen.blit(font.render("Cars: "+str(numCars)+". Time: "+str(time.time()), 1, THECOLORS["darkgrey"]), (5, 5))
pygame.display.flip()#flips the draw buffer to display the objects in current frame
dt = clock.tick(fps)#creates a delay
Hope this gives you an easy-to-understand intro to the awesomeness of Pymunk. I've created a slightly more advanced version of the above code, where the cars try to reach the end of the terrain using learning via an evolutionary algorithm.
I'm not ready to share the code yet, but when I am, I'll be posting the link on this page.
Here's the demo on YouTube:
Few other things:
To get started, install Pymunk and Pygame:
pip install pygame
pip install pymunk
You can also install using pip3 if you use Python 3. Pymunk is used only for doing the 2D physics simulations. It is Pygame that sets up the environment to display the objects created in Pymunk. Instead of Pygame, you can also use Pyglet. Pyglet has features like multiple window support which Pygame doesn't have (yet), but the documentation is in my opinion, in need of improvement.
Do note that although Pymunk has a debug_draw() function to display Pymunk objects, the function is just there for the sake of being there. You are actually supposed to use Pygame's drawing functions to draw Pymunk objects. It's more efficient and supports culling of objects (not needing to draw objects outside screen bounds).
Simple program:
import sys
import pygame
from pygame.locals import * #for key codes etc
from pygame.color import *
import pymunk
import pymunk.pygame_util
fps = 60.0 #frames per second
worldWidth = 1200
worldHeight = 300
#---initializations and setup
pygame.init()
screen = pygame.display.set_mode((worldWidth, worldHeight))
clock = pygame.time.Clock()
space = pymunk.Space()
space.gravity = 0, 900
space.sleep_time_threshold = 1 #The time a group of bodies must remain idle in order to fall asleep
draw_options = pymunk.pygame_util.DrawOptions(screen)
pymunk.pygame_util.positive_y_is_up = False
#---setup line segments in space
x = 10; y = 150; thickness = 2; friction = 1.0
points = [(-100,0),(100,0),(112,12),(125,0),(137,12),(150,0),(162,12),(175,0),(200,0),(250,25),(250,0),(315,0),(320,70),(375,70),(385,0),(425,0),(425,12),(448,12),(448,0),(475,0),(475,12),(485,12),(485,0),(512,0),(512,12),(527,12),(527,0),(590,0),(590,10),(610,10),(610,15),(625,15),(625,25),(646,25),(646,35),(660,35),(660,45),(680,48),(710,-10),(755,5),(755,20),(780,25),(850,25),(825,-20),(865,-20),(895,25),(925,25),(935,0),(980,-75),(1100,-75)];
for i in range(1, len(points)):
floor = pymunk.Segment(space.static_body, (x+points[i-1][0], y+points[i-1][1]), (x+points[i][0], y+points[i][1]), thickness)
floor.friction = friction
space.add(floor)
while True:
for event in pygame.event.get():
if event.type == QUIT or event.type == KEYDOWN and (event.key in [K_ESCAPE, K_q]):
sys.exit(0)
#---updations
space.step(1./fps)#Update the space for the given time step.
screen.fill(pygame.color.THECOLORS["black"])#clear screen
space.debug_draw(draw_options)#try using Pygame's drawing functions instead
pygame.display.flip()#flips the draw buffer to display the objects in current frame
dt = clock.tick(fps)#creates a delay
Adding ten cars to the space:
You'll notice the cars collide with each other. That's fixed in the next code example below.
import sys
import pygame
from pygame.locals import * #for key codes etc
from pygame.color import *
import pymunk
import pymunk.pygame_util
from pymunk import Vec2d
import random
class Car:
space = None
physics = None
x = 0
y = 0
friction = 1.5
#---range of values possible for properties of car
speed_range = range(10, 30, 5)#15
chWd_range = range(5, 70, 5)#50#chassis width
chHt_range = range(5, 50, 5)#10#chassis height
wheel1Radius_range = range(6, 25, 5)#15
wheel2Radius_range = range(6, 25, 5)#15
chassisMass_range = range(10, 500, 50)#100
wheel1Mass_range = range(10, 500, 50)#100
wheel2Mass_range = range(10, 500, 50)#100
#---properties of car
speed = 0
chWd = 0#chassis width
chHt = 0#chassis height
wheel1Radius = 0
wheel2Radius = 0
chassisMass = 0
wheel1Mass = 0
wheel2Mass = 0
#---parts to add or remove from space
chassis_b = None
chassis_s = None
wheel1_b = None
wheel1_s = None
wheel2_b = None
wheel2_s = None
pin1 = None
pin2 = None
pin3 = None
pin4 = None
motorJoint1 = None
motorJoint2 = None
def __init__(self, space, pymunk, xStartPos, yStartPos):
self.space = space
self.pymunk = pymunk
self.x = xStartPos
self.y = yStartPos
self.reinitializeWithRandomValues()
def reinitializeWithRandomValues(self):
self.speed = random.choice(self.speed_range)
self.chWd = random.choice(self.chWd_range)
self.chHt = random.choice(self.chHt_range)
self.wheel1Radius = random.choice(self.wheel1Radius_range)
self.wheel2Radius = random.choice(self.wheel2Radius_range)
self.chassisMass = random.choice(self.chassisMass_range)
self.wheel1Mass = random.choice(self.wheel1Mass_range)
self.wheel2Mass = random.choice(self.wheel2Mass_range)
def setValues(self, v):
i = 0
self.speed = v[i]; i = i + 1
if self.speed < self.speed_range[0] or self.speed > self.speed_range[-1]:
self.speed = random.choice(self.speed_range)
self.chWd = v[i]; i = i + 1
if self.chWd < self.chWd_range[0] or self.chWd > self.chWd_range[-1]:
self.chWd = random.choice(self.chWd_range)
self.chHt = v[i]; i = i + 1
if self.chHt < self.chHt_range[0] or self.chHt > self.chHt_range[-1]:
self.chHt = random.choice(self.chHt_range)
self.wheel1Radius = v[i]; i = i + 1
if self.wheel1Radius < self.wheel1Radius_range[0] or self.wheel1Radius > self.wheel1Radius_range[-1]:
self.wheel1Radius = random.choice(self.wheel1Radius_range)
self.wheel2Radius = v[i]; i = i + 1
if self.wheel2Radius < self.wheel2Radius_range[0] or self.wheel2Radius > self.wheel2Radius_range[-1]:
self.wheel2Radius = random.choice(self.wheel2Radius_range)
self.chassisMass = v[i]; i = i + 1
if self.chassisMass < self.chassisMass_range[0] or self.chassisMass > self.chassisMass_range[-1]:
self.chassisMass = random.choice(self.chassisMass_range)
self.wheel1Mass = v[i]; i = i + 1
if self.wheel1Mass < self.wheel1Mass_range[0] or self.wheel1Mass > self.wheel1Mass_range[-1]:
self.wheel1Mass = random.choice(self.wheel1Mass_range)
self.wheel2Mass = v[i]; i = i + 1
if self.wheel2Mass < self.wheel2Mass_range[0] or self.wheel2Mass > self.wheel2Mass_range[-1]:
self.wheel2Mass = random.choice(self.wheel2Mass_range)
def createCar(self):
chassisXY = Vec2d(self.x, self.y)
self.fitness = 0
moment = self.pymunk.moment_for_box(self.chassisMass, (self.chWd, self.chHt))
self.chassis_b = self.pymunk.Body(self.chassisMass, moment)
self.chassis_s = self.pymunk.Poly.create_box(self.chassis_b, (self.chWd, self.chHt))
self.chassis_s.color = 10,150,40
self.chassis_b.position = chassisXY + (0, 0)
self.space.add(self.chassis_b, self.chassis_s)
#---wheel1 (left side wheel)
#w1 = self.addChassis(chassisXY, -chWd, 0, mass, 5, 30) #special rectangular wheel
moment = self.pymunk.moment_for_circle(self.wheel1Mass, 0, self.wheel1Radius)
self.wheel1_b = self.pymunk.Body(self.wheel1Mass, moment)
self.wheel1_s = self.pymunk.Circle(self.wheel1_b, self.wheel1Radius)
self.wheel1_s.friction = self.friction
self.wheel1_s.color = 180,180,180
self.wheel1_b.position = chassisXY - (self.chWd, 0)
self.space.add(self.wheel1_b, self.wheel1_s)
#---wheel2 (right side wheel)
#w1 = self.addChassis(chassisXY, -chWd, 0, mass, 5, 30) #special rectangular wheel
moment = self.pymunk.moment_for_circle(self.wheel2Mass, 0, self.wheel2Radius)
self.wheel2_b = self.pymunk.Body(self.wheel2Mass, moment)
self.wheel2_s = self.pymunk.Circle(self.wheel2_b, self.wheel2Radius)
self.wheel2_s.friction = self.friction
self.wheel2_s.color = 180,180,180
self.wheel2_b.position = chassisXY - (-self.chWd, 0)
self.space.add(self.wheel2_b, self.wheel2_s)
self.pin1 = self.pymunk.PinJoint(self.wheel1_b, self.chassis_b, (0,0), (-self.chWd/2,0))
self.pin2 = self.pymunk.PinJoint(self.wheel2_b, self.chassis_b, (0,0), (self.chWd/2,0))
self.pin3 = self.pymunk.PinJoint(self.wheel1_b, self.chassis_b, (0,0), (0,-self.chHt/2))
self.pin4 = self.pymunk.PinJoint(self.wheel2_b, self.chassis_b, (0,0), (0,-self.chHt/2))
self.space.add(self.pin1, self.pin2, self.pin3, self.pin4)
self.motorJoint1 = self.pymunk.SimpleMotor(self.wheel1_b, self.chassis_b, self.speed); self.motorJoint1.max_force = 10000000
self.motorJoint2 = self.pymunk.SimpleMotor(self.wheel2_b, self.chassis_b, self.speed); self.motorJoint2.max_force = 10000000
self.space.add(self.motorJoint1, self.motorJoint2)
def stop(self):
self.motorJoint1.rate(0.0)
self.motorJoint2.rate(0.0)
def removeCar(self):
self.space.remove(self.chassis_b)
self.space.remove(self.chassis_s)
self.space.remove(self.wheel1_b)
self.space.remove(self.wheel1_s)
self.space.remove(self.wheel2_b)
self.space.remove(self.wheel2_s)
self.space.remove(self.pin1)
self.space.remove(self.pin2)
self.space.remove(self.pin3)
self.space.remove(self.pin4)
self.space.remove(self.motorJoint1)
self.space.remove(self.motorJoint2)
fps = 60.0 #frames per second
worldWidth = 1200
worldHeight = 300
#---initializations and setup
pygame.init()
screen = pygame.display.set_mode((worldWidth, worldHeight))
clock = pygame.time.Clock()
space = pymunk.Space()
space.gravity = 0, 900
space.sleep_time_threshold = 1 #The time a group of bodies must remain idle in order to fall asleep
draw_options = pymunk.pygame_util.DrawOptions(screen)
pymunk.pygame_util.positive_y_is_up = False
#---setup line segments in space
x = 10; y = 200; thickness = 2; friction = 1.0
points = [(-100,0),(100,0),(112,12),(125,0),(137,12),(150,0),(162,12),(175,0),(200,0),(250,25),(250,0),(315,0),(320,70),(375,70),(385,0),(425,0),(425,12),(448,12),(448,0),(475,0),(475,12),(485,12),(485,0),(512,0),(512,12),(527,12),(527,0),(590,0),(590,10),(610,10),(610,15),(625,15),(625,25),(646,25),(646,35),(660,35),(660,45),(680,48),(710,-10),(755,5),(755,20),(780,25),(850,25),(825,-20),(865,-20),(895,25),(925,25),(935,0),(980,-75),(1100,-75)];
for i in range(1, len(points)):
floor = pymunk.Segment(space.static_body, (x+points[i-1][0], y+points[i-1][1]), (x+points[i][0], y+points[i][1]), thickness)
floor.friction = friction
space.add(floor)
#---create cars
xStartPos = 20; yStartPos = 0; numCars = 10; cars = []
for i in range(0, numCars):
cars.append(Car(space, pymunk, xStartPos, yStartPos))
cars[i].createCar()
while True:
for event in pygame.event.get():
if event.type == QUIT or event.type == KEYDOWN and (event.key in [K_ESCAPE, K_q]):
sys.exit(0)
#---updations
space.step(1./fps)#Update the space for the given time step.
screen.fill(pygame.color.THECOLORS["black"])#clear screen
space.debug_draw(draw_options)#try using Pygame's drawing functions instead
pygame.display.flip()#flips the draw buffer to display the objects in current frame
dt = clock.tick(fps)#creates a delay
You'll notice that the cars collide with each other and the wheels of the cars collide with the car's own body too. There's a way to avoid that using ShapeFilters. The shape filter helps categorize objects into "player", "enemy" etc. to help decide which objects should collide and which shouldn't. This is very helpful because if the parts of the body of players collide, it produces very jittery motion.
Cars with ShapeFilters and text display:
import sys
import time
import pygame
from pygame.locals import * #for key codes etc
from pygame.color import *
import pymunk
import pymunk.pygame_util
from pymunk import Vec2d
import random
class Car:
space = None
physics = None
x = 0
y = 0
friction = 1.5
#---range of values possible for properties of car
speed_range = range(10, 30, 5)#15
chWd_range = range(5, 70, 5)#50#chassis width
chHt_range = range(5, 50, 5)#10#chassis height
wheel1Radius_range = range(6, 25, 5)#15
wheel2Radius_range = range(6, 25, 5)#15
chassisMass_range = range(10, 500, 50)#100
wheel1Mass_range = range(10, 500, 50)#100
wheel2Mass_range = range(10, 500, 50)#100
#---properties of car
speed = 0
chWd = 0#chassis width
chHt = 0#chassis height
wheel1Radius = 0
wheel2Radius = 0
chassisMass = 0
wheel1Mass = 0
wheel2Mass = 0
#---parts to add or remove from space
chassis_b = None
chassis_s = None
wheel1_b = None
wheel1_s = None
wheel2_b = None
wheel2_s = None
pin1 = None
pin2 = None
pin3 = None
pin4 = None
motorJoint1 = None
motorJoint2 = None
def __init__(self, space, pymunk, xStartPos, yStartPos):
self.space = space
self.pymunk = pymunk
self.x = xStartPos
self.y = yStartPos
self.reinitializeWithRandomValues()
def reinitializeWithRandomValues(self):
self.speed = random.choice(self.speed_range)
self.chWd = random.choice(self.chWd_range)
self.chHt = random.choice(self.chHt_range)
self.wheel1Radius = random.choice(self.wheel1Radius_range)
self.wheel2Radius = random.choice(self.wheel2Radius_range)
self.chassisMass = random.choice(self.chassisMass_range)
self.wheel1Mass = random.choice(self.wheel1Mass_range)
self.wheel2Mass = random.choice(self.wheel2Mass_range)
def setValues(self, v):
i = 0
self.speed = v[i]; i = i + 1
if self.speed < self.speed_range[0] or self.speed > self.speed_range[-1]:
self.speed = random.choice(self.speed_range)
self.chWd = v[i]; i = i + 1
if self.chWd < self.chWd_range[0] or self.chWd > self.chWd_range[-1]:
self.chWd = random.choice(self.chWd_range)
self.chHt = v[i]; i = i + 1
if self.chHt < self.chHt_range[0] or self.chHt > self.chHt_range[-1]:
self.chHt = random.choice(self.chHt_range)
self.wheel1Radius = v[i]; i = i + 1
if self.wheel1Radius < self.wheel1Radius_range[0] or self.wheel1Radius > self.wheel1Radius_range[-1]:
self.wheel1Radius = random.choice(self.wheel1Radius_range)
self.wheel2Radius = v[i]; i = i + 1
if self.wheel2Radius < self.wheel2Radius_range[0] or self.wheel2Radius > self.wheel2Radius_range[-1]:
self.wheel2Radius = random.choice(self.wheel2Radius_range)
self.chassisMass = v[i]; i = i + 1
if self.chassisMass < self.chassisMass_range[0] or self.chassisMass > self.chassisMass_range[-1]:
self.chassisMass = random.choice(self.chassisMass_range)
self.wheel1Mass = v[i]; i = i + 1
if self.wheel1Mass < self.wheel1Mass_range[0] or self.wheel1Mass > self.wheel1Mass_range[-1]:
self.wheel1Mass = random.choice(self.wheel1Mass_range)
self.wheel2Mass = v[i]; i = i + 1
if self.wheel2Mass < self.wheel2Mass_range[0] or self.wheel2Mass > self.wheel2Mass_range[-1]:
self.wheel2Mass = random.choice(self.wheel2Mass_range)
def createCar(self, shapeFilter):
chassisXY = Vec2d(self.x, self.y)
self.fitness = 0
moment = self.pymunk.moment_for_box(self.chassisMass, (self.chWd, self.chHt))
self.chassis_b = self.pymunk.Body(self.chassisMass, moment)
self.chassis_s = self.pymunk.Poly.create_box(self.chassis_b, (self.chWd, self.chHt))
self.chassis_s.color = 10,150,40
self.chassis_b.position = chassisXY + (0, 0)
self.chassis_s.filter = shapeFilter
self.space.add(self.chassis_b, self.chassis_s)
#---wheel1 (left side wheel)
moment = self.pymunk.moment_for_circle(self.wheel1Mass, 0, self.wheel1Radius)
self.wheel1_b = self.pymunk.Body(self.wheel1Mass, moment)
self.wheel1_s = self.pymunk.Circle(self.wheel1_b, self.wheel1Radius)
self.wheel1_s.friction = self.friction
self.wheel1_s.color = 180,180,180
self.wheel1_b.position = chassisXY - (self.chWd, 0)
self.wheel1_s.filter = shapeFilter
self.space.add(self.wheel1_b, self.wheel1_s)
#---wheel2 (right side wheel)
moment = self.pymunk.moment_for_circle(self.wheel2Mass, 0, self.wheel2Radius)
self.wheel2_b = self.pymunk.Body(self.wheel2Mass, moment)
self.wheel2_s = self.pymunk.Circle(self.wheel2_b, self.wheel2Radius)
self.wheel2_s.friction = self.friction
self.wheel2_s.color = 180,180,180
self.wheel2_b.position = chassisXY - (-self.chWd, 0)
self.wheel2_s.filter = shapeFilter
self.space.add(self.wheel2_b, self.wheel2_s)
self.pin1 = self.pymunk.PinJoint(self.wheel1_b, self.chassis_b, (0,0), (-self.chWd/2,0))
self.pin2 = self.pymunk.PinJoint(self.wheel2_b, self.chassis_b, (0,0), (self.chWd/2,0))
self.pin3 = self.pymunk.PinJoint(self.wheel1_b, self.chassis_b, (0,0), (0,-self.chHt/2))
self.pin4 = self.pymunk.PinJoint(self.wheel2_b, self.chassis_b, (0,0), (0,-self.chHt/2))
self.space.add(self.pin1, self.pin2, self.pin3, self.pin4)
self.motorJoint1 = self.pymunk.SimpleMotor(self.wheel1_b, self.chassis_b, self.speed); self.motorJoint1.max_force = 10000000
self.motorJoint2 = self.pymunk.SimpleMotor(self.wheel2_b, self.chassis_b, self.speed); self.motorJoint2.max_force = 10000000
self.space.add(self.motorJoint1, self.motorJoint2)
def stop(self):
self.motorJoint1.rate(0.0)
self.motorJoint2.rate(0.0)
def removeCar(self):
self.space.remove(self.chassis_b)
self.space.remove(self.chassis_s)
self.space.remove(self.wheel1_b)
self.space.remove(self.wheel1_s)
self.space.remove(self.wheel2_b)
self.space.remove(self.wheel2_s)
self.space.remove(self.pin1)
self.space.remove(self.pin2)
self.space.remove(self.pin3)
self.space.remove(self.pin4)
self.space.remove(self.motorJoint1)
self.space.remove(self.motorJoint2)
fps = 60.0 #frames per second
worldWidth = 1200
worldHeight = 300
#---initializations and setup
pygame.init()
screen = pygame.display.set_mode((worldWidth, worldHeight))
clock = pygame.time.Clock()
space = pymunk.Space()
space.gravity = 0, 900
space.sleep_time_threshold = 1 #The time a group of bodies must remain idle in order to fall asleep
draw_options = pymunk.pygame_util.DrawOptions(screen)
pymunk.pygame_util.positive_y_is_up = False
font = pygame.font.SysFont("Arial", 20)
#---setup line segments in space
x = 10; y = 200; thickness = 2; friction = 1.0
points = [(-100,0),(100,0),(112,12),(125,0),(137,12),(150,0),(162,12),(175,0),(200,0),(250,25),(250,0),(315,0),(320,70),(375,70),(385,0),(425,0),(425,12),(448,12),(448,0),(475,0),(475,12),(485,12),(485,0),(512,0),(512,12),(527,12),(527,0),(590,0),(590,10),(610,10),(610,15),(625,15),(625,25),(646,25),(646,35),(660,35),(660,45),(680,48),(710,-10),(755,5),(755,20),(780,25),(850,25),(825,-20),(865,-20),(895,25),(925,25),(935,0),(980,-75),(1100,-75)];
for i in range(1, len(points)):
floor = pymunk.Segment(space.static_body, (x+points[i-1][0], y+points[i-1][1]), (x+points[i][0], y+points[i][1]), thickness)
floor.friction = friction
space.add(floor)
#---create cars
xStartPos = 20; yStartPos = 0; numCars = 10; cars = []
sf = pymunk.ShapeFilter(group=1)
for i in range(0, numCars):
cars.append(Car(space, pymunk, xStartPos, yStartPos))
cars[i].createCar(sf)
while True:
for event in pygame.event.get():
if event.type == QUIT or event.type == KEYDOWN and (event.key in [K_ESCAPE, K_q]):
sys.exit(0)
#---updations
space.step(1./fps)#Update the space for the given time step.
screen.fill(pygame.color.THECOLORS["black"])#clear screen
space.debug_draw(draw_options)#try using Pygame's drawing functions instead
screen.blit(font.render("Cars: "+str(numCars)+". Time: "+str(time.time()), 1, THECOLORS["darkgrey"]), (5, 5))
pygame.display.flip()#flips the draw buffer to display the objects in current frame
dt = clock.tick(fps)#creates a delay
Hope this gives you an easy-to-understand intro to the awesomeness of Pymunk. I've created a slightly more advanced version of the above code, where the cars try to reach the end of the terrain using learning via an evolutionary algorithm.
I'm not ready to share the code yet, but when I am, I'll be posting the link on this page.
Here's the demo on YouTube:
Few other things:
- If you are looking to create something like a walking robot, there's an example here.
- I'm relatively new to Python, so forgive me if my code is not "Pythonic" enough :-)
- You can also look through the examples and test cases in Pymunk to see more examples of how to use various functions.