Pymunk Demo - Top Down#

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