How do I make python game, Flappy Bird's pipes to appear? - python

I created a Flappy Bird game with NEAT systems built by python, following this tutorial from YouTube: Tech With Tim
When I finished all of the code, where he ran it and had had the game run normally, mine didn't show pipes on screen. My code is provided below:
import pygame, neat, time, os, random
##import all of the variables we'll need for AI running
pygame.font.init()
##enables using fonts/text on screen.
WIN_WIDTH = 500
WIN_HEIGHT = 800
##define the size of the window. We're working with a GUI for flappy bird AI.
BIRD_IMGS = [pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird1.png"))), pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird2.png"))), pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bird3.png")))]
##import the images of the bird from the images directory. There are three as to make up the states of flapping.
PIPE_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "pipe.png")))
##import the texture for the pipes from the same directory.
BASE_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "base.png")))
BG_IMG = pygame.transform.scale2x(pygame.image.load(os.path.join("imgs", "bg.png")))
##repeat for the ground and the background. They are seperate pieces as to allow for handling the collision with the ground being death.
STAT_FONT = pygame.font.SysFont("comicsans", 50)
class Bird: ##define the bird and its system as a class.
IMGS = BIRD_IMGS ##easier system for calling back to our list of bird images
MAX_ROTATION = 25
ROT_VEL = 20
ANIMATION_TIME = 5
##some definitions to do with making sure the bird can't just start spinning around aimlessly instead of looking like it should be going forward.
def __init__(self, x, y): ##essentially telling the AI/game what the bird starts out as, and how to define each thing.
self.x = x ##the distance forward on the screen
self.y = y ##how high up on the screen the bird is
self.tilt = 0 ##the angle the bird is at
self.tick_count = 0 ##essentially handles all of the physics
self.vel = 0 ##speed.
self.height = self.y ##a bit more about the height
self.img_count = 0 ##which image of the bird to be automatically loading (pre-flight)
self.img = self.IMGS[0] ##references back to the IMGS variable at the start of the class, defining to the system that the 0 on the previous line means bird1.png
def jump(self):
self.vel = -10.5 ##tells the game where to start. 0,0 is in the top left. This is near to half way down.
self.tick_count = 0 ##keeps track of when the last jump was. All for physics of bird.
self.height = self.y ##where bird started from, where the bird is now.
def move(self):
self.tick_count += 1 ##it's jumped one, now the tick registers that.
d = self.vel*self.tick_count + 1.5*self.tick_count**2 ##this is literally just a physics equation to calculate where the bird is going. Tells game how much player is moving up/down and how long player has been moving for.
if d >= 16: ##assigning terminal velocity. Going much faster can make the game unplayable. If going faster than 16 pixels per second, just start going 16 pixels per second.
d = 16 ##if d has gone higher than 16, just make it 16 again. Terminal velocity.
if d < 0: ##if for some reason, there's no velocity, such as at the very start of the game, give a very slight jump boost of -2 pixels (upwards 2)
d -= 2
self.y = self.y + d ##make sure the new Y position is calculated with the variable we just made.
if d < 0 or self.y < self.height + 50: ##assign bird tilting for model
if self.tilt < self.MAX_ROTATION:
self.tilt = self.MAX_ROTATION
else:
if self.tilt > -90: ##eventually, nosedive when going down.
self.tilt -= self.ROT_VEL
def draw(self, win): ##this one draws the bird and window onto the screen. It creates the window, adds the bird to it, etc. Kind of confusing, but I will try to keep the comments simple enough.
self.img_count += 1 ##this just checks how many times/how long the current image of the bird has been on screen.
if self.img_count < self.ANIMATION_TIME:
self.img = self.IMGS[0]
elif self.img_count < self.ANIMATION_TIME*2:
self.img = self.IMGS[1]
elif self.img_count < self.ANIMATION_TIME*3:
self.img = self.IMGS[2]
elif self.img_count == self.ANIMATION_TIME*4 + 1:
self.img = self.IMGS[0]
self.img_count = 0 ##essentially, this just does a check for how long each anim has been playing, and if it should change the image. It gets really complicated to actually explain though, so sorry.
if self.tilt <= -80:
self.img = self.IMGS[1]
self.img_count = self.ANIMATION_TIME*2 ##this prevents frames being skipped.
rotated_image = pygame.transform.rotate(self.img, self.tilt) ##this will rotate the bird using all of the logic we just defined above.
new_rect = rotated_image.get_rect(center=self.img.get_rect(topleft = (self.x, self.y)).center) ##changes the center of rotation for the bird from the top left to the center of the screen.
win.blit(rotated_image, new_rect.topleft)
def get_mask(self):
return pygame.mask.from_surface(self.img) ##to be used for accurate collision later.
class Pipe: ##now we make the pipes.
GAP = 200 ##distance between each other.
VEL = 5 ##how fast they're moving towards the left.
def __init__(self, x):
self.x = x ##position as last time.
self.height = 0 ##height to be defined by a random system later.
self.gap = 100 ##gap again to be updated by a random system later.
self.top = 0 ##where the top pipe is. To be defined by random system later.
self.bottom = 0 ##same as prior, but for the bottom pipe instead.
self.PIPE_TOP = pygame.transform.flip(PIPE_IMG, False, True) ##flipping the top pipe. By default the model is upright, we need one going the other way as well.
self.PIPE_BOTTOM = PIPE_IMG ##here's the bottom pipe now imported.
self.passed = False ##has bird passed pipe? Default false
self.set_height() ##set the height for the pipe, dealt with later.
def set_height(self):
self.height = random.randrange(50, 450) ##random height for the pipes.
self.top = self.height - self.PIPE_TOP.get_height() ##defining the top pipe's height using the bottom pipe.
self.bottom = self.height + self.GAP ##defining the distance between the pipes
def move(self): ##defining the movement, just moving the pipes to the left.
self.x -= self.VEL
def draw(self, win): ##drawing the pipes as we want them to appear on screen
win.blit(self.PIPE_TOP, (self.x, self.top))
win.blit(self.PIPE_BOTTOM, (self.x, self.bottom))
def collide(self, bird): ##this is all the code for PIXEL PERFECT collision. Instead of box collision, it checks to see if coloured pixels are overlapping to avoid deaths that didn't deserve to be deaths.
bird_mask = bird.get_mask()
top_mask = pygame.mask.from_surface(self.PIPE_TOP)
bottom_mask = pygame.mask.from_surface(self.PIPE_BOTTOM) ##masks the images, allowing to spot pixels from blank space.
top_offset = (self.x - bird.x, self.top - round(bird.y)) ##checks distance from bird, round is because we cannot have decimal numbers, so it rounds to nearest whole
bottom_offset = (self.x - bird.x, self.bottom - round(bird.y))
b_point = bird_mask.overlap(bottom_mask, bottom_offset) ##checks the first point of overlapping for bird and bottom pipe
t_point = bird_mask.overlap(top_mask, top_offset) ##repeat for top pipe
if t_point or b_point:
return True
return False
class Base:
VEL = 5
WIDTH = BASE_IMG.get_width()
IMG = BASE_IMG
def __init__(self, y): ##same as pipe and bird, defining all of the variables.
self.y = y
self.x1 = 0
self.x2 = self.WIDTH
def move(self): ##defining movement speed to velocity.
self.x1 -= self.VEL
self.x2 -= self.VEL
if self.x1 + self.WIDTH < 0: ##it creates two versions of the image, one directly behind the other, therefore it looks like one continuous image. If one has left the screen completely, move it back to behind the other one and get going again.
self.x1 = self.x2 + self.WIDTH
if self.x2 + self.WIDTH < 0:
self.x2 = self.x1 + self.WIDTH
def draw(self, win):
win.blit(self.IMG, (self.x1, self.y))
win.blit(self.IMG, (self.x2, self.y))
def draw_window(win, birds, pipes, base, score):
win.blit(BG_IMG, (0,0)) ##set up the background image for the game
for pipe in pipes:
pipe.draw(win) ##draw the pipes
base.draw(win) ##draw the floor into the window
for bird in birds:
bird.draw(win) ##draw the bird into the window
pygame.display.update() ##update the display
text = STAT_FONT.render("Score: " + str(score), 1, (255,255,255))
win.blit(text, (WIN_WIDTH - text.get_width() - 15, 10))
def main(genomes, config):
nets = []
ge = []
birds = []
for _, g in genomes:
net = neat.nn.FeedForwardNetwork.create(g, config)
nets.append(net)
birds.append(Bird(230, 350))
g.fitness = 0
ge.append(g)
base = Base(730) ##place the base of the window at 730
pipes = [Pipe(600)]
score = 0
win = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT)) ##define the window.
clock = pygame.time.Clock() ##start a clock so that the game runs at a consistent speed and is not relative to FPS(*COUGH* BETHESDA AND FALLOUT 76 *COUGH*)
run = True ##by default of course, the game is running.
while run: ##while the game is running. Pygame doesn't like to close without being informed prior.
clock.tick(30) ##define the speed of that clock.
for event in pygame.event.get(): ##keeps track of whenever something is triggered, such as user clicking mouse, or AI activating output
if event.type == pygame.QUIT: ##check if the active event is a quit-based event to close the game.
run = False ##tell run it is now false as to allow the while loop to stop.
pygame.quit() ##when any quit function, such as clicking the red X is triggered (windows) it will close the game, and tell pygame and python to shut off.
quit()
pipe_ind = 0
if len(birds) > 0:
if len(pipes) > 1 and birds[0].x > pipes[0].x + pipes[0].PIPE_TOP.get_width():
pipe_ind = 1
else:
run = False
break
for x, bird in enumerate(birds):
bird.move()
ge[x].fitness += 0.1
output = nets[x].activate((bird.y, abs(bird.y - pipes[pipe_ind].height), abs(bird.y - pipes[pipe_ind].bottom)))
if output[0] > 0.5:
bird.jump()
add_pipe = False
base.move() ##moves the base.
rem = []
for pipe in pipes: ##it gets each pipe option in the list of pipes
for x, bird in enumerate(birds):
if pipe.collide(bird): ##if the pipe collides with the bird using specified collision in that section, kill player
ge[x].fitness -= 1
birds.pop(x)
nets.pop(x)
ge.pop(x)
if not pipe.passed and pipe.x < bird.x: ##once the pipe has been passed by the bird, add a new one.
pipe.passed = True
add_pipe = True
if pipe.x + pipe.PIPE_TOP.get_width() < 0: ##once the pipes has left the screen, remove the pipe, add it to a list of removed pipes
rem.append(pipe)
pipe.move() ##enable pipe movement.
if add_pipe: ##add one score to the player once the pipe has been passed.
score += 1
for g in ge:
g.fitness += 5
pipes.append(Pipe(600))
for r in rem:
pipes.remove(r)
for x, bird in enumerate(birds):
if bird.y + bird.img.get_height() >= 730 or bird.y < 0: ##handling for bird hitting floor.
birds.pop(x)
nets.pop(x)
ge.pop(x)
draw_window(win, birds, pipes, base, score) ##calls the above draw window stuff
def run(config_path): ##start running the new config.
config = neat.config.Config(neat.DefaultGenome, neat.DefaultReproduction, neat.DefaultSpeciesSet, neat.DefaultStagnation, config_path) ##pull in a ton of stuff for NEAT to work with
p = neat.Population(config) ##set our population (how many AIs will go per generation/how many birds are on screen at once. The config is 100, as this is best for evolution-performance.)
p.add_reporter(neat.StdOutReporter(True))
stats = neat.StatisticsReporter()
p.add_reporter(stats)
winner = p.run(main,50)
if __name__ == "__main__":##loading in the config file to create the network.
local_dir = os.path.dirname(__file__)
config_path = os.path.join(local_dir, "config-feedforward.txt")
run(config_path)
Any ideas why this is not working? Thanks.

you have indented the pipe.move() one too many times.
It should be
if pipe.x + pipe.PIPE_TOP.get_width() < 0:
rem.append(pipe)
pipe.move() #enable pipe movement.

Related

Screen looks like it's sinking due to weird collision/jumping values that affect camera movement

I'm trying to implement vertical camera movement in my PyGame platformer (horizontal camera movement works fine, almost definitely irrelevant to this) but the screen always appears to constantly be sinking unless the player jumps (but when the player finishes his jump, it starts sinking again). Here's the code:
if self.onGround == False: # falling
self.vermo += 0.4 # player's vertical momentum increasing as he falls
if self.onGround == True:
self.jump = False
self.fall = False
if self.vermo > 10: # can't fall too fast
self.vermo = 10
if self.vermo > 0 and self.collideWithBlock == False: # checks if the player is not on the ground but has also past climax/zenith of jump
self.fall = True
if keys[pygame.K_SPACE] and self.onGround == True: # makes player jump but only if touching the ground
self.vermo = -12
self.jump = True
if self.fall == True and self.onGround == False: # if the player is not on the ground but has past the climax.zenith of his jump, the camera should follow the player downward
for sprite in self.game.all_sprites: # this moves every sprite on the screen to give the illusion of camera movement
sprite.rect.y -= self.vermo
if self.jump == True: # if the player is shooting upwards (jumping) then the camera should follow him upward
for sprite in self.game.all_sprites:
sprite.rect.y -= self.vermo # I thought this should be += but apparently not
The reason this is happening is due to self.vermo looping the values below. The problem is, I have no idea what causes this.
These are the values:
0
0.4
0.8
1.2000000000000002
0
Here is the basic code for a camera class:
class Camera():
def __init__(self, width, height):
self.camera = pg.Rect(0,0,width,height)
self.width = width
self.height = height
def apply(self,entity):
return entity.rect.move(self.camera.topleft)
def update(self, target):
x = -target.rect.x + width//2
y = -target.rect.y + height//2
x = min(0, x)
y = min(0, y)
x = max(-(self.width - width), x)
y = max(-(self.height - height), y)
self.camera = pg.Rect(x,y,self.width,self.height)
Every time you update, you have to apply the update method, with target=player.
This will adjust the camera according the player's new position.
def update(self):
self.all_sprites.update()
self.camera.update(self.player)
When you blit the other objects to the screen, you create the illusion the player is moving.
for sprite in self.all_sprites:
self.screen.blit(sprite.image, self.camera.apply(sprite))
Keep in mind that the width and height parameters passed into the camera constructor are the width and height of the level, not the width and height of the screen.
I would try implementing this and see if it fixes your sinking problem.
You could also try adding this to your existing code:
if self.onGround == True:
self.vermo = 0
self.jump = False
self.fall = False
This will ensure that when the player is on the ground, no vertical momentum.
I really couldn't tell you why, but in this line of code:
if self.vermo > 0 and self.collideWithBlock == False:
self.fall = True
the 0 needs to be replaced with 1.6. Again, no idea why, but it works. I don't question the ways of the Python gods, but by all means if you know why this is I encourage you to comment it here.
The camera, as it is now, still has some issues, but I know why these issues are occurring and I'm currently working on fixing them.

Python Pyglet slows down the animation. Objects are drawn slowly on the screen

I have repeatedly encountered the problem of slowing down animation when drawing various objects in Python. The problem is especially acute when the number of objects being drawn increases. I would not say that there are too many objects: > = 200 (r=10) circles are already a slide show.
I think it's not about the library I use (pyglet). Before that I used the p5py analog p5.js the brakes were even stronger (in truth, the creators of p5py know that it is much slower than its counterparts (Issues from Github)). I also tried Tkinter - I thought that a clean canvas would solve my problems. But that didn't happen. And now I run the code in Pyglet - I get a slideshow.
I also noticed that the computer starts to take off on the jet thrust of coolers when the animation is slowing down. At the same time, the CPU load of the Python process never exceeds 20% (very strange). Computer not quite a calculator: Intel i5-8300H 4 cores (8 virtual) 2.30 GHz.
What could be the problem? An animation of 1000 points should not turn into a slideshow, even if it is Python.
PS. I also think that the problem is not in my code. Exactly the same (not about the code below) code on p5.js gives smooth animation in the browser (but the browser uses a video card). But just in case, I put my last count on Pyglet - this is not the top of optimization, but it should not work so slowly:
sketch.py
import pyglet
import random
from firework import Firework
window = pyglet.window.Window(400, 400)
main_batch = pyglet.graphics.Batch()
fireworks = [Firework(main_batch)]
#window.event
def on_draw():
window.clear()
main_batch.draw()
def update(dt):
if random.random() < 0.03:
fireworks.append(Firework(main_batch))
for f in fireworks:
f.update()
if f.done():
fireworks.remove(f)
if __name__ == '__main__':
pyglet.clock.schedule_interval(update, 1 / 120.0)
pyglet.app.run()
particle.py
import p5
import pyglet
class Particle(pyglet.shapes.Circle):
def __init__(self, x, y, color, firework, batch=None):
radius = 2
if firework:
radius = 4
super().__init__(x, y, radius, radius * 5, color, batch)
self.color = color
self.firework = firework
self.opacity = 255
self._done = False
self.acc = p5.Vector(0, 0)
if self.firework:
self.vel = p5.Vector(0, p5.random_uniform(8, 12))
else:
self.vel = p5.Vector.random_2D() * p5.random_uniform(-10, -2)
#property
def batch(self):
return self._batch
def update(self):
self.acc += p5.Vector(0, -0.2)
if not self.firework:
self.vel *= 0.9
if self.opacity > 4:
self.opacity -= 4
else:
self._done = True
self.opacity = 0
self.vel += self.acc
pos = p5.Vector(self.x, self.y) + self.vel
self.x = pos.x
self.y = pos.y
self.acc *= 0
def done(self):
return self._done
firework.py
import p5
import random
from particle import Particle
class Firework:
def __init__(self, batch):
self.color = tuple(random.randint(0, 255) for _ in range(3))
self.firework = Particle(p5.random_uniform(400), 0, self.color, True, batch=batch)
self.bach = batch
self._exploded = False
self.particles = []
def done(self):
if self._exploded and len(self.particles) == 0:
return True
else:
return False
def explode(self):
for _ in range(70):
p = Particle(self.firework.x, self.firework.y, self.color, False, batch=self.firework.batch)
self.particles.append(p)
def update(self):
if not self._exploded:
self.firework.update()
if self.firework.vel.y < 0:
self._exploded = True
self.explode()
self.firework.delete()
for p in self.particles:
p.update()
if p.done():
self.particles.remove(p)
p.delete()

Is there any way I can make my code easier to understand and simpler? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 3 years ago.
Improve this question
I am wondering if by any chance there is a way to optimise this portion of my code. Regarding the pathing system and the redraw section. I find it really difficult to follow through it. Even changing the whole thing into a more understanding format will suit me. Thank you very much.
class wolf(object): #creating the wolf class
wolf_right = ['pics/WR.png'] + ['pics/WR' + str(i) + '.png' for i in range(2, 18)] #identifies the right wolf pics
wolf_left = ['pics/WL.png'] + ['pics/WL' + str(i) + '.png' for i in range(2, 18)] #identifies the left wolf pics
run_right = [pg.transform.smoothscale(pg.image.load(img), (280,160)) for img in wolf_right] #loads the right wolf pics
run_left = [pg.transform.smoothscale(pg.image.load(img), (280,160)) for img in wolf_left] #load the left wolf pics
def __init__(self, x, y, width, height, finish): #initialising the object wolf
self.x = x #x coord of wolf
self.y = y #y coord of wolf
self.width = width #width of wolf
self.height = height #height of wolf
self.path = [x, finish] # This part here determines the movement limits of the wolf, back and forth
self.run_distance = 0 #wolf is initially did not move
self.velocity = 9 #speed of wolf moving
self.collision_box = (self.x + 60, self.y, 280, 160)#the arguments inside are the coordinates designating the sides of the box
#and then the width and the height of the box
def pathing(self): #pathing system
if self.velocity > 0: # If wolf moving to the right
if self.x < self.path[1] + self.velocity: #ensures that it keeps moving if wolf is not at finish
self.x += self.velocity #allows wolf to move
else: #if the finish is reached then go backwards
self.velocity = self.velocity * -1 #where velocity goes negative
#according to displacement, a particle with -ve velocity goes backwards
self.x += self.velocity #allows wolf to move
else: # If wold is going to the left
if self.x > self.path[0] - self.velocity: #ensures that it keeps moving if the wolf is not at finish
self.x += self.velocity #allows wolf to move
else: #if the finish is reached then go backwards
self.velocity = self.velocity * -1 #where velocity goes negative
#according to displacement, a particle with -ve velocity goes backwards
self.x += self.velocity #allows wolf to move
def got_hit(self): #function if the wolf takes damage from the ninja
print("Congrats, you have hit the wolf!")
def redraw(self, win):#just like for the ninja we do the same steps
self.pathing()
if self.run_distance + 1 >= 51: #This time I am running 17 sprites thus, 17 * 3 (where 3 sprites per second)
self.run_distance = 0
if self.velocity < 0: #if velocity is increasing meaning movement, links left images with left movement
win.blit(self.run_left[self.run_distance//3], (self.x,self.y))
self.run_distance += 1
else: #else linking right images with right movement
win.blit(self.run_right[self.run_distance//3], (self.x,self.y))
self.run_distance += 1
#pg.draw.rect(win, (0,200,0), self.collision_box,2) #this will draw a green box around the wolf of lines thickness 2
self.collision_box = (self.x + 50 , self.y, 200, 150) # ensures the box is drawn and is updated alongside motion
The code is not that complicated, but that's my opinion. I read it once through and it was easy to follow. Just one thing, self.x += self.velocity is done at the end of each of the 4 cases in pathing. It is sufficient to do it once at the end of pathing, instead of separately in each case. Something similar can be done for self.run_distance += 1 in redraw:
class wolf(object): #creating the wolf class
# [...]
def pathing(self): #pathing system
if self.velocity > 0 and self.x >= self.path[1] + self.velocity or \
self.velocity < 0 and self.x <= self.path[0] - self.velocity:
self.velocity = self.velocity * -1
self.x += self.velocity
# [...]
def redraw(self, win):#just like for the ninja we do the same steps
self.pathing()
if self.run_distance + 1 >= 51: #This time I am running 17 sprites thus, 17 * 3 (where 3 sprites per second)
self.run_distance = 0
run_surf = self.run_left if self.velocity < 0 else self.run_right
win.blit(run_surf[self.run_distance//3], (self.x,self.y))
self.run_distance += 1
#pg.draw.rect(win, (0,200,0), self.collision_box,2) #this will draw a green box around the wolf of lines thickness 2
self.collision_box = (self.x + 50 , self.y, 200, 150) # ensures the box is drawn and is updated alongside motion
Anyway in pygame it is intended to use pygame.Rect, pygame.sprite.Sprite and pygame.sprite.Group.
Each Sprite should have the attributes .rect and .image and the method update(). The Sprites should be contained in Groups. The Groups can be drawn (draw()) and updated (update()).
That makes the code easy to read, short, comprehensible and extendable. e.g.:
(Class Names should normally use the CapWords convention.)
class Wolf(pygame.sprite.Sprite):
wolf_right = ['pics/WR.png'] + ['pics/WR' + str(i) + '.png' for i in range(2, 18)] #identifies the right wolf pics
wolf_left = ['pics/WL.png'] + ['pics/WL' + str(i) + '.png' for i in range(2, 18)] #identifies the left wolf pics
run_right = [pg.transform.smoothscale(pg.image.load(img), (280,160)) for img in wolf_right] #loads the right wolf pics
run_left = [pg.transform.smoothscale(pg.image.load(img), (280,160)) for img in wolf_left] #load the left wolf pics
def __init__(self, x, y, finish):
super().__init__():
self.image = run_left[0]
self.rect = pygame.Rect(x, y, 280, 160)
self.path = [x, finish] # This part here determines the movement limits of the wolf, back and forth
self.run_distance = 0 #wolf is initially did not move
self.velocity = 9 #speed of wolf moving
self.collision_box = (self.rect.x + 60, self.rect.y, 280, 160) #the arguments inside are the coordinates designating the sides of the box
#and then the width and the height of the box
def update(self, angle):
if self.velocity > 0 and self.rect.x >= self.path[1] + self.velocity or \
self.velocity < 0 and self.rect.x <= self.path[0] - self.velocity:
self.velocity = self.velocity * -1
self.rect.x += self.velocity
if self.run_distance + 1 >= 51: #This time I am running 17 sprites thus, 17 * 3 (where 3 sprites per second)
self.run_distance = 0
run_surf = self.run_left if self.velocity < 0 else self.run_right
if self.run_distance//3 > len(run_surf):
self.run_distance = 0
self.image = run_surf[self.run_distance//3]
self.run_distance += 1
self.collision_box = (self.rect.x + 50 , self.rect.y, 200, 150) # ensures the box is drawn and is updated alongside motion
wolf = Wolf(........)
all_sprites = pygame.sprite.Group()
all_sprites.add(wolf)
while True:
# [...]
all_sprites.update(win)
# [...]
all_sprites.draw(win)
pygame.display.flip()

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?

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