How to make ball bounce off triangle in pygame? - python

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()

Related

How can i render non rectangular part of an image?

I'm learning how to create a multiplayer game with pygame by recreating Among-us.
I'm currently trying to recreate the vision system.
To make sure that the vision is blocked by walls, i've seen that a simple method is to place points at each corner of walls and to only draw the inner part of the triangles made between those points and the player.
(I'm aware that i'll have to stop the purple lines when they hit a wall.)
But my issue now is how can i draw the background (its an image of the Among_us map) only on those triangles and shade the rest of the image ?
PS: If you know how to integrate the shape bewteen the triangle and the and of the vision circle(like on the right of the picture) it would help to.
At first i thought about using the area parameter of blit but it only work with rectangles.
Maybe i could draw each pixel one by one but i don't think that it's a great idea.
I hope there is a better way.
To make sure that the vision is blocked by walls, i've seen that a simple method is to place points at each corner of walls and to only draw the inner part of the triangles made between those points and the player.
That's the way to go. Maybe you'll find this tutorial helpful.
But my issue now is how can i draw the background ... only on those triangles and shade the rest of the image ?
Create a black surface, "cut out" the visible area (for example, drawing a polygon with a color key will do), and just draw it on top; like Woodford already said in a comment.
PS: If you know how to integrate the shape bewteen the triangle and the and of the vision circle(like on the right of the picture) it would help to.
Here you can use the same idea. Create a black surface, draw a transparent circle with center = postion of the player and radius = 'max visible distance', and slap it on top.
Here I quickly ported the tutorial I linked to python/pygame:
# A port of https://ncase.me/sight-and-light/ to pygame
import pygame, math
from itertools import cycle,islice
from collections import namedtuple
FPS = 60
SCREEN_SIZE = 800, 600
SCREEN_RECT = pygame.Rect(0, 0, *SCREEN_SIZE)
COLOR_KEY = (12, 23, 34)
Point = namedtuple("Point", "x y")
Line = namedtuple("Ray", "a b")
Intersection = namedtuple("Intersection", "x y param angle", defaults=[0])
class Obstacle(pygame.sprite.Sprite):
def __init__(self, points, *args):
super().__init__(*args)
self.image = pygame.Surface(SCREEN_RECT.size)
self.image.set_colorkey(COLOR_KEY)
self.image.fill(COLOR_KEY)
self.rect = self.image.get_rect()
self.points = points
pygame.draw.polygon(self.image, (30, 255, 30), self.points, 4)
def __iter__(self):
return iter(self.points)
class Actor(pygame.sprite.Sprite):
def __init__(self, *args):
super().__init__(*args)
self.image = pygame.Surface((20, 20))
self.image.fill((255, 30, 30))
self.rect = self.image.get_rect(center=SCREEN_RECT.center)
def get_intersection(ray, segment):
"""Find intersection of RAY & SEGMENT"""
# RAY in parametric: Point + Delta*T1
r_px = ray.a.x
r_py = ray.a.y
r_dx = ray.b.x-ray.a.x
r_dy = ray.b.y-ray.a.y
# SEGMENT in parametric: Point + Delta*T2
s_px = segment.a.x
s_py = segment.a.y
s_dx = segment.b.x-segment.a.x
s_dy = segment.b.y-segment.a.y
# Are they parallel? If so, no intersect
r_mag = math.sqrt(r_dx*r_dx+r_dy*r_dy);
s_mag = math.sqrt(s_dx*s_dx+s_dy*s_dy);
if r_dx/r_mag==s_dx/s_mag and r_dy/r_mag==s_dy/s_mag:
return
try:
# SOLVE FOR T1 & T2
# r_px+r_dx*T1 = s_px+s_dx*T2 && r_py+r_dy*T1 = s_py+s_dy*T2
# ==> T1 = (s_px+s_dx*T2-r_px)/r_dx = (s_py+s_dy*T2-r_py)/r_dy
# ==> s_px*r_dy + s_dx*T2*r_dy - r_px*r_dy = s_py*r_dx + s_dy*T2*r_dx - r_py*r_dx
# ==> T2 = (r_dx*(s_py-r_py) + r_dy*(r_px-s_px))/(s_dx*r_dy - s_dy*r_dx)
T2 = (r_dx*(s_py-r_py) + r_dy*(r_px-s_px))/(s_dx*r_dy - s_dy*r_dx)
T1 = (s_px+s_dx*T2-r_px)/r_dx
# Must be within parametic whatevers for RAY/SEGMENT
if T1<0: return
if T2<0 or T2>1: return
# Return the POINT OF INTERSECTION
return Intersection(r_px+r_dx*T1, r_py+r_dy*T1, T1)
except ZeroDivisionError:
pass
def get_all_angles_from_points(start, points):
angles = []
for p in points:
angle = math.atan2(p[1]-start[1], p[0]-start[0])
angles.append(angle-0.0001)
angles.append(angle)
angles.append(angle+0.0001)
return angles
def get_blocker_surface(intersects):
intersects = sorted(intersects, key=lambda x: x.angle)
as_points = list(map(lambda r: (r.x, r.y), intersects))
blocker_surface = pygame.Surface(SCREEN_SIZE)
blocker_surface.set_colorkey(COLOR_KEY)
pygame.draw.polygon(blocker_surface, COLOR_KEY, as_points)
return blocker_surface
def fov(screen, start, obstacles):
points = set(p for o in obstacles for p in o)
angles = get_all_angles_from_points(start, points)
intersects = []
for angle in angles:
dx = math.cos(angle)
dy = math.sin(angle)
ray = Line(Point(start.x, start.y), Point(start.x + dx, start.y + dy))
closest_intersection = None
for o in obstacles:
connected_points = zip(o.points, islice(cycle(o.points), 1, None))
for segment in connected_points:
intersection = get_intersection(ray, Line._make(segment))
if not intersection: continue
if not closest_intersection or intersection.param < closest_intersection.param:
closest_intersection = intersection
if closest_intersection:
intersects.append(closest_intersection._replace(angle=angle))
return intersects
def main():
pygame.init()
pygame.display.set_caption("FOV demo")
clock = pygame.time.Clock()
screen = pygame.display.set_mode(SCREEN_SIZE)
sprites = pygame.sprite.Group()
obstacle_sprites = pygame.sprite.Group()
font = pygame.font.SysFont("Consolas", 20)
tmprect = SCREEN_RECT.inflate(-2, -2)
data = [
[tmprect.topleft, tmprect.topright, tmprect.bottomright, tmprect.bottomleft],
[(229, 211), (336, 294), (271, 406), (126, 323)],
[(386, 170), (487, 167), (393, 74), (309, 186)],
[(631, 502), (576, 457), (690, 389), (764, 493)],
[(519, 308), (489, 378), (589, 381), (532, 292)],
[(349, 552), (347, 480), (441, 484), (475, 566)],
[(637, 93), (592, 228), (726, 278), (661, 153)],
[(186, 494), (88, 431), (29, 533), (174, 566)],
[(97, 138), (32, 242), (150, 178), (161, 74)]
]
obstacles = [Obstacle([Point._make(t) for t in o], sprites, obstacle_sprites) for o in data]
player = Actor(sprites)
def stay_on_mouse(this):
this.rect.center = pygame.mouse.get_pos()
player.update = lambda *args: stay_on_mouse(player, *args)
do_fov = True
do_debug = False
do_draw_obstacles_on_top = True
fov_alpha = 255
while True:
evs = pygame.event.get()
for e in evs:
if e.type == pygame.QUIT:
return
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_d: do_debug = not do_debug
if e.key == pygame.K_o: do_draw_obstacles_on_top = not do_draw_obstacles_on_top
if e.key == pygame.K_SPACE: do_fov = not do_fov
if e.key == pygame.K_ESCAPE: return
sprites.update()
screen.fill((50, 50, 50))
# draw regular stuff
sprites.draw(screen)
# hide stuff due to FOV
intersects = fov(screen, Point._make(player.rect.center), obstacles)
blocker_surface = get_blocker_surface(intersects)
fov_alpha = min(fov_alpha+5, 255) if do_fov else max(fov_alpha-5, 0)
blocker_surface.set_alpha(fov_alpha)
screen.blit(blocker_surface, (0, 0))
if do_debug:
for i in intersects:
pygame.draw.line(screen, (100, 100, 100), (i.x, i.y), player.rect.center)
if do_draw_obstacles_on_top:
obstacle_sprites.draw(screen)
y = 24
for line in ["[SPACE] - Toggle FOV", "[D] - Draw debug lines", "[O] - Draw obstascles on top"]:
screen.blit(font.render(line, True, (0, 0, 0)), (22, y+2))
screen.blit(font.render(line, True, (255, 255, 255)), (20, y))
y+= 24
pygame.display.flip()
clock.tick(FPS)
main()
And it looks like this:
That should get you started.

what does draw.lines return?

from what I understand draw.lines joins each coordinate that is passed to it, so if we have [A, B, C]
will draw a line from coordinate A to coordinate B and from B to C and if closed is True it draws a line from coordinate A to coordinate C therefore it will always join the first coordinate with the last one its right?
what i don't understand is what the rect variable returns..
I think that every time I add a new coordinate it returns (starting_point, rectangle_size) where starting point is the first coordinate and the rectangle size is calculated by the distance of the first coordinate with the last one then draw the rectangle with draw.rect
but the reasoning I don't think is right because if I add a coordinate of this type to the list the rectangle remains unchanged
CODE:
"""Place a polygone line with the clicks of the mouse."""
import pygame
from pygame.locals import *
RED = (255, 0, 0)
GREEN = (0, 255, 0)
GRAY = (150, 150, 150)
pygame.init()
screen = pygame.display.set_mode((640, 240))
drawing = False
points = []
running = True
while running:
for event in pygame.event.get():
if event.type == MOUSEBUTTONDOWN:
points.append(event.pos)
drawing = True
elif event.type == MOUSEBUTTONUP:
drawing = False
elif event.type == MOUSEMOTION and drawing:
points[-1] = event.pos
screen.fill(GRAY)
if len(points)>1:
rect = pygame.draw.lines(screen, RED, True, points, 3)
pygame.draw.rect(screen, GREEN, rect, 1)
pygame.display.update()
pygame.quit()
pygame.draw.lines() returns a pygame.Rect object that encloses all the points of the line:
a rect bounding the changed pixels, if nothing is drawn the bounding rect's position will be the position of the first point in the points parameter (float values will be truncated) and its width and height will be 0
The rectangle does not start at any particular point on the line, but is just large enough to enclose all the points along the line.
It returns the same as:
list_x, list_y = zip(*points)
min_x, max_x = min(list_x), max(list_x)
min_y, max_y = min(list_y), max(list_y)
rect = pygame.Rect(min_x, min_y, max_x-min_x, max_y-min_y)
According to the docs:
Returns:
a rect bounding the changed pixels, if nothing is drawn the bounding rect's position will be the position of the first point in the points parameter (float values will be truncated) and its width and height will be 0
this is the reasoning I made:
import pygame
def minore(lista_coordinate, asse):
dimensione = lista_coordinate[0][asse]
for coordinata in lista_coordinate:
if coordinata[asse] < dimensione:
dimensione = coordinata[asse]
return dimensione
def maggiore(lista_coordinate, asse):
dimensione = lista_coordinate[0][asse]
for coordinata in lista_coordinate:
if coordinata[asse] > dimensione:
dimensione = coordinata[asse]
return dimensione
pygame.init()
schermo = pygame.display.set_mode((500, 400))
punti = [(100, 100), (200, 100), (200, 50), (150, 80)]
larghezza_minore = minore(punti, 0)
larghezza_maggiore = maggiore(punti, 0)
larghezza_rettangolo = larghezza_maggiore - larghezza_minore
altezza_minore = minore(punti, 1)
altezza_maggiore = maggiore(punti, 1)
altezza_rettangolo = altezza_maggiore - altezza_minore
dimensioni_rettangolo = (larghezza_rettangolo, altezza_rettangolo)
inizio = (larghezza_minore, altezza_minore)
pygame.draw.lines(schermo, (255, 0, 0), True, punti, 3)
pygame.draw.rect(schermo, (0, 255, 0), (inizio, dimensioni_rettangolo), 1)
pygame.display.update()

inconsistent motion with pygame

I am having a problem where i am trying to get a circle to go to the same spot every time i execute the program. But each time I run the code, the dot doesn't always line up. I have a test circle in the same place to compare run to run. The Red circle should cover the white circle perfectly but it changes every time i run the program. I am reseting the kernal as i am using pygame.time.get_ticks() to time everything.
import sys, pygame, math
from pygame.locals import *
# set up a bunch of constants
BLUE = ( 0, 0, 255)
WHITE = (255, 255, 255)
ORANGE = (255, 165, 0)
PINK = (255, 20, 147)
RED = (255, 0, 0)
GREEN = ( 0, 255, 0)
LIMEGREEN = ( 50, 205, 50)
YELLOW = (255, 255, 0)
PURPLE = (160, 32, 240)
BLACK = ( 0, 0, 0)
#Background Colour
BGCOLOR = BLACK
#Setting Window Size and finding window x and y centre
WINDOWWIDTH = 1918# width of the program's window, in pixels 960x540
WINDOWHEIGHT = 1078# height in pixels
WIN_CENTERX = int(WINDOWWIDTH / 2) # the midpoint for the width of the window
WIN_CENTERY = int(WINDOWHEIGHT / 2) # the midpoint for the height of the window
# frames per second to run at
FPS = 60
#intializing Variables
AMPLITUDE = 450
colourArray=[BLUE,WHITE,YELLOW,GREEN,RED,PINK,PURPLE,LIMEGREEN,ORANGE]
i=0
xPos = 0
step = 0
small_step =0
stop_step=step=0
xPos=0
yPos=0
c=RED
timestep=0
# standard pygame setup code
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),pygame.FULLSCREEN)
pygame.display.set_caption('Task1')
fontObj = pygame.font.Font('freesansbold.ttf', 16)
# main application loop
while True:
# event handling loop for quit events
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
#setup for label and time
tempTime=pygame.time.get_ticks()/1000
time_string=str(tempTime)
instructionsSurf = fontObj.render(time_string, True, WHITE, BGCOLOR)
instructionsRect = instructionsSurf.get_rect()
instructionsRect.left = 10
instructionsRect.bottom = WINDOWHEIGHT - 10
# fill the screen to draw from a blank state
DISPLAYSURF.fill(BGCOLOR)
DISPLAYSURF.blit(instructionsSurf, instructionsRect)
tempTime=pygame.time.get_ticks()/1000
#Color change loop
c=RED
if (0<=(tempTime)<3):
c=RED
if (3<=(tempTime)<5):
c=BLUE
if (5<=(tempTime)<7):
c=GREEN
if (7<=(tempTime)<9):
c=YELLOW
if (9<=(tempTime)<11):
c=WHITE
if (11<=(tempTime)<17):
c=RED
if (17<=(tempTime)<42):
c=RED
if (42<=(tempTime)<46):
c=RED
if (46<=(tempTime)<120):
c=colourArray[i]
#Setting position of x and y coordinates
if (0<=(tempTime)<14):
xPos = 0
yPos = 0
if (14<(tempTime)<17):
small_step += 5.111
xPos = small_step
yPos = 0
if (17<(tempTime)<43):
step += 0.05001
step %= 2 * math.pi
xPos = math.cos(step) * AMPLITUDE
yPos = math.sin(step) * AMPLITUDE
if (43<(tempTime)<46):
stop_step=step
xPos = math.cos(stop_step) * AMPLITUDE
yPos = math.sin(stop_step) * AMPLITUDE
if (46<(tempTime)<120):
step += 0.05001
step %= 2 * math.pi
xPos = math.cos(step) * AMPLITUDE
yPos = math.sin(step) * AMPLITUDE
#test dot
pygame.draw.circle(DISPLAYSURF, WHITE, (WIN_CENTERX+AMPLITUDE, 0+WIN_CENTERY),12,0)
# draw dot1
dot1=pygame.draw.circle(DISPLAYSURF, c, (int(xPos)+ WIN_CENTERX, int(yPos) + WIN_CENTERY), 12,0)
# draw dot2
dot2=pygame.draw.circle(DISPLAYSURF, BLACK, (int(xPos) + WIN_CENTERX, int(yPos) + WIN_CENTERY), 6,0)
#refresh
pygame.draw.rect(DISPLAYSURF, BLACK, (0, 0, WINDOWWIDTH, WINDOWHEIGHT), 1)
pygame.display.update()
FPSCLOCK.tick(FPS)
I've only scanned your code but I would guess your inconsistency is due to a high frame rate (60). FPSCLOCK.tick(FPS) will make sure you go up to 60, but does not mean you will go 60 fps. So if your computer can not handle 60 frames per second, it will go below 60 frames.
Cristph Terasa's recommendation of using busy_loop should do the job for ya, but I personally have no experience for it and want to share a method of normalizing game speed across different FPSs.
Rather than reinventing a wheel, here's a link to a question that explains it. I recommend the second answer, written by pmoleri.
In Pygame, normalizing game-speed across different fps values
This solution should help your game run at the same speed no matter the frame rate.

PyGame/Python: Placing a circle onto an ellipse

I am trying to place multiple circles onto an eclipse and be able to move that circle around the eclipse. From looking into PyGames examples I have seen that you can rotate a line around an eclipse however cannot figure out how to do with with a circle.
This is the error message I recieve upon trying:
Traceback (most recent call last):
File "C:/Python32/Attempts/simple_graphics_demo.py", line 66, in <module>
pygame.draw.circle(screen, BLUE, [x, y], 15, 3)
TypeError: integer argument expected, got float
.
import pygame
import math
# Initialize the game engine
pygame.init()
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
PI = 3.141592653
# Set the height and width of the screen
size = [400, 400]
screen = pygame.display.set_mode(size)
my_clock = pygame.time.Clock()
# Loop until the user clicks the close button.
done = False
angle = 0
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# Set the screen background
screen.fill(WHITE)
# Dimensions of radar sweep
# Start with the top left at 20,20
# Width/height of 250
box_dimensions = [20, 20, 250, 250]
# Draw the outline of a circle to 'sweep' the line around
pygame.draw.ellipse(screen, GREEN, box_dimensions, 2)
# Draw a black box around the circle
pygame.draw.rect(screen, BLACK, box_dimensions, 2)
# Calculate the x,y for the end point of our 'sweep' based on
# the current angle
x = 125 * math.sin(angle) + 145
y = 125 * math.cos(angle) + 145
# Draw the line from the center at 145, 145 to the calculated
# end spot
pygame.draw.line(screen, GREEN, [145, 145], [x, y], 2)
# Attempt to draw a circle on the radar
pygame.draw.circle(screen, BLUE, [x, y], 15, 3)
# Increase the angle by 0.03 radians
angle = angle + .03
# If we have done a full sweep, reset the angle to 0
if angle > 2 * PI:
angle = angle - 2 * PI
# Flip the display, wait out the clock tick
pygame.display.flip()
my_clock.tick(60)
# on exit.
pygame.quit()
The math.sin and math.cos functions return floats, and the pos keyword argument to pygame.draw.circle expects integer positions, so you'll want to actually cast your coordinates. You have a few options for doing this:
[int(x), int(y)]
[math.floor(x), math.floor(y)]
[math.ceil(x), math.ceil(y)]
Each comes with slightly different behaviours so you might want to figure out which fits your program best. (specifically: int and floor work differently for negative numbers -- int rounds towards 0 and floor rounds down, as expected)
It is not answer for your main question - because you already got answer.
To put more circles use list with angles and for loop to get angle from list (one-by-one) and draw circle.
import pygame
import math
# === CONSTANTS ===
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
SIZE = (400, 400)
TWO_PI = 2 * math.pi # you don't have to calculate it in loop
# === MAIN ===
# --- init ---
pygame.init()
screen = pygame.display.set_mode(SIZE)
# --- objects ---
angles = [0, 1, math.pi] # angles for many circles
box_dimensions = [20, 20, 250, 250] # create only once
# --- mainloop ---
clock = pygame.time.Clock()
done = False
while not done:
# --- events ---
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
done = True
# --- draws (without updates) ---
screen.fill(WHITE)
pygame.draw.ellipse(screen, GREEN, box_dimensions, 2)
pygame.draw.rect(screen, BLACK, box_dimensions, 2)
# draw many circles
for a in angles:
x = int(125 * math.sin(a)) + 145
y = int(125 * math.cos(a)) + 145
pygame.draw.line(screen, GREEN, [145, 145], [x, y], 2)
pygame.draw.circle(screen, BLUE, [x, y], 15, 3)
pygame.display.flip()
clock.tick(60)
# --- updates (without draws) ---
# new values for many angles
for i, a in enumerate(angles):
a += .03
if a > TWO_PI:
a -= TWO_PI
angles[i] = a
# --- the end ---
pygame.quit()

Adding gradient to moving objects in pygame

I want to add gradient to the ball in this program & also possibly the waves drawn to fade into the colour of the background (as if glowing) instead of one colour fills.
I've looked at tons of tutorials however none of them are making much sense to my syntax, the general idea to me is confusing as I have moving objects that draw the space I want to add gradient to quite slowly. Can anyone give an insight into how I can do this?
code:
import sys, pygame, math
from pygame.locals import *
# set up of constants
WHITE = (255, 255, 255)
DARKRED = (128, 0, 0)
RED = (255, 0, 0)
BLACK = ( 0, 0, 0)
GREEN = ( 0, 255, 0)
BLUE = ( 0, 0, 255)
BGCOLOR = WHITE
screen = pygame.display.set_mode()
WINDOWWIDTH = 800 # width of the program's window, in pixels
WINDOWHEIGHT = 800 # height in pixels
WIN_CENTERX = int(WINDOWWIDTH / 2) # the midpoint for the width of the window
WIN_CENTERY = int(WINDOWHEIGHT / 2) # the midpoint for the height of the window
screen = pygame.display.get_surface()
FPS = 160 # frames per second to run at
AMPLITUDE = 80 # how many pixels tall the waves with rise/fall.
# standard pygame setup code
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT), pygame.RESIZABLE)
pygame.display.set_caption('Window title')
fontObj = pygame.font.Font('freesansbold.ttf', 16)
# variables that track visibility modes
showSine = True
showSquare = True
pause = False
xPos = 0
step = 0 # the current input f
posRecord = {'sin': [], 'square': []} # keeps track of the ball positions for drawing the waves
yPosSquare = AMPLITUDE # starting position
# main application loop
while True:
# event handling loop for quit events
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
# fill the screen to draw from a blank state
DISPLAYSURF.fill(BGCOLOR)
# sine wave
yPos = -1 * math.sin(step) * AMPLITUDE
posRecord['sin'].append((int(xPos), int(yPos) + WIN_CENTERY))
if showSine:
# draw the sine ball and label
pygame.draw.circle(DISPLAYSURF, RED, (int(xPos), int(yPos) + WIN_CENTERY), 10)
sinLabelRect.center = (int(xPos), int(yPos) + WIN_CENTERY + 20)
DISPLAYSURF.blit(sinLabelSurf, sinLabelRect)
# draw the waves from the previously recorded ball positions
if showSine:
for x, y in posRecord['sin']:
pygame.draw.circle(DISPLAYSURF, DARKRED, (x,y), 4)
#drawing horizontal lines
# square
posRecord['square'].append((int(xPos), int(yPosSquare) + WIN_CENTERY))
if showSquare:
# draw the sine ball and label
pygame.draw.circle(DISPLAYSURF, GREEN, (int(xPos), int(yPosSquare) + WIN_CENTERY), 10)
squareLabelRect.center = (int(xPos), int(yPosSquare) + WIN_CENTERY + 20)
DISPLAYSURF.blit(squareLabelSurf, squareLabelRect)
# draw the waves from the previously recorded ball positions
if showSquare:
for x, y in posRecord['square']:
pygame.draw.circle(DISPLAYSURF, BLUE, (x, y), 4)
# draw the border
pygame.draw.rect(DISPLAYSURF, BLACK, (0, 0, WINDOWWIDTH, WINDOWHEIGHT), 1)
pygame.display.update()
FPSCLOCK.tick(FPS)
if not pause:
xPos += 1
#wave movement
if xPos > WINDOWWIDTH:
#sine
xPos = 0
posRecord['sin'] = []
step = 0
# square
yPosSquare = AMPLITUDE
posRecord['square'] = []
else:
#sine
step += 0.008
#step %= 2 * math.pi
# square
# jump top and bottom every 100 pixels
if xPos % 100 == 0:
yPosSquare *= -1
# add vertical line
for x in range(-AMPLITUDE, AMPLITUDE):
posRecord['square'].append((int(xPos), int(x) + WIN_CENTERY))
Use SPACE to change background color.
First line use only transparency - and has no problem with different background color.
Second line changes only circles color - and depends on background color.
Third and fourth line (it is the same line with different starting color) change circles color and transparency - and depends on background color.
Second and last line look good on one color background and need more work to find good-looking fading.
import pygame
pygame.init()
screen = pygame.display.set_mode((600,200))
#--------------------------------------
# circles positions and transparency (x,y, alpha)
circles = []
for x in range(100):
circles.append( [100+x*3, 200, x*2] )
#--------------------------------------
white = True # background color
#--------------------------------------
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_SPACE:
white = not white
#--------------------------------------
if white:
screen.fill((255,255,255))
else:
screen.fill((0,0,0))
#--------------------------------------
# first
circle_img = pygame.Surface((20,20))
pygame.draw.circle(circle_img, (255,0,0), (10,10), 10)
circle_img.set_colorkey(0)
for x in circles:
circle_img.set_alpha(x[2])
screen.blit(circle_img, (x[0],40))
#--------------------------------------
# second
circle_img = pygame.Surface((20,20))
for x in circles:
pygame.draw.circle(circle_img, (255,255-x[2],255-x[2]), (10,10), 10)
circle_img.set_colorkey(0)
screen.blit(circle_img, (x[0],90))
#--------------------------------------
# last
circle_img = pygame.Surface((20,20))
for x in circles:
pygame.draw.circle(circle_img, (255,255-x[2],255-x[2]), (10,10), 10)
circle_img.set_colorkey(0)
circle_img.set_alpha(x[2])
screen.blit(circle_img, (x[0],140))
#--------------------------------------
pygame.display.flip()
pygame.quit()

Categories

Resources