Last active
January 3, 2021 23:35
-
-
Save dfsklar/223ad0ae87f8ff0984c98a15f68e783b to your computer and use it in GitHub Desktop.
Seesaw program
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
""" | |
SEESAW version 4 | |
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 See-Saw Example" | |
SCREEN_WIDTH = 800 | |
SCREEN_HEIGHT = 800 | |
SCREEN_HALF_WIDTH = SCREEN_WIDTH / 2 | |
GROUND_Y_LOC = 300 | |
FULCRUM_HEIGHT_FROM_GROUND = 40 | |
GROUND_ENABLED = True | |
SEESAW_WIDTH = 400 | |
SEESAW_HALF_WIDTH = SEESAW_WIDTH / 2 | |
FULCRUM_Y_LOC = GROUND_Y_LOC + FULCRUM_HEIGHT_FROM_GROUND | |
MAKE_FULCRUM_BE_DYNAMIC = False | |
SCREEN_UPDATE_FREQUENCY = 120.0 # Larger values make the ball movement *SLOWER* | |
# If automatic ball dropping is disabled, then only a mouse click generates a new ball for the scene to process. | |
AUTOMATIC_BALL_DROP_ENABLED = False | |
AUTOMATIC_BALL_DROP_DELAY = 1 # Smaller values make the balls drop more frequently | |
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 drop from the sky onto our seesaw. | |
""" | |
class CircleSprite(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 MyGame(arcade.Window): | |
""" | |
This is the first time you're seeing a python concept called "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 is provided by Sklar -- DO NOT MODIFY THIS: | |
def register_body_into_scene(self, body, *objs): | |
print('###############') | |
print(str(body.body_type)) | |
# Register with the graphics part of the process | |
for obj in objs: | |
if type(obj) == pymunk.shapes.Segment: | |
self.list_of_segments.append(obj) | |
# Register with the physics engine | |
if not (body.body_type == pymunk.Body.STATIC): | |
self.space.add(body) | |
for shape in body.shapes: | |
self.space.add(shape) | |
else: | |
# Static bodies must not be registered, but their internal shapes must be. | |
for shape in body.shapes: | |
self.space.add(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 floor and the pegs must be set up here, but the balls are created/dropped dynamically | |
# after "time has begun" 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 | |
self.space = pymunk.Space() | |
self.space.gravity = (GRAVITY_RIGHTWARD, GRAVITY_DOWNWARD) | |
self.list_of_segments = [] | |
# FIRST PHYSICS BODY: The seesaw's fulcrum. | |
# We've tried both static and dynamic fulcrums. The dynamic case is interesting because it allows simulating | |
# playground equipment that is not properly installed! | |
if MAKE_FULCRUM_BE_DYNAMIC: | |
fulcrum = pymunk.Body(body_type=pymunk.Body.DYNAMIC) | |
else: | |
fulcrum = pymunk.Body(body_type=pymunk.Body.STATIC) | |
fulcrum.position = (SCREEN_HALF_WIDTH, GROUND_Y_LOC) # <<< world coordinate system | |
segment1 = pymunk.Segment(body=fulcrum, a=(0,FULCRUM_HEIGHT_FROM_GROUND), b=(-10,0), radius=1) # < LOCAL coords | |
segment2 = pymunk.Segment(body=fulcrum, a=(0,FULCRUM_HEIGHT_FROM_GROUND), b=( 10,0), radius=1) | |
segment3 = pymunk.Segment(body=fulcrum, a=(-10,0), b=(10,0), radius=1) | |
if MAKE_FULCRUM_BE_DYNAMIC: | |
# If we are specifying the fulcrum as being dynamic, we need to specify a mass value for each of its 3 sides. | |
for segment in (segment1, segment2, segment3): | |
segment.mass = 0.5 | |
self.register_body_into_scene(fulcrum, segment1, segment2, segment3) | |
# SECOND PHYSICS BODY: The ground. This is static. | |
# We are not required to specify the mass (weight) of a static body. | |
if GROUND_ENABLED: | |
ground_body = pymunk.Body(body_type=pymunk.Body.STATIC) | |
ground_body.position = (SCREEN_HALF_WIDTH, GROUND_Y_LOC) # world | |
self.register_body_into_scene(ground_body, | |
pymunk.Segment(body=ground_body, a=(-SCREEN_HALF_WIDTH, 0), b=(SCREEN_HALF_WIDTH, 0), radius=1)) # local | |
# THIRD PHYSICS BODY: The seesaw plank. This is dynamic of course! | |
seesaw_body = pymunk.Body(body_type=pymunk.Body.DYNAMIC) | |
seesaw_body.position = (SCREEN_HALF_WIDTH, GROUND_Y_LOC) | |
# Because this is a DYNAMIC body that is directly involved with collisions with other dynamic objects (balls), | |
# we must specify the friction and mass for it. | |
thickness = 1 | |
seesaw_plank = \ | |
pymunk.Segment(seesaw_body, | |
a=( -SEESAW_HALF_WIDTH, FULCRUM_HEIGHT_FROM_GROUND), | |
b=( SEESAW_HALF_WIDTH, FULCRUM_HEIGHT_FROM_GROUND), | |
radius=thickness) | |
seesaw_plank.friction = 1 | |
seesaw_plank.mass = 1 | |
self.register_body_into_scene(seesaw_body, seesaw_plank) | |
# Set up the connection between the fulcrum point and the seesaw "plank" | |
rotation_center_joint = pymunk.PinJoint(a=seesaw_body, b=fulcrum, anchor_a=(0,FULCRUM_HEIGHT_FROM_GROUND), anchor_b=(0,FULCRUM_HEIGHT_FROM_GROUND)) | |
self.space.add(rotation_center_joint) | |
# Let's start keeping a list of all of the dynamic balls in the scene. | |
self.ball_list = arcade.SpriteList() | |
if AUTOMATIC_BALL_DROP_ENABLED: | |
# How many "ticks" should the program wait before "dropping" the first ball into the playing area | |
self.ticks_to_next_ball = 10 | |
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 = 0.5 | |
radius = 15 | |
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 | |
self.register_body_into_scene(body, shape) | |
# The graphical representation of the balls must be done using Arcade sprites: | |
sprite = CircleSprite(":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() | |
if AUTOMATIC_BALL_DROP_ENABLED: | |
self.ticks_to_next_ball -= 1 | |
if self.ticks_to_next_ball <= 0: | |
self.ticks_to_next_ball = AUTOMATIC_BALL_DROP_DELAY | |
x = random.randint(0, SCREEN_WIDTH) | |
y = SCREEN_HEIGHT | |
self.drop_ball_into_scene(x, y) | |
# 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: | |
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