Use mask function (for collision detection) with multiple sprites - pygame

I am brand new to Pygame and am making a game for my A-level course. I am trying to have multiple bats that I can spawn and collide with. I am using Pygames mask function for 'pixel perfect collision' but I cannot get multiple bats to spawn at the same time with the collision system also working. I tried using groups but I haven't been able to get this to work. Does anyone know how to fix my code/ a better way around this problem? Thanks! The relevant code is below...
class Bat(pygame.sprite.Sprite):
def __init__(self, bat_x, bat_y):
pygame.sprite.Sprite.__init__(self)
self.bat1 = pygame.image.load("Sprites\Bat_enemy\Bat-1.png").convert_alpha() # For hit registration for bat
self.bat1 = pygame.transform.scale(self.bat1, (80, 70))
self.bat_mask = pygame.mask.from_surface(self.bat1)
self.bat_rect = self.bat1.get_rect()
self.bat_x = bat_x
self.bat_y = bat_y
bats = pygame.sprite.Group()
Then in main loop:
num_of_bats = [1]
#Bat#
for i in num_of_bats:
bat_x = (random.randint(0, 600))
bat_y = (random.randint(0, 600))
bat = Bat(bat_x, bat_y, i)
bats.add(bat)
for bat in bats:
offsetP2B = (int(x - batx), int(y - self.baty)) #Player to Bat
resultP2B = bat_mask.overlap(player_mask, offsetP2B)

First get rectangle based collision working, then worry about the bitmask accuracy!
There's a couple of problems with your Sprite. The big one is that PyGame uses sprite.image to draw the bitmap. Your sprite code is using bat1 instead. It also needs to position the sprite.rect to the co-ordinate of the Bat. Furthermore, the collision mask must be called mask, and the sprite's collision/position pygame.Rect must be called rect.
I'm not sure if it's just a paste-o, but the sprite group definition shouldn't be inside the Sprite class.
So ... with a few minor fixups:
class Bat(pygame.sprite.Sprite):
def __init__(self, bat_x, bat_y, bat_image):
pygame.sprite.Sprite.__init__(self)
self.image = bat_image
self.rect = self.image.get_rect()
self.mask = pygame.mask.from_surface( self.image )
self.rect.centre = ( bat_x, bat_y )
def update( self ):
# TODO: code to make this bat move/flat whatever
pass
There's only minor differences here. It's better to load the image in once, outside the sprite class, than loading it in hundreds(?) of times - once for each bat.
Now it's pretty easy to make a colony of bats:
import os.path
START_BAT_COUNT = 30
BAT_IMAGE_PATH = os.path.join( 'Sprites', 'Bat_enemy', 'Bat-1.png' )
# group to hold all the bat sprites
all_bats = pygame.sprite.Group()
# Going Batty!
bat_image = pygame.image.load( BAT_IMAGE_PATH ).convert_alpha()
for i in range( START_BAT_COUNT ):
bat_x = (random.randint(0, 600))
bat_y = (random.randint(0, 600))
new_bat = Bat( bat_x, bat_y, bat_image )
all_bats.add( new_bat )
The in your main loop:
# move every bat
all_bats.update()
...
# paint every bat
all_bats.draw( screen )

Related

Move a sprite, but cannot delete the precedent image of it [duplicate]

I'm building a pong game trying to get better at programming but Im having trouble moving the ball. When the move_right method is called the ellipse stretches to the right instead of moving to the right. I've tried putting the ball variable in the init method but that just makes it not move at all even though the variables should be changing on account of the move_right method. I have also tried setting the x and y positions as parameters in the Ball class,but that just stretches it also.
I don't understand why when I run the following code the ball I'm trying to move stretches to the right instead of moves to the right. Can someone explain why this is happening? I have tried everything I can think of but i can't get it to do what I want.
import pygame,sys
import random
class Ball:
def __init__(self):
self.size = 30
self.color = light_grey
self.x_pos = width/2 -15
self.y_pos = height/2 -15
self.speed = 1
#self.ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
def draw_ball(self):
ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
pygame.draw.ellipse(screen,self.color,ball)
def move_right(self):
self.x_pos += self.speed
class Player:
def __init__(self,x_pos,y_pos,width,height):
self.x_pos = x_pos
self.y_pos = y_pos
self.width = width
self.height = height
self.color = light_grey
def draw_player(self):
player = pygame.Rect(self.x_pos,self.y_pos,self.width,self.height)
pygame.draw.rect(screen,self.color,player)
class Main:
def __init__(self):
self.ball=Ball()
self.player=Player(width-20,height/2 -70,10,140)
self.opponent= Player(10,height/2-70,10,140)
def draw_elements(self):
self.ball.draw_ball()
self.player.draw_player()
self.opponent.draw_player()
def move_ball(self):
self.ball.move_right()
pygame.init()
size = 30
clock = pygame.time.Clock()
pygame.display.set_caption("Pong")
width = 1000
height = 600
screen = pygame.display.set_mode((width,height))
bg_color = pygame.Color('grey12')
light_grey = (200,200,200)
main = Main()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#player = pygame.Rect(width-20,height/2 -70,10,140)
#opponent = pygame.Rect(10,height/2-70,10,140)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#pygame.draw.rect(screen,light_grey,player)
#pygame.draw.rect(screen,light_grey,opponent)
#pygame.draw.ellipse(screen,light_grey,ball)
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
clock.tick(60)
You have to clear the display in every frame with pygame.Surface.fill:
while True:
# [...]
screen.fill(0) # <---
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
# [...]
Everything that is drawn is drawn on the target surface. The entire scene is redraw in each frame. Therefore the display needs to be cleared at the begin of every frame in the application loop. The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()

How to set the position of an image in pygame?

I created an object called "alien" in my game. Uploaded the image for "alien" and used "get_rect()" to set its rect attributes.
Now I want to change the x-coordinate value of "alien". Which of the following two ways is correct?
alien.x = ...... or alien.rect.x = ......
I saw in a textbook that the following codes are used:
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
Why didn't the author directly use alien.rect.x to change the x-coordinate value of "alien"? Like:
alien.rect.x = alien_width + 2 * alien_width * alien_number
Why does there have to be alien.x?
Unfortunately the answer is "it depends". Some people's code maintains the position of the object at an internal x and y, using a rect to handle collisions. Other code just maintains the rect, using the rect.x and rect.y if a single position is (ever) needed.
It's up to you really, but my advice is to keep it all inside a PyGame Rect, as this has benefits of easy collision detection, should you wish to use that down the track.
class Alien:
def __init__( self, x, y, image ):
self.image = image
self.rect = image.get_rect() # copy the image dimensions
self.rect.x = x
self.rect.y = y # move to location
def draw( self, window ):
window.blit( self.image, self.rect ) # paint it
When it's time to move the Alien, you can just as easy adjust the rectangle as an x and y
class Alien:
...
def moveBy( self, by_x, by_y ):
self.rect.move_ip( by_x, by_y )
def moveTo( self, x, y ):
self.rect.x = x
self.rect.y = y
Perhaps the author thought that having a separate x and y made the code easier to understand. This is a paramount reason that effects programming style. Program code is read many times more often than it is written, so often extra variables are included to illustrate the program flow better.
For example, like checking a mouse-click-event:
for event in pygame.event.get( ):
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONUP:
handleGameClick( mouse2Board( event.pos ) ) # <-- HERE
Adding extra variables documents what's going on:
elif event.type == pygame.MOUSEBUTTONUP:
mouse_click_coord = event.pos
game_board_coord = mouse2Board( mouse_click_coord )
handleGameClick( game_board_coord )
Here it tells the reader that event.pos is a co-ordinate (so probably a pair of values), and is from the mouse. Then it reenforces that the co-ordinate is then converted into a game-board space before being passed out to handleGameClick() for processing.
The two pieces of code have exactly the same outcome (and probably execution speed), but the second is much easier to follow.
IMHO one should ideally write code such that someone unfamiliar with the language (but still a programmer) can understand it without too much trouble. This is why in my answers you wont see much "pythonic" loop-in-list creation like:
[[col +1 for col in row] for row in a] # Taken from 10 vote answer
Because unless your very familiar with python syntax, it's unreadable.

Questions with creating sprites in pygame

So basically what I know how to do is add a player sprite (make a player class that inherits from pygame.sprite, etc...) this works for me.
What I would like to know how to do is iterate the creation of sprites and add them to the sprite group.
This is because I have a 2 dimensional array and I have a function that reads this and places the "tiles" accordingly in the 2d space, this is to create levels easier.
So what I want this function to do is create these sprites (I guess with a for loop that reads the array ?) and add them to do the group but this doesn't work so I have some questions first:
1)Can you create sprites outside of the init function in a class?
2)What really are sprites, is it a surface coupled to a rect ?
3)And finally do you have an idea of simply how to get this done: If I give you a 2d array, how would you make the function that reads this array and calculates the position (this is okay, I think I have it figured out) and most importantly, make sprites out of the given positions that can then be added to the sprites group.
Thanks in advance
Can you create sprites outside of the init function in a class?
Sure.
What really are sprites, is it a surface coupled to a rect ?
If we talk about pygame's Sprite class: yes.
Such a sprite is basically a Surface (the image attribute) and a Rect (the rect
attribute). They work best together with pygame's Group classes.
And finally do you have an idea of simply how to get this done ....
Just create a nested loop to iterate over the array.
Here's a simple example:
import pygame
pygame.init()
TILESIZE = 64
class Actor(pygame.sprite.Sprite):
def __init__(self, color, pos):
super().__init__()
self.image = pygame.Surface((TILESIZE, TILESIZE))
self.image.fill(pygame.Color(color))
self.rect = self.image.get_rect(topleft = pos)
def main():
data = [
' YUB ',
' G ',
' B ',
' ',
' Y ',
' U ',
]
screen = pygame.display.set_mode((len(data[0]) * TILESIZE, len(data) * TILESIZE))
sprites = pygame.sprite.Group()
colors = {
'G': 'green',
'B': 'black',
'Y': 'yellow',
'U': 'dodgerblue'
}
x, y = 0, 0
for line in data:
for char in line:
if char in colors:
sprites.add(Actor(colors[char], (x * TILESIZE, y * TILESIZE)))
x += 1
x = 0
y += 1
while True:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
pygame.quit()
return
sprites.update()
screen.fill((100, 100, 100))
sprites.draw(screen)
pygame.display.flip()
main()
You can find another example here.

Previous sprites staying when changing y_positions to move

I'm making a game where the player controls a sprite that catches falling food.
However, I'm having trouble with the falling food part.
import pygame
import random
import time
d_w = 800
d_h = 600
fall_speed = 5
screen = pygame.display.set_mode((d_w, d_h))
clock = pygame.time.Clock()
BGCOLOR = (0, 155, 0)
goodFood = pygame.image.load('burg.png')
def randFoodGen():
x_food = d_w/2
y_food = 0
return x_food,y_food
def BurgGen():
x_food, y_food = randFoodGen()
screen.fill(BGCOLOR)
while True:
y_food = y_food + fall_speed
if y_food > d_w:
x_food = (random.randrange(0, d_w))
y_food = -30
screen.blit(goodFood, [x_food, y_food])
pygame.display.update()
clock.tick(30)
BurgGen()
It ends up keeping the previous position of the falling food sprite, like this:
Anger
Your screen.fill(BGCOLOR) needs to be within the while loop.
def BurgGen():
x_food, y_food = randFoodGen()
while True:
screen.fill(BGCOLOR)
y_food = y_food + fall_speed
if y_food > d_w:
x_food = (random.randrange(0, d_w))
y_food = -30
screen.blit(goodFood, [x_food, y_food])
pygame.display.update()
clock.tick(30)
That will at least solve the persisting burger problem. The reason is that before you draw a new burger, you have to wipe the screen of the burger you drew previously.
Also, if you are going to have multiple falling objects, you might want to make a pygame.Rect to represent each falling object. You might also want to make a python list of those pygame.Rect objects.
falling_objects = [] # create empty list
Whenever you need to create a new falling object, create a pygame.Rect and .append it to the list.
new_object = pygame.Rect(x, y, width, height)
falling_objects.append(new_object)
Then, to update the objects positions, you loop through the list and update the Rects
for obj_rect in falling_objects:
obj_rect.move_ip(0, fall_speed)
Use a similar for-loop to blit the image at each location
for obj_rect in falling_objects:
screen.blit(food_image, obj_rect.top_left)
A bit in-depth but I hope that helps a bit.

pygame and sprite blur movement

I am struggling to move the sprite correctly. Instead of smooth move I can see blur move and I do not know how to solve it.
Is there any chance you can point what I do incorrectly ?
My target with it to drop the pizza so it hits the bottom and bounce back and bounc back if it hits the top and again the bottom -> bounce -> top -> bounce etc. etc.
import pygame
gravity = 0.5
class PizzaSprite:
def __init__(self, image, spritepos):
self.image = image
self.spos = spritepos
(x, y) = spritepos
self.posx = x
self.posy = y
self.xvel = 1
self.yvel = 1
print "x ", x
print "y ", y
def draw(self, target_surface):
target_surface.blit(self.image, self.spos)
def update(self):
self.posy -= self.yvel
self.spos = (self.posx, self.posy)
return
def main():
pygame.init()
screen_width = 800
screen_height = 600
x = screen_width
y = screen_height
screen = pygame.display.set_mode((screen_width, screen_height))
wall_image = pygame.image.load("wall.png")
sky_image = pygame.image.load("sky.png")
pizza_image = pygame.image.load("pizza.png")
screen.blit(wall_image,(0,200))
screen.blit(sky_image,(0,0))
all_sprites = []
pizza1 = PizzaSprite(pizza_image, (x/2, y/2))
all_sprites.append(pizza1)
while True:
ev = pygame.event.poll()
if ev.type == pygame.QUIT:
break
for sprite in all_sprites:
sprite.update()
for sprite in all_sprites:
sprite.draw(screen)
pygame.display.flip()
pygame.quit()
main()
in the beginning of your main game while loop add
white = (255,255,255)
screen.fill(white)
let me give you a small analogy of what is happening now,
you have paper and a lot of pizza stickers with the intent to make a flip book. To make the illusion of movement on each piece of paper you place a sticker a little bit lower. The screen.fill command essentially clears the screen with the rgb color value you give it. When you dont fill the screen essentially what you are doing is trying to make that flipbook on one piece of paper. You just keep placing more and more stickers a little bit lower making a blur when what you want is one on each page.
and place
pygame.init()
screen_width = 800
screen_height = 600
x = screen_width
y = screen_height
screen = pygame.display.set_mode((screen_width, screen_height))
wall_image = pygame.image.load("wall.png")
sky_image = pygame.image.load("sky.png")
all outside of your main game loop assuming you wont be making changes to these variables ever in your program it is tedious and inefficient to redefine screen,,x,y,and your two images over and over again if they dont change.
so to sum it all up:
use the screen.fill(white) command to reset the color of your screen
You only need to import pngs and define variables once if they are never going to change and don't need them in your main loop
hope this helps clear things up.