Pygame - add border to sprite - pygame

I'm a bit confused. I have a class that inherits from the pygame.sprite.Sprite class. I want to add a border around the sprite when it shows on the screen, but the border does not appear
class Bird(pygame.sprite.Sprite):
def __init__(self, filename, x, y):
super().__init__()
self.image = pygame.image.load('bird.png')
self.rect = self.image.get_rect(topleft = (x, y))
def draw(self, screen):
pygame.draw.rect(screen, red, self.rect, 2)
When I call the sprite in the game loop, no border appears
bird_group.draw(screen)
bird_group.update()
I guess I'm doing something wrong but not sure what!

pygame.sprite.Group.draw does not call the draw method of the Sprite. [pygame.sprite.Group.draw() uses the image and rect attributes of the contained pygame.sprite.Sprites to draw the objects — you have to ensure that the pygame.sprite.Sprites have the required attributes. See pygame.sprite.Group.draw():
Draws the contained Sprites to the Surface argument. This uses the Sprite.image attribute for the source surface, and Sprite.rect. [...]
Therefore you must draw the rectangle on the image:
class Bird(pygame.sprite.Sprite):
def __init__(self, filename, x, y):
super().__init__()
self.image = pygame.image.load('bird.png')
self.rect = self.image.get_rect(topleft = (x, y))
pygame.draw.rect(self.image, red, self.image.get_rect(), 2) # image.get_rect() not rect!
The border is drawn on self.image. It can't be removed. You have to use 2 images and switch the images:
class Bird(pygame.sprite.Sprite):
def __init__(self, filename, x, y):
super().__init__()
self.image_no_border = pygame.image.load('bird.png')
self.image_border = self.image.copy()
pygame.draw.rect(self.image_border, red, self.image.get_rect(), 2)
self.border = True
self.image = self.image_border if self.border else self.image_no_border
self.rect = self.image.get_rect(topleft = (x, y))
def click(self, pos):
if self.rect.collidepoint(pos):
self.border = not self.border
self.image = self.image_border if self.border else self.image_no_border

Related

Is it possible to resize objects in pygame based on screen_with and screen height

import pygame
from pygame.sprite import Sprite
import random
class Alien(Sprite):
"""A class to represent a single alien in the fleet"""
def __init__(self, ai_settings, screen):
"""Initialize the alien and set its starting position."""
super().__init__()
self.screen = screen
self.ai_settings = ai_settings
# load the alien image and set its rect attribute.
image_number = random.randint(1, 4)
if image_number == 1:
self.image = pygame.image.load('images/alien.png').convert_alpha()
infoObject = pygame.display.Info()
self.image = pygame.transform.scale(self.image, (infoObject.current_w / 10,infoObject.current_h / 10))
if image_number == 2:
self.image = pygame.image.load('images/alien 2.png').convert_alpha()
infoObject = pygame.display.Info()
self.image = pygame.transform.scale(self.image, (infoObject.current_w / 10,infoObject.current_h / 10))
if image_number == 3:
self.image = pygame.image.load('images/alien 3.png').convert_alpha()
infoObject = pygame.display.Info()
self.image = pygame.transform.scale(self.image, (infoObject.current_w / 10,infoObject.current_h / 10))
if image_number == 4:
self.image = pygame.image.load('images/alien 4.png').convert_alpha()
infoObject = pygame.display.Info()
self.image = pygame.transform.scale(self.image, (infoObject.current_w / 10,infoObject.current_h / 10))
if image_number == 5:
self.image = pygame.image.load('images/alien.png').convert_alpha()
infoObject = pygame.display.Info()
self.image = pygame.transform.scale(self.image, (infoObject.current_w / 10,infoObject.current_h / 10))
self.rect = self.image.get_rect()
With this code I randomize my aliens but i often switch between displays and this causes the amount of aliens to differ from screen to screen. So i was wondering if it is possible to change the size of the aliens based on the screen size. Currently im getting the error "integer argument expected, got float"
pygame.transform.scale takes a size parameter, which is (width, height). This width and height must be integers.
So in your case, instead of:
self.image = pygame.transform.scale(self.image, (infoObject.current_w / 10,infoObject.current_h / 10))
You could do: (cast the results into integers)
self.image = pygame.transform.scale(self.image, (int(infoObject.current_w / 10), int(infoObject.current_h / 10)))
However, there is a simpler way. Python has integer division built in, which achieves the same result are dividing then casting. This type of division uses two "/" characters and always outputs integers.
self.image = pygame.transform.scale(self.image, (infoObject.current_w // 10), infoObject.current_h // 10))
From a program design perspective, it's a bit gnarly to create a display Info object in every if statement, and recalculate the size every time. There's also a lot of other general repetition.
Here's my attempt at a cleaner section:
# load the alien image and set its rect attribute.
image_number = random.randint(1, 4)
self.image = pygame.image.load(f"images/alien {image_number}.png") # this does require you to rename "alien.png" to "alien 1.png"
w, h = screen.get_size() # you already have the screen surface, you can get size that way
self.image = pygame.transform.scale(self.image, (w//10, h//10))
Also, it doesn't affect you at all now, but I just so happened to have added float support to pygame.transform.scale and pygame.transform.smoothscale the other day, in https://github.com/pygame/pygame/pull/2581. So in the future, the integer division part may be unnecessary.

How do you fix: "AttributeError: 'Boundary' object has no attribute 'rect'"?

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)

pygame position error

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.

Moving a Sprite Class from outside of the original Class in pygame

Currently I am making a small game in pygame, I was wondering if there is a way to move the class from outside of the original class function. Currently it looks like:
class Player(pygame.sprite.Sprite):
def __init__(self,):
super().__init__()
self.image = Playerimg
self.rect = self.image.get_rect()
self.image = pygame.transform.scale(Playerimg,(42,42))
self.startposx = 298
self.startposy = 358
self.rect.x = self.startposx
self.rect.y = self.startposy
def update(self):
..... etc
Is there a way of making it so that you can change the value of self.rect.x and self.rect.y from outside the function?
Thanks
Yes. Once you created an instance of your class, e.g.:
player = Player()
you can simply access it fields, like
player.rect.x = 14
or
player.rect.move_ip(20, 3)

Space invaders clone issue

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.