216 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			216 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""
 | 
						|
Use Pymunk physics engine.
 | 
						|
 | 
						|
For more info on Pymunk see:
 | 
						|
http://www.pymunk.org/en/latest/
 | 
						|
 | 
						|
To install pymunk:
 | 
						|
pip install pymunk
 | 
						|
 | 
						|
Artwork from http://kenney.nl
 | 
						|
 | 
						|
If Python and Arcade are installed, this example can be run from the command line with:
 | 
						|
python -m arcade.examples.pymunk_box_stacks
 | 
						|
 | 
						|
Click and drag with the mouse to move the boxes.
 | 
						|
"""
 | 
						|
 | 
						|
import arcade
 | 
						|
import pymunk
 | 
						|
import timeit
 | 
						|
import math
 | 
						|
import os
 | 
						|
 | 
						|
SCREEN_WIDTH = 1800
 | 
						|
SCREEN_HEIGHT = 800
 | 
						|
SCREEN_TITLE = "Pymunk test"
 | 
						|
 | 
						|
 | 
						|
class PhysicsSprite(arcade.Sprite):
 | 
						|
    def __init__(self, pymunk_shape, filename):
 | 
						|
        super().__init__(filename, center_x=pymunk_shape.body.position.x, center_y=pymunk_shape.body.position.y)
 | 
						|
        self.pymunk_shape = pymunk_shape
 | 
						|
 | 
						|
 | 
						|
class CircleSprite(PhysicsSprite):
 | 
						|
    def __init__(self, pymunk_shape, filename):
 | 
						|
        super().__init__(pymunk_shape, filename)
 | 
						|
        self.width = pymunk_shape.radius * 2
 | 
						|
        self.height = pymunk_shape.radius * 2
 | 
						|
 | 
						|
 | 
						|
class BoxSprite(PhysicsSprite):
 | 
						|
    def __init__(self, pymunk_shape, filename, width, height):
 | 
						|
        super().__init__(pymunk_shape, filename)
 | 
						|
        self.width = width
 | 
						|
        self.height = height
 | 
						|
 | 
						|
 | 
						|
class MyGame(arcade.Window):
 | 
						|
    """ Main application class. """
 | 
						|
 | 
						|
    def __init__(self, width, height, title):
 | 
						|
        super().__init__(width, height, title)
 | 
						|
 | 
						|
        # Set the working directory (where we expect to find files) to the same
 | 
						|
        # directory this .py file is in. You can leave this out of your own
 | 
						|
        # code, but it is needed to easily run the examples using "python -m"
 | 
						|
        # as mentioned at the top of this program.
 | 
						|
        file_path = os.path.dirname(os.path.abspath(__file__))
 | 
						|
        os.chdir(file_path)
 | 
						|
 | 
						|
        arcade.set_background_color(arcade.color.DARK_SLATE_GRAY)
 | 
						|
 | 
						|
        # -- Pymunk
 | 
						|
        self.space = pymunk.Space()
 | 
						|
        self.space.iterations = 35
 | 
						|
        self.space.gravity = (0.0, -900.0)
 | 
						|
 | 
						|
        # Lists of sprites or lines
 | 
						|
        self.sprite_list = arcade.SpriteList()
 | 
						|
        self.static_lines = []
 | 
						|
 | 
						|
        # Used for dragging shapes around with the mouse
 | 
						|
        self.shape_being_dragged = None
 | 
						|
        self.last_mouse_position = 0, 0
 | 
						|
 | 
						|
        self.draw_time = 0
 | 
						|
        self.processing_time = 0
 | 
						|
 | 
						|
        # Create the floor
 | 
						|
        floor_height = 80
 | 
						|
        body = pymunk.Body(body_type=pymunk.Body.STATIC)
 | 
						|
        shape = pymunk.Segment(body, [0, floor_height], [SCREEN_WIDTH, floor_height], 0.0)
 | 
						|
        shape.friction = 10
 | 
						|
        self.space.add(shape)
 | 
						|
        self.static_lines.append(shape)
 | 
						|
 | 
						|
        # Create the stacks of boxes
 | 
						|
        for row in range(10):
 | 
						|
            for column in range(10):
 | 
						|
                size = 32
 | 
						|
                mass = 1.0
 | 
						|
                x = 500 + column * 32
 | 
						|
                y = (floor_height + size / 2) + row * size
 | 
						|
                moment = pymunk.moment_for_box(mass, (size, size))
 | 
						|
                body = pymunk.Body(mass, moment)
 | 
						|
                body.position = pymunk.Vec2d(x, y)
 | 
						|
                shape = pymunk.Poly.create_box(body, (size, size))
 | 
						|
                shape.elasticity = 0.2
 | 
						|
                shape.friction = 0.9
 | 
						|
                self.space.add(body, shape)
 | 
						|
                # body.sleep()
 | 
						|
 | 
						|
                sprite = BoxSprite(shape, "images/boxCrate_double.png", width=size, height=size)
 | 
						|
                self.sprite_list.append(sprite)
 | 
						|
 | 
						|
    def on_draw(self):
 | 
						|
        """
 | 
						|
        Render the screen.
 | 
						|
        """
 | 
						|
 | 
						|
        # This command has to happen before we start drawing
 | 
						|
        arcade.start_render()
 | 
						|
 | 
						|
        # Start timing how long this takes
 | 
						|
        draw_start_time = timeit.default_timer()
 | 
						|
 | 
						|
        # Draw all the sprites
 | 
						|
        self.sprite_list.draw()
 | 
						|
 | 
						|
        # Draw the lines that aren't sprites
 | 
						|
        for line in self.static_lines:
 | 
						|
            body = line.body
 | 
						|
 | 
						|
            pv1 = body.position + line.a.rotated(body.angle)
 | 
						|
            pv2 = body.position + line.b.rotated(body.angle)
 | 
						|
            arcade.draw_line(pv1.x, pv1.y, pv2.x, pv2.y, arcade.color.WHITE, 2)
 | 
						|
 | 
						|
        # Display timings
 | 
						|
        output = f"Processing time: {self.processing_time:.3f}"
 | 
						|
        arcade.draw_text(output, 20, SCREEN_HEIGHT - 20, arcade.color.WHITE, 12)
 | 
						|
 | 
						|
        output = f"Drawing time: {self.draw_time:.3f}"
 | 
						|
        arcade.draw_text(output, 20, SCREEN_HEIGHT - 40, arcade.color.WHITE, 12)
 | 
						|
 | 
						|
        self.draw_time = timeit.default_timer() - draw_start_time
 | 
						|
 | 
						|
    def on_mouse_press(self, x, y, button, modifiers):
 | 
						|
        if button == arcade.MOUSE_BUTTON_LEFT:
 | 
						|
            self.last_mouse_position = x, y
 | 
						|
            # See if we clicked on anything
 | 
						|
            shape_list = self.space.point_query((x, y), 1, pymunk.ShapeFilter())
 | 
						|
 | 
						|
            # If we did, remember what we clicked on
 | 
						|
            if len(shape_list) > 0:
 | 
						|
                self.shape_being_dragged = shape_list[0]
 | 
						|
 | 
						|
        elif button == arcade.MOUSE_BUTTON_RIGHT:
 | 
						|
            # With right mouse button, shoot a heavy coin fast.
 | 
						|
            mass = 60
 | 
						|
            radius = 10
 | 
						|
            inertia = pymunk.moment_for_circle(mass, 0, radius, (0, 0))
 | 
						|
            body = pymunk.Body(mass, inertia)
 | 
						|
            body.position = x, y
 | 
						|
            body.velocity = 2000, 0
 | 
						|
            shape = pymunk.Circle(body, radius, pymunk.Vec2d(0, 0))
 | 
						|
            shape.friction = 0.3
 | 
						|
            self.space.add(body, shape)
 | 
						|
 | 
						|
            sprite = CircleSprite(shape, "images/coin_01.png")
 | 
						|
            self.sprite_list.append(sprite)
 | 
						|
 | 
						|
    def on_mouse_release(self, x, y, button, modifiers):
 | 
						|
        if button == arcade.MOUSE_BUTTON_LEFT:
 | 
						|
            # Release the item we are holding (if any)
 | 
						|
            self.shape_being_dragged = None
 | 
						|
 | 
						|
    def on_mouse_motion(self, x, y, dx, dy):
 | 
						|
        if self.shape_being_dragged is not None:
 | 
						|
            # If we are holding an object, move it with the mouse
 | 
						|
            self.last_mouse_position = x, y
 | 
						|
            self.shape_being_dragged.shape.body.position = self.last_mouse_position
 | 
						|
            self.shape_being_dragged.shape.body.velocity = dx * 20, dy * 20
 | 
						|
 | 
						|
    def update(self, delta_time):
 | 
						|
        start_time = timeit.default_timer()
 | 
						|
 | 
						|
        # Check for balls that fall off the screen
 | 
						|
        for sprite in self.sprite_list:
 | 
						|
            if sprite.pymunk_shape.body.position.y < 0:
 | 
						|
                # Remove balls from physics space
 | 
						|
                self.space.remove(sprite.pymunk_shape, sprite.pymunk_shape.body)
 | 
						|
                # Remove balls from physics list
 | 
						|
                sprite.kill()
 | 
						|
 | 
						|
        # Update physics
 | 
						|
        # Use a constant time step, don't use delta_time
 | 
						|
        # See "Game loop / moving time forward"
 | 
						|
        # http://www.pymunk.org/en/latest/overview.html#game-loop-moving-time-forward
 | 
						|
        self.space.step(1 / 60.0)
 | 
						|
 | 
						|
        # If we are dragging an object, make sure it stays with the mouse. Otherwise
 | 
						|
        # gravity will drag it down.
 | 
						|
        if self.shape_being_dragged is not None:
 | 
						|
            self.shape_being_dragged.shape.body.position = self.last_mouse_position
 | 
						|
            self.shape_being_dragged.shape.body.velocity = 0, 0
 | 
						|
 | 
						|
        # Move sprites to where physics objects are
 | 
						|
        for sprite in self.sprite_list:
 | 
						|
            sprite.center_x = sprite.pymunk_shape.body.position.x
 | 
						|
            sprite.center_y = sprite.pymunk_shape.body.position.y
 | 
						|
            sprite.angle = math.degrees(sprite.pymunk_shape.body.angle)
 | 
						|
 | 
						|
        # Save the time it took to do this.
 | 
						|
        self.processing_time = timeit.default_timer() - start_time
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
 | 
						|
 | 
						|
    arcade.run()
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    main()
 |