Software Design and Development Major: Pygame Smudge Trails - python

First off, i have searched online and this website for solutions and the ones i have tried are not working so i decided to post my individual question and code. This program was created using Python 3.2.2 and the corresponding compatible version of pygame. I also realize a more efficient method would be to use sprites, sprite groups and 'dirty rect' updating but i unable to convert the program and so i will continue without the added benefits of such functions.
Problem: Smudge trails where the 'asteroids' are moving are left behind.
Hypothesis: Background is blitted onto the screen however the asteroids are blitted onto the Background.
Please Reply - btw i'm a highschooler from AUS :D
import pygame
import random
import math
pygame.init()
height = 550
width = 750
screen = pygame.display.set_mode((width, height))
background = pygame.image.load("Planet.jpg")
Clock = pygame.time.Clock()
class asteroid(pygame.sprite.Sprite):
def __init__(self, x, y, size):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.size = 15
self.speed = 0.0
self.angle = 0
self.colour = (171, 130, 255)
self.thickness = 0
def display(self):
pygame.draw.circle(background, self.colour, (int(self.x),int(self.y)), self.size, self.thickness)
pygame.draw.circle(background, (255, 255, 255), (int(self.x),int(self.y)), self.size, 1)
def move(self):
self.x += math.sin(self.angle) * self.speed
self.y -= math.cos(self.angle) * self.speed
def boundaries(self):
if self.x > width - self.size:
self.x = 0 + self.size
elif self.x < self.size:
self.x = width - self.size
if self.y > height - self.size:
self.y = 0 + self.size
elif self.y <self.size:
self.y = height - self.size
num_target = 5
my_particles = []
num_particles = len(my_particles)
while num_particles < 5:
for n in range(num_target):
size = 20
x = random.randint(size, height - size)
y = random.randint(size, width - size)
target = asteroid(x, y, size)
target.speed = random.uniform(1.0, 1.0)
target.angle = random.uniform(0, math.pi*2)
my_particles.append(target)
num_particles = num_particles + 1
def main():
pygame.display.set_caption("Anyu's Game")
screen.blit(background, (0,0))
pygame.display.update()
score = (pygame.time.get_ticks()/1000)
print (score)
while True:
pygame.display.update()
screen.blit(background, (0,0))
MouseP = pygame.mouse.get_pos()
frames = Clock.get_fps
pygame.mouse.set_visible
score = (pygame.time.get_ticks()/1000)
print (score)
print (MouseP)
for target in my_particles:
target.move()
target.boundaries()
target.display()
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit();
if __name__=='__main__':
main()

Basically, you are right! The circles are drawn directly onto the background, and everytime new circles are drawn, the old circles remain. Resulting in the smudges/trails.
You can just change background to screen in your draw method. This will fix it.
But it is really worth using the Sprite classes as intended. I've made a few changes to your code to switch it over for you. With these changes it runs without trails :)
Here are the changes and explainations:
Add this near the top:
#Create a new `pygame.Surface`, and draw a circle on it, then set transparency:
circle = pygame.Surface((30,30))
circle = circle.convert()
pygame.draw.circle(circle, (171, 130, 255), (int(15),int(15)), 15, 0)
circle.set_colorkey(circle.get_at((0, 0)), pygame.RLEACCEL)
Add this to the asteroid, __init__ method:
#Sets the asteroid image, and then the asteroids co-ords (these are in `rect`)
self.image = circle
self.rect = self.image.get_rect()
Add this to the end of def move(self):
self.rect[0] = self.x
self.rect[1] = self.y
change:
my_particles = []
to:
#This is a special pygame container class, it has a draw() method that tracks changed areas of the screen.
my_particles = pygame.sprite.RenderUpdates()
change:
my_particles.append(target)
to:
my_particles.add(target)
change:
while True:
pygame.display.update()
screen.blit(background, (0,0))
MouseP = pygame.mouse.get_pos()
frames = Clock.get_fps
pygame.mouse.set_visible
score = (pygame.time.get_ticks()/1000)
print (score)
print (MouseP)
for target in my_particles:
target.move()
target.boundaries()
target.display()
pygame.display.update()
to:
#initial screen draw:
screen.blit(background, (0,0))
pygame.display.update()
while True:
#remove previous drawn sprites and replaces with background:
my_particles.clear(screen, background)
MouseP = pygame.mouse.get_pos()
frames = Clock.get_fps
pygame.mouse.set_visible
score = (pygame.time.get_ticks()/1000)
print (score)
print (MouseP)
for target in my_particles:
target.move()
target.boundaries()
#draws changed sprites to the screen:
pygame.display.update(my_particles.draw(screen))
Remove the display method as it is no longer needed.
This will also run a lot faster than the your earlier code, as the time taken to draw something is proportional to the size of the drawing area, and previously it was drawing the whole background everytime - now it only draws the sprites and changes to the background!
Hope this helps :)

This already has an answer but this can be useful instead of other methods.
Make sure when you blit the images onto the screen, flip the display after blitting everything.
I would consider making a draw() function
Like this:
def draw(self):
# Blit images
self.screen.blit(image)
# Flip display
pygame.display.flip()
This will flip the display every frame and then draw the next frame without a trail.
Also quick notes, remember to do image = pygame.image.load(image).convert or .convert_alpha() else after adding more images the game will slow down.
Also, if you do import pygame as pg you don't have to type out pygame each time, instead you can just type pg.

Related

Sprite in not updated

This code is supposed to animate a sprite on a background, but it is just showing the sprite without any movement. I spend a day trying to figure out the issue, traced the code (I am novice so I might overlooked something), compared the code with the original author code line by line with no result, btw the original code runs smoothly meaning that it is not a problem in my PC.
Could you help me please
import os
import random
import math
import pygame
from os import listdir
from os.path import isfile, join
pygame.init()
pygame.display.set_caption("Platformer") #set the caption at the top of the window
BG_COLOR = (255,255,255) #White background, dont need it anymore
WIDTH, HEIGHT = 1000, 640 #screen dimensions
FPS = 60
PLAYER_VEL = 5 # the speed of the player
window = pygame.display.set_mode((WIDTH, HEIGHT)) #set the window with sizes
def flip(sprites):
return [pygame.transform.flip(sprite, True, False) for sprite in sprites]
def load_sprite_sheets(dir1, dir2, width, height, direction=False):
path = join("assets", dir1, dir2)
images = [f for f in listdir(path) if isfile(join(path, f))] #if f is a file put in the images list
all_sprites = {}
for image in images:
sprite_sheet = pygame.image.load(join(path, image)).convert_alpha() #loaded transparent bg image
sprites = []
for i in range(sprite_sheet.get_width() // width):
surface = pygame.Surface((width, height), pygame.SRCALPHA, 32)
rect = pygame.Rect(i * width, 0, width, height)
surface.blit(sprite_sheet, (0, 0), rect)
sprites.append(pygame.transform.scale2x(surface))
if direction:
all_sprites[image.replace(".png", "") + "_right"] = sprites
all_sprites[image.replace(".png", "") + "_left"] = flip(sprites)
else:
all_sprites[image.replace(".png", "")] = sprites
return all_sprites
class Player(pygame.sprite.Sprite): #sprite is useful for perfect pixel collision
COLOR = (0,0,255)
GRAVITY = 1
SPRITES = load_sprite_sheets("MainCharacters" , "MaskDude", 32 , 32 , True)
ANIMATION_DELAY = 3
def __init__(self, x,y, width , height):
self.rect = pygame.Rect(x,y,width,height)
self.x_vel = 0
self.y_vel = 0
self.mask = None
self.direction = "left" # to record which animation to show
self.animation_count = 0 #
self.fall_count = 0
def move(self,dx,dy):
self.rect.x += dx
self.rect.y += dy #here we draw only the motion calculated in the next functions
def move_left(self, vel):
self.x_vel = -vel
if self.direction != "left":
self.direction = "left"
self.animation_count = 0
def move_right(self, vel):
self.x_vel = vel
if self.direction != "right":
self.direction = "right"
self.animation_count = 0
def loop(self , fps):
# self.y_vel += min(1 , (self.fall_count/fps) * self.GRAVITY)
self.move(self.x_vel,self.y_vel)
self.fall_count += 1
self.update_sprite()
def update_sprite(self): #is about changing the sprite shape to look walking
sprite_sheet = "idle"
if self.x_vel !=0:
sprite_sheet = "run"
sprite_sheet_name = sprite_sheet + "_" + self.direction
sprites = self.SPRITES[sprite_sheet_name]
sprite_index = (self.animation_count //
self.ANIMATION_DELAY) % len(sprites) #set new index every ANIMATION_DELAY = 5
self.sprite = sprites[sprite_index]
self.animation_count += 1
def draw(self, win):
#print(self.SPRITES)
self.sprite = self.SPRITES["idle_"+self.direction][0]
win.blit(self.sprite , (self.rect.x , self.rect.y))
def get_background(name): #name is bg image, this create the bg image position list
image = pygame.image.load(join("assets", "Background", name))
_, _, width, height = image.get_rect()
tiles = [] #list of tles I need to fil my window bg
for i in range(WIDTH//width + 1):
for j in range (HEIGHT // height+1):
pos = tuple([i * width , j*height]) # convert list into tuple
tiles.append(pos)
return tiles, image # now I now the list of positions to fill the bg and the exact file to use
def draw(window, background, bg_image, player):
for tile in background:
window.blit(bg_image, tile) # drawing the image at every position
player.draw(window)
pygame.display.update()
def handle_move(player): #check keys and collision
keys = pygame.key.get_pressed()
player.x_vel = 0; #as moveleft change the velocity we have to change it to zero so w
if keys[pygame.K_LEFT]:
player.move_left(PLAYER_VEL)
if keys[pygame.K_RIGHT]:
player.move_right(PLAYER_VEL)
def main(window):
clock = pygame.time.Clock()
background, bg_image = get_background("Blue.png")
player = Player(100,100,50,50)
run = True
while(run):
clock.tick(FPS) #fix the refresh rate to this otherwise it will be dpending on the computer power
for event in pygame.event.get():
if event.type == pygame.QUIT: #if teh program detect an event of the user Quiting the game
run = False
break
player.loop(FPS)
handle_move(player)
draw(window, background, bg_image, player)
pygame.quit()
quit() #quiting the python itself
if __name__ == "__main__":
main(window) #when we run the file go to the main function with this arg
comparing the code with the original author code, change my code
I think that the problem lies in the following line of the Player.draw function:
self.sprite = self.SPRITES["idle_"+self.direction][0]
The sprite attribute was already set in the update_sprite function to the correct sprite taking the animation count and state into an account. However, this line resets it to the first image ([0]) from the idle spritesheet ("idle_"), without taking the animation count or state into an account. This does make that only this sprite and not the correct one is being drawn.
Removing the line should resolve the problem.

Why does this bug happen when I click on two sprites at the same time?

I'm making a simple game using pygame where you keep clicking on tiles as fast as you can until you miss a tile. this is the progress I've made so far. sometimes when I click on a tile (usually when 2 tiles are next to each other and you click between them) one of them does what they're supposed to while the other just disappears from the screen.
import pygame
import random
import sys
#Setting up all possible Tile positions
grid = [[0,0], [0,150], [0,300], [0,450], [0,600],
[150,0],[150,150],[150,300],[150,450],[150,600],
[300,0],[300,150],[300,300],[300,450],[300,600],
[450,0],[450,150],[450,300],[450,450],[450,600],
[600,0],[600,150],[600,300],[600,450],[600,600]]
taken = []
#Classes
class Cursor(pygame.sprite.Sprite):
def __init__(self, pic):
super().__init__()
self.image = pygame.image.load(pic).convert_alpha()
self.image = pygame.transform.scale(self.image, (50,50))
self.rect = self.image.get_rect()
def destroyTile(self):
pygame.sprite.spritecollide(cursor, tileGroup, True)
def update(self):
self.rect.topleft = pygame.mouse.get_pos()
class Tiles(pygame.sprite.Sprite):
def __init__(self, tileSize, color, x, y):
super().__init__()
self.image = pygame.Surface(([tileSize, tileSize]))
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.topleft = [x, y]
def drawTiles():
takenLen = len(taken)
while takenLen != 3:
m = random.randint(0,24)
x, y = grid[m]
if grid[m] not in taken:
blackTile = Tiles(150, black, x, y)
blackTile.add(tileGroup)
taken.append(grid[m])
takenLen += 1
def handleTiles():
mx, my = pygame.mouse.get_pos()
modx = mx % 150
mody = my % 150
x = mx - modx
y = my - mody
taken.remove([x, y])
drawTiles()
def drawRedTile():
mx, my = pygame.mouse.get_pos()
modx = mx % 150
mody = my % 150
x = mx - modx
y = my - mody
redTile = Tiles(150, red, x, y)
redTile.add(tileGroup)
#Colours
white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)
blue = (0, 0, 255)
grey = (46, 46, 46)
#Initializing Pygame
pygame.init()
clock = pygame.time.Clock()
#Screen
screenWidth = 750
screenHeight = 900
screen = pygame.display.set_mode((screenWidth, screenHeight))
pygame.display.set_caption("Tiles Game")
whiteSurface = pygame.Surface((750, 750))
whiteSurface.fill(white)
pygame.mouse.set_visible(False)
#Blue line
line = pygame.Surface((750, 10))
line.fill(blue)
#Groups
tileGroup = pygame.sprite.Group()
cursor = Cursor("cursor.png")
cursorGroup = pygame.sprite.Group()
cursorGroup.add(cursor)
score = 0
drawTiles()
while True:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
score += 1
print(score)
print(taken)
print(tileGroup)
cursor.destroyTile()
handleTiles()
#Background
screen.fill(grey)
screen.blit(whiteSurface, (0,0))
screen.blit(line, (0,750))
tileGroup.draw(screen)
cursorGroup.draw(screen)
cursorGroup.update()
pygame.display.update()
In the code I tried using print statements to see if the tile that seems to have disappeared is still there. When this happens, I assume that the tile is not in its group anymore since the number of sprites in the tile group went from 3 to 2. But the list showing all the taken positions still shows that there are 3 positions that are taken. I can still click on the tile if I just click on the space where there should be a tile and the tile comes back. I thought the game should exit when a tile isn't clicked on but it doesn't if there is an "invisible" tile in that position.
How do I make it so that this bug doesn't happen and every new tile made is visible?
The problem is that the cursor has an area and can hit more than one block at a time. So in destroyTile more than 1 block can be removed at once:
def destroyTile(self):
pygame.sprite.spritecollide(cursor, tileGroup, True)
However, the function handleTiles cannot handle this, because it can only remove one block position from the taken list. I suggest to simplify the code and recreate the taken list completely from tileGroup when blocks are removed:
def handleTiles():
taken.clear()
for tile in tileGroup:
x, y = tile.rect.topleft
taken.append([x, y])
drawTiles()

Python programming trouble

I was programming a game and I did not know how to program in python 3.7 so I had to get a book called Code This Game, book from OddDot. And When I got to chapter 6 I was learning how to create a group for the enemy sprites. and every time I ran the program all it drew was the background image and the grid(because the book is not only teaching me how to code in Python, while I am learning I am making a game from the book while I learn from the book.) it did not draw the group of Vampire Pizzas in the right column if fact it doesn't even draw them at all. I have tried redoing the chapter over and over again and I can't do it
here is my code
#Import Libraries
import pygame
from pygame import *
from random import randint
#Initialize pygame
pygame.init()
#Define constant variables
WINDOW_WIDTH = 1100
WINDOW_HEIGHT = 600
WINDOW_RES = (WINDOW_WIDTH, WINDOW_HEIGHT)
#Define Tile Parameters
WIDTH = 100
HEIGHT = 100
#Define Tile colors
WHITE = (255, 255, 255)
#Set up rates
SPAWN_RATE = 360
#This is the code where the game window will show up
GAME_WINDOW = display.set_mode(WINDOW_RES)
display.set_caption('Attack of the Vampire Pizzaas!')
#---------------------------------------------------------
#Set up the enemy image
pizza_img = image.load('vampire.png')
pizza_surf = Surface.convert_alpha(pizza_img)
VAMPIRE_PIZZA = transform.scale(pizza_surf, (HEIGHT, WIDTH))
#Create a subclass of Sprite called VampireSprite
class VampireSprite(sprite.Sprite):
#Set up enemy instances
def __init__(self):
super().__init__()
self.speed = 2
self.lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center = (1100, y))
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))
#Set up the background image
background_img = image.load('restaurant.jpg')
background_surf = Surface.convert_alpha(background_img)
BACKGROUND = transform.scale(background_surf, WINDOW_RES)
#------------------------------------------------------
all_vampires = sprite.Group()
#Initialile and draw background grid
tile_color = WHITE
for row in range(6):
for column in range(11):
draw.rect(BACKGROUND, tile_color, (WIDTH * column,
HEIGHT * row, WIDTH, HEIGHT),1)
GAME_WINDOW.blit(BACKGROUND, (0, 0))
#---------------------------------------------------------
#Start The Main Game Loop
#Game Loop
game_running = True
while game_running:
#Check for Events
for event in pygame.event.get():
if event.type == QUIT:
game_running = False
#Spawn vampire pizza sprites
if randint(1, SPAWN_RATE) == 1:
VampireSprite()
#Update displays
for vampire in all_vampires:
vampire.update(GAME_WINDOW)
display.update()
#End of the Main game loop
#---------------------------------------------------------
#Clean up game
pygame.quit()
I don't know if I am doing something wrong or what
If my eyes are working fine(I guess) the code is valid
Make sure that the pygame version you are using is the one compatible with Python 3.7 and not older versions such as Python 2.
I had the same issue while working my way through this fun coding book. Comparing the code from the book's website and my own code I found the issue was incorrect spacing while defining the update() function within the VampireSprite class.
# Create a subclass of Sprite called VampireSprite
class VampireSprite(sprite.Sprite):
# Set up enemy instances
def __init__(self):
super().__init__()
self.Speed = 2
self. Lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center = (1100, y))
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))
The correct code is:
# Create an enemy class
class VampireSprite(sprite.Sprite):
# This function creates an instance of the enemy
def __init__(self):
super().__init__()
self.speed = 2
self.lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center=(1100, y))
# This function moves the enemies from right to left and destroys them after they've left the screen
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))

Pygame, Collision between 2 objects in the same group

So, i am trying to create a game where aliens spawn from 3 specific places. Each Alien will spawn randomly in one of the 3. But there will always be at least one alien, that will spawn on top of another one. I want to delete that alien and spawn him randomly in another spawn point. If it is empty he will stay if not the process will be repeated. The thing is that i cannot find a way to detect collision of 2 objects that are in the same group.
I just started learning pygame so 1) My question may be stupid 2) My way of spawning probably is very inefficient
Here is the Alien class:
class Alien(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((80,60))
self.image.fill(GREY)
self.rect = self.image.get_rect()
spawn_point1 = x1,y1 = -30, 70
spawn_point2 = x2,y2 = -30, 150
spawn_point3 = x3,y3 = -30, 230
random_spawn = random.choice([spawn_point1,spawn_point2,spawn_point3])
self.rect.center = random_spawn
self.speedx = 10
def update(self):
spawn_point1 = x1,y1 = -30, 70
spawn_point2 = x2,y2 = -30, 150
spawn_point3 = x3,y3 = -30, 230
self.speedx = 10
random_spawn = random.choice([spawn_point1,spawn_point2,spawn_point3])
self.rect.x += self.speedx
if self.rect.x > WIDTH + 20:
self.rect.center = random_spawn
And here is the part where i detect collision(This part doesnt work)
aliens_col = pygame.sprite.groupcollide(aliens, aliens, True, False)
for i in aliens_col:
alien = Alien()
aliens.add(alien)
all_sprites.add(aliens)
Here is an implementation of the Bounding Box test.
import random
class Rectangle:
def __init__(self, height, width, x, y):
self.height = height
self.width = width
self.x = x
self.y = y
def collided_with_another_rectangle(self, rect):
""" Assumes rectangles are same size or that this rectangle is smaller than the other rectangle"""
if self.x > (rect.x + rect.width):
# Is to the right of the other rectangle
return False
elif (self.x + self.width) < rect.x:
# is to the left of the other rectangle
return False
elif (self.y + self.height) < rect.y:
# is above the other rectangle
return False
elif self.y > (rect.y + rect.height):
# is below the other rectangle
return False
else:
return True
collision_count = 0
for i in range(0, 1000):
# Here I pick random locations on a 1000X1000 screen for the first rectangle
x1 = random.randint(0, 1000)
y1 = random.randint(0, 1000)
# Here I pick random locations on a 1000X1000 screen for the second rectangle
rect1 = Rectangle(100, 100, x1, y1)
x2 = random.randint(0, 1000)
y2 = random.randint(0, 1000)
rect2 = Rectangle(100, 100, x2, y2)
"""
I use the collided with another rectangle function to test if the first rectangle is above,below,
to the right or to the left of the other rectangle. If neither of these are true then the rectangles
have collided.
"""
if rect1.collided_with_another_rectangle(rect2):
collision_count += 1
print("Rect1 X and Y:" + str(x1) + " " + str(y1))
print("Rect2 X and Y:" + str(x2) + " " + str(y2))
print("collided")
print("Collision Count:" + str(collision_count))
I'm still not absolutely sure what you want to achieve, but I think this example will be helpful to you.
When a sprite leaves the screen, I call the reset_pos method in which I iterate over the three spawn points to set the position to one spawn after the other and then I use another for loop to iterate over the sprites to check if one collides.
If a sprite collides, I continue with the next spawn point.
If no sprite collides, I just return from the method.
If no spawn is free, I remove the sprite (but you can do something else).
import random
import pygame
from pygame.math import Vector2
pygame.init()
WIDTH, HEIGHT = 640, 480
class Alien(pygame.sprite.Sprite):
def __init__(self, aliens):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((80, 60))
self.image.fill((120, random.randrange(255), random.randrange(255)))
self.rect = self.image.get_rect()
self.spawn_points = [(-30, 70), (-30, 150), (-30, 230)]
self.aliens = aliens
self.reset_pos()
self.speedx = 10
def update(self):
self.rect.x += self.speedx
if self.rect.x > WIDTH + 20:
self.reset_pos()
def reset_pos(self):
random.shuffle(self.spawn_points) # Shuffle the spawns.
for spawn in self.spawn_points:
# Set the position to one of the spawns.
self.rect.center = spawn
# Check if this sprite collides with another one.
for sprite in self.aliens:
if sprite is self: # Skip self.
continue
if self.rect.colliderect(sprite.rect):
break # Break out of the loop if the spawn is occupied.
else: # The else means no 'break' occurred in the for loop above,
# so the spawn must be free.
return # Break out of the method if the spawn is free.
# I just remove the sprite if no spawn is free. You can do something else here.
self.kill()
def main():
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
aliens = pygame.sprite.Group()
for _ in range(3):
# I pass the aliens group to the sprite because we need to
# iterate over it to see if a sprite collides.
alien = Alien(aliens)
aliens.add(alien)
all_sprites = pygame.sprite.Group(aliens)
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.MOUSEBUTTONDOWN:
al = Alien(aliens)
all_sprites.add(al)
aliens.add(al)
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pygame.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pygame.quit()
When using the same group in both of the group-paramaters of groupcollide it will always consider the sprite it is checking in group_a as colliding with that same sprite in group_b. This results in groupcollide always returning a collision.
To get around this I created a new function in pygame's sprite.py that ignores single collisions and only returns collisions >= 2. My only change was to add:
if len(collision) >=2:
And then the required tab for the following line(s).
The code I added to sprite.py is pasted below but the tab for the def intra_groupcollide is one too far:
def intra_groupcollide(groupa, groupb, dokilla, dokillb, collided=None):
"""detect collision between a group and itself.
This is modified from groupcollide but excludes collisions <=1
pygame.sprite.groupcollide(groupa, groupb, dokilla, dokillb):
return dict
"""
crashed = {}
# pull the collision function in as a local variable outside
# the loop as this makes the loop run faster
sprite_collide_func = spritecollide
if dokilla:
for group_a_sprite in groupa.sprites():
collision = sprite_collide_func(group_a_sprite, groupb,
dokillb, collided)
if collision:
if len(collision) >=2:
crashed[group_a_sprite] = collision
group_a_sprite.kill()
else:
for group_a_sprite in groupa:
collision = sprite_collide_func(group_a_sprite, groupb,
dokillb, collided)
if collision:
if len(collision) >=2:
crashed[group_a_sprite] = collision
#print(crashed)
return crashed
Then in my own python program, I simply replaced groupcollide with intra_groupcollide. I set both kill paramaters as 'false' because in my usage I'm bouncing them off each other. I have not tested this code with them set to 'true'.
I found sprite.py in my file system by following this answer:
Where are the python modules stored?

Pygame. How to make a rect change direction on collision (boundary check)

Part of an assignment I'm working on is making a ball bounce around the screen, I can make it move, but my boundary test doesn't seem to be working: the ball simply moves in direction instead of changing direction. So to clarify, what I want to ball to do is change direction as it hits the screen edge.
import sys
import pygame
SCREEN_SIZE = 750, 550
BALL_DIAMETER = 16
BALL_RADIUS = BALL_DIAMETER // 2
MAX_BALL_X = SCREEN_SIZE[0] - BALL_DIAMETER
MAX_BALL_Y = SCREEN_SIZE[1] - BALL_DIAMETER
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
LEFT = 11
RIGHT = 12
pygame.init()
clock = pygame.time.Clock()
pygame.display.init()
font = pygame.font.SysFont("impact", 20)
pygame.display.set_caption("Breakout")
screen = pygame.display.set_mode(SCREEN_SIZE)
class Ball:
def __init__(self):
''' '''
self.ball = pygame.Rect(300, 730 -
BALL_DIAMETER,
BALL_DIAMETER, BALL_DIAMETER)
# Draw ball
def draw_ball(self):
pygame.draw.circle(screen,
WHITE, (self.ball.left
+ BALL_RADIUS, self.ball.top +
BALL_RADIUS), BALL_RADIUS)
# Updates the coordinates by adding the speed components
def move_ball(self, x, y):
self.xspeed = x
self.yspeed = y
self.ball = self.ball.move(self.xspeed, self.yspeed)
# bounds check
if self.ball.left <= 0:
self.ball.left = 0
self.xspeed = -self.xspeed
elif self.ball.left >= MAX_BALL_X:
self.ball.left = MAX_BALL_X
self.xspeed = -self.xspeed
if self.ball.top < 0:
self.ball.top = 0
self.yspeed = -self.yspeed
elif self.ball.top >= MAX_BALL_Y:
self.ball.top = MAX_BALL_Y
self.yspeed = -self.yspeed
# shows a message on screen, for testing purposes
class Text:
def show_message(self, message):
self.font = pygame.font.SysFont("impact", 20)
font = self.font.render(message,False, WHITE)
screen.blit(font, (200, 400))
class Game:
def __init__(self):
''' '''
def run(self):
b = Ball()
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
keys = pygame.key.get_pressed()
# fps lock, screen fill and method call for input
clock.tick(60)
screen.fill(BLACK)
b.draw_ball()
b.move_ball(5, -5)
# used to keep track of various elements
# Text().show_message("P: " + str(p))
pygame.display.flip()
# Creates instance of the game class, and runs it
if __name__ == "__main__":
Game().run()
Your only call to move_ball uses a constant vector.
Since you never change the call parameters, the ball moves only that way.
b.move_ball(5, -5)
Yes, you change the vector components within move_ball when you hit a wall. However, on the next call, you change them back to the original values and move the ball in the original direction.
You have to initialize the vector outside move_ball, and then let the routine access the existing vector when it's called.

Categories