I'm learning how to code games in pygame and I wrote a simple pygame code that loads a background and draws a player sprite. I drew the background image, only to draw the player afterwards, so the image doesn't overlap with the player image, and then called pygame.display.flip() to flip the screen. It still doesn't work, why? I pasted the images used below
import pygame
pygame.init()
black = (0, 0, 0)
width = 800
height = 600
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
FPS = 60
background_img = pygame.image.load("environment_forest_alt1.png")
backgroundimg_rect = background_img.get_rect()
player_img_idle = pygame.image.load("adventurer-idle-00.png")
player_img_run = pygame.image.load("adventurer-run-00.png")
player_img_attack = pygame.image.load("adventurer-attack1-01.png")
player_img_attack2 = pygame.image.load("adventurer-attack1-02.png")
player_img_attack3 = pygame.image.load("adventurer-attack1-03.png")
player_img_attack4 = pygame.image.load("adventurer-attack1-04.png")
player_img_attacks = [player_img_attack, player_img_attack2, player_img_attack3, player_img_attack4]
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.transform.scale(player_img_idle, (200, 100))
self.image.set_colorkey(black)
self.rect = self.image.get_rect()
self.rect.x = 10
self.rect.y = height - 10
all_sprites = pygame.sprite.Group()
player = Player()
all_sprites.add(player)
exitGame = False
while not exitGame:
clock.tick(FPS)
screen.blit(background_img, backgroundimg_rect)
for event in pygame.event.get():
if event.type == pygame.QUIT:
exitGame = True
all_sprites.draw(screen)
pygame.display.flip()
pygame.quit()
quit()
You just draw the player sprite mostly outside the screen. You would see it if the player image had not so much empty space at the top.
Just change the line
self.rect.y = height - 10
to
self.rect.y = height - 100
or even
self.rect.y = height - self.rect.height
Related
I am making a platformer game where there is a boundary in the beginning of the level, so the player can't just keep going to the left for no reason. I decided to make a class called boundary and add it into a list where the rules are you can't pass it. However, I keep getting this error:
"AttributeError: 'Boundary' object has no attribute 'rect'". Can anybody fix this? Also, a better way to do this would also be accepted. Thanks!
class Boundary(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.boundary = pygame.Surface([15,600])
self.boundary.fill(WHITE)
self.boundary.set_colorkey(WHITE)
self.boundary_rect =
self.boundary.get_rect()
self.boundary_rect.x = -50
self.boundary_rect.y = 0
class Level01(Level):
def __init__(self, player):
Level.__init__(self, player)
level_boundary = [Boundary()]
for _ in level_boundary:
boundary = Boundary()
boundary.player = self.player
self.platform_boundary_list.add
(boundary)
class Player(pygame.sprite.Sprite):
def__init__(self):
super().init()
self.rect.x += self.change_x
block_hit_list = pygame.sprite.spritecollide(self,
self.level.platform_boundary_list, False)
for block in block_hit_list:
if self.change_x > 0:
self.rect.right = block.rect.left
elif self.change_x < 0:
self.rect.left = block.rect.right
self.rect.y += self.change_y
block_hit_list = pygame.sprite.spritecollide(self,
self.level.platform_boundary_list, False)
for block in block_hit_list:
if self.change_y > 0:
self.rect.bottom = block.rect.top
elif self.change_y < 0:
self.rect.top = block.rect.bottom
self.change_y = 0
Haven't ran the code, but the error message seems reasonable. Your Boundary class has a property, boundary_rect rather than rect (which doesn't appear to be directly exposed by pygame's Sprite class). Replacing block.rect with block.boundary_rect should correct this.
Update:
Looking through your code, I saw a few issues, with both the Player and the Boundary classes referring to rect properties that did not directly belong their parent, pygame.sprite.Sprite. Based on your comments, I decided to rewrite the code into a demo collision test to not only fix the errors but also provide some ideas for how you could consider organizing your code.
The demo is pretty simple; a player and a bunch of random blocks are drawn to the screen. The player block bounces around the edges of the screen, and the colliding blocks are redrawn in a different color. The results look like this:
Here is the code for the above demo. I added a bunch of comments to clarify what the code does. If anything is unclear, let me know:
import random
import pygame
from pygame.rect import Rect
from pygame.sprite import Sprite
from pygame.surface import Surface
class Block(Sprite):
def __init__(self, rect):
super().__init__()
self.idle_color = (255, 255, 255, 255)#white - if not colliding
self.hit_color = (0, 255, 0, 255)#green - if colliding
self.image = Surface((rect.w, rect.h))
self.color = self.idle_color#default
#Do NOT set color here, decided by collision status!
self.rect = rect
class Player(Sprite):
def __init__(self, rect):
super().__init__()
self.color = (255, 0, 0, 255)#red
self.image = Surface((rect.w, rect.h))
self.image.fill(self.color)
self.rect = rect
class Level(object):
def __init__(self, screen, player, blocks):
self.color = (20, 20, 20, 255)#gray background
self.screen = screen
self.player = player
self.blocks = blocks
#hard-coded player x and y speed for bounding around
self.player_speed_x = 1
self.player_speed_y = 1
#Bounces player off the screen edges
#Simply dummy method - no collisions here!
def move_player(self):
p_rect = self.player.rect
s_rect = self.screen.get_rect()
if p_rect.right >= s_rect.right or p_rect.left <= s_rect.left:
self.player_speed_x *= -1
if p_rect.top <= s_rect.top or p_rect.bottom >= s_rect.bottom:
self.player_speed_y *= -1
p_rect.move_ip(self.player_speed_x, self.player_speed_y)#modifies IN PLACE!
def handle_collisions(self):
#First set all blocks to default color
for block in self.blocks:
block.color = block.idle_color
hit_blocks = pygame.sprite.spritecollide(self.player, self.blocks, False)
for block in hit_blocks:
block.color = block.hit_color
#Clear screen with background color, then draw blocks, then draw player on top!
def draw(self):
self.screen.fill(self.color)
for block in self.blocks:
#update fill to color decided by handle_collisions function...
block.image.fill(block.color)
self.screen.blit(block.image, block.rect)
self.screen.blit(self.player.image, self.player.rect)
def update(self):
self.move_player()
self.handle_collisions()
self.draw()
if __name__ == "__main__":
pygame.init()
width = 400
height = 300
fps = 60
title = "Collision Test"
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption(title)
clock = pygame.time.Clock()
running = True
#Create a player
player_size = 20
player_x = random.randint(0, width - player_size)
player_y = random.randint(0, height - player_size)
player_rect = Rect(player_x, player_y, player_size, player_size)
player = Player(player_rect)
#Create some random blocks
blocks = []
num_blocks = 50
for i in range(num_blocks):
block_size = 20
block_x = random.randint(0, width - block_size)
block_y = random.randint(0, height - block_size)
block_rect = Rect(block_x, block_y, block_size, block_size)
block = Block(block_rect)
blocks.append(block)
#Create the level
level = Level(screen, player, blocks)
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
level.update()
pygame.display.update()
clock.tick(fps)
I have a group of rects, they display in a row. I want them to change their colour when they have been clicked, until they are clicked again
I have this code so far to create the sprites:
class DrawableRect(pygame.sprite.Sprite):
def __init__(self,color,width,height,value=0):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(color)
self.rect = self.image.get_rect()
self.value = value
self.x = 0
self.y = 0
def change_value(self,color,value):
self.image.fill(color)
self.value=value
def DrawRects(start_x, start_y, rect_spacing, colour_list):
current_x_pos = start_x
for rect_num in range(0,8):
rect = DrawableRect(colour_list[rect_num], boxW, boxH)
rect.rect.x = current_x_pos
rect.rect.y = start_y
current_x_pos = current_x_pos + rect.rect.width + rect_spacing
rects.add(rect)
rects.draw(screen)
The idea of the app is for each rectangle to represent a bit, and when pressed it alternates between 0 and 1, the makeup of each bit displays the decimal equivalent somewhere.
I read that groups are unordered therefore indexing wouldn't work, is that true?
Here's an example I've modified to suit your purposes. I have a bunch of sprites (coloured rectangles) in a sprite group and I change* the colour of any sprite that collides with the mouse pointer when a mouse button is pressed.
Here's the code, you're probably most interested in the change_color() method and the MOUSEBUTTONUP event handling code.
import random
import pygame
screen_width, screen_height = 640, 480
def get_random_position():
"""return a random (x,y) position in the screen"""
return (random.randint(0, screen_width - 1), #randint includes both endpoints.
random.randint(0, screen_height - 1))
color_list = ["red", "orange", "yellow", "green", "cyan", "blue", "blueviolet"]
colors = [pygame.color.Color(c) for c in color_list]
class PowerUp(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
width, height = 64, 32
self.image = pygame.Surface([width, height])
self.clicked = False # track whether we've been clicked or not
# initialise color
self.color = random.choice(colors)
self.image.fill(self.color)
# Fetch the rectangle object that has the dimensions of the image
self.rect = self.image.get_rect()
# then move to a random position
self.update()
def update(self):
#move to a random position
self.rect.center = get_random_position()
def random_color(self):
# randomise color
self.clicked = not self.clicked
if self.clicked:
color = random.choice(colors)
else:
color = self.color
self.image.fill(color)
if __name__ == "__main__":
pygame.init()
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Sprite Color Switch Demo')
clock = pygame.time.Clock() #for limiting FPS
FPS = 60
exit_demo = False
pygame.key.set_repeat(300, 200)
#create a sprite group to track the power ups.
power_ups = pygame.sprite.Group()
for _ in range(10):
power_ups.add(PowerUp()) # create a new power up and add it to the group.
# main loop
while not exit_demo:
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit_demo = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
exit_demo = True
elif event.key == pygame.K_SPACE:
power_ups.update()
elif event.type == pygame.MOUSEBUTTONUP:
# check for collision
for p in power_ups:
if p.rect.collidepoint(event.pos): # maybe use event?
p.random_color()
screen.fill(pygame.Color("black")) # use black background
power_ups.draw(screen)
pygame.display.update()
clock.tick(FPS)
pygame.quit()
quit()
Let me know if you have any questions. Obviously this doesn't do row alignment of the sprites, I think you have a handle on that. I would suggest that you have all of your screen drawing operations in one place so your code can be clearer.
*The new colour is randomised from a short list, so there's a 14% chance it won't change from the starting colour.
First of all ,I loaded a picture of the ship and initialized its location. thereafter I add bullet to my program. After that, I found that no matter how I debug it, it can't be in the right place.
# 1. - import library
import pygame,sys
from pygame.locals import *
from pygame.sprite import Sprite
class Player(Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load('image/pig.bmp')
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
class Bullet(Sprite):
def __init__(self, player):
super().__init__()
self.rect = pygame.Rect(0, 0, bullet_width, bullet_height )
self.color = bullet_color
self.rect.center = player.rect.center
self.rect.left = player.rect.right
# 2. - Initialize the game
pygame.init()
width,height = 800,600
screen = pygame.display.set_mode((width,height))
keys = [False,False,False,False]
playerpos = [0,288]
bullet_width = 15
bullet_height = 6
bullet_color = (200, 200 , 0)
player = Player()
bullet = Bullet(player)
grass = pygame.image.load("image/bg.bmp")
# 4. - keep looping through
while True:
# 5. - clear the screen before drawing it again.
screen.fill(0)
# 6. - Draw the screen elements.
screen.blit(grass,(0,0))
screen.blit(player.image, playerpos)
pygame.draw.rect(screen, bullet.color, bullet.rect)
# 7. - update the screen
pygame.display.flip()
# 8. - loop through the events
for event in pygame.event.get():
# check if the event is the X button.
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
and why bullet appear in top-left
enter image description here
I hope bullet appear in ship's right side,but I can't do it if I don't use coordinate(x,y),how can I do it?
You are drawing the ship in a position unrelated to its rect's position, using playerpos. You need to make the link the ship's position linked to its rect, so that the bullet can access it:
# 1. - import library
import pygame,sys
from pygame.locals import *
from pygame.sprite import Sprite
class Player(Sprite):
def __init__(self):
super().__init__()
self.image = pygame.image.load('image/pig.bmp')
self.image.fill((255, 0, 0))
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
class Bullet(Sprite):
def __init__(self, player):
super().__init__()
self.rect = pygame.Rect(0, 0, bullet_width, bullet_height )
self.color = bullet_color
self.rect.center = player.rect.center
self.rect.left = player.rect.right
# 2. - Initialize the game
pygame.init()
width,height = 800,600
screen = pygame.display.set_mode((width,height))
keys = [False,False,False,False]
bullet_width = 15
bullet_height = 6
bullet_color = (200, 200 , 0)
player = Player()
player.rect.topleft = [0,288]
bullet = Bullet(player)
grass = pygame.image.load("image/bg.bmp")
# 4. - keep looping through
while True:
# 5. - clear the screen before drawing it again.
screen.blit(grass, (0, 0))
# 6. - Draw the screen elements.
screen.blit(player.image, player.rect.topleft)
pygame.draw.rect(screen, bullet.color, bullet.rect)
# 7. - update the screen
pygame.display.flip()
# 8. - loop through the events
for event in pygame.event.get():
# check if the event is the X button.
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
This is because a surface's get_rect() method has no idea where the surface is going to be blitted on to another surface, so it just gives its position as (0, 0). get_rect() is only useful for obtaining a surface's dimensions.
As the background scrolls along the side the the end of the image stretches and the rest of the image doesn't appear just the same hill elongated. It then suddenly resets. Also a portion of the image (a rectangle)is displaced from the rest of the image making it uneven and stays like that until it disappears from view.
Here is the code I use to side scroll
import pygame, sys, time, random
from pygame.locals import *
class Background(pygame.sprite.Sprite): #Creates space background
def __init__(self, image_file, location):
pygame.sprite.Sprite.__init__(self) #call Sprite initializer
self.bgimage = pygame.image.load(image_file)
self.bgimage = pygame.transform.scale(self.bgimage, (1333, 600))
self.rectBGimg = self.bgimage.get_rect()
self.bgY1 = 0
self.bgX1 = 0
self.bgY2 = 0
self.bgX2 = self.rectBGimg.width
self.movingUpSpeed = 5
def update(self):
self.bgX1 -= self.movingUpSpeed
self.bgX2 -= self.movingUpSpeed
if self.bgX1 <= -self.rectBGimg.height:
self.bgX1 = self.rectBGimg.height
if self.bgX2 <= -self.rectBGimg.height:
self.bgY2 = self.rectBGimg.height
def render(self):
screen.blit(self.bgimage, (self.bgX1, self.bgY1))
screen.blit(self.bgimage, (self.bgX2, self.bgY2))
pygame.init()
FPS = 15 # frames per second setting
clock = pygame.time.Clock()
window_width = 1333
window_height = 600
# set up the window
screen = pygame.display.set_mode((window_width, window_height))
pygame.display.set_caption('Deterred Journey')
BackGround = Background('scrollingBackground.png', [0,0])
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#Adds images and text
BackGround.render()
BackGround.update()
pygame.display.flip()
pygame.display.update()
clock.tick(30)
pygame.quit()
sys.exit()
Looks like my update function was checking bgX1 and bgX2 against height causing displacement and I set self.bgY2 = self.rectBGimg.heightcausing elongation. Since it's scrolling vertically bgX2 must be set to the width
updated function should look like
def update(self):
self.bgX1 -= self.movingUpSpeed
self.bgX2 -= self.movingUpSpeed
if self.bgX1 <= -self.rectBGimg.width:
self.bgX1 = self.rectBGimg.width
if self.bgX2 <= -self.rectBGimg.width:
self.bgX2 = self.rectBGimg.width
i am working on something like Space Invaders since i just started to learn programing i try to keep it simple, what i want is enemy ships coming from top of the screen and then settling in one line.I managed to make them coming from top at some speed but i dont know how to make them stop at a line,for example at y = 40.The code is below:
# Sprites vjezba.py
import pygame
# Define colors
black = (0,0,0)
white = (255,255,255)
red = (255,0,0)
green = (0,0,255)
# Define screen size
SCREEN_WIDTH = 420
SCREEN_HEIGHT = 400
# Classes
class Square(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([20,20])
self.image.fill(red)
self.rect = self.image.get_rect()
def update(self):
pos = pygame.mouse.get_pos()
self.rect.x = pos[0]
self.rect.y = pos[1]
class Enemies(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([20,20])
self.image.fill(black)
self.rect = self.image.get_rect()
def update(self):
speed_y = 1
self.rect.y += speed_y
# Initialize pygame
pygame.init()
# Initialize screen
screen = pygame.display.set_mode([SCREEN_WIDTH,SCREEN_HEIGHT])
# Set the clock
clock = pygame.time.Clock()
# Create sprites lists
square_list = pygame.sprite.Group()
enemies_list = pygame.sprite.Group()
all_sprites_list = pygame.sprite.Group()
# Create sprites
#--- Enemies sprites
diff_x = 0
diff_y = 0
for i in range(10):
enemies = Enemies()
enemies.rect.x = 20 + diff_x
diff_x += 40
enemies.rect.y = 20 - diff_y
diff_y += 20
enemies_list.add(enemies)
all_sprites_list.add(enemies)
# --- Square sprite
square = Square()
square.rect.x = 200
square.rect.y = 370
square_list.add(square)
all_sprites_list.add(square)
# -------Main Loop----------
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
all_sprites_list.update()
screen.fill(white)
all_sprites_list.draw(screen)
pygame.display.flip()
clock.tick(40)
pygame.quit()
At the moment, your update() for the Enemies looks like this:
def update(self):
speed_y = 1
self.rect.y += speed_y
This has two obvious flaws:
The speed is set locally, then discarded again at the end of the method; and
It doesn't know anything about position.
To fix this, I suggest making speed_y an instance attribute:
def __init__(self):
...
self.speed_y = 1
Allowing the setting of a target position
def set_target(y_pos):
self.y_target = y_pos
And using this information in update, for example:
def update(self):
self.rect.y += self.speed_y
if self.rect.y >= self.y_target:
self.speed_y = 0
This is a very basic example that just stops at the target y (and only works in one dimension), but hopefully gives you an idea of how to control the movement of your Enemies.