Last active
January 23, 2021 01:17
-
-
Save dfsklar/08f75bf79eb09c5fc786e37770160afc to your computer and use it in GitHub Desktop.
arcmunk_bouncing_v_funnel.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
BOUNCING "V" program version 1 | |
This is the version that is the "starter" for the coding class exercises. | |
For commentary and an introduction to world/local coordinate systems: | |
https://docs.google.com/document/d/14PU6DO_4kwF27xfv4P_2hXptYvCZkTm0tff5BC7V0PQ | |
""" | |
import arcade | |
import pymunk | |
import random | |
import timeit | |
import math | |
SCREEN_TITLE = "Pymunk Bouncing 'V' Example" | |
SCREEN_WIDTH = 800 | |
SCREEN_HEIGHT = 800 | |
SCREEN_HALF_WIDTH = SCREEN_WIDTH / 2 | |
ROTATION_SPEED = 2.9 # Increase the number to make the motor spin the attached body faster | |
GROUND_Y_LOC = 100 | |
MOTOR_PIVOT_POSITION = (150,600) | |
SCREEN_UPDATE_FREQUENCY = 120.0 # Larger values make the ball movement *SLOWER* | |
GRAVITY_DOWNWARD = -900 | |
GRAVITY_RIGHTWARD = 0 | |
""" | |
The arcade system automatically provides you with a Python "class" called "arcade.Sprite". | |
You use a sprite to draw any object that is not just "background". | |
For example, if your platformer game has a hero character and coins that the hero is supposed to collect, | |
the hero and coins are sprites, but the scenery (e.g. clouds and forests in the background) are not sprites. | |
Here we extend the Sprite class to create a new class called CircleSprite that provides the feature of having | |
a bitmap image "paint" a circular sprite. | |
We use this for the display of the balls that we can drop into the scene. | |
""" | |
class CircleSprite_for_pymunk_shape(arcade.Sprite): | |
def __init__(self, image_file_location, pymunk_shape): | |
super().__init__(image_file_location, center_x=pymunk_shape.body.position.x, center_y=pymunk_shape.body.position.y) | |
self.width = pymunk_shape.radius * 2 | |
self.height = pymunk_shape.radius * 2 | |
setattr(self, 'pymunk_shape', pymunk_shape) | |
class CircleSprite(arcade.Sprite): | |
def __init__(self, image_file_location, centerX, centerY, radius): | |
super().__init__(image_file_location, center_x=centerX, center_y=centerY) | |
self.width = radius * 2 | |
self.height = radius * 2 | |
setattr(self, 'arcade_shape', ( centerX, centerY, radius )) | |
class MyGame(arcade.Window): | |
""" | |
This is the first time you're seeing a python concept called a "class". | |
Think of it as a giant storage box that contains not only data-storage variables but also has "behavior" | |
in the form of built-in functions. | |
So it is "data PLUS behavior" all wrapped up in one giant box. | |
The most important thing to note about using classes is that you must use "self." to refer to a | |
function or data-storage box that is part of the class. | |
You'll see a lot of references to "self." below. | |
""" | |
# This function is provided by Sklar -- DO NOT MODIFY THIS: | |
def register_body_into_scene(self, body, *objs): | |
print('###############\nWe are now registering a body into the physics universe.') | |
print('Body type is: ' + 'STATIC' if body.body_type == pymunk.Body.STATIC else 'DYNAMIC') | |
# First register the BODY with the physics universe | |
self.space.add(body) | |
for shape in body.shapes: | |
# Next register the SHAPE (for example, a segment) with the physics universe. | |
self.space.add(shape) | |
# Next, for segment shapes, register with the graphics system to ensure they are drawn. | |
if type(shape) == pymunk.shapes.Segment: | |
self.list_of_segments.append(shape) | |
# Every arcade program must have an INITIALIZATION method that sets up the physics engine and creates | |
# the initial set of physics objects and "sprites". | |
# In this case, the static ramps/walls must be set up here, but the balls are created/dropped dynamically | |
# based on user click activity, so the balls are not created here. | |
def __init__(self, width, height, title): | |
super().__init__(width, height, title) | |
arcade.set_background_color(arcade.color.DARK_SLATE_GRAY) | |
self.time = 0 | |
# Set up the physics "universe" -- an empty space initially having only gravity in place. | |
self.space = pymunk.Space() | |
self.space.gravity = (GRAVITY_RIGHTWARD, GRAVITY_DOWNWARD) | |
# The graphics system needs a list of all line-segments that need to be drawn for each animation frame. | |
# The function "register_body_into_scene" that I've provided automatically handles populating this list. | |
self.list_of_segments = [] | |
# Similarly, we need to keep a list of the circular balls that need to be drawn. | |
self.ball_list = arcade.SpriteList() | |
# PHYSICS BODY: The "V" shape funnel -- this is a STATIC physics body. | |
# | |
v_body = pymunk.Body(body_type=pymunk.Body.STATIC) | |
v_body.position = (SCREEN_HALF_WIDTH, GROUND_Y_LOC) # <<< WORLD coordinate system | |
segment1 = pymunk.Segment(body=v_body, a=(50,300), b=(12,0), radius=1) # < LOCAL coordinate system | |
segment2 = pymunk.Segment(body=v_body, a=(-50,300), b=(-12,0), radius=1) | |
# For STATIC segments/shapes, you do NOT need to specify mass. | |
# BUT: you must specify elasticity if you want other things to be able to "bounce" off. | |
for a_segment in (segment1, segment2): | |
a_segment.elasticity = 0.8 | |
self.register_body_into_scene(v_body) | |
# PHYSICS BODY: The entrance ramp. This is also STATIC. | |
ground_body = pymunk.Body(body_type=pymunk.Body.STATIC) | |
ground_body.position = (0, 700) # WORLD coordinates | |
self.register_body_into_scene(ground_body, | |
pymunk.Segment(body=ground_body, a=(0, -100), b=(400, -300), radius=1)) # LOCAL coordinates | |
# PHYSICS BODY: The "centerpoint" for the motorized box-with-hole | |
motorcenter_body = pymunk.Body(body_type=pymunk.Body.STATIC) | |
# Right now, I'm placing this motor at a random location. You will want to give it a different position. | |
motorcenter_body.position = MOTOR_PIVOT_POSITION | |
# Most of the time, we want a physics body to have at least one line segment. This lets it act | |
# as a ramp, or a seesaw plank, etc. | |
# But in this case, the motor is a static thing that really is just a "pivot point" sitting out in space. | |
# It's not made up of any line segments at all. | |
# So we are going to register the "motor center" as a body with NO children segments: | |
self.register_body_into_scene(motorcenter_body) | |
# But it would be nice to give the pivot point an appearance even though it's not in the physics engine. | |
# We can thus register an Arcade circular sprite just to give it an appearance. | |
# We're treating it like a ball, but this ball is static and thus does not "fall". | |
sprite = CircleSprite(":resources:images/pinball/bumper.png", \ | |
motorcenter_body.position[0], | |
motorcenter_body.position[1], 8) | |
self.ball_list.append(sprite) | |
# PHYSICS BODY: The plank that the motor keeps rotating. This is dynamic of course! | |
# Currently, this is a simple line segment, but you might want to think about making it | |
# a "bucket" of some sort, providing a way to launch balls in a "spaced-out" interval way. | |
motor_controlled_body = pymunk.Body(body_type=pymunk.Body.DYNAMIC) | |
motor_controlled_body.position = MOTOR_PIVOT_POSITION | |
thickness = 0.5 | |
motor_controlled_shape = \ | |
pymunk.Segment(motor_controlled_body, | |
a=( -60, 0), | |
b=( 60, 0), | |
radius=thickness) | |
motor_controlled_shape.friction = 1 | |
motor_controlled_shape.mass = 4 | |
self.register_body_into_scene(motor_controlled_body, motor_controlled_shape) | |
# Set up the connection between the pivot point and the seesaw "plank" | |
self.rotation_center_joint = pymunk.PinJoint( \ | |
a=motor_controlled_body, b=motorcenter_body, \ | |
anchor_a=(0,0), anchor_b=(0,0)) | |
self.space.add(self.rotation_center_joint) | |
# Now, motorize that connection | |
motor = pymunk.SimpleMotor(motorcenter_body, motor_controlled_body, ROTATION_SPEED) | |
self.space.add(motor) | |
def draw_all_linesegments(self): | |
for line in self.list_of_segments: | |
body = line.body | |
if body.body_type == pymunk.Body.STATIC: | |
color = arcade.color.GRAY | |
else: | |
color = arcade.color.WHITE | |
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, color, 2) | |
# DO NOT MODIFY THIS: | |
def on_draw(self): | |
""" | |
Draw the screen, showing the current locations/orientations of all objects in the physics universe. | |
""" | |
# This command has to happen before we start drawing | |
arcade.start_render() | |
draw_start_time = timeit.default_timer() | |
self.draw_all_linesegments() | |
self.ball_list.draw() | |
def on_mouse_press(self, x, y, which_button, which_modifiers): | |
shift_key_was_pressed = (which_modifiers == 1) # You might want to behave differently if SHIFT key pressed! | |
self.drop_ball_into_scene(x, y) | |
def drop_ball_into_scene(self, x, y): | |
mass = 1.0 | |
radius = 11 | |
friction = 0.3 | |
body = pymunk.Body(body_type=pymunk.Body.DYNAMIC) # mass, inertia) | |
body.position = (x, y) | |
shape = pymunk.Circle(body, radius) | |
shape.mass = mass | |
shape.friction = friction | |
shape.elasticity = 1.0 | |
self.register_body_into_scene(body, shape) | |
# The graphical representation of the balls must be done using Arcade sprites: | |
sprite = CircleSprite_for_pymunk_shape(":resources:images/items/gold_1.png", shape) | |
self.ball_list.append(sprite) | |
# DO NOT MODIFY THIS: | |
def on_update(self, delta_time): | |
start_time = timeit.default_timer() | |
# Check for balls that fall off the screen. | |
for ball in []: #self.ball_list: | |
# If the ball's Y coordinate is below the screen (Y is negative): | |
if ball.pymunk_shape.body.position.y < 0: | |
# Remove this ball from physics engine | |
self.space.remove(ball.pymunk_shape, ball.pymunk_shape.body) | |
# Remove this ball from the list of sprites used for the graphics engine | |
ball.remove_from_sprite_lists() | |
# Update physics | |
# See "Game loop / moving time forward" | |
# http://www.pymunk.org/en/latest/overview.html#game-loop-moving-time-forward | |
self.space.step(1 / SCREEN_UPDATE_FREQUENCY) | |
# Keep in mind: The "physics engine" has its own non-graphics representation of where every ball is located. | |
# The physics engine does not automatically update the "sprites" that represent the balls graphically. | |
# It is your job to visit every ball and "copy" the physics location into the sprite's location. | |
for ball in self.ball_list: | |
if hasattr(ball, 'pymunk_shape'): | |
ball.center_x = ball.pymunk_shape.body.position.x | |
ball.center_y = ball.pymunk_shape.body.position.y | |
ball.angle = math.degrees(ball.pymunk_shape.body.angle) | |
self.time = timeit.default_timer() - start_time | |
# OK HERE WE GO! All of the above is simply "teaching" the python system about our game. | |
# But the next two lines actually "create" the game and then "launches" the Arcade scheduling system. | |
MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE) | |
arcade.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment