Skip to content

Instantly share code, notes, and snippets.

@dfsklar
Last active January 9, 2021 02:00
Show Gist options
  • Save dfsklar/a8fb22d543189646a492fe4d9ea8b885 to your computer and use it in GitHub Desktop.
Save dfsklar/a8fb22d543189646a492fe4d9ea8b885 to your computer and use it in GitHub Desktop.
"""
INCLINED-PLANK version 1
"""
import arcade
import pymunk
import random
import timeit
import math
SCREEN_TITLE = "Pymunk Inclined-Plane 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
GROUND_WIDTH = 560
GROUND_HALF_WIDTH = GROUND_WIDTH / 2
FULCRUM_Y_LOC = GROUND_Y_LOC + FULCRUM_HEIGHT_FROM_GROUND
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
class ArcadeGame(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.
"""
def register_body_into_scene(self, body, *objs):
if not (body.body_type == pymunk.Body.STATIC):
print("Adding a dynamic body")
self.space.add(body)
else:
print("Adding a static body")
for shape in body.shapes:
self.space.add(shape)
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 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: INCLINED PLANE/PLANK
# It is a *static* body -- it will never move.
# We are not required to specify the mass (weight) of a static body.
# But we MUST specify a friction value for any segment that might be touched by another object.
# So in this case, this one-segment body must have a friction value on that one segment.
plank = pymunk.Body(body_type=pymunk.Body.STATIC)
plank.position = (SCREEN_HALF_WIDTH, GROUND_Y_LOC)
plank_segment = (pymunk.Segment(body=plank, a=(0,0), b=(200,100), radius=2))
plank_segment.friction = 1.8 # Without this setting, the plank will be "covered in slippery ice"!
plank_segment.color = arcade.color.YELLOW
self.register_body_into_scene(plank)
# SECOND PHYSICS BODY: GROUND (static)
ground_body = pymunk.Body(body_type=pymunk.Body.STATIC)
ground_body.position = (SCREEN_HALF_WIDTH, GROUND_Y_LOC)
ground_segment = \
pymunk.Segment(body=ground_body, a=[-(GROUND_WIDTH/2), 0], b=[GROUND_WIDTH/2, 0], radius=3)
ground_segment.friction = 0.8 # Giving the ground *less* friction than the inclined plank
ground_segment.color = arcade.color.GREEN
self.register_body_into_scene(ground_body)
# THIRD PHYSICS BODY: The concrete block. This is dynamic of course!
# Any dynamic body needs to have a mass; the best approach is to set the mass information on its shapes, not on the body itself.
block_body = pymunk.Body(body_type=pymunk.Body.DYNAMIC)
block_body.position = (550, GROUND_Y_LOC+200)
thickness = 1
# Obviously, the next few lines are awkward. I'm going to build a utility function that
# will make it easier for you to build polygonal objects, assign them colors/friction amounts, etc.
# So that's forthcoming - meanwhile, this approach of having to create a variable to store each segment
# is unfortunately necessary.
segment1 = pymunk.Segment(body=block_body, a=(0,0), b=(20,0), radius=thickness) # < LOCAL coords
segment2 = pymunk.Segment(body=block_body, a=(20,0), b=(20,20), radius=thickness)
segment3 = pymunk.Segment(body=block_body, a=(20,20), b=(0,20), radius=thickness)
# NOTE: We are intentionally giving only one line segment an actual mass value and friction value.
# This is what leads to the delightful tumbling that the block does when it falls.
# I'm giving this segment an orange color so you can distinguish this special segment from the others.
segment4 = pymunk.Segment(body=block_body, a=(0,20), b=(0,0), radius=thickness)
segment4.color = arcade.color.ORANGE
segment4.friction = 1
segment4.mass = 300
# Obviously, in the "real world", all 4 sides would have mass and friction. Keep that in mind...
self.register_body_into_scene(block_body)
def on_draw(self):
"""
Render the screen.
"""
arcade.start_render()
for line in self.list_of_segments:
body = line.body
pv1 = body.position + line.a.rotated(body.angle)
pv2 = body.position + line.b.rotated(body.angle)
try:
color = line.color
except:
color = arcade.color.WHITE
arcade.draw_line(pv1.x, pv1.y, pv2.x, pv2.y, color, 2)
def on_update(self, delta_time):
# 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)
# So now that the class definition is finished, we must *build* an object of the class, and then run it.
ArcadeGame(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