pymunk_demo_platformer_08.py Diff

pymunk_demo_platformer_08.py
--- /home/docs/checkouts/readthedocs.org/user_builds/arcade-library/checkouts/stable/doc/tutorials/pymunk_platformer/pymunk_demo_platformer_07.py
+++ /home/docs/checkouts/readthedocs.org/user_builds/arcade-library/checkouts/stable/doc/tutorials/pymunk_platformer/pymunk_demo_platformer_08.py
@@ -1,7 +1,7 @@
 """
 Example of Pymunk Physics Engine Platformer
 """
-from typing import Optional
+
 import arcade
 
 SCREEN_TITLE = "PyMunk Platformer"
@@ -54,24 +54,117 @@
 # Strength of a jump
 PLAYER_JUMP_IMPULSE = 1800
 
+# Close enough to not-moving to have the animation go to idle.
+DEAD_ZONE = 0.1
+
+# Constants used to track if the player is facing left or right
+RIGHT_FACING = 0
+LEFT_FACING = 1
+
+# How many pixels to move before we change the texture in the walking animation
+DISTANCE_TO_CHANGE_TEXTURE = 20
+
+
+class PlayerSprite(arcade.Sprite):
+    """Player Sprite"""
+
+    def __init__(self):
+        """Init"""
+        # Let parent initialize
+        super().__init__(scale=SPRITE_SCALING_PLAYER)
+
+        # Images from Kenney.nl's Character pack
+        # main_path = ":resources:images/animated_characters/female_adventurer/femaleAdventurer"
+        main_path = ":resources:images/animated_characters/female_person/femalePerson"
+        # main_path = ":resources:images/animated_characters/male_person/malePerson"
+        # main_path = ":resources:images/animated_characters/male_adventurer/maleAdventurer"
+        # main_path = ":resources:images/animated_characters/zombie/zombie"
+        # main_path = ":resources:images/animated_characters/robot/robot"
+
+        # Load textures for idle, jump, and fall states
+        idle_texture = arcade.load_texture(f"{main_path}_idle.png")
+        jump_texture = arcade.load_texture(f"{main_path}_jump.png")
+        fall_texture = arcade.load_texture(f"{main_path}_fall.png")
+        # Make pairs of textures facing left and right
+        self.idle_texture_pair = idle_texture, idle_texture.flip_left_right()
+        self.jump_texture_pair = jump_texture, jump_texture.flip_left_right()
+        self.fall_texture_pair = fall_texture, fall_texture.flip_left_right()
+
+        # Load textures for walking and make pairs of textures facing left and right
+        self.walk_textures = []
+        for i in range(8):
+            texture = arcade.load_texture(f"{main_path}_walk{i}.png")
+            self.walk_textures.append((texture, texture.flip_left_right()))
+
+        # Set the initial texture
+        self.texture = self.idle_texture_pair[0]
+
+        # Default to face-right
+        self.character_face_direction = RIGHT_FACING
+
+        # Index of our current texture
+        self.cur_texture = 0
+
+        # How far have we traveled horizontally since changing the texture
+        self.x_odometer = 0
+
+    def pymunk_moved(self, physics_engine, dx, dy, d_angle):
+        """Handle being moved by the pymunk engine"""
+        # Figure out if we need to face left or right
+        if dx < -DEAD_ZONE and self.character_face_direction == RIGHT_FACING:
+            self.character_face_direction = LEFT_FACING
+        elif dx > DEAD_ZONE and self.character_face_direction == LEFT_FACING:
+            self.character_face_direction = RIGHT_FACING
+
+        # Are we on the ground?
+        is_on_ground = physics_engine.is_on_ground(self)
+
+        # Add to the odometer how far we've moved
+        self.x_odometer += dx
+
+        # Jumping animation
+        if not is_on_ground:
+            if dy > DEAD_ZONE:
+                self.texture = self.jump_texture_pair[self.character_face_direction]
+                return
+            elif dy < -DEAD_ZONE:
+                self.texture = self.fall_texture_pair[self.character_face_direction]
+                return
+
+        # Idle animation
+        if abs(dx) <= DEAD_ZONE:
+            self.texture = self.idle_texture_pair[self.character_face_direction]
+            return
+
+        # Have we moved far enough to change the texture?
+        if abs(self.x_odometer) > DISTANCE_TO_CHANGE_TEXTURE:
+            # Reset the odometer
+            self.x_odometer = 0
+
+            # Advance the walking animation
+            self.cur_texture += 1
+            if self.cur_texture > 7:
+                self.cur_texture = 0
+            self.texture = self.walk_textures[self.cur_texture][self.character_face_direction]
+
 
 class GameWindow(arcade.Window):
-    """ Main Window """
+    """Main Window"""
 
     def __init__(self, width, height, title):
-        """ Create the variables """
+        """Create the variables"""
 
         # Init the parent class
         super().__init__(width, height, title)
 
         # Player sprite
-        self.player_sprite: arcade.Sprite|None = None
+        self.player_sprite: PlayerSprite | None = None
 
         # Sprite lists we need
-        self.player_list: arcade.SpriteList|None = None
-        self.wall_list: arcade.SpriteList|None = None
-        self.bullet_list: arcade.SpriteList|None = None
-        self.item_list: arcade.SpriteList|None = None
+        self.player_list: arcade.SpriteList | None = None
+        self.wall_list: arcade.SpriteList | None = None
+        self.bullet_list: arcade.SpriteList | None = None
+        self.item_list: arcade.SpriteList | None = None
 
         # Track the current state of what key is pressed
         self.left_pressed: bool = False
@@ -84,7 +177,7 @@
         self.background_color = arcade.color.AMAZON
 
     def setup(self):
-        """ Set up everything with the game """
+        """Set up everything with the game"""
 
         # Create the sprite lists
         self.player_list = arcade.SpriteList()
@@ -101,8 +194,8 @@
         self.item_list = tile_map.sprite_lists["Dynamic Items"]
 
         # Create player sprite
-        self.player_sprite = arcade.Sprite(":resources:images/animated_characters/female_person/femalePerson_idle.png",
-                                           SPRITE_SCALING_PLAYER)
+        self.player_sprite = PlayerSprite()
+
         # Set player location
         grid_x = 1
         grid_y = 1
@@ -125,8 +218,7 @@
         gravity = (0, -GRAVITY)
 
         # Create the physics engine
-        self.physics_engine = arcade.PymunkPhysicsEngine(damping=damping,
-                                                         gravity=gravity)
+        self.physics_engine = arcade.PymunkPhysicsEngine(damping=damping, gravity=gravity)
 
         # Add the player.
         # For the player, we set the damping to a lower value, which increases
@@ -138,13 +230,15 @@
         # Friction is between two objects in contact. It is important to remember
         # in top-down games that friction moving along the 'floor' is controlled
         # by damping.
-        self.physics_engine.add_sprite(self.player_sprite,
-                                       friction=PLAYER_FRICTION,
-                                       mass=PLAYER_MASS,
-                                       moment_of_inertia=arcade.PymunkPhysicsEngine.MOMENT_INF,
-                                       collision_type="player",
-                                       max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,
-                                       max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED)
+        self.physics_engine.add_sprite(
+            self.player_sprite,
+            friction=PLAYER_FRICTION,
+            mass=PLAYER_MASS,
+            moment_of_inertia=arcade.PymunkPhysicsEngine.MOMENT_INF,
+            collision_type="player",
+            max_horizontal_velocity=PLAYER_MAX_HORIZONTAL_SPEED,
+            max_vertical_velocity=PLAYER_MAX_VERTICAL_SPEED,
+        )
 
         # Create the walls.
         # By setting the body type to PymunkPhysicsEngine.STATIC the walls can't
@@ -153,18 +247,20 @@
         # PymunkPhysicsEngine.KINEMATIC objects will move, but are assumed to be
         # repositioned by code and don't respond to physics forces.
         # Dynamic is default.
-        self.physics_engine.add_sprite_list(self.wall_list,
-                                            friction=WALL_FRICTION,
-                                            collision_type="wall",
-                                            body_type=arcade.PymunkPhysicsEngine.STATIC)
+        self.physics_engine.add_sprite_list(
+            self.wall_list,
+            friction=WALL_FRICTION,
+            collision_type="wall",
+            body_type=arcade.PymunkPhysicsEngine.STATIC,
+        )
 
         # Create the items
-        self.physics_engine.add_sprite_list(self.item_list,
-                                            friction=DYNAMIC_ITEM_FRICTION,
-                                            collision_type="item")
+        self.physics_engine.add_sprite_list(
+            self.item_list, friction=DYNAMIC_ITEM_FRICTION, collision_type="item"
+        )
 
     def on_key_press(self, key, modifiers):
-        """Called whenever a key is pressed. """
+        """Called whenever a key is pressed."""
 
         if key == arcade.key.LEFT:
             self.left_pressed = True
@@ -178,7 +274,7 @@
                 self.physics_engine.apply_impulse(self.player_sprite, impulse)
 
     def on_key_release(self, key, modifiers):
-        """Called when the user releases a key. """
+        """Called when the user releases a key."""
 
         if key == arcade.key.LEFT:
             self.left_pressed = False
@@ -186,7 +282,7 @@
             self.right_pressed = False
 
     def on_update(self, delta_time):
-        """ Movement and game logic """
+        """Movement and game logic"""
 
         is_on_ground = self.physics_engine.is_on_ground(self.player_sprite)
         # Update player forces based on keys pressed
@@ -216,15 +312,16 @@
         self.physics_engine.step()
 
     def on_draw(self):
-        """ Draw everything """
+        """Draw everything"""
         self.clear()
         self.wall_list.draw()
         self.bullet_list.draw()
         self.item_list.draw()
         self.player_list.draw()
 
+
 def main():
-    """ Main function """
+    """Main function"""
     window = GameWindow(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
     window.setup()
     arcade.run()