Working with classes (creating a class efficiently, drawing and updating all instances of the class every frame) - python

I've been working on a "bullet hell" game of my own, but I am struggling very much with working with classes (im new to python), below i've enclosed my code(I wouldnt include so much but i dont know howo to go about the problem). At the moment what i've been attempting to
1. Create different instances of the class
2. Draw them every frame
3. Update their position every frame according to the x and y speed
import pygame
# Define colors needed
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
bulletArray = []
BulletSprite = ("Bullet4.png")
pygame.init()
class Bullet:
def __init__(self, sprite, x, y, xSpeed, ySpeed):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.xSpeed = xSpeed
self.ySpeed = ySpeed
bulletArray.append(self)
def update(self):
pass
def draw(screen):
self.screen = screen
screen.blit(screen, (x, y))
#Set the width and height of the game screen
size = (700, 500)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Bullet stuff")
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
testBullet = Bullet(BulletSprite, 200, 200, 2, 2)
#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
print(bulletArray)
# --- background image.
screen.fill(WHITE)
# --- Drawing code should go here
# --- Updates the screen every frame with whats been drawn
pygame.display.flip()
# --- Limit to 60 frames per second
clock.tick(60)
# Close the window and quit.
pygame.quit()

Much of the this mechanism is already handled automatically if you use the PyGame Sprite Class.
A PyGame sprite is essentially made of:
An Image to draw to represent the object
A rectangle around the object to remember the location and check for collisions
An update() function to handle any movement of the sprite
It's handy to work with the existing PyGame Sprite layout because a lot of functionality is already provided by the library code.
So let's make a BulletSprite:
class BulletSprite( pygame.sprite.Sprite ):
def __init__( self, bitmap, x, y ):
pygame.sprite.Sprite.__init__( self )
self.image = bitmap # How the sprite looks
self.rect = bitmap.get_rect() # Bounding box the size of the image
self.rect.centerx = x # position the bullet
self.rect.centery = y # about its centre
And that's it. This will give you a stationary sprite, painted as the given bitmap at (x,y) on-screen.
So let's now use it:
# Create a group of 100 bullets
sprite_image = pygame.image.load( "bullet.png" )
all_bullets = pygame.sprite.Group() # Group to hold the sprites
for i in range( 100 ):
random_x = 50 + random.randrange( 950 ) # TODO: use proper screen size
random_y = 50 + random.randrange( 950 )
new_sprite = BulletSprite( sprite_image, random_x, random_y )
all_bullets.add( new_sprite )
# Main Window loop
while True:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
break
# Paint the window
window.fill( ( 0, 0, 0 ) ) # Paint it Black
all_bullets.update() # move all the bullets
all_bullets.draw( window ) # Paint all the bullets
pygame.display.flip()
pygame.quit()
And that's it. After the sprites are created, it takes two lines of code to move them all, and draw them all.
But they wont move yet. For them to move, the BulletSprite needs to have an update() function. This function does whatever is necessary to move the sprite - apply velocity, gravity, whatever. The net effect is the sprite's rectangle's (x,y) is changed.
So adding some changes to the sprite - first an x and y velocity, and then the movement code.
class BulletSprite( pygame.sprite.Sprite ):
def __init__( self, bitmap, x, y ):
pygame.sprite.Sprite.__init__( self )
self.image = bitmap # How the sprite looks
self.rect = bitmap.get_rect() # Bounding box the size of the image
self.rect.centerx = x # position the bullet
self.rect.centery = y # about its centre
self.x_move = random.randrange( -3, 3 ) # simple random velocity
self.y_move = random.randrange( -3, 3 )
def update( self ):
x = self.rect.centerx + self.x_move # calculate the new position
y = self.rect.centerx + self.y_move # by applying an offset
# has the sprite gone off-screen
if ( x < 0 or x > 1000 or y < 0 or y > 1000 )
self.kill() # remove from the group
else:
self.rect.centerx = x # RE-position the bullet
self.rect.centery = y
Every time the update() is called on your sprite group (all_bullets), the sprite library will go and call the update() function on every sprite in the group. The code checks to see if the bullet is off-screen (I lazily used 1000 as a placeholder for screen width & height), and if-so, the sprite.kill() function handles the removing from the group and cleaning up. It's very easy.

Related

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

Is there a way to use for loops to make a game map in pygame?

So I want to make a gamemap in my pygame using for loops, but apparently the way I am doing it does not work. The code is below and if you can take a look at it, that would be nice!
Pygame
import pygame
import sys
import random
import subprocess
# _______initiate Game______________ #
class Game:
pygame.init()
width = 800
height = 800
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Maze Game")
pygame.draw.circle(screen, (0, 0, 255), (250, 250), 75)
white = [255, 255, 255]
black = [0, 0, 0]
lblue = [159, 210, 255]
background = input(
"What color background would you like? (White, Black, or Light Blue): ")
if background == "White":
screen.fill(white)
pygame.display.flip()
elif background == "Black":
screen.fill(black)
pygame.display.update()
elif background == "Light Blue":
screen.fill(lblue)
pygame.display.update()
else:
screen.fill(black)
pygame.display.update()
for "." in "map1.txt":
pygame.image.load("winter.Wall.png")
# ___________________TO RUN___________________________ #
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
command = "python JakeGame.py"
subprocess.call(command)
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.type == pygame.K_ESCAPE:
pygame.quit()
# _______________________________________________ #
pygame.quit()
This is the map txt file and the error I am getting
...1...........................................
...11111111111111..............................
1111............11111111111111.................
1............................111...............
111............................................
1..............................................
111111111111111111111111111....................
..................1............................
1111111111111111111............................
..................1............................
..................11111111.....................
..........111111111......111111111.............
.................................1.............
So basically, what I am trying to do is convert all those periods to be loaded with an image called winterWall.png .
The error I am getting is "can't assign literal" Thank you guys for helping :p
It's a bit more complicated than just what you propose (which is invalid syntax).
The code needs to open the file, then iterate over the rows and columns of map data. Then, for each type of letter it finds, render some kind of tile-based representation - here I've just used red and green blocks. An alternate version could just as easily render bitmaps.
The first thing you need to do is open the file and read the text into a list of strings - one for each line:
map_data = open( map_filename, "rt" ).readlines()
One caveat to this method, is that each line still has its end-of-line on the end. And it would be better to handle errors (like the file being missing), rather than crashing.
Then we create a Surface, which is just an off-screen image. It needs to be as big as the map is, times the size of the tiles. Since this is created inside the TileMap class, this gets stored in TileMap.image (i.e: self.image).
Next the code iterates through each line, and then each letter drawing a rectangle. The code just uses the constant TILE_SIZE for this. You need to decide if this is going to work with coloured squares, or bitmaps, etc. - but obviously all your tiles need to use the same size.
# iterate over the map data, drawing the tiles to the surface
x_cursor = 0 # tile position
y_cursor = 0
for map_line in map_data: # for each row of tiles
x_cursor = 0
for map_symbol in map_line: # for each tile in the row
tile_rect = pygame.Rect( x_cursor, y_cursor, TileMap.TILE_SIZE, TileMap.TILE_SIZE )
if ( map_symbol == '.' ):
pygame.draw.rect( self.image, RED, tile_rect )
elif ( map_symbol == '1' ):
pygame.draw.rect( self.image, GREEN, tile_rect )
else:
pass # ignore \n etc.
x_cursor += TileMap.TILE_SIZE
y_cursor += TileMap.TILE_SIZE
Note that we make an x_cursor and y_cursor. This is the top-left corner of the map-tile about to be drawn. As we step through each character, we update this to the next position on the map.
At each point, the map has a TILE_SIZE by TILE_SIZE (16x16) "cell" into which we draw a coloured square, depending on the type of map item.
At the end of the operation, we have loaded in all the map tiles, and draw the map to an off-screen Surface (self.image), which can be draw to the screen quickly and easily.
The TileMap class, simply wraps all this together.
Reference Code:
import pygame
WINDOW_WIDTH = 800
WINDOW_HEIGHT= 600
SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
#define colours
BLACK = (0,0,0)
RED = ( 200, 0, 0 )
GREEN = (0,255,0)
### Class to render map-data to a surface image
class TileMap:
TILE_SIZE=16 # size of map elements
def __init__( self, map_filename ):
""" Load in the map data, generating a tiled-image """
map_data = open( map_filename, "rt" ).readlines() # Load in map data TODO: handle errors
map_width = len( map_data[0] ) - 1
map_length = len( map_data )
# Create an image to hold all the map tiles
self.image = pygame.Surface( ( map_width * TileMap.TILE_SIZE, map_length * TileMap.TILE_SIZE ) )
self.rect = self.image.get_rect()
# iterate over the map data, drawing the tiles to the surface
x_cursor = 0 # tile position
y_cursor = 0
for map_line in map_data: # for each row of tiles
x_cursor = 0
for map_symbol in map_line: # for each tile in the row
tile_rect = pygame.Rect( x_cursor, y_cursor, TileMap.TILE_SIZE, TileMap.TILE_SIZE )
if ( map_symbol == '.' ):
pygame.draw.rect( self.image, RED, tile_rect )
elif ( map_symbol == '1' ):
pygame.draw.rect( self.image, GREEN, tile_rect )
else:
pass # ignore \n etc.
x_cursor += TileMap.TILE_SIZE
y_cursor += TileMap.TILE_SIZE
def draw( self, surface, position=(0,0) ):
""" Draw the map onto the given surface """
self.rect.topleft = position
surface.blit( self.image, self.rect )
### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), SURFACE )
pygame.display.set_caption("Render Tile Map")
### Load the map
tile_map = TileMap( "map1.txt" )
### Main Loop
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
# Update the window, but not more than 60fps
window.fill( BLACK )
# Draw the map to the window
tile_map.draw( window )
pygame.display.flip()
clock.tick_busy_loop(60)
pygame.quit()

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.

Software Design and Development Major: Pygame Smudge Trails

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.

Categories