Graphics.py circle won't bounce off walls - python

I am trying to get a ball to bounce off of the walls of my bounds. Currently, my circle is supposed to hit the wall and then change velocities and move in the opposite direction but this is not happening. I would appreciate some help :) Thank you
from graphics import*
from random import*
from time import*
win = GraphWin('My Program', 600, 600)
win.setBackground('pink')
my_circle = Circle(Point(200,300),30)
my_circle.setFill('blue')
my_circle.setOutline('darkorchid1')
my_circle.draw(win)
key = win.checkKey()
while key == '':
vel_x = randint(-30,30)
vel_y = randint(-30,30)
my_circle.move(vel_x, vel_y)
sleep(0.1)
for bounce in range(600):
find_center = my_circle.getCenter()
center_x = find_center.getX()
center_y = find_center.getY()
if center_x == 600 or center_y == 600:
vel_x = -randint(30,-30)
vel_y = -randint(30,-30)
my_circle.move(vel_x, vel_y)
sleep(0.1)
key = win.checkKey()

Several things that may affect the problem.
You should set your velocities once, as you're doing in the first lines in the while loop. Whenever it bounces you should move in the opposite direction using vel_x = -vel_x and vel_y = -vel_y.
Right now you're updating both of velocities which might lead to some wierd bounces when only one of the centeres hits a wall.
It might be more logical to check wether the distance from the center to the wall is less than the radius of the circle. This will prevent the issue when the ball moves from x=599 to x=601 in one iteration, skipping the if statement. (This would also make it so that the circle bounces when its edge hits the wall instead of the center)
Currently you're only checking 2 walls, unless you meant to do this you should add the if statements for the other walls aswell.
Small other thing, the second time you draw the random velocities you draw from the range 30 to -30, which is invalid.

Related

How to make ball bounce off of triangle in Py.Processing?

Im building a game in processing python. It is similar to pong except the paddles can rotate thus changing the direction of the ball. There is also supposed to be a feature where if you hit a smaller ball, triangles pop up on each the top and botton making it harder to get the ball through to the other side. So the ball is supposed to bounce off of the triangle and and make a 45 degree angle in reference to the x and y axels. Im having a hard time solving this problem. picture of game, white circle is the ball that I that is supposed to bounce on the triangle. My initial thought was to make an if statement containing two mx + c functions as follows.
if triangle_score == 1 and ball_y >= (ball_x * -1) + 350 and ball_y >= (ball_x - 450):
ball_dx = ball_dx * -1
ball_dy = ball_dy * -1
Needless to say it didn't work very well. Does anyone know processing. Would be sooo thankful for any help I can get.

Pygame mask for pixel perfect collision between player sprite and platform sprites

I am successfully detecting collisions between my player sprite and platform sprites using masks in pygame, but my issue is stopping the player from falling through a platform when jumping to it.
I tried solving the issue with mask.overlap(). Using this, I'm able to identify the point on my player sprite that has come into contact with the platform during a collision. When the bottom of the player sprite (her shoes) is colliding with a platform, she should stop falling. I can get this point from mask.overlap() and I hard-coded the y-coordinate of this point (which is a point on the sprite itself, not the screen) y = 155 in the program:
hits = pygame.sprite.spritecollide(player, platform_group, False)
if hits:
hits = pygame.sprite.spritecollide(player, platform_group, False, pygame.sprite.collide_mask)
for platform in hits:
offset = (platform.rect.x - player.rect.x), (platform.rect.y - player.rect.y)
if player.mask.overlap(platform.mask, offset):
x = player.mask.overlap(platform.mask, offset)[0]
y = player.mask.overlap(platform.mask, offset)[1]
pygame.draw.circle(display, s.RED, (x + player.rect.x, y + player.rect.y), 2)
print('Sprite pixel coll y: ' + str(y), 'Platform rect y top: ' + str(platform.rect.top))
if y == 155:
player.v_y = 0
player.rect.y = platform.rect.y - y
The red dot is the point at which collision on the player sprite has been detected.
The problem with the current code (apart from it being a very ugly solution) is that it doesn't work for all cases. When the player falls too fast, the detected collision point will not be her feet (i.e. not when y = 155´) and she will fall through the platform since the if-condition will not be fulfilled.
I could try a limit like if y >= 145 and y <= 160: but that still doesn't cover all cases and can cause her to "bounce" up when landing.
I'm currently stuck and wondering if anyone has any suggestions. I know I can use sprite Rects and go with colliderect but that will not give me the desired effect.
Thanks alot
you could check to see if the character is touching the platform ( using the mask.overlap method) and then set the velocity to 0 and gradually increase the y value until they are no longer colliding then continue with the rest of the game loop. (this is how I checked for collision but I used images instead of sprites) hope this helps.

Dealing with Sprites and Collisions Using Pygame

I am learning python using pygame and I am working on something that involves sprites and collisions. I've looked at some examples but I still don't quite understand it. What I am attempting to do is to be able to add sprites(a ball) when the user presses the "=" key and also be able to remove the last sprite added when pressing "-". I am not able to remove just the last one, I have only been able to remove all of them.
So far I have been able to add the balls to the window and have them bounce off the walls and one another(sort of). When 2 balls collide, they don't completely touch yet they bounce off. Sometimes the balls get stuck and won't move and sometimes the balls bounce off the frame which they aren't suppose to.
Its my first time working with sprite groups and would appreciate any help/guidance into making this work smoothly.Thanks.
The code:
ball.py
import pygame
from pygame.locals import *
class Ball(pygame.sprite.Sprite):
def __init__(self, x, y, vx, vy):
super().__init__();
self.image = pygame.image.load("ball.png").convert()
self.image.set_colorkey(pygame.Color(0, 0, 0))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.vx = vx
self.vy = vy
def draw(self, SCREEN):
SCREEN.blit(self.image, (self.rect.x, self.rect.y))
def move(self, SCREEN, balls):
l_collide = self.rect.x + self.image.get_width() + self.vx > SCREEN.get_width()
r_collide = self.rect.x + self.vx < 0
t_collide = self.rect.y + self.vy < 0
b_collide = self.rect.y + self.image.get_height() + self.vy > SCREEN.get_height()
a = pygame.sprite.spritecollide(self, balls, False, False)
if len(a) > 1:
self.vx *= -1
self.vy *= -1
if l_collide or r_collide:
self.vx *= -1
if t_collide or b_collide:
self.vy *= -1
self.rect.x += self.vx
self.rect.y += self.vy
ball_animation.py
import pygame
import sys
import random
import math
from pygame.locals import *
from ball.ball import Ball
from random import randint
def ball_list(num):
ball_list = pygame.sprite.Group()
for x in range(num):
rand_x = random.randint(0,400)
rand_y = random.randint(0,400)
vx = 4
vy = 5
ball_list.add(Ball(rand_x, rand_y, vx, vy))
return ball_list
def main():
pygame.init()
FPS = 30
FPS_CLOCK = pygame.time.Clock()
# COLOR LIST
BLACK = pygame.Color(0, 0, 0)
# Code to create the initial window
window_size = (500, 500)
SCREEN = pygame.display.set_mode(window_size)
# set the title of the window
pygame.display.set_caption("Bouncing Ball Animation")
# change the initial background color to white
SCREEN.fill(BLACK)
balls = ball_list(0)
while True: # <--- main game loop
for event in pygame.event.get():
if event.type == QUIT: # QUIT event to exit the game
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_EQUALS:
balls.add(Ball(randint(0,400),randint(0,400), 4,5))
if event.key == K_MINUS:
try:
balls.remove()
except IndexError:
print('There is no balls to take!')
SCREEN.fill(BLACK)
for x in balls:
x.move(SCREEN,balls)
x.draw(SCREEN)
pygame.display.update() # Update the display when all events have been processed
FPS_CLOCK.tick(FPS)
if __name__ == "__main__":
main()
Removing Sprites on Press
The problem is sprite.Group.remove(sprites) wants you to specify which sprites it should remove. sprites here should be a sprite/list of sprites that you want to remove from the group. This means to remove the last ball added on key press you need to keep a list of the ball sprites and pop() the most recently added item from it, and then use the result of the pop() as the sprite to remove from the group. sprite.Group has a .sprites() method which returns a list of all sprites in the group, in the order they were added. This list is generated from the group and is not actually an interface with it, so doing things to this list won't affect the group. We can still however use it to get the last added sprite. Here is what it looks like:
elif event.key == K_0:
try:
sprite_list = balls.sprites()
to_remove = sprite_list[-1] # Get last element of list
balls.remove(to_remove)
except IndexError:
print('There is no balls to take!')
Collisions
So this is a bit more involved and not so simple to fix in your code. To understand what the problem is, look at what your collision velocity adjustments are actually doing for the screen border case.
l_collide = self.rect.x + self.image.get_width() + self.vx > SCREEN.get_width()
r_collide = self.rect.x + self.vx < 0
t_collide = self.rect.y + self.vy < 0
b_collide = self.rect.y + self.image.get_height() + self.vy > SCREEN.get_height()
#################
if l_collide or r_collide:
self.vx *= -1
if t_collide or b_collide:
self.vy *= -1
Consider a single time-step in your code. We check to see if the sprite is sitting over the edge of the boundaries by any amount. If its hanging over, we reverse the velocity. There is a case where your edge checking will get you into trouble. If your self.vx is less than the difference between your current position X and the boundary of the x dimension, you will reverse your speed, travel self.vx back towards the boundary, but not make it past. In the next time-step, you will see that you are still over the boundary, and your program will again reverse self.vx, actually sending you away from the boundary. In this case you will bound back and forth each time-step by self.vx. Normally this wouldn't happen in your code, except for when you spawn a new ball sprite over the boundary further than your self.vx or self.vy for that ball. This can be remedied by making sure you don't spawn balls off the edges, or better yet, only reversing your velocity if you need to.
if (l_collide and self.vx>0) or (r_collide and self.vx<0):
self.vx *= -1
if (t_collide and self.vy<0) or (b_collide and self.vy>0):
self.vy *= -1
Notice here we only reverse the velocity if we are over the edge AND the velocity is headed deeper in that direction. Now for your sprites you have two options, just like with the boundaries:
Only initiate a new ball in empty space where it cannot collide.
Implement some way to calculate the correct velocity adjustment and only apply it if the velocity is headed in the opposite direction.
From what I read in the documentation, sprite.Group looks like it is meant for checking if sprites are overlapping, and not for physics simulation. I recommend doing some research on 2d physics simulation to get a nice conceptualization of what information you should want to communicate between objects. I'm sure there are some nice tutorials out there.
Finally, to address your other question about why they are colliding when they don't appear to be touching. sprite.spritecollide is returning which sprites have rectangles that intersect. If your ball.png is color keyed for transparency, this does not affect the rect of the sprite. Pygame appears to have functionality implemented designed to handle this problem in the collided keyword of sprite.spritecollide:
pygame.sprite.spritecollide()
Find sprites in a group that intersect another sprite.
spritecollide(sprite, group, dokill, collided = None) -> Sprite_list
The collided argument is a callback function used to calculate if two sprites >are colliding. it should take two sprites as values, and return a bool value >indicating if they are colliding. If collided is not passed, all sprites must >have a “rect” value, which is a rectangle of the sprite area, which will be >used to calculate the collision.
collided callables:
collide_rect
collide_rect_ratio
collide_circle
collide_circle_ratio
collide_mask
That's from the pygame documentation. The documentation for the collide_circle function states that your sprite should have a radius attribute, or else one will be calculated to fit the entire rectangle inside a circle. As such, in your Ball.__init__ function I would recommend adding:
self.radius = self.rect.width/2
This will make collide_circle use a radius that approximates your ball image, assuming it is centered and circular and occupies the entire image. Next, you must add the collision specification to your collision check by changing:
a = pygame.sprite.spritecollide(self, balls, False, False)
to
a = pygame.sprite.spritecollide(self, balls, False, pygame.sprite.collide_circle)
If you solve the problem of not spawning new ball objects inside each other, this should all work nicely. If you can't get them to spawn inside each other, think about a different data-structure or different way of collision checking to get the results you want. Best of luck!
I can see two questions in your text
You want to only remove one sprite, rather than all the sprites in the spritegroup
If you look at the pygame documentation, you can see that spritegroup.remove has an optional argument. You can remove a single sprite by putting your desired sprite as the argument, such as myspritegroup.remove(mysprite).
You have issues with the colliding
Your collision works for me as long as the balls don't spawn on top of each other on creation which you can simply check. Good luck :)

Pygame wall collision detection seems "bugged"

Here is my code
import pygame, sys
pygame.init() #load pygame modules
size = width, height = 800, 600 #size of window
speed = [25,25] #speed and direction
x= 100
y= 100
screen = pygame.display.set_mode(size) #make window
s=pygame.Surface((50,50)) #create surface 50px by 50px
s.fill((33,66,99)) #color the surface blue
r=s.get_rect() #get the rectangle bounds for the surface
r[0] = x #changes initial x position
r[1] = y #changes initial y position
clock=pygame.time.Clock() #make a clock
while 1: #infinite loop
clock.tick(30) #limit framerate to 30 FPS
for event in pygame.event.get(): #if something clicked
if event.type == pygame.QUIT:#if EXIT clicked
pygame.quit()
sys.exit() #close cleanly
r=r.move(speed) #move the box by the "speed" coordinates
#if we hit a wall, change direction
if r.left <= 0 or r.right >= width:
speed[0] = -(speed[0])*0.9 #reduce x axis "speed" by 10% after hitting
if r.top <= 0 or r.bottom >= height:
speed[1] = -speed[1]*0.9 #reduce y axis "speed" by 10% after hitting
screen.fill((0,0,0)) #make redraw background black
screen.blit(s,r) #render the surface into the rectangle
pygame.display.flip() #update the screen
It's a simple window that shows a square moving, hitting the edges and bouncing back. However, in this particular example (speed set to 25 on both axis) and speed reduction set to 0.9 after bouncing back (less 10%), my square seems to get stuck on the left side of the window (I suggest you to copy and paste it and see for yourself)
If I change the speed to a lower value or set no speed reduction whatsoever after bouncing everything works fine.
Any reason on why this is happening?
Let's step through this code one by one:
speed = [25,25] #speed and direction
if r.left <= 0 or r.right >= width:
speed[0] = -(speed[0])*0.9
Let's just look at what happens at the x axis.
Let's assume the position before this check is 1. In the next frame, the value of position is 1-25 = -24. Since the condition is now fulfilled, the speed becomes 25 * 0.9 = 22.5.
The rectangle moves to position -1.5 and we are still on the wrong side of the wall. Since you change the direction of speed on each frame, the rectangle will be stuck there.
There are 2 solutions to the problem, the first one is already described by Alex.
The second one, is to move the rectangle first, and if the rectangle moves out of bounds, return it to before the wall.
Right! To make the square move and bounce freely without getting potentially stuck in an edge what you need to do is to reverse the speed (and decrease it by 10%) before you actually move the ball! This is my simple suggestion
if r.left + speed[0] <= 0 or r.right + speed[0] >= width:
speed[0] = - (speed[0])*0.9
if r.top + speed[1] <= 0 or r.bottom + speed[1] >= height:
speed[1] = -(speed[1])*0.9
What the above modifications manages, is that it essentialy does not allow the square to go out of bounds at any time! As far as to what caused your above issue, after doing some debugging, it is apparent that the square manages to move outside the screen! (i.e. negative x,negative y etc), While it might seem harmless, this behaviour especially at lower speeds can cause the square to rapidly reverse and at the same time lower it's speed by 10%!
For example if at any point the square is at position x = - 1 with an xspeed of 1. Due to this condition: if r.left + speed[0] <= 0 or r.right + speed[0] >= width: it's speed will reverse multiple times back and forth and decrease at the same time not allowing the square to escape this edge!
Phew! Sorry for the long answer and I hope I helped!
Cheers!,
Alex

In pygame how to make an object controlled with arrow keys not move of the edge of the screen

I have made an object controlled with arrow keys. When I move it to the edge of the pygame screen, the object moves off the screen. I was wondering how to keep the object on the screen. Any suggestions?
On each handle of the input, check if the object's target x position plus its width exceeds the width of the canvas or if it is less than 0. Deny the movement if so.
Repeat for the y coordinate and the height.
Do something like this:
if player.x == #edge of screen:
player.x -= 0
if player.y == #edge of screen:
player.y -= 0
player.x being the players current x position and player.y being the players current y position or you can do the same thing but when the player goes of the screen it automatically goes to the other side of the screen it will probably take some tweaking to get it to look perfect
last = player.rect.copy()
player.update()
if not screen.get_rect().contains(player.rect):
player.rect = last

Categories