I have been working on an "Asteroids" remake. However I can not get the objects to move in a random motion. I'm sure this evolves using vectors, however I do not know how to randomly generate a vector for each individual asteroid.
I am looking for a motion similar to this motion shown in this asteroid game, however I have no idea how to even start. This is my code so far:
import pygame as game
import random as r
import math
game.init()
game.display.set_caption("Asteroids")
screen = game.display.set_mode([800,600])
time=0
gameon=True
bgcolor = game.color.Color("#f6cb39")
black=game.color.Color("black")
badguy = game.image.load("asteroid.png")
badguy = game.transform.scale(badguy, (50, 50))
badguys=[]
SPAWNENEMY=10
CLOCK=11
game.time.set_timer(SPAWNENEMY,800)
game.time.set_timer(CLOCK,1000)
font=game.font.Font(None,20)
timetext=font.render("Time: 0", 0, black)
while gameon:
screen.fill(bgcolor)
event=game.event.poll()
if event.type==SPAWNENEMY:
bgbox = game.Rect(badguy.get_rect())
bgbox.x = r.randint(50,800)
bgbox.y = r.randint(50,600)
badguys.append(bgbox)
if event.type==game.QUIT:
gameon=False;
for bg in badguys:
'''
This is where I tried to put the mocment code,
but I was unableto get randmom movments,
only for asteroids to randomly appear or consistently
move in one direction something like "bg.x+=2"
'''
for bg in badguys:
screen.blit(badguy,(bg.x,bg.y))
game.display.flip()
I apologize if its a little long, I don't know what else I can cut out to create an MCV.
Here's how to do it using vectors. Each item in the badguy list is now a pair of items, its current position and an associated speed vector. Note that the position itself is also a vector (aka a "position vector").
Updating the current position is accomplished by simply adding each badguy's speed vector to its current position. i.e. bg[0] += bg[1].
import pygame as game
import pygame.math as math
from pygame.time import Clock
import random as r
game.init()
game.display.set_caption("Asteroids")
screen = game.display.set_mode([800, 600])
time = 0
gameon = True
bgcolor = game.color.Color("#f6cb39")
black = game.color.Color("black")
clock = Clock()
badguy = game.image.load("asteroid.png")
badguy = game.transform.scale(badguy, (50, 50))
badguys = []
SPAWNENEMY = 10
CLOCK = 11
game.time.set_timer(SPAWNENEMY, 800)
game.time.set_timer(CLOCK, 1000)
font=game.font.Font(None,20)
timetext=font.render("Time: 0", 0, black)
while gameon:
screen.fill(bgcolor)
event = game.event.poll()
if event.type == SPAWNENEMY:
# Select a random initial position vector.
posn = math.Vector2(r.randint(50, 800), r.randint(50, 600))
# Create a random speed vector.
speed = r.randint(1, 10)
dx = r.random()*speed * r.choice((-1, 1))
dy = r.random()*speed * r.choice((-1, 1))
vector = math.Vector2(dx, dy)
# Each badguy item is a [position, speed vector].
badguys.append([posn, vector])
if event.type == game.QUIT:
gameon = False;
for bg in badguys:
# Update positions.
bg[0] += bg[1] # Update position using speed vector.
for bg in badguys:
screen.blit(badguy, bg[0])
clock.tick(60)
game.display.flip()
So each asteroid in your game is represented by a Rect, stored in badguys.
With a Rect, you're able to store a postion and a size (since a Rect has the attributes x, y, width and height).
Now, you want to store additional information/state for each asteroid, so only using a Rect is not enough. You need a different data structure that holds more fields.
Since you use python, the fitting data structure is a class that is able to hold the random vector.
But let's think a little bit further. Since you use pygame, pygame already offers a class for represting your game objects, and that class is called Sprite.
So here we go (note the comments in the code):
import pygame
import random
screen = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
class Asteroid(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
# let's create an image of an asteroid by drawing some lines
self.image = pygame.Surface((50, 50))
self.image.set_colorkey((11, 12, 13))
self.image.fill((11, 12, 13))
pygame.draw.polygon(self.image, pygame.Color('grey'), [(0, 11), (20, 0), (50, 10), (15, 22), (27, 36), (10, 50), (0, 11)], 1)
# Let's store a copy of that image to we can easily rotate the image
self.org_image = self.image.copy()
# The rect is used to store the position of the Sprite
# this is required by pygame
self.rect = self.image.get_rect(topleft=(x, y))
# Let's create a random vector for the asteroid
self.direction = pygame.Vector2(0, 0)
while self.direction.length() == 0:
self.direction = pygame.Vector2(random.uniform(-1, 2), random.uniform(-1, 2))
# Also we want a constant, random speed
self.direction.normalize_ip()
self.speed = random.uniform(0.1, 0.3)
# we additionaly store the position in a vector, so the math is easy
self.pos = pygame.Vector2(self.rect.center)
# Aaaaaaaaaand a random rotation, because why not
self.rotation = random.uniform(-0.3, 0.3)
self.angle = 0
def update(self, dt):
# movement is easy, just add the position and direction vector
self.pos += self.direction * self.speed * dt
self.angle += self.rotation * dt
self.image = pygame.transform.rotate(self.org_image, self.angle)
# update the rect, because that's how pygame knows where to draw the sprite
self.rect = self.image.get_rect(center=self.pos)
SPAWNENEMY = pygame.USEREVENT + 1
pygame.time.set_timer(SPAWNENEMY, 800)
asteroids = pygame.sprite.Group()
dt = 0
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
quit()
if e.type == SPAWNENEMY:
asteroids.add(Asteroid(random.randint(50, 200), random.randint(50, 200)))
screen.fill(pygame.Color('black'))
asteroids.draw(screen)
asteroids.update(dt)
pygame.display.flip()
dt = clock.tick(60)
Related
I am trying to draw squares in random positions and random rgb values and I want 1000 of them to be created. The problem I'm facing is that everytime the loop for drawing occurs, it randomizes it all again, is there any way to make this not happen
import pygame
import sys
import random
pygame.init()
win = pygame.display.set_mode((800,600))
pygame.display.set_caption("Simulation")
def safeZone():
#Draws a top rectangle
pygame.draw.rect(win, (50,205,50), (0, 0, 800, 100))
def dot():
width = 10
height = 10
spawnX = random.randrange(1, 801)
spawnY = random.randrange(1, 601)
r = random.randrange(1, 256)
g = random.randrange(1, 256)
b = random.randrange(1, 256)
pygame.draw.rect(win, (r, g, b), (spawnX, spawnY, width, height))
def population(size):
for x in range(size):
dot()
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill((255, 255, 255))
safeZone() # Always draw dots after safe zone
population(1000)
pygame.display.update()
pygame.quit()
Create a dot collection, then just draw that dot collection. Now you can update the dot positions separately, and they will redraw in the new positions. Here, I'm having each dot move a random amount in every loop.
import pygame
import sys
import random
pygame.init()
win = pygame.display.set_mode((800,600))
pygame.display.set_caption("Simulation")
class Dot:
def __init__(self):
self.spawnX = random.randrange(0, 800)
self.spawnY = random.randrange(0, 600)
self.r = random.randrange(0, 256)
self.g = random.randrange(0, 256)
self.b = random.randrange(0, 256)
def safeZone():
#Draws a top rectangle
pygame.draw.rect(win, (50,205,50), (0, 0, 800, 100))
def drawdot(dot):
width = 10
height = 10
pygame.draw.rect(win, (dot.r, dot.g, dot.b), (dot.spawnX, dot.spawnY, width, height))
def population(dots):
for dot in dots:
dot.spawnX += random.randrange(-3,4)
dot.spawnY += random.randrange(-3,4)
drawdot(dot)
alldots = [Dot() for _ in range(1000)]
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill((255, 255, 255))
safeZone() # Always draw dots after safe zone
population(alldots)
pygame.display.update()
A worthwhile modification is to store the whole rectangle in the object:
...
class Dot:
def __init__(self):
self.location = [
random.randrange(0, 800),
random.randrange(0, 600),
10, 10
]
self.color = (
random.randrange(0, 256),
random.randrange(0, 256),
random.randrange(0, 256)
)
def move(self, dx, dy ):
self.location[0] += dx
self.location[1] += dy
def drawdot(dot):
pygame.draw.rect(win, dot.color, dot.location)
def population(dots):
for dot in dots:
dot.move( random.randrange(-3,4), random.randrange(-3,4) )
drawdot(dot)
...
You call a function dot() in which you have assigned randomization. You should introduce a piece of code that randomizes the values outside of the dot() function, and store them in a separate array, and then call the function.
Your description sounds like you aren't necessarily trying to store the result so much as you want the process to be the same every time, but still sort of random? You could just use a hard-coded seed?
import random
random.seed(10)
print(random.random())
See this link for more detail: Random Seed
Hello I'm a pretty new programmer and I'm trying to make a ball bounce off a 45 degree triangle. Here is my code:
This program makes the ball bounce when it hits the sides of the window, but I don't know how to make it bounce off a triangle.
import pygame # importing the pygame
import sys # importing the system libraries
import time # importing timer
import random
from pygame.locals import * # importing the locals functions from the pygame library set
pygame.init() # the function from pygame that initializes all relevant variable
# setting length and width
width = 500
length = 300
# colour variables
WHITE = (255,255,255)
BLUE = (0,0,255)
# importing ball image
ball = pygame.image.load('ball.png')
ballRect = ball.get_rect()
ballRect.left = 300
ballRect.right = 300
# setting speed
x_speed = 2
y_speed = 2
# setting window size
WINDOW = pygame.display.set_mode((width, length))# setting the size of the window
pygame.display.update()
# loop
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
ballRect = ballRect.move(x_speed,y_speed)
WINDOW.fill(WHITE) # changing screen colour to white
WINDOW.blit(ball,ballRect) # printing the ball to screen
pygame.display.update()
pygame.display.flip()
time.sleep(0.002) # to slow down the speed of bouncing
pygame.display.update()
# if the left side of ballRect is in a position less than 0, or the right side of ballRect is greater than 500
if ballRect.left < 0 or ballRect.right > (width):
x_speed = x_speed * -1
# if the top of ballRect is in a position less than 0, or the bottom of ballRect is greater than the length
elif ballRect.top < 0 or ballRect.bottom > (length):
y_speed = y_speed * -1
pygame.display.update()
I haven't drawn in the triangle because I don't know where to, but I expect the ball to bounce off the triangle like it does when it hits the sides of the window. Any help would be great!
Interesting task. A triangle can be defined by a simple list:
triangle = [(250, 220), (400, 300), (100, 300)]
The triangle can be drawn by pygame.draw.polygon()
pygame.draw.polygon(WINDOW, RED, triangle, 0)
Use pygame.math.Vector2 to define the position and the motion vector of the ball:
ballvec = pygame.math.Vector2(1, 1)
ballpos = pygame.math.Vector2(150, 250)
balldiameter = 64
Create a function, which does the collision detection. The function has to detect if the ball hits a line. If the line is hit, then the motion vector of the ball is reflected on the line.
The line is represented by 2 points (lp0, lp1), which are pygame.math.Vector2 objects.
The position of the ball (pt) and the motion vector (dir) are pygame.math.Vector2 objects, too:
def isect(lp0, lp1, pt, dir, radius):
# direction vector of the line
l_dir = (lp1 - lp0).normalize()
# normal vector to the line
nv = pygame.math.Vector2(-l_dir[1], l_dir[0])
# distance to line
d = (lp0-pt).dot(nv)
# intersection point on endless line
ptX = pt + nv * d
# test if the ball hits the line
if abs(d) > radius or dir.dot(ptX-pt) <= 0:
return dir
if (ptX-lp0).dot(l_dir) < 0 or (ptX-lp1).dot(l_dir) > 0:
return dir
# reflect the direction vector on the line (like a billiard ball)
r_dir = dir.reflect(nv)
return r_dir
Append the window rectangle and the triangle to a list of lines. Ech line is represented by a tuple of 2 pygame.math.Vector2 objects:
# add screen rect
screen_rect = [(0, 0), (0, 300), (500, 300), (500, 0)]
for i in range(len(screen_rect)):
p0, p1 = screen_rect[i], screen_rect[(i+1) % len(screen_rect)]
line_list.append((pygame.math.Vector2(p0[0], p0[1]), pygame.math.Vector2(p1[0], p1[1])))
# add red trianlge
triangle = [(250, 220), (400, 300), (100, 300)]
for i in range(len(triangle)):
p0, p1 = triangle[i], triangle[(i+1) % len(triangle)]
line_list.append((pygame.math.Vector2(p0[0], p0[1]), pygame.math.Vector2(p1[0], p1[1])))
Do the collision detection in a loop, which traverse the lines. If the ball hits a line, then the motion vector is replaced by the reflected motion vector:
for line in line_list:
ballvec = isect(*line, ballpos, ballvec, balldiameter/2)
Finally update the position of the ball an the ball rectangle:
ballpos = ballpos + ballvec
ballRect.x, ballRect.y = ballpos[0]-ballRect.width/2, ballpos[1]-ballRect.height/2
See the example code, where I applied the suggested changes to your original code. My ball image has a size of 64x64. The ball diameter has to be set to this size (balldiameter = 64):
Minimal example
import pygame
pygame.init()
window = pygame.display.set_mode((500, 300))
try:
ball = pygame.image.load("Ball64.png")
except:
ball = pygame.Surface((64, 64), pygame.SRCALPHA)
pygame.draw.circle(ball, (255, 255, 0), (32, 32), 32)
ballvec = pygame.math.Vector2(1.5, 1.5)
ballpos = pygame.math.Vector2(150, 250)
balldiameter = ball.get_width()
def reflect_circle_on_line(lp0, lp1, pt, dir, radius):
l_dir = (lp1 - lp0).normalize() # direction vector of the line
nv = pygame.math.Vector2(-l_dir[1], l_dir[0]) # normal vector to the line
d = (lp0-pt).dot(nv) # distance to line
ptX = pt + nv * d # intersection point on endless line
if (abs(d) > radius or dir.dot(ptX-pt) <= 0 or # test if the ball hits the line
(ptX-lp0).dot(l_dir) < 0 or (ptX-lp1).dot(l_dir) > 0):
return dir
r_dir = dir.reflect(nv) # reflect the direction vector on the line (like a billiard ball)
return r_dir
triangle1 = [(250, 220), (400, 300), (100, 300)]
triangle2 = [(250, 80), (400, 0), (100, 0)]
screen_rect = [(0, 0), (0, window.get_height()), window.get_size(), (window.get_width(), 0)]
line_list = []
for p0, p1 in zip(triangle1, triangle1[1:] + triangle1[:1]):
line_list.append((pygame.math.Vector2(p0), pygame.math.Vector2(p1)))
for p0, p1 in zip(triangle2, triangle2[1:] + triangle2[:1]):
line_list.append((pygame.math.Vector2(p0), pygame.math.Vector2(p1)))
for p0, p1 in zip(screen_rect, screen_rect[1:] + screen_rect[:1]):
line_list.append((pygame.math.Vector2(p0), pygame.math.Vector2(p1)))
clock = pygame.time.Clock()
run = True
while run:
clock.tick(250)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for line in line_list:
ballvec = reflect_circle_on_line(*line, ballpos, ballvec, balldiameter/2)
ballpos = ballpos + ballvec
window.fill((64, 64, 64))
pygame.draw.polygon(window, (255, 0, 0), triangle1, 0)
pygame.draw.polygon(window, (0, 0, 255), triangle2, 0)
window.blit(ball, (round(ballpos[0]-balldiameter/2), round(ballpos[1]-balldiameter/2)))
pygame.display.flip()
pygame.quit()
I am using Pymunk to generate videos of balls bouncing in a frame. To render the frames, I run:
import pygame
from pymunk.pygame_util import DrawOptions
screen = pygame.display.set_mode((500, 500))
draw_options = DrawOptions(screen)
# define `space` with six balls
# ...
space.debug_draw(draw_options)
which gives the following output:
As we can see, it draws a black line going from the center to the edge of the ball; I suppose it is to indicate the rotation of each ball.
I would like NOT to have this line drawn: i.e. explicitly pass a flag to prevent pygame from drawing it, or specify the color of this line to be the same as the rest of the ball.
Would anyone know how to do that?
The debug_draw method should really be used mainly for debugging. I usually create sprite subclasses for my physics objects and give them a Pymunk body and a shape as attributes. Then I can update the self.rect in the update method by setting the coordinates to the coords of the body and also use it to rotate the image.
import math
import random
import pygame as pg
import pymunk as pm
from pymunk import Vec2d
def flipy(p):
"""Convert chipmunk physics to pygame coordinates."""
return Vec2d(p[0], -p[1]+800)
class Entity(pg.sprite.Sprite):
def __init__(self, pos, space, radius, mass=1):
super().__init__()
# The surface is a bit bigger, so that the circle fits better.
self.orig_image = pg.Surface((radius*2+2, radius*2+2), pg.SRCALPHA)
self.image = self.orig_image
# Draw a circle onto the image.
pg.draw.circle(
self.image,
pg.Color(random.randrange(256),
random.randrange(256),
random.randrange(256)),
(radius+1, radius+1), # +1 looks a bit better.
radius)
self.rect = self.image.get_rect(topleft=pos)
# Create a Pymunk body and a shape and add them to the space.
moment = pm.moment_for_circle(mass, radius, radius)
self.body = pm.Body(mass, moment)
self.shape = pm.Circle(self.body, radius)
self.shape.friction = .1
self.shape.elasticity = .99
self.body.position = pos
self.space = space
self.space.add(self.body, self.shape)
def update(self):
# Update the rect because it's used to blit the image.
self.rect.center = flipy(self.body.position)
# Use the body's angle to rotate the image.
self.image = pg.transform.rotozoom(self.orig_image, math.degrees(self.body.angle), 1)
self.rect = self.image.get_rect(center=self.rect.center)
if self.rect.left < 0 or self.rect.right > 1280 or self.rect.y > 790:
self.space.remove(self.body, self.shape)
self.kill()
class Game:
def __init__(self):
pg.init()
self.screen = pg.display.set_mode((1280, 800))
self.done = False
self.clock = pg.time.Clock()
# Pymunk stuff
self.space = pm.Space()
self.space.gravity = Vec2d(0.0, -900.0)
self.space.damping = .9
self.static_lines = [
pm.Segment(self.space.static_body, flipy((60.0, 780.0)), flipy((650.0, 780.0)), .0),
pm.Segment(self.space.static_body, flipy((650.0, 780.0)), flipy((1218.0, 660.0)), .0)
]
for lin in self.static_lines:
lin.friction = 0.2
lin.elasticity = 0.99
self.space.add(self.static_lines)
self.all_sprites = pg.sprite.Group()
def run(self):
while not self.done:
self.dt = self.clock.tick(60) / 1000
self.handle_events()
self.run_logic()
self.draw()
self.current_fps = self.clock.get_fps()
pg.quit()
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
elif event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1: # Left mouse button.
# Spawn an entity.
radius = random.randrange(20, 50)
self.all_sprites.add(Entity(flipy(pg.mouse.get_pos()), self.space, radius))
if pg.mouse.get_pressed()[2]: # Right mouse button.
radius = random.randrange(20, 50)
self.all_sprites.add(Entity(flipy(pg.mouse.get_pos()), self.space, radius))
def run_logic(self):
self.space.step(1/60)
self.all_sprites.update()
def draw(self):
self.screen.fill(pg.Color(140, 120, 110))
self.all_sprites.draw(self.screen) # Draw the images of all sprites.
# Draw the static lines.
for line in self.static_lines:
body = line.body
p1 = flipy(body.position + line.a.rotated(body.angle))
p2 = flipy(body.position + line.b.rotated(body.angle))
pg.draw.lines(self.screen, pg.Color('lightgray'), False, (p1, p2), 5)
pg.display.flip()
if __name__ == '__main__':
Game().run()
I also tried to find a way to hide these direction/rotation lines that debug_draw creates and found out that the lines are colored in the draw_options.shape_outline_color and the circles use the draw_options.shape_dynamic_color by default. So, when you set
draw_options.shape_outline_color = draw_options.shape_dynamic_color
the circles are completely blue. However, if you assign a specific color to each shape, it will still use the blue color for the lines.
And with the draw_options.flags attribute, I could only turn off the collision points or the shapes completely but not the lines:
# This is how you can turn off the collision points.
draw_options.flags ^= draw_options.DRAW_COLLISION_POINTS
# Stops drawing the constraints.
draw_options.flags ^= draw_options.DRAW_CONSTRAINTS
# Stops drawing all shapes.
draw_options.flags ^= draw_options.DRAW_SHAPES
I have a program with a player (who is an image) and a rectangle and I want that when the player has a collision with the rectangle, the size of the image increase.
For now, I have this code :
import pygame
from random import randint
WIDTH, HEIGHT = 800, 800
FPS = 60
pygame.init()
win = pygame.display.set_mode((WIDTH, HEIGHT))
fenetre_rect = pygame.Rect(0, 0, WIDTH, HEIGHT)
pygame.display.set_caption("Hagar.io")
clock = pygame.time.Clock()
bg = pygame.image.load("bg.png").convert()
bg_surface = bg.get_rect(center=(WIDTH / 2, HEIGHT / 2))
bg_x = bg_surface.x
bg_y = bg_surface.y
x_max = WIDTH / 2
y_max = HEIGHT / 2
# player
player = pygame.transform.scale(pygame.image.load("player.png").convert_alpha(), (i, i))
player_rect = player.get_rect(center=(x_max, y_max))
# cell
rect_surface = pygame.Rect(300, 500, 20, 20)
# Game loop
running = True
while running:
dt = clock.tick(FPS) / 1000
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if player_rect.colliderect(rect_surface):
print("collide")
bg_surface.x = bg_x
bg_surface.y = bg_y
# draw on screen
win.blit(bg, bg_surface)
pygame.draw.rect(win, (255, 0, 0), rect_surface)
win.blit(player, player_rect)
pygame.display.flip()
pygame.quit()
I have try to add in the "colliderect" condition but it does not work :
player_rect.width += 1
player_rect.height += 1
Thanks for your help !
This line
player = pygame.transform.scale(pygame.image.load("player.png").convert_alpha(), (i, i))
is using the variable i but it is not defined in your code. I'm not sure where it is defined, but it is key to what you want. I will try to answer without this information anyway:
Thing is, enlarging the rect won't do anything, because a rect is just coordinates. You have to scale the actual image, and pygame.transform.scale does exactly that.
You can keep the image in a separate variable player_img:
player_img = pygame.image.load("player.png").convert_alpha()
player = pygame.transform.scale(player_img, (i, i))
Then when you want to scale it differently, just call .scale() again:
double_size_player = pygame.transform.scale(player_img, (i*2, i*2))
That still leaves us to the mistery of your undefined i variable, but I think you get the gist of it. Remeber that you have to extract a new rect from the scaled image because it will be bigger.
is it possible to get the coordinates of the place where the sprites are colliding?, and if it more than one is it possible to get both?
Thanks a lot for the solvers
You can use the Pymunk physics library to get the contact points. Of course that means you'll have to familiarize yourself with this library first (it can be a bit difficult for beginners). You especially need to know how collision handlers, arbiters and callback functions work.
So you create a collision handler which checks if there were collisions between shapes of two specified collision types, e.g. handler = space.add_collision_handler(1, 1). When a collsion occurs the handler calls some callback functions (in the example I set handler.post_solve to the callback function) which receive an arbiter object as an argument that holds the collision data. Then you can extract the needed information from this arbiter, add it to a list or other data structure and use it in your main loop.
import sys
import random
import pygame as pg
import pymunk as pm
from pymunk import Vec2d
def flipy(p):
"""Convert chipmunk coordinates to pygame coordinates."""
return Vec2d(p[0], -p[1]+600)
class Ball(pg.sprite.Sprite):
def __init__(self, space, pos, mass=5, radius=30, elasticity=0.9):
super().__init__()
self.image = pg.Surface((60, 60), pg.SRCALPHA)
pg.draw.circle(self.image, pg.Color('royalblue'), (30, 30), radius)
self.rect = self.image.get_rect(center=pos)
# Set up the body and shape of this object and add them to the space.
inertia = pm.moment_for_circle(mass, 0, radius, (0, 0))
self.body = pm.Body(mass, inertia)
self.body.position = flipy(pos)
self.shape = pm.Circle(self.body, radius, (0, 0))
self.shape.elasticity = elasticity
# This type will be used by the collision handler.
self.shape.collision_type = 1
self.space = space
self.space.add(self.body, self.shape)
def update(self):
pos = flipy(self.body.position)
self.rect.center = pos
if pos.y > 600:
self.space.remove(self.body, self.shape)
self.kill()
def main():
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
done = False
contact_points = []
def contact_callback(arbiter, space, data):
"""Append the contact point to the contact_points list."""
if arbiter.is_first_contact:
for contact in arbiter.contact_point_set.points:
contact_points.append(contact.point_a)
# Pymunk stuff.
space = pm.Space()
space.gravity = Vec2d(0, -900)
# This collision handler will be used to get the contact points.
# It checks if shapes with `collision_type` 1 collide with others
# that also have type 1.
handler = space.add_collision_handler(1, 1)
# After a collision is solved, the callback funtion will be called
# which appends the contact point to the `contact_points` list.
handler.post_solve = contact_callback
# Create some static lines.
static_lines = [
pm.Segment(space.static_body, (170, 200), (0, 300), .1),
pm.Segment(space.static_body, (170, 200), (500, 200), .1),
pm.Segment(space.static_body, (500, 200), (600, 260), .1),
]
for line in static_lines:
line.elasticity = 0.9
space.add(static_lines)
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEBUTTONDOWN:
all_sprites.add(Ball(space, event.pos))
contact_points = []
space.step(1/60) # Update the physics space.
all_sprites.update()
screen.fill((60, 70, 80))
all_sprites.draw(screen) # Draw the sprite group.
# Draw static_lines.
for line in static_lines:
body = line.body
p1 = flipy(body.position + line.a.rotated(body.angle))
p2 = flipy(body.position + line.b.rotated(body.angle))
pg.draw.line(screen, pg.Color('gray68'), p1, p2, 5)
# Draw contact_points.
for point in contact_points:
x, y = flipy(point)
x, y = int(x), int(y)
pg.draw.circle(screen, pg.Color('orange'), (x, y), 8)
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()