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