Pymunk Physics Engine - Stacks of Boxes
This uses the Pymunk physics engine to manage stacks of boxes. The user can interact with the boxes using the mouse.

pymunk_box_stacks.py
1"""
2Use Pymunk physics engine.
3
4For more info on Pymunk see:
5https://www.pymunk.org/en/latest/
6
7To install pymunk:
8pip install pymunk
9
10Artwork from https://kenney.nl
11
12If Python and Arcade are installed, this example can be run from the command line with:
13python -m arcade.examples.pymunk_box_stacks
14
15Click and drag with the mouse to move the boxes.
16"""
17
18import arcade
19import pymunk
20import timeit
21import math
22
23WINDOW_WIDTH = 1800
24WINDOW_HEIGHT = 800
25WINDOW_TITLE = "Pymunk test"
26
27
28class PhysicsSprite(arcade.Sprite):
29 def __init__(self, pymunk_shape, filename):
30 super().__init__(
31 filename,
32 center_x=pymunk_shape.body.position.x,
33 center_y=pymunk_shape.body.position.y,
34 )
35 self.pymunk_shape = pymunk_shape
36
37
38class CircleSprite(PhysicsSprite):
39 def __init__(self, pymunk_shape, filename):
40 super().__init__(pymunk_shape, filename)
41 self.width = pymunk_shape.radius * 2
42 self.height = pymunk_shape.radius * 2
43
44
45class BoxSprite(PhysicsSprite):
46 def __init__(self, pymunk_shape, filename, width, height):
47 super().__init__(pymunk_shape, filename)
48 self.width = width
49 self.height = height
50
51
52class GameView(arcade.View):
53 """ Main application class. """
54
55 def __init__(self):
56 super().__init__()
57
58 self.background_color = arcade.color.DARK_SLATE_GRAY
59
60 # -- Pymunk
61 self.space = pymunk.Space()
62 self.space.iterations = 35
63 self.space.gravity = (0.0, -900.0)
64
65 # Lists of sprites or lines
66 self.sprite_list: arcade.SpriteList[PhysicsSprite] = arcade.SpriteList()
67 self.static_lines = []
68
69 # Used for dragging shapes around with the mouse
70 self.shape_being_dragged = None
71 self.last_mouse_position = 0, 0
72
73 self.draw_time = 0
74 self.processing_time = 0
75
76 # Create the floor
77 floor_height = 80
78 body = pymunk.Body(body_type=pymunk.Body.STATIC)
79 shape = pymunk.Segment(body, [0, floor_height], [WINDOW_WIDTH, floor_height], 0.0)
80 shape.friction = 10
81 self.space.add(shape, body)
82 self.static_lines.append(shape)
83
84 # Create the stacks of boxes
85 for row in range(10):
86 for column in range(10):
87 size = 32
88 mass = 1.0
89 x = 500 + column * 32
90 y = (floor_height + size / 2) + row * size
91 moment = pymunk.moment_for_box(mass, (size, size))
92 body = pymunk.Body(mass, moment)
93 body.position = pymunk.Vec2d(x, y)
94 shape = pymunk.Poly.create_box(body, (size, size))
95 shape.elasticity = 0.2
96 shape.friction = 0.9
97 self.space.add(body, shape)
98 # body.sleep()
99
100 sprite = BoxSprite(
101 shape,
102 ":resources:images/tiles/boxCrate_double.png",
103 width=size,
104 height=size,
105 )
106 self.sprite_list.append(sprite)
107
108 def on_draw(self):
109 """
110 Render the screen.
111 """
112
113 # This command has to happen before we start drawing
114 self.clear()
115
116 # Start timing how long this takes
117 draw_start_time = timeit.default_timer()
118
119 # Draw all the sprites
120 self.sprite_list.draw()
121
122 # Draw the lines that aren't sprites
123 for line in self.static_lines:
124 body = line.body
125
126 pv1 = body.position + line.a.rotated(body.angle)
127 pv2 = body.position + line.b.rotated(body.angle)
128 arcade.draw_line(pv1.x, pv1.y, pv2.x, pv2.y, arcade.color.WHITE, 2)
129
130 # Display timings
131 output = f"Processing time: {self.processing_time:.3f}"
132 arcade.draw_text(output, 20, WINDOW_HEIGHT - 20, arcade.color.WHITE, 12)
133
134 output = f"Drawing time: {self.draw_time:.3f}"
135 arcade.draw_text(output, 20, WINDOW_HEIGHT - 40, arcade.color.WHITE, 12)
136
137 self.draw_time = timeit.default_timer() - draw_start_time
138
139 def on_mouse_press(self, x, y, button, modifiers):
140 if button == arcade.MOUSE_BUTTON_LEFT:
141 self.last_mouse_position = x, y
142 # See if we clicked on anything
143 shape_list = self.space.point_query((x, y), 1, pymunk.ShapeFilter())
144
145 # If we did, remember what we clicked on
146 if len(shape_list) > 0:
147 self.shape_being_dragged = shape_list[0]
148
149 elif button == arcade.MOUSE_BUTTON_RIGHT:
150 # With right mouse button, shoot a heavy coin fast.
151 mass = 60
152 radius = 10
153 inertia = pymunk.moment_for_circle(mass, 0, radius, (0, 0))
154 body = pymunk.Body(mass, inertia)
155 body.position = x, y
156 body.velocity = 2000, 0
157 shape = pymunk.Circle(body, radius, pymunk.Vec2d(0, 0))
158 shape.friction = 0.3
159 self.space.add(body, shape)
160
161 sprite = CircleSprite(shape, ":resources:images/items/coinGold.png")
162 self.sprite_list.append(sprite)
163
164 def on_mouse_release(self, x, y, button, modifiers):
165 if button == arcade.MOUSE_BUTTON_LEFT:
166 # Release the item we are holding (if any)
167 self.shape_being_dragged = None
168
169 def on_mouse_motion(self, x, y, dx, dy):
170 if self.shape_being_dragged is not None:
171 # If we are holding an object, move it with the mouse
172 self.last_mouse_position = x, y
173 self.shape_being_dragged.shape.body.position = self.last_mouse_position
174 self.shape_being_dragged.shape.body.velocity = dx * 20, dy * 20
175
176 def on_update(self, delta_time):
177 start_time = timeit.default_timer()
178
179 # Check for balls that fall off the screen
180 for sprite in self.sprite_list:
181 if sprite.pymunk_shape.body.position.y < 0:
182 # Remove balls from physics space
183 self.space.remove(sprite.pymunk_shape, sprite.pymunk_shape.body)
184 # Remove balls from physics list
185 sprite.remove_from_sprite_lists()
186
187 # Update physics
188 # Use a constant time step, don't use delta_time
189 # See "Game loop / moving time forward"
190 # https://www.pymunk.org/en/latest/overview.html#game-loop-moving-time-forward
191 self.space.step(1 / 60.0)
192
193 # If we are dragging an object, make sure it stays with the mouse. Otherwise
194 # gravity will drag it down.
195 if self.shape_being_dragged is not None:
196 self.shape_being_dragged.shape.body.position = self.last_mouse_position
197 self.shape_being_dragged.shape.body.velocity = 0, 0
198
199 # Move sprites to where physics objects are
200 for sprite in self.sprite_list:
201 sprite.center_x = sprite.pymunk_shape.body.position.x
202 sprite.center_y = sprite.pymunk_shape.body.position.y
203 sprite.angle = -math.degrees(sprite.pymunk_shape.body.angle)
204
205 # Save the time it took to do this.
206 self.processing_time = timeit.default_timer() - start_time
207
208
209def main():
210 """ Main function """
211 # Create a window class. This is what actually shows up on screen
212 window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
213
214 # Create the GameView
215 game = GameView()
216
217 # Show GameView on screen
218 window.show_view(game)
219
220 # Start the arcade game loop
221 arcade.run()
222
223
224if __name__ == "__main__":
225 main()