How to detect collision/mouse-over between the mouse and a sprite? - python-3.x

Goal: to know when a sprite is clicked / active with the mouse
Using: Python 3.2 64bit, Pygame 1.92 64bit, windows 7 64bit
I spend 6 hours to no avail...I've tried :
s.rect.collidepoint(pygame.mouse.get_pos())
s.collidepoint(pygame.mouse.get_pos())
s.sprite.spritecollide(pygame.mouse.get_pos())
s.spritecollide(pygame.mouse.get_pos())
s.sprite.collide_rect(pygame.mouse.get_pos())
s.collide_rect(pygame.mouse.get_pos())
I've also tried turning the mouse location, which I really don't want to do, like someone else mentioned here on another post, into a sprite and collide like that with the same results ;(
I'm able to successfully mouse-collide with an image, but as soon as I turn the image into a sprite class, it becomes a nightmare...what's wrong with the sprite class? Or am I wasting time trying to use sprites for the nice collision features and just use images with rect collision instead?
Keep on getting the AttributeError: 'Sheldon' object has no attribute 'Rect' (s.Rect.collidepoint)
or AttributeError: 'tuple' object has no attribute 'collidepoint' (s.collidepoint)
or AttributeError: 'Rake' object has no attribute 'sprite' (s.sprite.collidepoint)
Since I'm new to python/pygame, should I be putting this detection in an Update/Render method in the sprite class itself, or am I using the wrong event polling???
I haven't bothered trying to recode the mousedown/up/dragging since I can't even get the mouse-over to work
Hopefully this time the post gets a working response...the others didn't ;(
Thanks for your help.
Code:
import pygame
from pygame import *
from pygame.locals import *
from pygame.sprite import *
class Sheldon(Sprite):
def __init__(self):
Sprite.__init__(self)
self.image = transform.scale(image.load('sheldon.jpg').convert(),(230,310))
self.rect = self.image.get_rect()
class Rake(Sprite):
def __init__(self):
Sprite.__init__(self)
self.image = transform.scale(image.load('rake.jpg').convert(),(230,310))
self.rect = self.image.get_rect()
class Sprite_Mouse_Location(Sprite):
def __init__(self,x,y):
Sprite.__init__(self)
self.rect = pygame.Rect(x,y,1,1)
print(self.rect)
pygame.init()
window = display.set_mode( (800,600) )
sheldon = Sheldon()
sheldon.rect = (10,10)
all_sprites = Group(sheldon)
rake = Rake()
rake.rect = (400,250)
all_sprites.add(rake)
x,y = pygame.mouse.get_pos()
mouse_sprite = Sprite_Mouse_Location(x,y)
running = True
while running == True:
for event in pygame.event.get():
if event.type == QUIT or event.type == KEYUP and event.key == K_ESCAPE :
pygame.quit()
elif event.type == MOUSEMOTION :
for s in all_sprites :
if pygame.sprite.collide_rect(s,mouse_sprite):
print("hit")
window.fill( (0,0,0) )
all_sprites.update()
all_sprites.draw(window)
display.update()

You don't need Sprite_Mouse_Location.
BTW: to set position you need
rake.rect.topleft = (400, 250)
# or
rake.rect.x = 400
rake.rect.y = 250
not
rake.rect = (400, 250)
because it replace pygame.Rect() with tuple
Example code:
I use Surface instead of image.load() so everyone can run it without images.
import pygame
# --- constants --- (UPPER_CASE names)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
# --- classes --- (CamelCase names)
class Sheldon(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((230, 310))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
def check_click(self, mouse):
if self.rect.collidepoint(mouse):
print("hit RED")
class Rake(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((230, 310))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
def check_click(self, mouse):
if self.rect.collidepoint(mouse):
print("hit GREEN")
# --- main --- (lower_case names)
# - init -
pygame.init()
window = pygame.display.set_mode((800,600))
# - objects -
sheldon = Sheldon(10, 10)
#sheldon.rect.topleft = (10, 10)
rake = Rake(400, 250)
#rake.rect.topleft = (400, 250)
all_sprites = pygame.sprite.Group()
all_sprites.add(sheldon, rake)
# - mainloop -
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT or \
(event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
for s in all_sprites:
s.check_click(event.pos)
window.fill(BLACK)
all_sprites.update()
all_sprites.draw(window)
pygame.display.update()
# - end -
pygame.quit()

There are a lot of things that are going on with your code. Coding can be hard at first, but keep sticking to! You'll better as long as you keep trying and don't give up! Your error is occurring because above the loop, you are setting your various sprites' rect variables to tuples instead of rects. Rects are not tuples, they have a bunch of variables that tuples don't have and collide_rect needs some of those variables. I put a couple comments in the code below about your other questions.
import pygame
from pygame import *
from pygame.locals import *
from pygame.sprite import *
class Sheldon(Sprite):
def __init__(self):
Sprite.__init__(self)
self.image = transform.scale(image.load('sheldon.jpg').convert(),(230,310))
self.rect = self.image.get_rect()
class Rake(Sprite):
def __init__(self):
Sprite.__init__(self)
self.image = transform.scale(image.load('rake.jpg').convert(),(230,310))
self.rect = self.image.get_rect()
class Sprite_Mouse_Location(Sprite):
def __init__(self,x,y):
Sprite.__init__(self)
self.rect = pygame.Rect(x,y,1,1)
print(self.rect)
pygame.init()
window = display.set_mode( (800,600) )
sheldon = Sheldon()
sheldon.rect = (10,10) # (10,10) is not a rect!
# pygame.Rect(0,0,10,10) is a rect!
all_sprites = Group(sheldon)
rake = Rake()
rake.rect = (400,250) # Same as above comment.
all_sprites.add(rake)
x,y = pygame.mouse.get_pos() # If you are going to make the mouse
mouse_sprite = Sprite_Mouse_Location(x,y) # a sprite, then update your mouse position in the loop.
running = True
while running == True:
for event in pygame.event.get():
if event.type == QUIT or event.type == KEYUP and event.key == K_ESCAPE :
pygame.quit()
elif event.type == MOUSEMOTION :
# You could have this instead.
# mouse_sprite.rect.x, mouse_sprite.rect.y = pygame.mouse.get_pos()
# Then move the for loop below to be out of the event loop.
for s in all_sprites :
if pygame.sprite.collide_rect(s,mouse_sprite):
print("hit")
window.fill( (0,0,0) )
all_sprites.update()
all_sprites.draw(window)
display.update()
I'd also recommend going through a pygame tutorial as well as looking at other people's pygame code and trying to understand it line by line. Both those tips helped me out. Good luck.

thanks to alanxoc3 and a new day, code working
posting here because I'm sure another newbie is going to want to do this type of mouse click
# to create a group of sprites and use the mouse over them individually
import pygame
from pygame import *
from pygame.locals import *
from pygame.sprite import *
class Sheldon(Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self,self.groups) #have to have groups here, not documented well
self.image = transform.scale(image.load('sheldon.jpg').convert(),(230,310))
self.rect = self.image.get_rect()
def clickCheck(self,smouse):
if pygame.sprite.collide_rect(smouse, self): #could not use sritecollison because it would flag whole group
print('hit sheldon') # this if the check mouse that its working
#------------------------------------------
class Rake(Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self,self.groups)
self.image = transform.scale(image.load('rake.jpg').convert(),(230,310))
self.rect = self.image.get_rect()
def clickCheck(self,smouse):
if pygame.sprite.collide_rect( smouse, self ):
print('hit rake')
#-------------------------------------------
class Sprite_Mouse_Location(Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.rect = pygame.Rect( 0 , 0 , 1 , 1 ) # no updating needed here
#--------------------------------------------
pygame.init()
window = display.set_mode( (800,600) )
mouse_sprite = Sprite_Mouse_Location() # had to create a mouse as sprite to use the sprite collision feature
all_sprites = pygame.sprite.Group() # have to declare first, no well documented
Sheldon.groups = all_sprites # better than all_sprites = Group(sheldon), and can be assigned before instantiation of sheldon!!!
sheldon = Sheldon()
sheldon.rect = (10,10,230,310)
Rake.groups = all_sprites
rake = Rake()
rake.rect = (400,250,230,310)
running = True
while running == True:
for event in pygame.event.get():
if event.type == QUIT or event.type == KEYUP and event.key == K_ESCAPE :
pygame.quit()
elif event.type == MOUSEBUTTONDOWN:
if event.button == 1:
mouse_sprite.rect.x , mouse_sprite.rect.y = pygame.mouse.get_pos() # have to have this to update mouse here or get wrong location
for s in all_sprites: #can't have outside the event or it will continuously check
s.clickCheck(mouse_sprite)
window.fill( (0,0,0) )
all_sprites.update() # have to have this for group sprites
all_sprites.draw(window) # have to have this for sprites
display.flip()

Related

Python crash coursebook alien invasion game

I encountered an error while trying to fire bullets in the alien invasion game I copied most of the code from the book but I must have left somethings out. This is my whole code and since I started to add the bullets i haven't been able to move the ship does anyone have a solution?
pythoncrashcoursebook.py
import pygame
import refactors as rf
from game_settings import Settings
from ship import Ship
from pygame.sprite import Group
def run_game():
# Initialize game and make a screen object and caption
pygame.init()
# Make a settings instance
settings = Settings()
# Make a screen
screen = pygame.display.set_mode((settings.screen_height, settings.screen_width))
# Make a ship
ship = Ship(screen, settings)
# Make a group to store bullets
bullets = Group()
# Display caption
pygame.display.set_caption("Alien Invasion")
# Main game loop
while True:
rf.check_events(settings, screen, ship, bullets)
ship.update()
bullets.update()
rf.update_screen(settings, screen, ship, bullets)
run_game()
ship.py
import pygame
class Ship:
def __init__(self, screen, settings):
# Initialize the ship and start position
self.screen = screen
self.settings = settings
# Load the image and get the rect of the ship
self.image = pygame.image.load("spaceship.png")
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
# Set the ship at the bottom of the screen
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
# Store a decimal value for the ship's center
self.center = float(self.rect.centerx)
# Moving flag
self.moving_right = False
self.moving_left = False
def update(self):
if self.moving_right and self.rect.right < self.screen_rect.right:
self.center += self.settings.ship_speed_factor
if self.moving_left and self.rect.left > 0:
self.center -= self.settings.ship_speed_factor
# Update rect object from self.center
self.rect.centerx = self.center
def blitme(self):
# Draw the ship
self.screen.blit(self.image, self.rect)
bullet.py
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
"""Class to control bullet"""
def __init__(self, settings, screen, ship):
"""Create bullet at ship position"""
super(Bullet, self).__init__()
self.screen = screen
# Create bullet rect at (0, 0) and then correct position
self.rect = pygame.Rect(0, 0, settings.bullet_width, settings.bullet_height)
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
# Store the bullet's position as a decimal
self.y = float(self.rect.y)
# Store bullet colour and speed
self.colour = settings.bullet_colour
self.bullet_speed = settings.bullet_speed_factor
def update_bullet(self):
"""Move the bullet up the screen"""
self.y -= self.bullet_speed
# Update rect position
self.rect.y = self.y
def draw_bullet(self):
"""Draw bullet on the screen"""
pygame.draw.rect(self.screen, self.colour, self.rect)
game_settings.py
class Settings:
# A class to store all the game settings
def __init__(self):
# Ship settings
self.ship_speed_factor = 1.5
# Screen settings
self.bg_colour = (0, 0, 255)
self.screen_height = 800
self.screen_width = 600
# Bullet settings
self.bullet_colour = 60, 60, 60
self.bullet_height = 15
self.bullet_width = 3
self.bullet_speed_factor = 1
refactors.py
import sys
import pygame
from bullet import Bullet
def check_events(ship, settings, screen, bullets):
# Check for events and assign functions to them
for event in pygame.event.get():
# To quit
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_key_down_event(event, ship, settings, screen, bullets)
elif event.type == pygame.KEYUP:
check_key_up_event(event, ship)
def check_key_down_event(event, ship, settings, screen, bullets):
"""Respond to keypress"""
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.key == pygame.K_SPACE:
# Create a mew bullet and add it to the bullet group
new_bullet = Bullet(settings, screen, ship)
bullets.add(new_bullet)
def check_key_up_event(event, ship):
"""Respond to key releases"""
if event.key == pygame.K_RIGHT:
ship.moving_right = False
elif event.key == pygame.K_LEFT:
ship.moving_left = False
def update_screen(settings, screen, ship, bullets):
screen.fill(settings.bg_colour)
# Redraw all bullets behind aliens and ship
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
pygame.display.flip()
And this is the error
line 14, in __init__
self.rect = pygame.Rect(0, 0, settings.bullet_width, settings.bullet_height)
AttributeError: 'pygame.Surface' object has no attribute 'bullet_width'
You should try changing:
rf.check_events(settings, screen, ship, bullets)
to:
rf.check_events(ship, settings, screen, bullets)
in your main code.

How do I create a button that changes sprites when clicked in pygame?

I am currently having trouble with buttons in pygame. At the moment I am testing myself in pygame and I am trying to create a flappy bird type of game. What I am trying to achieve is that when I click on the play button on the main menu it will change the button sprite and run the main game.
I have managed to define the button function and get it to switch to the main game when I click. The only problem is that it doesn't show a different sprite when clicked and I can click anywhere in the application to switch to the main game instead of having to click on the button.
Any help would be much appreciated.
Thanks in Advance
import time
import random
import pygame
from pygame.locals import *
pygame.init()
#Predefined Colors
white = (255,255,255)
black = (0,0,0)
red = (200,0,0)
light_red = (255,0,0)
yellow = (200,200,0)
light_yellow = (255,255,0)
green = (34,177,76)
light_green = (0,255,0)
blue = (0,0,255)
light_blue = (0, 0, 200)
player_list = (
# Red Bird
('assets/sprites/redbird-upflap.png', 'assets/sprites/redbird-midflap.png',
'assets/sprites/redbird-downflap.png'),
# Blue Bird
('assets/sprites/bluebird-upflap.png', 'assets/sprites/bluebird-midflap.png',
'assets/sprites/bluebird-downflap.png'),
# Yellow Bird
('assets/sprites/yellowbird-upflap.png', 'assets/sprites/yellowbird-midflap.png',
'assets/sprites/yellowbird-downflap.png')
)
background_list = (
('assets/sprites/background-day.png', 'assets/sprites/background-night.png')
)
pipe_list = (
('assets/sprites/pipe-green.png', 'assets/sprites/pipe-red.png')
)
FPS = 30
images, sounds = {}, {}
def main():
global base_x, base_y, clock, gameDisplay, display_height, display_width
display_width = 288
display_height = 512
base_x = 0
base_y = display_height * 0.79
clock = pygame.time.Clock()
gameDisplay = pygame.display.set_mode((display_width, display_height))
pygame.display.set_caption("Flappy Bird")
#Loading icon sprite
images['Icon'] = pygame.image.load('assets/sprites/yellowbird-midflap.png')
pygame.display.set_icon(images['Icon'])
#Loading all the Numbers sprites
images['Numbers'] = (
pygame.image.load('assets/sprites/0.png').convert_alpha(),
pygame.image.load('assets/sprites/1.png').convert_alpha(),
pygame.image.load('assets/sprites/2.png').convert_alpha(),
pygame.image.load('assets/sprites/3.png').convert_alpha(),
pygame.image.load('assets/sprites/4.png').convert_alpha(),
pygame.image.load('assets/sprites/5.png').convert_alpha(),
pygame.image.load('assets/sprites/6.png').convert_alpha(),
pygame.image.load('assets/sprites/7.png').convert_alpha(),
pygame.image.load('assets/sprites/8.png').convert_alpha(),
pygame.image.load('assets/sprites/9.png').convert_alpha()
)
#Game Over Sprite
images['Game Over'] = pygame.image.load('assets/sprites/gameover.png').convert_alpha()
#Starting Game sprite
images['Starting Game'] = pygame.image.load('assets/sprites/startgame-screen.png').convert_alpha()
#Flappy Bird Logo sprite
images['Flappy Bird Logo'] = pygame.image.load('assets/sprites/flappybird-logo.png').convert_alpha()
#Base Ground sprite
images['Base Ground'] = pygame.image.load('assets/sprites/base.png').convert_alpha()
#Play Button Up sprite
images['Play Button Up'] = pygame.image.load('assets/sprites/playbutton-up.png').convert_alpha()
#Play Button Down sprite
images['Play Button Down'] = pygame.image.load('assets/sprites/playbutton-down.png').convert_alpha()
#Quit Button Up sprite
#images['Quit Button Up'] = pygame.image.load('assets/sprites/quitbutton-up.png').convert_alpha()
#Quit Button Down sprite
#images['Quit Button Down'] = pygame.image.load('assets/sprites/quitbutton-down.png').convert_alpha()
#Sounds
# sounds['Die'] = pygame.mixer.Sound('assets/audio/die.wav')
# sounds['Hit'] = pygame.mixer.Sound('assets/audio/hit.wav')
# sounds['Point'] = pygame.mixer.Sound('assets/audio/point.wav')
# sounds['swoosh'] = pygame.mixer.Sound('assets/audio/swoosh.wav')
# sounds['wing'] = pygame.mixer.Sound('assets/audio/wing.wav')
while True:
#Select random Background sprites
random_background = random.randint(0, len(background_list) - 1)
images['Background'] = pygame.image.load(background_list[random_background]).convert()
#Select random Player sprites
random_player = random.randint(0, len(player_list) - 1)
images['Player'] = (
pygame.image.load(player_list[random_player][0]).convert_alpha(),
pygame.image.load(player_list[random_player][1]).convert_alpha(),
pygame.image.load(player_list[random_player][2]).convert_alpha()
)
#Select random Pipe sprite
random_pipe = random.randint(0, len(pipe_list) - 1)
images['Pipe'] = pygame.image.load(pipe_list[random_pipe])
main_menu()
pygame.display.update()
clock.tick(FPS)
def button(action = None):
cur = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
if action == 'Play':
button = images['Play Button Up'].get_rect()
for event in pygame.event.get():
if click[0] == 1:
if button.collidepoint(cur):
print ('Mouse Over')
images['Play Button Down']
main_game()
else:
gameDisplay.blit(images['Play Button Up'], (0, -10))
def main_menu():
global player_index, player_x, player_y
player_index = 0
player_x = int(display_width * 0.2)
player_y = int((display_height - images['Player'] [0].get_height()) / 2)
menu = True
while menu:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
gameDisplay.blit(images['Background'], (0, 0))
gameDisplay.blit(images['Base Ground'], (base_x, base_y))
gameDisplay.blit(images['Flappy Bird Logo'], (50, -30))
gameDisplay.blit(images['Player'][player_index], (125, 140))
gameDisplay.blit(images['Play Button Up'], (10, 10))
button(action = 'Play')
pygame.display.update()
clock.tick(FPS)
def main_game():
gameExit = False
gameOver = False
player_x = 0
player_y = 0
while not gameExit:
if gameOver == True:
gameDisplay.blit(images['Game Over'], 50, 50)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
gameDisplay.blit(images['Background'], (0, 0))
gameDisplay.blit(images['Starting Game'], (0, 0))
gameDisplay.blit(images['Base Ground'], (base_x, base_y))
gameDisplay.blit(images['Player'][player_index], (player_x, player_y))
pygame.display.update()
clock.tick(FPS)
main()
Here's a minimal example to demonstrate how you can switch between different button images.
When the user presses a mouse button (a pygame.MOUSEBUTTONDOWN event is added to the queue), check if the event.pos collides with the button rect and if it collides, set the image to the "down" version.
When the user releases the button (pygame.MOUSEBUTTONUP event), just set the image back to the original version.
import pygame as pg
pg.init()
screen = pg.display.set_mode((640, 480))
GRAY = pg.Color('gray15')
BLUE = pg.Color('dodgerblue1')
LIGHTBLUE = pg.Color('lightskyblue1')
BUTTON_UP_IMG = pg.Surface((50, 30))
BUTTON_UP_IMG.fill(BLUE)
BUTTON_DOWN_IMG = pg.Surface((50, 30))
BUTTON_DOWN_IMG.fill(LIGHTBLUE)
def main():
clock = pg.time.Clock()
font = pg.font.Font(None, 30)
# Currently selected button image.
button_image = BUTTON_UP_IMG
button_rect = button_image.get_rect(topleft=(200, 200))
x = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
if event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1:
if button_rect.collidepoint(event.pos):
button_image = BUTTON_DOWN_IMG
elif event.type == pg.MOUSEBUTTONUP:
if event.button == 1:
button_image = BUTTON_UP_IMG
if button_rect.collidepoint(event.pos):
print('Button pressed.')
x += 1
screen.fill(GRAY)
screen.blit(button_image, button_rect)
txt = font.render(str(x), True, BLUE)
screen.blit(txt, (260, 206))
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
I would actually recommend to use classes, pygame sprites and sprite groups instead of just rects and images. Then you can easily create as many instances of the button class as you want.
import pygame as pg
pg.init()
GRAY= pg.Color('gray12')
BLUE = pg.Color('dodgerblue1')
FONT = pg.font.Font(None, 30)
BUTTON_UP_IMG = pg.Surface((50, 30))
BUTTON_UP_IMG.fill(BLUE)
BUTTON_DOWN_IMG = pg.Surface((50, 30))
BUTTON_DOWN_IMG.fill(pg.Color('lightskyblue1'))
# The Button is a pygame sprite, that means we can add the
# instances to a sprite group and then update and render them
# by calling `sprite_group.update()` and `sprite_group.draw(screen)`.
class Button(pg.sprite.Sprite):
def __init__(self, pos, callback):
pg.sprite.Sprite.__init__(self)
self.image = BUTTON_UP_IMG
self.rect = self.image.get_rect(topleft=pos)
self.callback = callback
def handle_event(self, event):
"""Handle events that get passed from the event loop."""
if event.type == pg.MOUSEBUTTONDOWN:
if event.button == 1:
if self.rect.collidepoint(event.pos):
self.image = BUTTON_DOWN_IMG
elif event.type == pg.MOUSEBUTTONUP:
if event.button == 1:
self.image = BUTTON_UP_IMG
if self.rect.collidepoint(event.pos):
print('Button pressed.')
# Call the function that we passed during the
# instantiation. (In this case just `increase_x`.)
self.callback()
class Game:
def __init__(self):
self.screen = pg.display.set_mode((800, 600))
self.clock = pg.time.Clock()
self.x = 0
self.buttons = pg.sprite.Group(
Button((200, 200), callback=self.increase_x),
Button((500, 200), callback=self.decrease_x))
self.done = False
# A callback function that we pass to the button instance.
# It gets called if a collision in the handle_event method
# is detected.
def increase_x(self):
"""Increase self.x if button is pressed."""
self.x += 1
def decrease_x(self):
"""Decrease self.x if button is pressed."""
self.x -= 1
def run(self):
while not self.done:
self.handle_events()
self.run_logic()
self.draw()
self.clock.tick(30)
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
for button in self.buttons:
button.handle_event(event)
def run_logic(self):
self.buttons.update()
def draw(self):
self.screen.fill(GRAY)
self.buttons.draw(self.screen)
txt = FONT.render(str(self.x), True, BLUE)
self.screen.blit(txt, (360, 206))
pg.display.flip()
if __name__ == "__main__":
Game().run()
pg.quit()
Thanks for the Answer to my previous question your answer solved my previous issue, but now I have ran into another issue.
When I press my button on the main menu it changes sprites correctly and loads the main game correctly, but as soon as I move my mouse it switches back to the main menu. My best guess is that it is because it is looping the whole button sequence, but I am not entirely sure.
import pygame
pygame.init()
display_width = 288
display_height = 512
def main_menu():
done = False
play_button_image = images['Play Button Up']
play_button_rect = play_button_image.get_rect(topleft=(30,15))
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
quit()
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
if play_button_rect.collidepoint(event.pos):
play_button_image = images['Play Button Down']
elif event.type == MOUSEBUTTONUP:
if event.button == 1:
play_button_image = images['Play Button Up']
if play_button_rect.collidepoint(event.pos):
main_game()
gameDisplay.blit(play_button_image, play_button_rect)
pygame.display.update()
clock.tick(FPS)
def main_game():
gameExit = False
gameOver = False
player_index = 0
player_x = int(display_width * 0.2)
player_y = int((display_height - images['Player'][0].get_height()) / 2)
player_index_gen = cycle([0, 1, 2, 1])
loop_iter = 0
starting_game_x = int((display_width - images['Starting Game'].get_width()) / 2)
starting_game_y = int(display_height * 0.12)
base_x = 0
base_shift = images['Base Ground'].get_width() - images['Background'].get_width()
player_move_vals = {'val': 0, 'dir': 1}
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.MOUSEBUTTONDOWN or pygame.K_SPACE or pygame.K_UP:
sounds['Wing']
return {
'player_y': player_y + player_move_vals['val'],
'base_x': base_x,
'player_index_gen': player_index_gen
}
if (loop_iter + 1) % 5 == 0:
player_index = next(player_index_gen)
loop_iter = (loop_iter + 1) % 30
base_x = -((-base_x + 4) % base_shift)
player_move(player_move_vals)
# draw sprites
gameDisplay.blit(images['Background'], (0, 0))
gameDisplay.blit(images['Player'][player_index],
(player_x, player_y + player_move_vals['val']))
gameDisplay.blit(images['Starting Game'], (starting_game_x, starting_game_y))
gameDisplay.blit(images['Base Ground'], (base_x, base_y))
pygame.display.update()
clock.tick(FPS)
if __name__ == '__main__':
main()

Understanding & making infinite bullets to shoot in Pygame

I'm learning python and also pygame, and I want to know why the bullets doesn't display, I tough that maybe the screen.update_screen() can be interfering but no, that's not the case, I need help to undertand how Bullets work in python because clearly my method is not working, I've seen many methods in other posts, and they use a limited ammount of bullets to shoot (don't know why, in my case I want infinite bullets) so what should I add to see the Bullets, I know that I need to add a remover for seing a "movement" in the display, but I don't know how, any help is appreciated.
# -*- coding: utf-8 -*-
import pygame
class Screen(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
pygame.display.set_caption("Space Game")
self.screen_width = 800
self.screen_heigh = 600
self.picture = pygame.image.load("screen.png")
self.screen = pygame.display.set_mode((self.screen_width, self.screen_heigh))
def update_screen(self):
self.screen.blit(self.picture, (0, 0))
def update_obj(self, object):
self.screen.blit(object.picture, object.rect)
def update_shoot(self, object):
for y in range(object.rect.centery, 600, 10):
self.screen.blit(object.picture, (object.rect.centerx, object.rect.centery + y))
class Ship(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.picture = pygame.image.load("ship.png")
self.rect = self.picture.get_rect()
self.rect.centerx = 400
self.rect.centery = 500
class Bullet(pygame.sprite.Sprite):
def __init__(self, object):
pygame.sprite.Sprite.__init__(self)
self.picture = pygame.image.load("shoot.png")
self.rect = self.picture.get_rect()
self.rect.centerx = object.rect.centerx
self.rect.centery = (object.rect.centery + 25)
def main():
pygame.init()
done = False
clock = pygame.time.Clock()
screen = Screen()
ship = Ship()
bullet = Bullet(ship)
while not done:
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
ship.rect.centerx -= 5
if keys[pygame.K_RIGHT]:
ship.rect.centerx += 5
if keys[pygame.K_UP]:
ship.rect.centery -= 5
if keys[pygame.K_DOWN]:
ship.rect.centery += 5
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
done = True
if event.key == pygame.K_SPACE:
print ("shoot")
#for some weird reason the bullet doesn't display
screen.update_shoot(bullet)
screen.update_screen()
screen.update_obj(ship)
pygame.display.update()
clock.tick(10)
pygame.quit()
if __name__ == "__main__":
main()
To shoot bullets you usually create instances of a Bullet class and add them to a list, pygame.sprite.Group or another container. Then you iterate over this container and call the update method of the bullet in which its position is changed. To blit the images of the sprites/objects you iterate again over the container and just blit the images onto the screen. With sprite groups you can just call sprite_group.update() and sprite_group.draw(screen) instead of iterating yourself. BTW, pygame sprites have to have a self.image attribute not a self.picture in order to work with sprite groups (take a look at Program Arcade Games for more information).
I started to modify a few things in your example to show you how to use sprite groups, but then ended up changing your whole Screen class into a Game class (which I recommend to use in the future).
import sys
import pygame
class Game:
def __init__(self):
self.done = False
self.screen_width = 800
self.screen_height = 600
self.image = pygame.Surface((800, 600))
self.image.fill((30, 40, 50))
self.screen = pygame.display.set_mode(
(self.screen_width, self.screen_height))
# all_sprites is used to update and draw all sprites together.
self.all_sprites = pygame.sprite.Group()
# You'll probably need a separate bullet_group
# later for collision detection with enemies.
self.bullet_group = pygame.sprite.Group()
self.ship = Ship()
self.all_sprites.add(self.ship)
bullet = Bullet(self.ship)
self.bullet_group.add(bullet)
self.all_sprites.add(bullet)
def handle_events(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.ship.rect.centerx -= 5
if keys[pygame.K_RIGHT]:
self.ship.rect.centerx += 5
if keys[pygame.K_UP]:
self.ship.rect.centery -= 5
if keys[pygame.K_DOWN]:
self.ship.rect.centery += 5
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.done = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
self.done = True
if event.key == pygame.K_SPACE:
bullet = Bullet(self.ship)
self.bullet_group.add(bullet)
self.all_sprites.add(bullet)
def update(self):
# Calls `update` methods of all contained sprites.
self.all_sprites.update()
def draw(self):
self.screen.blit(self.image, (0, 0))
self.all_sprites.draw(self.screen) # Draw the contained sprites.
pygame.display.update()
class Ship(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((20, 30))
self.image.fill((50, 170, 230))
# A nicer way to set the start pos with `get_rect`.
self.rect = self.image.get_rect(center=(400, 500))
class Bullet(pygame.sprite.Sprite):
def __init__(self, ship):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((7, 7))
self.image.fill((230, 140, 30))
self.rect = self.image.get_rect()
self.rect.centerx = ship.rect.centerx
self.rect.centery = ship.rect.centery - 25
def update(self):
self.rect.y -= 5 # Move up 5 pixels per frame.
def main():
pygame.init()
pygame.display.set_caption('Space Game')
clock = pygame.time.Clock()
game = Game()
while not game.done:
game.handle_events()
game.update()
game.draw()
clock.tick(30)
if __name__ == '__main__':
main()
pygame.quit()
sys.exit()

Don't know how to spawn multiple enemies

My code should spawn multiple enemies that chase around my player. Instead, the enemy just stays in one place. This only happens when I try to input a value into the enemy-spawning function for the spawn coordinates. Heres the code:
class spawn(object):
def __init__(self,place1,place2):
self.place1=place1
self.place2=place2
def AIPrototype(self):#The important parts to this error star here
global x,y,x1,y1
pygame.draw.rect(screen,THECOLORS['blue'],(self.place1,self.place2,50,50))
x1=self.place1
y1=self.place2#end here
if x1<x:
xspeed1=1
slopex1=x-x1
if x1>x:
xspeed1=-1
slopex1=x1-x
if y1<y:
yspeed1=1
slopey1=y-y1
if y1>y:
yspeed1=-1
slopey1=y1-y
#
hypo1=((slopex1**2)+(slopey1**2))**0.5
speedmark1=hypo1/3
speedy1=slopey1/speedmark1
speedx1=slopex1/speedmark1
movex1=speedx1
movey1=speedy1
if x1<=640 and x1>=0:
if x1>x:
x1+=xspeed1*movex1
if x1<x:
xspeed1=0
if y1<=480 and x1>=0:
if y1>y:
y1+=yspeed1*movey1
if y1<y:
yspeed1=0
if x1<=640 and x1>=0:
if x1<x:
x1+=xspeed1*movex1
if x1>x:
xspeed1=0
if y1<=480 and x1>=0:
if y1<y:
y1+=yspeed1*movey1
if y1>y:
yspeed1=0
#
if x1>640:
x1=640
if x1<0:
x1=0
if y1>480:
y1=480
if y1<0:
y1=0
self.place1=x1#start
self.place2=y1#end
That's the fucntion for spawning the enemies. The fucntion is called here:
clock = pygame.time.Clock()
keepGoing = True
try:
while keepGoing:
clock.tick(60)
screen.fill(THECOLORS['red'])
pygame.draw.rect(screen,THECOLORS['green'],(x,y,50,50))
char()
spawn1=spawn(200,200)#start
spawn1.AIPrototype()#end
pygame.display.flip()
I don't know where my error in the coding is, so if someone could help me out, that would be great.
Here's an example with vectors. To get the velocity of the enemies, I calculate the vector to the player, then normalize it and scale by 4 (the final speed).
vel = (player.center - self.pos).normalize() * 4
Then you can just add the velocity to the pos and set the rect.center to the updated pos vector to move the object.
To spawn new enemies, first create a list that should hold the enemy instances and then just append new instances when an enemy spawns (press the f-key to spawn them). You can also use pygame sprites and sprite groups instead of the Enemy class and the list.
import sys
import pygame as pg
class Enemy:
def __init__(self, pos, color):
self.rect = pg.Rect(pos, (26, 45))
self.color = color
self.pos = pg.math.Vector2(pos)
def update(self, player):
vel = (player.center - self.pos).normalize() * 4
self.pos += vel
self.rect.center = self.pos
def draw(self, screen):
pg.draw.rect(screen, self.color, self.rect)
def main():
screen = pg.display.set_mode((640, 480))
bg_color = pg.Color('gray12')
player_color = pg.Color('dodgerblue1')
enemy_color = pg.Color('sienna1')
clock = pg.time.Clock()
player = pg.Rect((100, 300), (26, 50))
enemy_list = [Enemy((100, 300), enemy_color)]
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_f:
enemy_list.append(Enemy((400, 0), enemy_color))
keys = pg.key.get_pressed()
if keys[pg.K_a]:
player.x -= 5
elif keys[pg.K_d]:
player.x += 5
if keys[pg.K_w]:
player.y -= 5
elif keys[pg.K_s]:
player.y += 5
for enemy in enemy_list:
enemy.update(player)
screen.fill(bg_color)
pg.draw.rect(screen, player_color, player)
for enemy in enemy_list:
enemy.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()

Positioning an Entity in a Class (Pygame)

I'm trying to blit an image of grass onto my game in a specific area, but I'm having trouble doing so because it's in a class. I want to do something like this..
grass_platform.draw(screen, (200, 200))
NOTE: I know this actually doesn't work
Here's my code...
import pygame
pygame.init()
#Screen Size
screen_size = [1024,576]
#Display Window
screen = pygame.display.set_mode(screen_size)
pygame.display.set_caption('The Adventures of Fresco the Explorer')
#Clock
clock = pygame.time.Clock()
#Colors
black = (0,0,0)
white = (255,255,255)
#Game Start
gameStart = False
#Backgrounds
forest = pygame.image.load('forest.png')
#Gravity
gravity =-10
fall = True
#Player
fresco = pygame.image.load('fresco v2.png').convert()
fresco = pygame.transform.scale(fresco,(32,136))
class Player(pygame.sprite.Sprite):
def __init__(self, x, y, filename):
pygame.sprite.Sprite.__init__(self)
self.x = x
self.y = y
self.image = fresco
self.rect = self.image.get_rect()
def update (self):
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_a:
self.rect.x -= 6
self.image = pygame.transform.flip(fresco, True, False)
elif event.key == pygame.K_d:
self.rect.x += 6
self.image = pygame.transform.flip(fresco, False, False)
elif event.key == pygame.K_w:
self.rect.y -= 20
def draw(self, screen):
screen.blit(self.image, self.rect)
player = Player(0, 0, 'fresco v2.png')
#Grass Platform
grass = pygame.image.load('grass.png')
grass = pygame.transform.scale(grass, (90, 90))
class Grass (pygame.sprite.Sprite):
def __init__(self, filename):
self.image = grass
self.rect = self.image.get_rect()
def draw(self, screen):
screen.blit(self.image,self.rect)
grass_platform = Grass('grass.png')
#Game Loop
while not gameStart:
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameStart = True
#Background Blitted
screen.blit(forest,(0,0))
#Falling Event
if fall == True:
if gravity:
player.rect.y -= gravity
#Class Blitted
player.update()
player.draw(screen)
grass_platform.draw(screen)
#Updates Screen
pygame.display.update()
#FPS
clock.tick(30)
pygame.quit()
I dont know if i understood you correctly but if you mean just passing a coordinate parameter and blitting to that position that should be quite easy.
class Grass (pygame.sprite.Sprite):
def __init__(self, filename):
self.image = grass
self.rect = self.image.get_rect()
def draw(self, screen, pos):
screen.blit(self.image, pos)
and then when you draw() you just do this:
grass_platform.draw(screen, (300, 200))
or on the other hand if you want to keep the position you could add
this code to the init method:
def __init__(self, filename, x, y):
self.x = x
self.y = y
and then acces it later
def draw(self, screen):
window.blit(img, (self.x, self.y))
Some notes:
Player and Grass are subclasses from Sprite, so they have already a draw function which does exactly what you are doing. Just remove them.
the following part
#Falling Event
if fall == True:
if gravity:
player.rect.y -= gravity
is specific to the Player class and should be moved to the Player class' update function
When using the Sprite class, the rect attribute is used to store the position of the sprite, hence the x and y attribute in your Player class don't make much sense. Remove them, and use the x and y parameters to change the x and y attribute of the rect instead:
self.rect = self.image.get_rect(x=x, y=y)
Hence, if you want to draw grass_platform at a specific position, just alter it's rect, e.g.:
grass_platform.rect.topleft = (200, 200)
instead of calling draw and update on each of your sprites manually, just but them into a Group, and call draw and update on that Group.

Resources