Pymunk Demo - Top Down
pymunk_demo_top_down.py
1"""
2Example of Pymunk Physics Engine
3Top-down
4
5If Python and Arcade are installed, this example can be run from the command line with:
6python -m arcade.examples.pymunk_demo_top_down
7"""
8import math
9import random
10import arcade
11from arcade.pymunk_physics_engine import PymunkPhysicsEngine
12
13WINDOW_TITLE = "PyMunk Top-Down"
14SPRITE_SCALING_PLAYER = 0.5
15MOVEMENT_SPEED = 5
16
17SPRITE_IMAGE_SIZE = 128
18SPRITE_SIZE = int(SPRITE_IMAGE_SIZE * SPRITE_SCALING_PLAYER)
19
20WINDOW_WIDTH = SPRITE_SIZE * 15
21WINDOW_HEIGHT = SPRITE_SIZE * 10
22
23# Physics force used to move the player. Higher number, faster accelerating.
24PLAYER_MOVE_FORCE = 4000
25BULLET_MOVE_FORCE = 2500
26
27
28class GameView(arcade.View):
29 def __init__(self):
30 """ Init """
31 super().__init__()
32
33 self.background_color = arcade.color.AMAZON
34
35 self.player_list = None
36 self.wall_list = None
37 self.bullet_list = None
38 self.rock_list = None
39 self.gem_list = None
40 self.player_sprite = None
41 self.physics_engine: PymunkPhysicsEngine | None = None
42
43 # Track the current state of what key is pressed
44 self.left_pressed = False
45 self.right_pressed = False
46 self.up_pressed = False
47 self.down_pressed = False
48
49 def setup(self):
50 """ Set up everything """
51 # Create the sprite lists
52 self.player_list = arcade.SpriteList()
53 self.wall_list = arcade.SpriteList()
54 self.bullet_list = arcade.SpriteList()
55 self.rock_list = arcade.SpriteList()
56 self.gem_list = arcade.SpriteList()
57
58 # Set up the player
59 self.player_sprite = arcade.Sprite(
60 ":resources:images/animated_characters/female_person/femalePerson_idle.png",
61 scale=SPRITE_SCALING_PLAYER)
62 self.player_sprite.center_x = 250
63 self.player_sprite.center_y = 250
64 self.player_list.append(self.player_sprite)
65
66 # Set up the walls
67 for x in range(0, WINDOW_WIDTH + 1, SPRITE_SIZE):
68 wall = arcade.Sprite(":resources:images/tiles/grassCenter.png",
69 scale=SPRITE_SCALING_PLAYER)
70 wall.center_x = x
71 wall.center_y = 0
72 self.wall_list.append(wall)
73
74 wall = arcade.Sprite(":resources:images/tiles/grassCenter.png",
75 scale=SPRITE_SCALING_PLAYER)
76 wall.center_x = x
77 wall.center_y = WINDOW_HEIGHT
78 self.wall_list.append(wall)
79
80 # Set up the walls
81 for y in range(SPRITE_SIZE, WINDOW_HEIGHT, SPRITE_SIZE):
82 wall = arcade.Sprite(":resources:images/tiles/grassCenter.png",
83 scale=SPRITE_SCALING_PLAYER)
84 wall.center_x = 0
85 wall.center_y = y
86 self.wall_list.append(wall)
87
88 wall = arcade.Sprite(":resources:images/tiles/grassCenter.png",
89 scale=SPRITE_SCALING_PLAYER)
90 wall.center_x = WINDOW_WIDTH
91 wall.center_y = y
92 self.wall_list.append(wall)
93
94 # Add some movable rocks
95 for x in range(SPRITE_SIZE * 2, SPRITE_SIZE * 13, SPRITE_SIZE):
96 rock = random.randrange(4) + 1
97 item = arcade.Sprite(f":resources:images/space_shooter/meteorGrey_big{rock}.png",
98 scale=SPRITE_SCALING_PLAYER)
99 item.center_x = x
100 item.center_y = 400
101 self.rock_list.append(item)
102
103 # Add some movable coins
104 for x in range(SPRITE_SIZE * 2, SPRITE_SIZE * 13, SPRITE_SIZE):
105 items = [":resources:images/items/gemBlue.png",
106 ":resources:images/items/gemRed.png",
107 ":resources:images/items/coinGold.png",
108 ":resources:images/items/keyBlue.png"]
109 item_name = random.choice(items)
110 item = arcade.Sprite(item_name,
111 scale=SPRITE_SCALING_PLAYER)
112 item.center_x = x
113 item.center_y = 300
114 self.gem_list.append(item)
115
116 # --- Pymunk Physics Engine Setup ---
117
118 # The default damping for every object controls the percent of velocity
119 # the object will keep each second. A value of 1.0 is no speed loss,
120 # 0.9 is 10% per second, 0.1 is 90% per second.
121 # For top-down games, this is basically the friction for moving objects.
122 # For platformers with gravity, this should probably be set to 1.0.
123 # Default value is 1.0 if not specified.
124 damping = 0.7
125
126 # Set the gravity. (0, 0) is good for outer space and top-down.
127 gravity = (0, 0)
128
129 # Create the physics engine
130 self.physics_engine = PymunkPhysicsEngine(damping=damping,
131 gravity=gravity)
132
133 def rock_hit_handler(sprite_a, sprite_b, arbiter, space, data):
134 """ Called for bullet/rock collision """
135 bullet_shape = arbiter.shapes[0]
136 bullet_sprite = self.physics_engine.get_sprite_for_shape(bullet_shape)
137 bullet_sprite.remove_from_sprite_lists()
138 print("Rock")
139
140 def wall_hit_handler(sprite_a, sprite_b, arbiter, space, data):
141 """ Called for bullet/rock collision """
142 bullet_shape = arbiter.shapes[0]
143 bullet_sprite = self.physics_engine.get_sprite_for_shape(bullet_shape)
144 bullet_sprite.remove_from_sprite_lists()
145 print("Wall")
146
147 self.physics_engine.add_collision_handler(
148 "bullet",
149 "rock",
150 post_handler=rock_hit_handler,
151 )
152 self.physics_engine.add_collision_handler(
153 "bullet",
154 "wall",
155 post_handler=wall_hit_handler,
156 )
157
158 # Add the player.
159 # For the player, we set the damping to a lower value, which increases
160 # the damping rate. This prevents the character from traveling too far
161 # after the player lets off the movement keys.
162 # Setting the moment to PymunkPhysicsEngine.MOMENT_INF prevents it from
163 # rotating.
164 # Friction normally goes between 0 (no friction) and 1.0 (high friction)
165 # Friction is between two objects in contact. It is important to remember
166 # in top-down games that friction moving along the 'floor' is controlled
167 # by damping.
168 self.physics_engine.add_sprite(self.player_sprite,
169 friction=0.6,
170 moment_of_inertia=PymunkPhysicsEngine.MOMENT_INF,
171 damping=0.01,
172 collision_type="player",
173 max_velocity=400)
174
175 # Create the walls.
176 # By setting the body type to PymunkPhysicsEngine.STATIC the walls can't
177 # move.
178 # Movable objects that respond to forces are PymunkPhysicsEngine.DYNAMIC
179 # PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be
180 # repositioned by code and don't respond to physics forces.
181 # Dynamic is default.
182 self.physics_engine.add_sprite_list(self.wall_list,
183 friction=0.6,
184 collision_type="wall",
185 body_type=PymunkPhysicsEngine.STATIC)
186
187 # Create some boxes to push around.
188 # Mass controls, well, the mass of an object. Defaults to 1.
189 self.physics_engine.add_sprite_list(self.rock_list,
190 mass=2,
191 friction=0.8,
192 damping=0.1,
193 collision_type="rock")
194 # Create some boxes to push around.
195 # Mass controls, well, the mass of an object. Defaults to 1.
196 self.physics_engine.add_sprite_list(self.gem_list,
197 mass=0.5,
198 friction=0.8,
199 damping=0.4,
200 collision_type="rock")
201
202 def on_mouse_press(self, x, y, button, modifiers):
203 """ Called whenever the mouse button is clicked. """
204
205 bullet = arcade.SpriteSolidColor(width=5, height=5, color=arcade.color.RED)
206 self.bullet_list.append(bullet)
207
208 # Position the bullet at the player's current location
209 start_x = self.player_sprite.center_x
210 start_y = self.player_sprite.center_y
211 bullet.position = self.player_sprite.position
212
213 # Get from the mouse the destination location for the bullet
214 # IMPORTANT! If you have a scrolling screen, you will also need
215 # to add in self.view_bottom and self.view_left.
216 dest_x = x
217 dest_y = y
218
219 # Do math to calculate how to get the bullet to the destination.
220 # Calculation the angle in radians between the start points
221 # and end points. This is the angle the bullet will travel.
222 x_diff = dest_x - start_x
223 y_diff = dest_y - start_y
224 angle = math.atan2(y_diff, x_diff)
225
226 force = [math.cos(angle), math.sin(angle)]
227 size = max(self.player_sprite.width, self.player_sprite.height) / 2
228
229 bullet.center_x += size * force[0]
230 bullet.center_y += size * force[1]
231
232 self.physics_engine.add_sprite(bullet,
233 mass=0.1,
234 damping=1.0,
235 friction=0.6,
236 collision_type="bullet",
237 elasticity=0.9)
238
239 # Taking into account the angle, calculate our force.
240 force[0] *= BULLET_MOVE_FORCE
241 force[1] *= BULLET_MOVE_FORCE
242
243 self.physics_engine.apply_force(bullet, force)
244
245 def on_key_press(self, key, modifiers):
246 """Called whenever a key is pressed. """
247
248 if key == arcade.key.UP:
249 self.up_pressed = True
250 elif key == arcade.key.DOWN:
251 self.down_pressed = True
252 elif key == arcade.key.LEFT:
253 self.left_pressed = True
254 elif key == arcade.key.RIGHT:
255 self.right_pressed = True
256 elif key == arcade.key.SPACE:
257 bullet = arcade.SpriteSolidColor(9, 9, arcade.color.RED)
258 bullet.position = self.player_sprite.position
259 bullet.center_x += 30
260 self.bullet_list.append(bullet)
261 self.physics_engine.add_sprite(bullet,
262 mass=0.2,
263 damping=1.0,
264 friction=0.6,
265 collision_type="bullet")
266 force = (3000, 0)
267 self.physics_engine.apply_force(bullet, force)
268
269 def on_key_release(self, key, modifiers):
270 """Called when the user releases a key. """
271
272 if key == arcade.key.UP:
273 self.up_pressed = False
274 elif key == arcade.key.DOWN:
275 self.down_pressed = False
276 elif key == arcade.key.LEFT:
277 self.left_pressed = False
278 elif key == arcade.key.RIGHT:
279 self.right_pressed = False
280
281 def on_update(self, delta_time):
282 """ Movement and game logic """
283
284 # Calculate speed based on the keys pressed
285 self.player_sprite.change_x = 0
286 self.player_sprite.change_y = 0
287
288 if self.up_pressed and not self.down_pressed:
289 force = (0, PLAYER_MOVE_FORCE)
290 self.physics_engine.apply_force(self.player_sprite, force)
291 elif self.down_pressed and not self.up_pressed:
292 force = (0, -PLAYER_MOVE_FORCE)
293 self.physics_engine.apply_force(self.player_sprite, force)
294 if self.left_pressed and not self.right_pressed:
295 self.player_sprite.change_x = -MOVEMENT_SPEED
296 force = (-PLAYER_MOVE_FORCE, 0)
297 self.physics_engine.apply_force(self.player_sprite, force)
298 elif self.right_pressed and not self.left_pressed:
299 force = (PLAYER_MOVE_FORCE, 0)
300 self.physics_engine.apply_force(self.player_sprite, force)
301
302 # --- Move items in the physics engine
303 self.physics_engine.step()
304
305 def on_draw(self):
306 """ Draw everything """
307 self.clear()
308 self.wall_list.draw()
309 self.bullet_list.draw()
310 self.rock_list.draw()
311 self.gem_list.draw()
312 self.player_list.draw()
313
314def main():
315 """ Main function """
316 # Create a window class. This is what actually shows up on screen
317 window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE)
318
319 # Create and setup the GameView
320 game = GameView()
321 game.setup()
322
323 # Show GameView on screen
324 window.show_view(game)
325
326 # Start the arcade game loop
327 arcade.run()
328
329
330if __name__ == "__main__":
331 main()