I am developing a game in python 3.10.4 using pygame but I am struggling to generate a flow of user class objects in the main loop from a list using random.choice.
def run_game(self):
"""Start the main loop for the game."""
list_of_particles = [Particle__SO2(self),Particle__H20(self),Particle__CO2(self),Particle__H2S(self), Particle__CO2(self)]
while True:
self._check_events()
self.clock.tick(25)
new_particle = random.choice(list_of_particles)
self.particles.add(new_particle)
self._update_particles()
The code works but the list seemingly exhausts itself after generating each object exactly once and I am at a loss to see why. Full code below:
import sys
import pygame
from pygame.sprite import Sprite
import random
class Volcano_game:
"""Overall class to manage game assets and behavior."""
def __init__(self):
"""Initialize the game, and create game resources."""
pygame.init()
self.screen = pygame.display.set_mode((1920, 1080))
self.particles = pygame.sprite.Group()
self.vent_location_xy = (952, 881)
self.io_surface_y = 950
self.gravity = 1
self.clock = pygame.time.Clock()
self.alpha_surf = pygame.Surface(self.screen.get_size(), pygame.SRCALPHA)
def run_game(self):
"""Start the main loop for the game."""
list_of_particles = [Particle__SO2(self),Particle__H20(self),Particle__CO2(self),Particle__H2S(self), Particle__CO2(self)]
while True:
self._check_events()
self.clock.tick(25)
new_particle = random.choice(list_of_particles)
self.particles.add(new_particle)
self._update_particles()
self.update_screen()
self.alpha_surf.fill((255, 255, 255, 250),
special_flags=pygame.BLEND_RGBA_MULT)
def _update_particles(self):
"""Update position of particles and get rid of old particles."""
# Update bullet positions.
self.particles.update()
# Get rid of particles that have landed or gone off screen.
for particle in self.particles.copy():
if particle.y > self.io_surface_y:
self.particles.remove(particle)
elif particle.y < 0:
self.particles.remove(particle)
elif particle.x < 0:
self.particles.romove(particle)
elif particle.x > 1920:
self.particles.remove(particle)
def update_screen(self):
"""Update images on the screen, and flip to the new screen."""
self.screen.fill((0, 0, 0))
self.screen.blit(self.alpha_surf, (0, 0))
for particle in self.particles.sprites():
particle.draw_particle()
pygame.display.flip()
def _check_events(self):
"""Respond to keypresses and mouse events."""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
class Particle(Sprite):
"""a class to manage sprites fired from the volcano"""
def __init__(self, vg_game):
super().__init__()
self.screen = vg_game.alpha_surf
self.colour = (60, 255, 60)
self.rect_x = 952
self.rect_y = 881
self.y = self.rect_y
self.x = self.rect_x
self.speed_y = -15
self.speed_x = (random.randint(-11, 11))
self.gravity = vg_game.gravity
def update(self):
"""move the particle around the screen"""
# update the decimal position of the particle
self.y += self.speed_y
self.x += self.speed_x
self.speed_y += self.gravity
self.rect_y = self.y
self.rect_x = self.x
def draw_particle(self):
"""draw the particle on the screen"""
pygame.draw.circle(self.screen, self.colour,
(self.rect_x, self.rect_y), 2)
class Particle__SO2(Particle):
def __init__(self, vg_game):
super().__init__(vg_game)
self.colour = (255, 255, 255)
self.speed_y = -17
class Particle__H20(Particle):
def __init__(self, vg_game):
super().__init__(vg_game)
self.colour = (0, 0, 255)
self.speed_y = -52.5
class Particle__CO2(Particle):
def __init__(self, vg_game):
super().__init__(vg_game)
self.colour = (255, 0, 0)
self.speed_y = -24.72
class Particle__H2S(Particle):
def __init__(self, vg_game):
super().__init__(vg_game)
self.colour = (255, 255, 0)
self.speed_y = -31.9
if __name__ == '__main__':
# Make a game instance, and run the game.
vg = Volcano_game()
vg.run_game()
You just create 4 particle objects. You need to choose a random particle type (class) and create a new instance object of that type in each iteration of the loop:
class Volcano_game:
"""Overall class to manage game assets and behavior."""
# [...]
def run_game(self):
"""Start the main loop for the game."""
# EDIT:
# list_of_particles = [Particle__SO2(self),Particle__H20(self),Particle__CO2(self),Particle__H2S(self), Particle__CO2(self)]
list_of_particles = [Particle__SO2, Particle__H20, Particle__CO2, Particle__H2S, Particle__CO2]
while True:
self._check_events()
self.clock.tick(25)
# EDIT:
# new_particle = random.choice(list_of_particles)
new_particle = random.choice(list_of_particles)(self)
self.particles.add(new_particle)
self._update_particles()
self.update_screen()
self.alpha_surf.fill((255, 255, 255, 250), special_flags=pygame.BLEND_RGBA_MULT)
The list isn't being "exhausted". The problem is that you only have five particle objects - the ones you defined in list_of_particles. Once a given particle is no longer within your desired bounds you remove it from the volcano game's tracked list of particles (which is fine), but the particle instance itself doesn't get deleted or reset. Your main loop will happily pick a particle instance it has seen before, update its position (which will now be even further outside of your desired bounds), add it to the volcano game's list of tracked particles, and then immediately remove it.
I would suggest instead to randomly pick a type of particle, and then to instantiate that type, so that you have a brand new particle object each time. Something like:
particle_types = [Particle__SO2, Particle__H20, Particle__CO2, Particle__H2S] # Note: We are not instantiating any particle objects. We're creating a list of particle types - referring to the particle classes directly.
ParticleType = random.choice(particle_types)
particle = ParticleType(self)
self.particles.add(particle)
self._update_particles()
Related
So I am trying to fade my screen out and back in after completing a level using PyGame. My problem is that only the fadeout() works and not the fadein(). When calling the fadein() the screen turns black for a few seconds then suddenly shows the next level. I can't find the problem, any ideas?
def fadeout():
fadeout = pg.Surface((screen_width, screen_height))
fadeout = fadeout.convert()
fadeout.fill(black)
for i in range(255):
fadeout.set_alpha(i)
screen.blit(fadeout, (0, 0))
pg.display.update()
def fadein():
fadein = pg.Surface((screen_width, screen_height))
fadein = fadein.convert()
fadein.fill(black)
for i in range(255):
fadein.set_alpha(255-i)
screen.blit(fadein, (0, 0))
pg.display.update()
Your problem is that you fade in to a black screen, so you don't see any effect. A black screen with a black half-translucent Surface drawn on top is still a black Surface.
You should render the first frame of your level, and blit that Surface to the screen before blitting the fadein surface onto the screen.
Here's a simple example that I hacked together. Press a key to switch from one scene to the next.
import pygame
import random
from itertools import cycle
class Cloud(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface((50, 20))
self.image.set_colorkey((11, 12, 13))
self.image.fill((11, 12, 13))
pygame.draw.ellipse(self.image, pygame.Color('white'), self.image.get_rect())
self.rect = self.image.get_rect(topleft=(x,y))
def update(self, dt, events):
self.rect.move_ip(dt/10, 0)
if self.rect.left >= pygame.display.get_surface().get_rect().width:
self.rect.right = 0
class DayScene:
def __init__(self):
self.clouds = pygame.sprite.Group(Cloud(0, 30), Cloud(100, 40), Cloud(400, 50))
def draw(self, screen):
screen.fill(pygame.Color('lightblue'))
self.clouds.draw(screen)
def update(self, dt, events):
self.clouds.update(dt, events)
class NightScene:
def __init__(self):
sr = pygame.display.get_surface().get_rect()
self.sky = pygame.Surface(sr.size)
self.sky.fill((50,0,50))
for x in random.sample(range(sr.width), 50):
pygame.draw.circle(self.sky, (200, 200, 0), (x, random.randint(0, sr.height)), 1)
self.clouds = pygame.sprite.Group(Cloud(70, 70), Cloud(60, 40), Cloud(0, 50), Cloud(140, 10), Cloud(100, 20))
def draw(self, screen):
screen.blit(self.sky, (0, 0))
self.clouds.draw(screen)
def update(self, dt, events):
self.clouds.update(dt, events)
class Fader:
def __init__(self, scenes):
self.scenes = cycle(scenes)
self.scene = next(self.scenes)
self.fading = None
self.alpha = 0
sr = pygame.display.get_surface().get_rect()
self.veil = pygame.Surface(sr.size)
self.veil.fill((0, 0, 0))
def next(self):
if not self.fading:
self.fading = 'OUT'
self.alpha = 0
def draw(self, screen):
self.scene.draw(screen)
if self.fading:
self.veil.set_alpha(self.alpha)
screen.blit(self.veil, (0, 0))
def update(self, dt, events):
self.scene.update(dt, events)
if self.fading == 'OUT':
self.alpha += 8
if self.alpha >= 255:
self.fading = 'IN'
self.scene = next(self.scenes)
else:
self.alpha -= 8
if self.alpha <= 0:
self.fading = None
def main():
screen_width, screen_height = 300, 300
screen = pygame.display.set_mode((screen_width, screen_height))
clock = pygame.time.Clock()
dt = 0
fader = Fader([DayScene(), NightScene()])
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
if e.type == pygame.KEYDOWN:
fader.next()
fader.draw(screen)
fader.update(dt, events)
pygame.display.flip()
dt = clock.tick(30)
main()
By abstracting each scene into it's own class and delegating the scene change to the Fader class, we're able to let the scenes continue (or add a simple if statement to prevent that) and to handle events while fading.
This question already has answers here:
Why is my collision test always returning 'true' and why is the position of the rectangle of the image always wrong (0, 0)?
(1 answer)
How to detect collisions between two rectangular objects or images in pygame
(1 answer)
Closed 2 years ago.
I am trying to make a canvas for pixel art.
class Canvas:
def __init__(self):
self.__blocks = []
self.__positions = []
for i in range(1830):
self.__blocks.append(pygame.Surface((20, 20)).convert())
for y in range(30):
y *= 20
for x in range(61):
x = x* 20
self.__positions.append([x, y])
self.__color = False
def draw(self, window):
for i in range(1830):
self.__color = not self.__color
if self.__color:
self.__blocks[i].fill((200, 200, 200))
else:
self.__blocks[i].fill((50, 50, 50))
window.blit(self.__blocks[i], (self.__positions[i][0]
, self.__positions[i][1]))
Here I am trying to generate and draw 1830 unique surfaces and this works. I then tried implementing collision detection between each block and the mouse and failed.
def collided(self, pos):
for i in range(1380):
block = self.__blocks[i].get_rect()
if block.collidepoint(pos[0], pos[1]):
print(block.x, block.y)
Then I did different tests on why it might be failing. Here is one of them. I will change a single block's color, in our case the 10th block self.__blocks[10].fill((255, 0, 0)) to red so we know which box to click on. Then we will try to check for collision for that particular block.
def testBlock(self, pos):
block = self.__blocks[10].get_rect()
if block.collidepoint(pos[0], pos[1]):
print(block.x)
And it doesn't work, but the weird thing is it works for the first block(in the 0th index) and only the first block no matter which surface I test. Any idea on how to fix this would be appreciated. The following is copy and paste code.
import pygame
pygame.init()
win = pygame.display
D = win.set_mode((1220, 600))
class Canvas:
def __init__(self):
self.__blocks = []
self.__positions = []
for i in range(1830):
self.__blocks.append(pygame.Surface((20, 20)).convert())
for y in range(30):
y *= 20
for x in range(61):
x = x* 20
self.__positions.append([x, y])
self.__color = False
self.testBlock = 10
def draw(self, window):
for i in range(1830):
self.__color = not self.__color
if self.__color:
self.__blocks[i].fill((200, 200, 200))
else:
self.__blocks[i].fill((50, 50, 50))
self.__blocks[self.testBlock].fill((255, 0, 0)) # Changing the color for testing
window.blit(self.__blocks[i], (self.__positions[i][0]
, self.__positions[i][1]))
def test(self, pos):
block = self.__blocks[self.testBlock].get_rect()
if block.collidepoint(pos[0], pos[1]):
print(block.x, block.y)
canvas = Canvas()
while True:
D.fill((0, 0, 0))
pygame.event.get()
mousepos = pygame.mouse.get_pos()
canvas.draw(D)
canvas.test(mousepos)
win.flip()
When you call .get_rect() on a Surface, it does not know its current position, because that is not Surface information. So you need to assign the location to the Rect before collision detection.
With your current code layout, you could do this during the construction. With the Canvass blocks position now held in the __rects list, the __positions list becomes superfluous.
class Canvass:
def __init__(self):
self.__blocks = []
self.__rects = []
for y in range( 30 ):
for x in range( 61 ):
self.__blocks.append(pygame.Surface((20, 20)).convert())
self.__rects.append( self.__blocks[-1].get_rect() )
self.__rects[-1].topleft = ( x, y )
self.__color = False
self.testBlock = 10
This gives you a simple test:
def collided(self, pos):
hit = False
for i in range( len( self.__rects ) ):
if ( self.__rects[i].collidepoint( pos[0], pos[1] ) ):
print( "Click on block %d" % ( i ) )
hit = True
break
return hit, i
.get_rect() gives rect with block's size but with position (0, 0)
you have real position in __positions and you would need
.get_rect(topleft=self.__positions[self.testBlock])
def test(self, pos):
block = self.__blocks[self.testBlock].get_rect(topleft=self.__positions[self.testBlock])
if block.collidepoint(pos[0], pos[1]):
print(block.x, block.y)
But it would be better to get rect and set its position at start and later not use get_rect().
You could also create class Pixel similar to class Sprite with self.image to keep surface and self.rect to keep its size and position. And then you could use Group to check collision with all pixels.
EDIT:
Example which uses class pygame.sprite.Sprite to create class Pixel and it keeps all pixels in pygame.sprite.Group
It also handle events (MOUSEBUTTONDOWN) to change color in any pixel when it is clicked.
import pygame
# --- classes ---
class Pixel(pygame.sprite.Sprite):
def __init__(self, x, y, color, width=20, height=20):
super().__init__()
self.color_original = color
self.color = color
self.image = pygame.Surface((20, 20)).convert()
self.image.fill(self.color)
self.rect = pygame.Rect(x, y, width, height)
def handle_event(self, event):
if event.type == pygame.MOUSEBUTTONDOWN:
if self.rect.collidepoint(event.pos):
if self.color != self.color_original:
self.color = self.color_original
else:
self.color = (255,0,0)
self.image.fill(self.color)
# event handled
return True
# event not handled
return False
class Canvas:
def __init__(self):
# create group for sprites
self.__blocks = pygame.sprite.Group()
# create sprites
self.__color = False
for y in range(30):
y *= 20
for x in range(61):
x *= 20
self.__color = not self.__color
if self.__color:
color = (200, 200, 200)
else:
color = (50, 50, 50)
self.__blocks.add(Pixel(x, y, color))
# changing the color for testing
self.testBlock = 10
all_sprites = self.__blocks.sprites()
block = all_sprites[self.testBlock]
block.image.fill((255, 0, 0))
def draw(self, window):
# draw all sprites in group
self.__blocks.draw(window)
def test(self, pos):
# test collision with one sprite
all_sprites = self.__blocks.sprites()
block = all_sprites[self.testBlock]
if block.rect.collidepoint(pos):
print(block.rect.x, block.rect.y)
def handle_event(self, event):
for item in self.__blocks:
if item.handle_event(event):
# don't check other pixels if event already handled
return True
# --- main ---
pygame.init()
win = pygame.display
D = win.set_mode((1220, 600))
canvas = Canvas()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
canvas.handle_event(event)
#mousepos = pygame.mouse.get_pos()
#canvas.test(mousepos)
# draws (without updates, etc)
#D.fill((0, 0, 0)) # no need clean screen if it will draw all elements again
canvas.draw(D)
win.flip()
I have tried everything I can think of to fix this, but I can't seem to find it. I know it is probably a simple fix, but I cannot find what is making this happen. This is the first part of my code :
import pygame, sys, time
from pygame.locals import *
pygame.init()
WINDOWWIDTH = 900
WINDOWHEIGHT = 400
MOVERATE = 5
screen = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
TEXTCOLOR = (255, 255, 255)
BACKGROUNDCOLOR = (0, 0, 0)
FPS = 40
clock = pygame.time.Clock()
x = 200
y = 150
class player(pygame.sprite.Sprite):
def __init__(self, x, y):
super(player, self).__init__()
temp_image = pygame.image.load("stand_down.png").convert_alpha()
self.image_down = pygame.transform.scale(temp_image, (100, 200))
temp_image = pygame.image.load("standleft.png").convert_alpha()
self.image_left = pygame.transform.scale(temp_image, (100, 200))
temp_image = pygame.image.load("standright.png").convert_alpha()
self.image_right = pygame.transform.scale(temp_image, (100, 200))
self.image = self.image_down
# keep position and size in pygame.Rect()
# to use it in collision checking
self.rect = self.image.get_rect(x=x, y=y)
def draw(self, x, y):
screen.blit(self.image, self.rect)
def handle_event(self):#, event)
self.image = self.image_down.get_rect()
self.image = pygame.Surface((x, y))
key = pygame.key.get_pressed()
if key[K_LEFT]:
self.rect.x -= 50
self.image = self.image_left
if key[K_RIGHT]:
self.rect.x += 50
self.image = self.image_right
class room1():
#bedroom
def __init__(self):
self.x, self.y = 16, WINDOWHEIGHT/2
self.speed = 3
def draw(self):
background = pygame.image.load("bedroom.jpg").convert()
background = pygame.transform.scale(background, (WINDOWWIDTH, WINDOWHEIGHT))
screen.blit(background, (0, 0))
And this is my main function :
def main():
while True:
for event in pygame.event.get():
player.handle_event.get(event)
player.handle_event(screen)
room1.draw(screen)
player.draw(screen, x, y)
pygame.display.update()
pygame.display.flip()
clock.tick(FPS)
main()
I keep getting the same error :
File "C:\Python32\Project3\proj3pt2.py", line 220, in handle_event
self.image = self.image_down.get_rect()
AttributeError: 'pygame.Surface' object has no attribute 'image_down'
I know it's probably an easy fix, but I don't know where to look for it, and how I messed up. If someone could explain that, it would be much appreciated!
When you have an instance and call one of its methods, the instance gets automatically passed as the first argument, self. So if you have a class MyClass and an instance my_instance and you call its handle_event method, it's the same as calling MyClass.handle_event(my_instance).
In your program you never create an instance of the player class and so you're passing the screen as the self argument directly to the class (the screen is actually a pygame.Surface). That means the self in the handle_event method actually refers to the screen surface and since surfaces don't have an image_down attribute, Python raises an error when the self.image_down.get_rect() part is reached.
To fix this problem, you have to create an instance (also called object) of the player class and must not pass an argument to handle_event (unless you add more parameters to the method):
player_instance = player(x_position, y_position)
Then use the instance inside of the while and event loops:
while True:
player_instance.handle_event()
You also have to create an instance of the room1 class instead of using the class directly.
Here's a complete example with some comments about other problems:
import pygame
pygame.init()
WINDOWWIDTH = 900
WINDOWHEIGHT = 400
screen = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
FPS = 40
clock = pygame.time.Clock()
# Load images once globally and reuse them in the program.
background = pygame.image.load("bedroom.jpg").convert()
background = pygame.transform.scale(background, (WINDOWWIDTH, WINDOWHEIGHT))
temp_image = pygame.image.load("stand_down.png").convert_alpha()
image_down = pygame.transform.scale(temp_image, (100, 200))
temp_image = pygame.image.load("standleft.png").convert_alpha()
image_left = pygame.transform.scale(temp_image, (100, 200))
temp_image = pygame.image.load("standright.png").convert_alpha()
image_right = pygame.transform.scale(temp_image, (100, 200))
class player(pygame.sprite.Sprite):
def __init__(self, x, y):
super(player, self).__init__()
self.image_down = image_down
self.image_left = image_left
self.image_right = image_right
self.image = self.image_down
# keep position and size in pygame.Rect()
# to use it in collision checking
self.rect = self.image.get_rect(x=x, y=y)
# You don't have to pass x and y, since you already
# use the `self.rect` as the blit position.
def draw(self, screen):
screen.blit(self.image, self.rect)
def handle_event(self):
# These two lines don't make sense.
#self.image = self.image_down.get_rect()
#self.image = pygame.Surface((x, y))
# I guess you want to switch back to image_down.
self.image = self.image_down
key = pygame.key.get_pressed()
if key[pygame.K_LEFT]:
self.rect.x -= 5
self.image = self.image_left
if key[pygame.K_RIGHT]:
self.rect.x += 5
self.image = self.image_right
class room1():
def __init__(self):
self.x, self.y = 16, WINDOWHEIGHT/2
# Reference to the background image.
self.background = background
def draw(self, screen): # Pass the screen.
screen.blit(self.background, (0, 0))
def main():
# Create player and room instances.
player_instance = player(200, 150)
room1_instance = room1()
while True:
for event in pygame.event.get():
# Users can press the "X" button to quit.
if event.type == pygame.QUIT:
return
player_instance.handle_event()
room1_instance.draw(screen)
player_instance.draw(screen)
# You don't need both update and flip.
# pygame.display.update()
pygame.display.flip()
clock.tick(FPS)
main()
pygame.quit()
Side note: PEP 8 recommends uppercase names for classes, so Player instead of player. Then you could call the instance player.
I suspect you do somewhere something like this
player = player.transform.scale(player.image)
player is Sprite but scale returns Surface - so you replace Sprite with Surface and later you have problems.
(BTW: I saw the same problem in some question few days ago)
If you have to rescale image then do it in __init__ as you already do with some images.
In real game you should create images with correct sizes using any Image Editor so you don't have to use scale()
BTW: in handle_event you do
self.image = self.image_down.get_rect()
self.image = pygame.Surface((x, y))
You assign Rect to Surface (self.image) and later you assing new empty Surface with size x, y. Surface doesn't keep positon, it uses only width, height.
You have self.rect to keep positiona and you already change it with
self.rect.x -= 50
and
self.rect.x += 50
BTW: use UpperCaseNames for classes to make code more readable
class Player(...)
class Room1(...)
Event Stackoverflow knows this rule and it uses light blue color for classes to make code more readable.
More: PEP 8 -- Style Guide for Python Code
BTW: in room1.draw() you read and rescale image again and again - it can slow down program. Do it in room.__init__
I am trying to implement a Python game (aliens.py from the PyGame package) but when I run it, I can't move my player or do any shooting...
I run it on Mac OS X, python3 and have a french keyboard. Could this have anything to do the fact that it does not take any of my keyboard commands?
On the screen of my terminal I see:
^[ (when I press esc),
^[[D (when I press the left arrow),
^[[C (when I press the right arrow),
^[[A (when I press the upwards arrow),
^[[B (when I press the downwards arrow
...
Is this normal? It does not help replacing K_RIGHT with ^[[C.
#!/usr/bin/env python
import random, os.path
#import basic pygame modules
import pygame
from pygame.locals import *
#see if we can load more than standard BMP
if not pygame.image.get_extended():
raise SystemExit("Sorry, extended image module required")
#game constants
MAX_SHOTS = 2 #most player bullets onscreen
ALIEN_ODDS = 22 #ances a new alien appears
BOMB_ODDS = 60 #chances a new bomb will drop
ALIEN_RELOAD = 12 #frames between new aliens
SCREENRECT = Rect(0, 0, 940, 480)
SCORE = 0
main_dir = os.path.split(os.path.abspath(__file__))[0]
def load_image(file):
"loads an image, prepares it for play"
file = os.path.join(main_dir, 'data', file)
try:
surface = pygame.image.load(file)
except pygame.error:
raise SystemExit('Could not load image "%s" %s'%(file, pygame.get_error()))
return surface.convert()
def load_images(*files):
imgs = []
for file in files:
imgs.append(load_image(file))
return imgs
class dummysound:
def play(self): pass
def load_sound(file):
if not pygame.mixer: return dummysound()
file = os.path.join(main_dir, 'data', file)
try:
sound = pygame.mixer.Sound(file)
return sound
except pygame.error:
print ('Warning, unable to load, %s' % file)
return dummysound()
# each type of game object gets an init and an
# update function. the update function is called
# once per frame, and it is when each object should
# change it's current position and state. the Player
# object actually gets a "move" function instead of
# update, since it is passed extra information about
# the keyboard
class Player(pygame.sprite.Sprite):
speed = 10
bounce = 24
gun_offset = -11
images = []
def __init__(self):
pygame.sprite.Sprite.__init__(self, self.containers)
self.image = self.images[0]
self.rect = self.image.get_rect(midbottom=SCREENRECT.midbottom)
self.reloading = 0
self.origtop = self.rect.top
self.facing = -1
def move(self, direction):
if direction: self.facing = direction
self.rect.move_ip(direction*self.speed, 0)
self.rect = self.rect.clamp(SCREENRECT)
if direction < 0:
self.image = self.images[0]
elif direction > 0:
self.image = self.images[1]
self.rect.top = self.origtop - (self.rect.left//self.bounce%2)
def gunpos(self):
pos = self.facing*self.gun_offset + self.rect.centerx
return pos, self.rect.top
class Alien(pygame.sprite.Sprite):
speed = 13
animcycle = 12
images = []
def __init__(self):
pygame.sprite.Sprite.__init__(self, self.containers)
self.image = self.images[0]
self.rect = self.image.get_rect()
self.facing = random.choice((-1,1)) * Alien.speed
self.frame = 0
if self.facing < 0:
self.rect.right = SCREENRECT.right
def update(self):
self.rect.move_ip(self.facing, 0)
if not SCREENRECT.contains(self.rect):
self.facing = -self.facing;
self.rect.top = self.rect.bottom + 1
self.rect = self.rect.clamp(SCREENRECT)
self.frame = self.frame + 1
self.image = self.images[self.frame//self.animcycle%3]
class Explosion(pygame.sprite.Sprite):
defaultlife = 12
animcycle = 3
images = []
def __init__(self, actor):
pygame.sprite.Sprite.__init__(self, self.containers)
self.image = self.images[0]
self.rect = self.image.get_rect(center=actor.rect.center)
self.life = self.defaultlife
def update(self):
self.life = self.life - 1
self.image = self.images[self.life//self.animcycle%2]
if self.life <= 0: self.kill()
class Shot(pygame.sprite.Sprite):
speed = -11
images = []
def __init__(self, pos):
pygame.sprite.Sprite.__init__(self, self.containers)
self.image = self.images[0]
self.rect = self.image.get_rect(midbottom=pos)
def update(self):
self.rect.move_ip(0, self.speed)
if self.rect.top <= 0:
self.kill()
class Bomb(pygame.sprite.Sprite):
speed = 9
images = []
def __init__(self, alien):
pygame.sprite.Sprite.__init__(self, self.containers)
self.image = self.images[0]
self.rect = self.image.get_rect(midbottom=
alien.rect.move(0,5).midbottom)
def update(self):
self.rect.move_ip(0, self.speed)
if self.rect.bottom >= 470:
Explosion(self)
self.kill()
class Score(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.font = pygame.font.Font(None, 20)
self.font.set_italic(1)
self.color = Color('white')
self.lastscore = -1
self.update()
self.rect = self.image.get_rect().move(10, 450)
def update(self):
if SCORE != self.lastscore:
self.lastscore = SCORE
msg = "Score: %d" % SCORE
self.image = self.font.render(msg, 0, self.color)
def main(winstyle = 0):
# Initialize pygame
pygame.init()
if pygame.mixer and not pygame.mixer.get_init():
print ('Warning, no sound')
pygame.mixer = None
# Set the display mode
winstyle = 0 # |FULLSCREEN
bestdepth = pygame.display.mode_ok(SCREENRECT.size, winstyle, 32)
screen = pygame.display.set_mode(SCREENRECT.size, winstyle, bestdepth)
#Load images, assign to sprite classes
#(do this before the classes are used, after screen setup)
img = load_image('player1.gif')
Player.images = [img, pygame.transform.flip(img, 1, 0)]
img = load_image('explosion1.gif')
Explosion.images = [img, pygame.transform.flip(img, 1, 1)]
Alien.images = load_images('alien1.gif', 'alien2.gif', 'alien3.gif')
Bomb.images = [load_image('bomb.gif')]
Shot.images = [load_image('shot.gif')]
#decorate the game window
icon = pygame.transform.scale(Alien.images[0], (32, 32))
pygame.display.set_icon(icon)
pygame.display.set_caption('Pygame Aliens')
pygame.mouse.set_visible(0)
#create the background, tile the bgd image
bgdtile = load_image('background.gif')
background = pygame.Surface(SCREENRECT.size)
for x in range(0, SCREENRECT.width, bgdtile.get_width()):
background.blit(bgdtile, (x, 0))
screen.blit(background, (0,0))
pygame.display.flip()
#load the sound effects
boom_sound = load_sound('boom.wav')
shoot_sound = load_sound('car_door.wav')
if pygame.mixer:
music = os.path.join(main_dir, 'data', 'house_lo.wav')
pygame.mixer.music.load(music)
pygame.mixer.music.play(-1)
# Initialize Game Groups
aliens = pygame.sprite.Group()
shots = pygame.sprite.Group()
bombs = pygame.sprite.Group()
all = pygame.sprite.RenderUpdates()
lastalien = pygame.sprite.GroupSingle()
#assign default groups to each sprite class
Player.containers = all
Alien.containers = aliens, all, lastalien
Shot.containers = shots, all
Bomb.containers = bombs, all
Explosion.containers = all
Score.containers = all
#Create Some Starting Values
global score
alienreload = ALIEN_RELOAD
kills = 0
clock = pygame.time.Clock()
#initialize our starting sprites
global SCORE
player = Player()
Alien() #note, this 'lives' because it goes into a sprite group
if pygame.font:
all.add(Score())
while player.alive():
#get input
for event in pygame.event.get():
if event.type == QUIT or \
(event.type == KEYDOWN and event.key == K_ESCAPE):
return
keystate = pygame.key.get_pressed()
# clear/erase the last drawn sprites
all.clear(screen, background)
#update all the sprites
all.update()
#handle player input
direction = keystate[K_RIGHT] - keystate[K_LEFT]
player.move(direction)
firing = keystate[K_SPACE]
if not player.reloading and firing and len(shots) < MAX_SHOTS:
Shot(player.gunpos())
shoot_sound.play()
player.reloading = firing
# Create new alien
if alienreload:
alienreload = alienreload - 1
elif not int(random.random() * ALIEN_ODDS):
Alien()
alienreload = ALIEN_RELOAD
# Drop bombs
if lastalien and not int(random.random() * BOMB_ODDS):
Bomb(lastalien.sprite)
# Detect collisions
for alien in pygame.sprite.spritecollide(player, aliens, 1):
boom_sound.play()
Explosion(alien)
Explosion(player)
SCORE = SCORE + 1
player.kill()
for alien in pygame.sprite.groupcollide(shots, aliens, 1, 1).keys():
boom_sound.play()
Explosion(alien)
SCORE = SCORE + 1
for bomb in pygame.sprite.spritecollide(player, bombs, 1):
boom_sound.play()
Explosion(player)
Explosion(bomb)
player.kill()
#draw the scene
dirty = all.draw(screen)
pygame.display.update(dirty)
#cap the framerate
clock.tick(40)
if pygame.mixer:
pygame.mixer.music.fadeout(1000)
pygame.time.wait(1000)
pygame.quit()
#call the "main" function if running this script
if __name__ == '__main__': main()
Event KEYDOWN sends values unicode, key, mod and you can display it to see what codes (numeric values) uses your keyboard.
if event.type == KEYDOWN;
print('key:', event.key)
print('unicode:', event.uniconde)
print('mod:', event.mod)
And then you can use them to test your keys and move objects.
It is surely due to keyboard keys problem , just go to this documentation and find out proper keyword for your respective keyboard button:
"https://www.pygame.org/docs/ref/key.html"
I've been banging my head against this for a while. I am trying to make a game with PyGame and I got up to the collision segment and have been stuck for a while and have checked a few threads.
This is the code I have (removed other methods and conditional statements in between, but left the relevant parts). I am a little confused by the error because I do a self.imageRect = self.image.get_rect() in both classes init, yet I have this error. The error was specifically:
"AttributeError: 'Pebble' object has no attribute 'rect'" when the program attempts to carry out the collision detection part in the dog class. What have I been doing wrong?
import random
import pygame, sys
pygame.init()
clock = pygame.time.Clock() # fps clock
screenSize = WIDTH, HEIGHT = [800, 600]
screen = pygame.display.set_mode(screenSize)
background = pygame.Surface(screen.get_size())
bgColorRGB = [153, 204, 255]
background.fill(bgColorRGB)
pebbleGroup = pygame.sprite.Group()
pebbleSingle = pygame.sprite.GroupSingle()
dogSingle = pygame.sprite.GroupSingle()
#----------------------------------------------------------------------
class Dog(pygame.sprite.Sprite):
def __init__(self, path, speed):
pygame.sprite.Sprite.__init__(self) #call Sprite initializer
self.image = pygame.image.load(path) # load sprite from path/file loc.
self.imageRect = self.image.get_rect() # get bounds of image
self.imageWidth = self.image.get_width()
self.imageHeight = self.image.get_height()
self.speed = speed
# sets location of the image, gets the start location of object
# sets the start location as the image's left and top location
def setLocation(self, location):
self.imageRect.left, self.imageRect.top = location
def checkCollision(self, pebble, dogGroup):
if pygame.sprite.spritecollide(pebble, dogGroup, False):
print "collided"
#---------------------------------------------------------------------
class Pebble(pygame.sprite.Sprite):
def __init__(self, path, speed, location):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(path)
self.imageRect = self.image.get_rect()
self.imageWidth = self.image.get_width()
self.imageHeight = self.image.get_height()
self.imageRect.left, self.imageRect.top = location
self.speed = speed # initialize speed
self.isDragged = False
#----------------------------------------------------------------------
def startGame():
pebblePaths = ['images/pebble/pebble1.jpg', 'images/pebble/pebble2.jpg']
for i in range(3):
pebblePath = pebblePaths[random.randrange(0, len(pebblePaths))]
pebbleSpeed = [random.randrange(1, 7), 0]
pebbleLocation = [0, random.randrange(20, HEIGHT - 75)]
pebble = Pebble(pebblePath, pebbleSpeed, pebbleLocation)
pebbleGroup.add(pebble)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for pebble in pebbleGroup:
dog.checkCollision(pebble, dogSingle)
pygame.display.flip()
clock.tick(30) # wait a little before starting again
startGame()
It's expecting Sprite.rect, so change from
self.imageRect = self.image.get_rect()
#to
self.rect = self.image.get_rect()
Note
self.imageWidth = self.image.get_width()
self.imageHeight = self.image.get_height()
self.imageRect.left, self.imageRect.top = location
These are not necessary, since rect's have many properties, like self.rect.width or self.rect.topleft = location . Another useful one is centerx.
full list at: pygame Rect docs