Created
January 21, 2021 22:30
-
-
Save dfsklar/5d9bee04f050ab4adf28e6ea949defe8 to your computer and use it in GitHub Desktop.
arcmunk_simplemotor.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
""" | |
SIMPLE MOTOR version 1 | |
""" | |
import arcade | |
import pymunk | |
import random | |
import timeit | |
import math | |
SCREEN_TITLE = "Pymunk Motor 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 = 150 | |
SEESAW_HALF_WIDTH = SEESAW_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 | |
""" | |
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_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 "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 add_linesegment_to_scene(self, new_segment): | |
x = new_segment | |
return | |
#self.space.add(new_segment) | |
#self.list_of_segments.append(new_segment) | |
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) | |
for shape in body.shapes: | |
self.space.add(shape) | |
self.list_of_segments.append(shape) | |
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) | |
# Let's start keeping a list of all of the dynamic balls in the scene. | |
self.ball_list = arcade.SpriteList() | |
self.list_of_segments = [] | |
# FIRST PHYSICS BODY: The seesaw's fulcrum. | |
# It is a *static* body -- it will never move. | |
# We are not required to specify the mass (weight) of a static body. | |
fulcrum = pymunk.Body(body_type=pymunk.Body.STATIC) | |
fulcrum.position = (SCREEN_HALF_WIDTH, GROUND_Y_LOC) | |
# Remember the triangular fulcrum shape we used in the see-saw example? | |
# Well, in this case, we do NOT want the fulcrum to have any physics shape at all because we'd | |
# like the plank to be able to completely rotate 360 degrees without "hitting" anything that might interfere. | |
# So this fulcrum's "body" is "empty" -- we do not place any segments in it. | |
self.register_body_into_scene(fulcrum) | |
# 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. | |
sprite = CircleSprite(":resources:images/items/gold_1.png", \ | |
fulcrum.position[0], fulcrum.position[1] + FULCRUM_HEIGHT_FROM_GROUND, 5) | |
self.ball_list.append(sprite) | |
# PHYSICS BODY: The seesaw plank. 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. | |
seesaw_body = pymunk.Body(body_type=pymunk.Body.DYNAMIC) | |
seesaw_body.position = (SCREEN_HALF_WIDTH, GROUND_Y_LOC) | |
# Segments inside the body use coordinate system relative to the body's position ?? | |
thickness = 1 | |
seesaw_shape = \ | |
pymunk.Segment(seesaw_body, | |
a=( -SEESAW_HALF_WIDTH, FULCRUM_HEIGHT_FROM_GROUND), | |
b=( SEESAW_HALF_WIDTH, FULCRUM_HEIGHT_FROM_GROUND), | |
radius=thickness) | |
seesaw_shape.friction = 1 | |
seesaw_shape.mass = 4 | |
chamber_width = 38 | |
self.register_body_into_scene(seesaw_body, seesaw_shape, | |
pymunk.Segment(seesaw_body, | |
a=( -SEESAW_HALF_WIDTH, FULCRUM_HEIGHT_FROM_GROUND), | |
b=( -SEESAW_HALF_WIDTH-10, FULCRUM_HEIGHT_FROM_GROUND+chamber_width), | |
radius=thickness), | |
pymunk.Segment(seesaw_body, | |
a=( -SEESAW_HALF_WIDTH+120, FULCRUM_HEIGHT_FROM_GROUND+chamber_width), | |
b=( -SEESAW_HALF_WIDTH-10, FULCRUM_HEIGHT_FROM_GROUND+chamber_width), | |
radius=thickness), | |
pymunk.Segment(seesaw_body, | |
a=( SEESAW_HALF_WIDTH, FULCRUM_HEIGHT_FROM_GROUND), | |
b=( SEESAW_HALF_WIDTH+10, FULCRUM_HEIGHT_FROM_GROUND+chamber_width), | |
radius=thickness)) | |
# Set up the connection between the fulcrum point and the seesaw "plank" | |
self.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(self.rotation_center_joint) | |
# Now, motorize that connection | |
motor = pymunk.SimpleMotor(fulcrum, seesaw_body, 2) | |
self.space.add(motor) | |
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 on_draw(self): | |
""" | |
Render the screen. | |
""" | |
# This command has to happen before we start drawing | |
arcade.start_render() | |
draw_start_time = timeit.default_timer() | |
self.ball_list.draw() | |
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) | |
arcade.draw_line(pv1.x, pv1.y, pv2.x, pv2.y, arcade.color.WHITE, 2) | |
def on_mouse_press(self, x, y, which_button, which_modifiers): | |
self.drop_ball_into_scene(x, y) | |
def drop_ball_into_scene(self, x, y): | |
mass = 0.5 | |
radius = 15 | |
#inertia = pymunk.moment_for_circle(mass, 0, radius, (0, 0)) | |
body = pymunk.Body(body_type=pymunk.Body.DYNAMIC) # mass, inertia) | |
body.position = (x, y) | |
shape = pymunk.Circle(body, radius) | |
shape.friction = 0.3 | |
shape.mass = mass | |
self.space.add(body, shape) | |
sprite = CircleSprite_for_pymunk_shape(":resources:images/items/gold_1.png", shape) | |
self.ball_list.append(sprite) | |
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): | |
try: | |
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() | |
except: | |
pass | |
# 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 | |
def main(): | |
MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE) | |
arcade.run() | |
if __name__ == "__main__": | |
main() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment