Make a rectangle bounce depending on the wall it touched in PYGAME - python

Im trying to make a rectangle bounce, without going off limits.
I want my rectangle to bounce depending on the wall it touched.
In this code im trying to bounce the rectangle in a 90º angle, but it isn't working.
Im using this to calculate each advance:
rect_x += rectxSpeed
rect_y += rectySpeed
When it reachs the limit
if rect_y>450 or rect_y<0:
rectySpeed=5
rect_y=rectySpeed*-(math.pi/2)
if rect_x>650 or rect_x<0:
rectxSpeed=5
rectx_y=rectxSpeed*-(math.pi/2)
Whole code here:
import pygame
import random
import math
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
rect_x= 50.0
rect_y = 50.0
rectxSpeed=5
rectySpeed=5
pygame.init()
# Set the width and height of the screen [width, height]
size = (700, 500)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My Game")
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# -------- Main Program Loop -----------
while not done:
# --- Main event loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# --- Game logic should go here
# --- Screen-clearing code goes here
# Here, we clear the screen to white. Don't put other drawing commands
# above this, or they will be erased with this command.
# If you want a background image, replace this clear with blit'ing the
# background image.
screen.fill(BLACK)
string=str(rect_x)
string2=str(rect_y)
string3="["+string+"]"+"["+string2+"]"
font = pygame.font.SysFont('Calibri', 25, True, False)
text = font.render(string3,True,RED)
screen.blit(text, [0, 0])
#Main rectangle
pygame.draw.rect(screen, WHITE, [rect_x, rect_y, 50, 50])
#Second rectangle inside the rectangle 1
pygame.draw.rect(screen, RED, [rect_x+10, rect_y+10, 30, 30])
rect_x += rectxSpeed
rect_y+=rectySpeed
if rect_y>450 or rect_y<0:
rectySpeed=5
rect_y=rectySpeed*-(math.pi/2)
if rect_x>650 or rect_x<0:
rectxSpeed=5
rect_x=rectxSpeed*-(math.pi/2)
# --- Drawing code should go here
# --- Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# --- Limit to 60 frames per second
clock.tick(20)
# Close the window and quit.
pygame.quit()
¿How can i adjust the advance?
This code produce this:
By Changing the reach limit code with:
if rect_y>450 or rect_y<0:
rectySpeed=rectySpeed*-(math.pi/2)
if rect_x>650 or rect_x<0:
rectxSpeed=rectxSpeed*-(math.pi/2)
Produces this:

I think it's important to recognize here that the speed of the rectangle object here is a scalar value, not it's vector counterpart velocity. You are attempting to multiply the rectySpeed (a scalar value) by -(math/pi)/2, which is a value that will be returned in radians as #0x5453 mentioned. As far as the rectangle bouncing differently depending on the wall that it contacted, you have not specified the differing constraints that you intend to impose. I think you may want to consider why you want the rectangle to always bounce at a 90° angle. Note that a rectangle that always bounces at a 90° angle would yield some very odd functionality to the player/user.
The angle that the rectangle is approaching the wall measured from the horizontal will be equal to the angle that the rectangle will rebound off the wall measured from the vertical line of the wall to it's new path (in the case of an x-directional bounce).
In terms of an accurate physics engine, you may want to consider just simplifying your mechanics to the following in the case that the rectangle contacts a wall:
if rect_y>450 or rect_y<0:
rectySpeed=rectySpeed*-1
if rect_x>650 or rect_x<0:
rectxSpeed=rectxSpeed*-1
The above ensures that the rectangle object simply changes direction, therefore the magnitude of the rectangle's speed does not change, however the velocity indeed does since it is a vector quantity.

Related

How do I make the whole scene invisible, pygame

I am trying to make a game where if someone press left-click, pygame draws a circle, then a line connecting the circle to the upper left corner. If he/she press right-click, it removes everything created from the screen. I thought about just covering everything with a big black square, but that is not really clearing the screen, just covering it. My code is as follow:
from pygame import *
init()
size = width, height = 650, 650
screen = display.set_mode(size)
button = 0
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
def drawScene(screen, button):
# Draw circle if the left mouse button is down.
if button==1:
draw.circle(screen,GREEN,(mx,my), 10)
draw.line(screen,GREEN, (0,0),(mx,my))
if button == 3:
mouse.set_visible(False)
display.flip()
running = True
myClock = time.Clock()
# Game Loop
while running:
for e in event.get(): # checks all events that happen
if e.type == QUIT:
running = False
if e.type == MOUSEBUTTONDOWN:
mx, my = e.pos
button = e.button
drawScene(screen, button)
myClock.tick(60) # waits long enough to have 60 fps
quit()
Kindly guide me in this situation. Thanks in advance.
I thought about just covering everything with a big black square, but that is not really clearing the screen, just covering it.
There is not any difference between cover and clear. The screen consist of pixels. Drawing an object just changes the color of the pixels. Drawing a black square changes the color of the pixel again.
Note, operations like pygame.draw.circle() and pygame.draw.line() do not create any object. They just change the colors of some pixels of the surface which is passed to the operation.
The easiest way to clear the entire window is pygame.Surface.fill():
screen.fill((0, 0, 0))
respectively
screen.fill(BLACK)
In computer graphics, there is no such thing as "clearing" a screen. you can only overwrite the previous drawing with new pixels. your idea of drawing a big black rectangle would work perfectly but I may be more efficient to use screen.fill(0)

Blitting a Surface onto another without combining alpha

In a pygame project I'm working on, sprites of characters and objects cast a shadow onto the terrain. Both the shadow and the terrain are normal pygame surfaces so, to show them, the shadow is blitted onto the terrain. When there's no other shadow (only one shadow and the terrain) everything works fine, but when the character walks into the area of a shadow, while casting its own shadow, both shadows combine their alpha values, obscuring the terrain even more.
What I want is to avoid this behaviour, keeping the alpha value stable. Is there any way to do it?
EDIT: This is an image, that I made in Photoshop, to show the issue
EDIT2: #sloth's answer is ok, but I neglected to comment that my project is more complicated than that. The shadows are not whole squares, but more akin to “stencils”. Like real shadows, they are silhouettes of the objects they are cast from, and therefore they need per pixel alphas which are not compatible with colorkey and whole alpha values.
Here is a YouTube video that shows the issue a bit more clearly.
An easy way to solve this is to blit your shadows on another Surface first which has an alpha value, but no per pixel alpha. Then blit that Surface to your screen instead.
Here's a simple example showing the result:
from pygame import *
import pygame
pygame.init()
screen = pygame.display.set_mode((800, 600))
# we create two "shadow" surfaces, a.k.a. black with alpha channel set to something
# we use these to illustrate the problem
shadow = pygame.Surface((128, 128), pygame.SRCALPHA)
shadow.fill((0, 0, 0, 100))
shadow2 = shadow.copy()
# a helper surface we use later for the fixed shadows
shadow_surf = pygame.Surface((800, 600))
# we set a colorkey to easily make this surface transparent
colorkey_color = (2,3,4)
shadow_surf.set_colorkey(colorkey_color)
# the alpha value of our shadow
shadow_surf.set_alpha(100)
# just something to see the shadow effect
test_surface = pygame.Surface((800, 100))
test_surface.fill(pygame.Color('cyan'))
running = True
while running:
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
screen.fill(pygame.Color('white'))
screen.blit(test_surface, (0, 150))
# first we blit the alpha channel shadows directly to the screen
screen.blit(shadow, (100, 100))
screen.blit(shadow2, (164, 164))
# here we draw the shadows to the helper surface first
# since the helper surface has no per-pixel alpha, the shadows
# will be fully black, but the alpha value for the full Surface image
# is set to 100, so we still have transparent shadows
shadow_surf.fill(colorkey_color)
shadow_surf.blit(shadow, (100, 100))
shadow_surf.blit(shadow2, (164, 164))
screen.blit(shadow_surf, (400, 0))
pygame.display.update()
You could create a function that tests for shadow collision and adjust the blend values of the shadows accordingly.
You can combine per-pixel alpha shadows by blitting them onto a helper surface and then fill this surface with a transparent white and pass the pygame.BLEND_RGBA_MIN flag as the special_flags argument. The alpha value of the fill color should be equal or lower than the alphas of the shadows. Passing the pygame.BLEND_RGBA_MIN flag means that for each pixel the lower value of each color channel will be taken, so it will reduce the increased alpha of the overlapping shadows to the fill color alpha.
import pygame as pg
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
shadow = pg.image.load('shadow.png').convert_alpha()
# Shadows will be blitted onto this surface.
shadow_surf = pg.Surface((800, 600), pg.SRCALPHA)
running = True
while running:
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
screen.fill((130, 130, 130))
screen.blit(shadow, (100, 100))
screen.blit(shadow, (154, 154))
shadow_surf.fill((0, 0, 0, 0)) # Clear the shadow_surf each frame.
shadow_surf.blit(shadow, (100, 100))
shadow_surf.blit(shadow, (154, 154))
# Now adjust the alpha values of each pixel by filling the `shadow_surf` with a
# transparent white and passing the pygame.BLEND_RGBA_MIN flag. This will take
# the lower value of each channel, therefore the alpha should be lower than
# the shadow alphas.
shadow_surf.fill((255, 255, 255, 120), special_flags=pg.BLEND_RGBA_MIN)
# Finally, blit the shadow_surf onto the screen.
screen.blit(shadow_surf, (300, 0))
pg.display.update()
clock.tick(60)
Here's the shadow.png.

Creating a grid in pygame using for loops

This is different to the other questions as it uses another method. I have the following code and need to change it so that it produces a grid (all rows and columns filled in) as per Figure 16.7 on this link: http://programarcadegames.com/index.php?chapter=array_backed_grids
The following code produces a full row and a full column, but I can't quite work out how to extend it to fill the whole screen with rectangles with the appropriate margin built in.
Code:
"""
Create a grid with rows and colums
"""
import pygame
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
pygame.init()
# Set the width and height of the screen [width, height]
size = (255, 255)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("My Game")
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
width=20
height=20
margin=5
# -------- Main Program Loop -----------
while not done:
# --- Main event loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# --- Game logic should go here
# --- Screen-clearing code goes here
# Here, we clear the screen to white. Don't put other drawing commands
# above this, or they will be erased with this command.
# If you want a background image, replace this clear with blit'ing the
# background image.
screen.fill(BLACK)
# --- Drawing code should go here
#for column (that is along the x axis) in range (0 = starting position, 100=number to go up to, width+margin =step by (increment by this number)
#adding the 255 makes it fill the entire row, as 255 is the size of the screen (both ways)
for column in range(0+margin,255,width+margin):
pygame.draw.rect(screen,WHITE, [column,0+margin,width,height])
for row in range(0+margin,255,width+margin):
pygame.draw.rect(screen,WHITE,[0+margin,row,width,height])
#This simply draws a white rectangle to position (column)0,(row)0 and of size width(20), height(20) to the screen
# --- Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# --- Limit to 60 frames per second
clock.tick(60)
# Close the window and quit.
pygame.quit()
The problem lies in the inner loop (for row in...),
where the rect is drawn with:
pygame.draw.rect(screen,WHITE,[0+margin,row,width,height])
Note that the x coordinate always is 0+margin,
no matter which column is currently drawn. So
the code draws 10 columns on top of each other.
As a simple fix, change the line to:
pygame.draw.rect(screen,WHITE,[column,row,width,height])
You might then notice that the other call of the draw method in the outer loop is completely unnecessary. After all, the inner call now draws a rectangle for each row in each column. So you can reduce the loop code to:
for column in range(0+margin, 255, width+margin):
for row in range(0+margin, 255, height+margin):
pygame.draw.rect(screen, WHITE, [column,row,width,height])

Python: Drawing a Diagonal Line in Animation

I am trying to make an animation but I am not sure how to draw a diagonal line and move it.
import pygame
import sys
WINDOW=pygame.display.set_mode((800,300))
RED=(255,0,0)
WHITE=(255,255,255)
CRIMSON=(220,20,60)
BURGUNDY=(102,0,0)
CERULEAN=(153,255,255)
PINK=(255,102,102)
FPS=100
fpsClock=pygame.time.Clock()
x=0
pygame.display.set_caption("Animation")
while True:
for event in pygame.event.get():
if event.type=="QUIT":
pygame.quit()
sys.exit()
#Animation
WINDOW.fill(CERULEAN)
x=x+1
pygame.draw.circle(WINDOW, CRIMSON, (x,100),20)
pygame.draw.circle(WINDOW, BURGUNDY, (x, 92),5)
pygame.draw.line(WINDOW, PINK, (x,30),(x,70),3)
pygame.display.update()
fpsClock.tick(FPS)
The drawing is supposed to be a fish with a triangle as its tail. I originally tried to use the polygon function but wasn't sure how to input the x and where to input the x so just decided to draw three lines for the triangle.
I just need help as to how and where I would input the x into the line or even polygon function. Like for the circle one would simply put it first but how would it be for a line and/or polygon function?
Use pygame.draw.aalines() instead.
Give it a list of points forming the triangle.
Your problem here is:
X is increasing indefinitely
It increases too fast.
If you want to make this nice, make a object called Fish, with a method move, which wil move the fish. This way you will have clean code and minimal confusion.
You must remove the old fish from the window, then draw the new one on the new position. That's why you should keep the fish in its own object.
Choose one point of the fish that will act as an universal position indicator. E.g. a nose or centre of the mass, or something. Then you just change that pos and your move method adjusts all other coordinates accordingly.
EDIT:
This is an example. I didn't try it, just put it together to show you how it is done.
I hope this will make it clearer. You see, I draw the fish once on itsown surface, then I move this surface around.
As you move the mouse, the fish will be moved.
It may be slow, and flickery, and stuff. This code has some problem as yours.
The new fish should be drawn every 4 pixels, not each one.
As fish is in an object you can have multiple fishes of different sizes.
Each fish keeps portion of a screen that replaces in an origsurf attribute.
Before it moves, it returns the screen in previous position.
So your fishes can overlap on the screen.
To make all work smooth, you will have to do some more work.
For example, there are no safeguards against going over display bounds.
As I said, it is an example on how it is done, not a full code.
class Fish:
def __init__ (self, pos=(0, 0), size=(60, 40), bgcolour=(255, 255, 255)):
self.pos = pos; self.size = size
# Use a little surface and draw your fish in it:
self.surf = surf = pygame.Surface(size)
surf.fill(bgcolour)
# Draw something:
pygame.draw.aalines(surf, (0, 0, 0), 1, [(0, 0), (0, size[1]), (size[0], size[1]/2)]) # Triangle
# Save how screen looks where fish will be:
self.origsurf = Surface(size)
sub = screen.subsurface((pos, size))
self.origsurf.blit(sub)
# Show the fish:
screen.blit(surf, pos)
pygame.display.flip()
def move (self, newpos):
# Remove the fish from old position
screen.blit(self.origsurf, self.pos)
# Save how screen looks where fish will be:
self.origsurf = Surface(self.size)
sub = screen.subsurface((newpos, self.size))
self.origsurf.blit(sub)
# Then show the fish on new location:
screen.blit(self.surf, newpos)
self.pos = newpos
pygame.display.flip()
screen = pygame.display.set_mode()
fish = Fish()
FPS=100
fpsClock=pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type=="QUIT":
pygame.quit()
sys.exit()
elif event.type==pygame.MOUSEMOTION:
fish.move(event.pos)
fpsClock.tick(FPS)

Anti-aliased Arc Pygame

I am trying to code a simple circle timer in Python using Pygame.
At the moment it looks like this:
As you can see, the blue line is very wavy and has white dots in it. I am achieving this blue line by using pygame.draw.arc() function, but it is not anti-aliased and looks bad. I would like it to be anti-aliased, but gfxdraw module which should let me achieve this, doesn't support arc width selection. Here's code snippet:
pygame.draw.arc(screen, blue, [center[0] - 120, center[1] - 120, 240, 240], pi/2, pi/2+pi*i*koef, 15)
pygame.gfxdraw.aacircle(screen, center[0], center[1], 105, black)
pygame.gfxdraw.aacircle(screen, center[0], center[1], 120, black)
I did it creating the arc with a polygon.
def drawArc(surface, x, y, r, th, start, stop, color):
points_outer = []
points_inner = []
n = round(r*abs(stop-start)/20)
if n<2:
n = 2
for i in range(n):
delta = i/(n-1)
phi0 = start + (stop-start)*delta
x0 = round(x+r*math.cos(phi0))
y0 = round(y+r*math.sin(phi0))
points_outer.append([x0,y0])
phi1 = stop + (start-stop)*delta
x1 = round(x+(r-th)*math.cos(phi1))
y1 = round(y+(r-th)*math.sin(phi1))
points_inner.append([x1,y1])
points = points_outer + points_inner
pygame.gfxdraw.aapolygon(surface, points, color)
pygame.gfxdraw.filled_polygon(surface, points, color)
The for loop could certainly be created more elegantly with a generator, but I am not very sophisticated with python.
The arc definitely looks nicer than pygame.draw.arc, but when I compare it to the screen rendering on my mac, there is room for improvement.
I am not aware of any pygame function that would solve this problem, meaning you basically have to program a solution yourself (or use something other than pygame), since draw is broken as you've noted and gfxdraw won't give you the thickness.
One very ugly but simple solution is to draw multiple times over the arc segments, always slightly shifted to "fill in" the missing gaps. This will still leave some aliasing at the very front of the timer arc, but the rest will be filled in.
import pygame
from pygame.locals import *
import pygame.gfxdraw
import math
# Screen size
SCREEN_HEIGHT = 350
SCREEN_WIDTH = 500
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREY = (150, 150, 150)
RED = (255,0,0)
# initialisation
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
done = False
clock = pygame.time.Clock()
# We need this if we want to be able to specify our
# arc in degrees instead of radians
def degreesToRadians(deg):
return deg/180.0 * math.pi
# Draw an arc that is a portion of a circle.
# We pass in screen and color,
# followed by a tuple (x,y) that is the center of the circle, and the radius.
# Next comes the start and ending angle on the "unit circle" (0 to 360)
# of the circle we want to draw, and finally the thickness in pixels
def drawCircleArc(screen,color,center,radius,startDeg,endDeg,thickness):
(x,y) = center
rect = (x-radius,y-radius,radius*2,radius*2)
startRad = degreesToRadians(startDeg)
endRad = degreesToRadians(endDeg)
pygame.draw.arc(screen,color,rect,startRad,endRad,thickness)
# fill screen with background
screen.fill(WHITE)
center = [150, 200]
pygame.gfxdraw.aacircle(screen, center[0], center[1], 105, BLACK)
pygame.gfxdraw.aacircle(screen, center[0], center[1], 120, BLACK)
pygame.display.update()
step = 10
maxdeg = 0
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
maxdeg = maxdeg + step
for i in range(min(0,maxdeg-30),maxdeg):
drawCircleArc(screen,RED,(150,200),119,i+90,max(i+10,maxdeg)+90,14)
#+90 will shift it from starting at the right to starting (roughly) at the top
pygame.display.flip()
clock.tick(2) # ensures a maximum of 60 frames per second
pygame.quit()
Note that I have copied degreesToRadians and drawCircleArc from https://www.cs.ucsb.edu/~pconrad/cs5nm/08F/ex/ex09/drawCircleArcExample.py
I do not generally recommend this solution, but it might do in a pinch.
You are right, some pygame rendering functions do indeed suck, so you can achieve something like this with PIL instead.
pie_size = (40, 40) # defining constants
pil_img = PIL.Image.new("RGBA", pie_size) # PIL template image
pil_draw = PIL.ImageDraw.Draw(pil_img) # drawable image
pil_draw.pieslice((0, 0, *[ps - 1 for ps in pie_size]), -90, 180, fill=(0, 0, 0)) # args: (x0, y0, x1, y1), start, end, fill
This will create a PIL shape. Now we can convert it to pygame.
data = pil_img.tobytes()
size = pil_img.size
mode = pil_img.mode
pygame_img = pygame.image.fromstring(data, size, mode).convert_alpha()
But don't forget to pip install pillow and
import PIL.Image
import PIL.ImageDraw
Ok, this is really old, but why not try to draw pies instead. For example draw a pie, then an unfilled circle as the outside ring and then a filled circle as the inside and another unfilled circle as the inside ring.
So pie -> unfilled circle -> filled circle -> unfilled.
The order is somewhat arbitrary but if u still have this problem give it a try. (Btw I haven't tried it but I think it will work)
For my own uses, I wrote a simple wrapper function, and to deal with the spotty arc drawing, I used an ugly loop to draw the same arc several times.
def DrawArc(surface, color, center, radius, startAngle, stopAngle, width=1):
width -= 2
for i in range(-2, 3):
# (2pi rad) / (360 deg)
deg2Rad = 0.01745329251
rect = pygame.Rect(
center[0] - radius + i,
center[1] - radius,
radius * 2,
radius * 2
)
pygame.draw.arc(
surface,
color,
rect,
startAngle * deg2Rad,
stopAngle * deg2Rad,
width
)
I'm aware this is not a great solution, but it works alright for my uses.
An important note is I added that "width -= 2" to hopefully preserve the intended size of the arc at least a little more accurately, but this results in increasing the minimum width by 2.
In your case, you might want to consider doing something more to fix the issues this results in.
If the start and end aren't all that important, one can create many circles following an arc trajectory and when done ie small circles drawn 360 time, you finally have a big circle with no wavy effect:
MWE:
#!/usr/bin/env python3
import pygame
import math
# Initialize pygame
pygame.init()
# Set the screen size
screen = pygame.display.set_mode((400, 300))
# Set the center point of the arc
center_x = 200
center_y = 150
arc_radius = 100
circle_radius = 6
# Set the start and stop angles of the arc
start_angle = 0
stop_angle = 360
angle_step = 1
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# Clear the screen
screen.fill((0, 0, 0))
# Draw the overlapping circles
for i in range(start_angle, stop_angle, angle_step):
angle = math.radians(i)
x = center_x + arc_radius * math.cos(angle)
y = center_y + arc_radius * math.sin(angle)
pygame.draw.circle(screen, "red", (int(x), int(y)), circle_radius)
# Update the display
pygame.display.flip()
pygame.quit()
Having a start_angle and stop_angle of 0 to 360 respectively yields a fill circle with an output:
To change it to a 1/3 of a circle, one would change the stop_angle from 360 to 120 (1/3 x 360 = 120) and this would then yield:

Categories

Resources