I am creating a game in which two players fire their tanks at one another to try and kill the other player. My current system uses a bullet to fly towards the target, and once it hits the ground, an explosion sprite is created, that expands over time. I currently have an issue where the expanding explosion sprite will collide with the tank before it actually reaches the tank. This is an issue as I wish to deduct damage based on the distance the explosion sprite is away from the tank.
This is my current code:
def explode(self, x, y, alive):
explosionList.add(self)
explosionList.draw(gameDisplay)
self.rect.center = x, y
damage = 100
collideTank = pygame.sprite.spritecollide(self, tankList, False)
tank = False
for i in range(20):
damage -= 5
time.sleep(0.01)
if len(collideTank) > 0 and tank == False:
tank = True
collideTank[0].damageTank(alive, damage)
self.image = explosionImage
self.image = pygame.transform.scale(self.image, ((i + 1) * 3, (i + 1) * 3))
self.rect = self.image.get_rect(center = self.rect.center)
explosionList.clear(gameDisplay, background)
explosionList.draw(gameDisplay)
pygame.display.update()
explosionList.clear(gameDisplay, background)
pygame.sprite.spritecollide
uses rect of both sprites to determine the collision. It's very likely that the two rectangles overlap, even if the graphics say otherwise. You might consider smaller rect representations of your sprites or maybe using
pygame.sprite.collide_circle(left, right)
could help.
Related
So I'm making a platformer in pygame and I was quite far along in the code when I changed how the levels where gonna be set up so I now needed to have the player start at the top of the screen instead of the bottom so it was simple I just needed to change the attributes witch I call into my player class so tat the position can change as that's how it was set in the first place but the player just kept starting in the same place
player = player(100,screen_height+100)
def tryagain(self,x,y):
player1 = pygame.image.load('guy.png')
self.image = pygame.transform.scale(player1, (40,75))
self.rect = self.image.get_rect()
self.rect.center = (x,y)
2
class player(): def __init__(self, x, y): self.tryagain(x,y)
screen.blit(self.image, self.rect)
i even coded all the code from the start but now the Collisions with platforms refuse to work even thoi never chaged that code
The top left of the pygame coordinate system is (0, 0):
player = player(100,screen_height+100)
player = player(100, 100)
I am designing a game however I have postponed my development to focus on the collisions of sprites in the shape of circles.
I am not 100% sure how the 'pygame.sprite.collide_circle' command works with its parenthesis however I have included it within my Collidecheck variable as well as created a new variable called 'collided' to then include within Collidecheck as 'collided = None'.
My Ball class uses the ball objects which are all circles. There are two which should interact being ball and llab.
class Ball(pygame.sprite.Sprite):
def __init__(self, width, height, herex, herey):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
self.rect = self.image.get_rect()
pygame.draw.circle(self.image, BLACK, [int(width/2),int(height/2)], int(width/2), 10)
self.rect.x = herex
self.rect.y = herey
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
def moveUp(self, pixels):
self.rect.y -= pixels
def moveDown(self, pixels):
self.rect.y += pixels
The 2 circles are in sprite groups as I am practising using them
ball = Ball(100, 100, 50, 50)
llab = Ball(100, 100, 350, 250)
g = pygame.sprite.Group(ball)
h = pygame.sprite.Group(llab)
Within the main game loop, ball moves so I can test the hitboxes. If you watch the console as it is printing the collision check, you can see it working.
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
ball.moveLeft(5)
ballFollow.moveLeft(5)
if keys[pygame.K_RIGHT]:
ball.moveRight(5)
ballFollow.moveRight(5)
if keys[pygame.K_UP]:
ball.moveUp(5)
ballFollow.moveUp(5)
if keys[pygame.K_DOWN]:
ball.moveDown(5)
ballFollow.moveDown(5)
Collidecheck = pygame.sprite.spritecollide(ball, h, False, pygame.sprite.collide_circle)
print(Collidecheck)
screen.fill(RED)
g.draw(screen)
h.draw(screen)
I originally expected to see within the console the collision be between ball and llab but ball and llab have a really large hitbox which I don't want. I want to only see the hitboxes of both these circles extend to their actual shape and no larger. It seems their hitboxes are double their sizes I think although I am not sure
P.S I understand I could use maths and might have to, I was wondering if anyone had better knowledge of the sprite functions to help me.
See documentation for pygame.sprite.collide_circle
If the sprites have a "radius" attribute, that is used to create the circle, otherwise a circle is created that is big enough to completely enclose the sprites rect as given by the "rect" attribute.
So you have to create self.radius in Ball and it can be smaller then ball's size.
How it works ? It gets centers of both sprites and use Pythagorean theorem (a**2 + b**2 = c**2) to calculate distance between these points. If it is smaller then sprite1.radius1 + sprite2.radius2 then you have collision.
BTW: PyGame has pygame.math.Vector2.distance_to() to calculate it.
I have this classic example of a pygame program:
import pygame
import random
# Define some colors
BLACK = ( 0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
class Block(pygame.sprite.Sprite):
"""
This class represents the ball.
It derives from the "Sprite" class in Pygame.
"""
def __init__(self, color, width, height):
""" Constructor. Pass in the color of the block,
and its x and y position. """
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
self.image = pygame.Surface([width, height])
self.image.fill(color)
# Fetch the rectangle object that has the dimensions of the image
# image.
# Update the position of this object by setting the values
# of rect.x and rect.y
self.rect = self.image.get_rect()
# Initialize Pygame
pygame.init()
# Set the height and width of the screen
screen_width = 700
screen_height = 400
screen = pygame.display.set_mode([screen_width, screen_height])
# This is a list of 'sprites.' Each block in the program is
# added to this list. The list is managed by a class called 'Group.'
block_list = pygame.sprite.Group()
# This is a list of every sprite.
# All blocks and the player block as well.
all_sprites_list = pygame.sprite.Group()
for i in range(100):
# This represents a block
block = Block(BLACK, 20, 15)
# Set a random location for the block
block.rect.x = random.randrange(screen_width)
block.rect.y = random.randrange(screen_height)
# Add the block to the list of objects
block_list.add(block)
all_sprites_list.add(block)
# Create a RED player block
player = Block(RED, 20, 15)
all_sprites_list.add(player)
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
score = 0
# -------- Main Program Loop -----------
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
# Clear the screen
screen.fill(WHITE)
# Get the current mouse position. This returns the position
# as a list of two numbers.
pos = pygame.mouse.get_pos()
# Fetch the x and y out of the list,
# just like we'd fetch letters out of a string.
# Set the player object to the mouse location
player.rect.x = pos[0]
player.rect.y = pos[1]
# See if the player block has collided with anything.
blocks_hit_list = pygame.sprite.spritecollide(player, block_list, True)
# Check the list of collisions.
for block in blocks_hit_list:
score += 1
print(score)
# Draw all the spites
for block in all_sprites_list:
if block.rect.y + 1 > screen_height:
block.rect.y = 0
block.rect.y = block.rect.y + 1
all_sprites_list.draw(screen)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# Limit to 60 frames per second
clock.tick(120)
pygame.quit()
Well, I notice that when I move the mouse, the rest of the blocks are moving slower(like freezing for a fraction of the second) but when it is in one place everything is moving smooth. The player sprite is in the list of all sprites and there is no calculation of the position of the player - only getting mouse's coordinates. And in my opinion there shouldn't be difference between getting (100,100) all the time and getting something else for coordinates of the mouse.
Could someone please explain why motion of the mouse (player's sprite) affects the rest of the sprites?
Thanks!
The problem is probably that you've been running Python for too long a while without restarting, and a fair bit of data has accumulated in memory without being deallocated during previous runs. As noted in the comments, rebooting will usually solve it.
I am currently making a Guitar Hero like game in python and I am trying to blit a note multiple times, but I can't seem to get it to work (the note is called Red)!
#Sprite Class
class Sprite(pygame.sprite.Sprite):
# What has to be passed when you create a sprite.
# image_file: The filename of the image.
# lacation: The initial location of where to draw the image.
def __init__(self, image_file, location):
pygame.sprite.Sprite.__init__(self) # Call Sprite initializer
self.image = pygame.image.load(image_file)
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = location
self.xSpeed = 15 # The default xSpeed
self.ySpeed = 15 # The default ySpeed
self.name = "Not Assigned"
self.speed = 10
self.direction = 0
def setSpeed(self, speed):
self.speed = speed
self.calcXYSpeeds()
def setDirection(self, direction):
self.direction = direction
self.calcXYSpeeds()
def calcXYSpeeds(self):
angleRadians = math.radians(self.direction) # Convert the direction to radians.
self.xSpeed= self.speed*math.cos(angleRadians)
self.ySpeed = self.speed*math.sin(angleRadians)
def move(self):
self.rect = self.rect.move(self.xSpeed,self.ySpeed)
def setDirectionTowards(self, (x,y)):
self.direction = math.degrees(math.atan2((self.rect.x - x), (self.rect.y - y)))
self.direction = 270 - self.direction
self.calcXYSpeeds()
#Object Variables
Red = Sprite("Red.png", (100,25))
note1 = Sprite("note1.jpg", (50,650))
#add sprite to group
notes = pygame.sprite.Group()
notes.add(Red)
# Create an clock to keep track time
clock = pygame.time.Clock()
#Scoring Variables
font=pygame.font.Font(None,50)
score=0
score_text=font.render(str(score),1,(225,225,225))
#other Variables
Red1=0
ySpeed = 10
running = 1
while (running==1):
# Sets the frame rate to 30 frames/second.
clock.tick(30)
# Gets any events like keyboard or mouse.
event = pygame.event.poll()
key=pygame.key.get_pressed()
#object random spawn
time = pygame.time.get_ticks()
if (time==30*(random.randint(0,1000))):
screen.blit(Red.image, Red.rect)
pygame.display.update()
#Object Movement
Red.rect.y = Red.rect.y + ySpeed
#Keys to complete notes
if key[pygame.K_a]and Red1==0 and pygame.sprite.collide_rect(Red, note1) == True:
font=pygame.font.Font(None,50)
score = score+1
score_text=font.render(str(score),1,(225,225,225))
Red1=1
#where i currently working to fix the problem
notes.Group.clear(screen, background)
notes.Group.update()
notes.draw(screen)
# Sets the exit flag if the X was clicked on the run window.
if event.type == pygame.QUIT:
running = 0
# Color the whole screen with a solid color.
screen.fill((0, 255, 255))
#prints objects
screen.blit(note1.image, note1.rect)
screen.blit(Red.image, Red.rect)
screen.blit(score_text,(500,50))
# Update the window.
pygame.display.update()
You are cleaning and updating the screen multiple times in the same cycle.
Clean the screen in every cycle is a correct approach, but you need to clean only once otherwise you are erasing everything you blitted before.
Also, for notes to persist between cycles you need to keep them in a collection, like notes, that way you blit every note in every cycle.
Keep in mind this order in your main loop:
Control FPS (clock.tick())
Check events
Update state (ie: add/remove notes collection, etc)
Clear screen
Draw current state (notes, static elements, etc)
Update display
Another important thing, don't use the same Sprite() nor the same Rectangle in different locations. If you want to draw another copy of the same image in a new location make a copy of the rectangle, then create a new Sprite() object.
I'm learning Object Orientated Python and understand the main principals of classes and creating objects from classes however I need something explained Re: the pygame code below. I'm struggling to get my head around what's happening when sprite lists are being created and the two lines of code under the code which creates the ball object (allsprites.add etc). In other words what are sprites and why are lists of them created? Why isn't the ball object just created from the class on its own? Why does it need to be added to a sprite list?? What's going on? Any explanation would be greatly appreciated.
"""
Sample Breakout Game
Sample Python/Pygame Programs
Simpson College Computer Science
http://programarcadegames.com/
http://simpson.edu/computer-science/
"""
# --- Import libraries used for this program
import math
import pygame
# Define some colors
black = (0, 0, 0)
white = (255, 255, 255)
blue = (0, 0, 255)
# Size of break-out blocks
block_width = 23
block_height = 15
class Block(pygame.sprite.Sprite):
"""This class represents each block that will get knocked out by the ball
It derives from the "Sprite" class in Pygame """
def __init__(self, color, x, y):
""" Constructor. Pass in the color of the block,
and its x and y position. """
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
# Create the image of the block of appropriate size
# The width and height are sent as a list for the first parameter.
self.image = pygame.Surface([block_width, block_height])
# Fill the image with the appropriate color
self.image.fill(color)
# Fetch the rectangle object that has the dimensions of the image
self.rect = self.image.get_rect()
# Move the top left of the rectangle to x,y.
# This is where our block will appear..
self.rect.x = x
self.rect.y = y
class Ball(pygame.sprite.Sprite):
""" This class represents the ball
It derives from the "Sprite" class in Pygame """
# Speed in pixels per cycle
speed = 10.0
# Floating point representation of where the ball is
x = 0.0
y = 180.0
# Direction of ball (in degrees)
direction = 200
width = 10
height = 10
# Constructor. Pass in the color of the block, and its x and y position
def __init__(self):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
# Create the image of the ball
self.image = pygame.Surface([self.width, self.height])
# Color the ball
self.image.fill(white)
# Get a rectangle object that shows where our image is
self.rect = self.image.get_rect()
# Get attributes for the height/width of the screen
self.screenheight = pygame.display.get_surface().get_height()
self.screenwidth = pygame.display.get_surface().get_width()
def bounce(self, diff):
""" This function will bounce the ball
off a horizontal surface (not a vertical one) """
self.direction = (180 - self.direction) % 360
self.direction -= diff
def update(self):
""" Update the position of the ball. """
# Sine and Cosine work in degrees, so we have to convert them
direction_radians = math.radians(self.direction)
# Change the position (x and y) according to the speed and direction
self.x += self.speed * math.sin(direction_radians)
self.y -= self.speed * math.cos(direction_radians)
# Move the image to where our x and y are
self.rect.x = self.x
self.rect.y = self.y
# Do we bounce off the top of the screen?
if self.y <= 0:
self.bounce(0)
self.y = 1
# Do we bounce off the left of the screen?
if self.x <= 0:
self.direction = (360 - self.direction) % 360
self.x = 1
# Do we bounce of the right side of the screen?
if self.x > self.screenwidth - self.width:
self.direction = (360 - self.direction) % 360
self.x = self.screenwidth - self.width - 1
# Did we fall off the bottom edge of the screen?
if self.y > 600:
return True
else:
return False
class Player(pygame.sprite.Sprite):
""" This class represents the bar at the bottom that the player controls. """
def __init__(self):
""" Constructor for Player. """
# Call the parent's constructor
pygame.sprite.Sprite.__init__(self)
self.width = 75
self.height = 15
self.image = pygame.Surface([self.width, self.height])
self.image.fill((white))
# Make our top-left corner the passed-in location.
self.rect = self.image.get_rect()
self.screenheight = pygame.display.get_surface().get_height()
self.screenwidth = pygame.display.get_surface().get_width()
self.rect.x = 0
self.rect.y = self.screenheight-self.height
def update(self):
""" Update the player position. """
# Get where the mouse is
pos = pygame.mouse.get_pos()
# Set the left side of the player bar to the mouse position
self.rect.x = pos[0]
# Make sure we don't push the player paddle
# off the right side of the screen
if self.rect.x > self.screenwidth - self.width:
self.rect.x = self.screenwidth - self.width
# Call this function so the Pygame library can initialize itself
pygame.init()
# Create an 800x600 sized screen
screen = pygame.display.set_mode([800, 600])
# Set the title of the window
pygame.display.set_caption('Breakout')
# Enable this to make the mouse disappear when over our window
pygame.mouse.set_visible(0)
# This is a font we use to draw text on the screen (size 36)
font = pygame.font.Font(None, 36)
# Create a surface we can draw on
background = pygame.Surface(screen.get_size())
# Create sprite lists
blocks = pygame.sprite.Group()
balls = pygame.sprite.Group()
allsprites = pygame.sprite.Group()
# Create the player paddle object
player = Player()
allsprites.add(player)
# Create the ball
ball = Ball()
allsprites.add(ball)
balls.add(ball)
# The top of the block (y position)
top = 80
# Number of blocks to create
blockcount = 32
# --- Create blocks
# Five rows of blocks
for row in range(5):
# 32 columns of blocks
for column in range(0, blockcount):
# Create a block (color,x,y)
block = Block(blue, column * (block_width + 2) + 1, top)
blocks.add(block)
allsprites.add(block)
# Move the top of the next row down
top += block_height + 2
# Clock to limit speed
clock = pygame.time.Clock()
# Is the game over?
game_over = False
# Exit the program?
exit_program = False
# Main program loop
while exit_program != True:
# Limit to 30 fps
clock.tick(30)
# Clear the screen
screen.fill(black)
# Process the events in the game
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit_program = True
# Update the ball and player position as long
# as the game is not over.
if not game_over:
# Update the player and ball positions
player.update()
game_over = ball.update()
# If we are done, print game over
if game_over:
text = font.render("Game Over", True, white)
textpos = text.get_rect(centerx=background.get_width()/2)
textpos.top = 300
screen.blit(text, textpos)
# See if the ball hits the player paddle
if pygame.sprite.spritecollide(player, balls, False):
# The 'diff' lets you try to bounce the ball left or right
# depending where on the paddle you hit it
diff = (player.rect.x + player.width/2) - (ball.rect.x+ball.width/2)
# Set the ball's y position in case
# we hit the ball on the edge of the paddle
ball.rect.y = screen.get_height() - player.rect.height - ball.rect.height - 1
ball.bounce(diff)
# Check for collisions between the ball and the blocks
deadblocks = pygame.sprite.spritecollide(ball, blocks, True)
# If we actually hit a block, bounce the ball
if len(deadblocks) > 0:
ball.bounce(0)
# Game ends if all the blocks are gone
if len(blocks) == 0:
game_over = True
# Draw Everything
allsprites.draw(screen)
# Flip the screen and show what we've drawn
pygame.display.flip()
pygame.quit()
You don't need to add the balls and blocks to sprite lists - it's just a matter of convenience. You could manually check each ball for a collision, but it's easier to just tell pygame to check them all for you
# See if the ball hits the player paddle
if pygame.sprite.spritecollide(player, balls, False):
# The 'diff' lets you try to bounce the ball left or right
# depending where on the paddle you hit it
diff = (player.rect.x + player.width/2) - (ball.rect.x+ball.width/2)
# Set the ball's y position in case
# we hit the ball on the edge of the paddle
ball.rect.y = screen.get_height() - player.rect.height - ball.rect.height - 1
ball.bounce(diff)
You could draw each thing to the screen separately on each frame, but it's easier just to tell pygame to do it for you:
# Draw Everything
allsprites.draw(screen)
Things can be in more than one list as required, for example a ball is added to the balls list so that you can easily check for collisions, but also added to the allsprites list so that you can easily draw everything on the screen
# Create the ball
ball = Ball()
allsprites.add(ball)
balls.add(ball)
Edit:
An important distinction is that allsprites is actually a sprite.Group. It has a list of sprites inside it, but it also has other methods like draw.
To address your question of "what is a Sprite", it's simply a thing that gets drawn on screen. pygame methods like sprite.Group.draw expect a list of things with certain attributes - such as update. The easiest way to make sure that you provide all of those attributes with the right names is to subclass Sprite, however this is also a (strongly recommended) convenience thing - for instance, this is from the pygame source code:
While it is possible to design sprite and group classes that don't
derive from the Sprite and AbstractGroup classes below, it is strongly
recommended that you extend those when you add a Sprite or Group
class.
So what specifically does subclassing Sprite get you? Let's take a look at the source. Here's how to find the source code for a python module:
>>> import pygame.sprite
>>> pygame.sprite.__file__
'c:\\Python27\\lib\\site-packages\\pygame\\sprite.py'
>>>
Every python module has a __file__ attribute that tells you where the source is located (well not quite every). If you open it up in your editor, and scroll down, you see the class definition for Sprite:
class Sprite(object):
"""simple base class for visible game objects
pygame.sprite.Sprite(*groups): return Sprite
The base class for visible game objects. Derived classes will want to
override the Sprite.update() and assign a Sprite.image and
Sprite.rect attributes. The initializer can accept any number of
Group instances to be added to.
When subclassing the Sprite, be sure to call the base initializer before
adding the Sprite to Groups.
"""
def __init__(self, *groups):
self.__g = {} # The groups the sprite is in
if groups: self.add(groups)
def add(self, *groups):
"""add the sprite to groups
Sprite.add(*groups): return None
Any number of Group instances can be passed as arguments. The
Sprite will be added to the Groups it is not already a member of.
"""
has = self.__g.__contains__
for group in groups:
if hasattr(group, '_spritegroup'):
if not has(group):
group.add_internal(self)
self.add_internal(group)
else: self.add(*group)
def remove(self, *groups):
"""remove the sprite from groups
Sprite.remove(*groups): return None
Any number of Group instances can be passed as arguments. The Sprite will
be removed from the Groups it is currently a member of.
"""
has = self.__g.__contains__
for group in groups:
if hasattr(group, '_spritegroup'):
if has(group):
group.remove_internal(self)
self.remove_internal(group)
else: self.remove(*group)
def add_internal(self, group):
self.__g[group] = 0
def remove_internal(self, group):
del self.__g[group]
def update(self, *args):
"""method to control sprite behavior
Sprite.update(*args):
The default implementation of this method does nothing; it's just a
convenient "hook" that you can override. This method is called by
Group.update() with whatever arguments you give it.
There is no need to use this method if not using the convenience
method by the same name in the Group class.
"""
pass
def kill(self):
"""remove the Sprite from all Groups
Sprite.kill(): return None
The Sprite is removed from all the Groups that contain it. This won't
change anything about the state of the Sprite. It is possible to continue
to use the Sprite after this method has been called, including adding it
to Groups.
"""
for c in self.__g.keys():
c.remove_internal(self)
self.__g.clear()
def groups(self):
"""list of Groups that contain this Sprite
Sprite.groups(): return group_list
Return a list of all the Groups that contain this Sprite.
"""
return self.__g.keys()
def alive(self):
"""does the sprite belong to any groups
Sprite.alive(): return bool
Returns True when the Sprite belongs to one or more Groups.
"""
return (len(self.__g) != 0)
def __repr__(self):
return "<%s sprite(in %d groups)>" % (self.__class__.__name__, len(self.__g))
So in summary, you don't have to subclass Sprite - you could just provide all of these methods on your own - but it's easier if you do ;)