Pygame limiting collision targets [duplicate] - python-3.x

I have made a list of bullets and a list of sprites using the classes below. How do I detect if a bullet collides with a sprite and then delete that sprite and the bullet?
#Define the sprite class
class Sprite:
def __init__(self,x,y, name):
self.x=x
self.y=y
self.image = pygame.image.load(name)
self.rect = self.image.get_rect()
def render(self):
window.blit(self.image, (self.x,self.y))
# Define the bullet class to create bullets
class Bullet:
def __init__(self,x,y):
self.x = x + 23
self.y = y
self.bullet = pygame.image.load("user_bullet.BMP")
self.rect = self.bullet.get_rect()
def render(self):
window.blit(self.bullet, (self.x, self.y))

In PyGame, collision detection is done using pygame.Rect objects. The Rect object offers various methods for detecting collisions between objects. Even the collision between a rectangular and circular object such as a paddle and a ball can be detected by a collision between two rectangular objects, the paddle and the bounding rectangle of the ball.
Some examples:
pygame.Rect.collidepoint:
Test if a point is inside a rectangle
repl.it/#Rabbid76/PyGame-collidepoint
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(100, 100)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
point = pygame.mouse.get_pos()
collide = rect.collidepoint(point)
color = (255, 0, 0) if collide else (255, 255, 255)
window.fill(0)
pygame.draw.rect(window, color, rect)
pygame.display.flip()
pygame.quit()
exit()
pygame.Rect.colliderect
Test if two rectangles overlap
See also How to detect collisions between two rectangular objects or images in pygame
repl.it/#Rabbid76/PyGame-colliderect
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
rect1 = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
rect2 = pygame.Rect(0, 0, 75, 75)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
rect2.center = pygame.mouse.get_pos()
collide = rect1.colliderect(rect2)
color = (255, 0, 0) if collide else (255, 255, 255)
window.fill(0)
pygame.draw.rect(window, color, rect1)
pygame.draw.rect(window, (0, 255, 0), rect2, 6, 1)
pygame.display.flip()
pygame.quit()
exit()
Furthermore, pygame.Rect.collidelist and pygame.Rect.collidelistall can be used for the collision test between a rectangle and a list of rectangles. pygame.Rect.collidedict and pygame.Rect.collidedictall can be used for the collision test between a rectangle and a dictionary of rectangles.
The collision of pygame.sprite.Sprite and pygame.sprite.Group objects, can be detected by pygame.sprite.spritecollide(), pygame.sprite.groupcollide() or pygame.sprite.spritecollideany(). When using these methods, the collision detection algorithm can be specified by the collided argument:
The collided argument is a callback function used to calculate if two sprites are colliding.
Possible collided callables are collide_rect, collide_rect_ratio, collide_circle, collide_circle_ratio, collide_mask
Some examples:
pygame.sprite.spritecollide()
repl.it/#Rabbid76/PyGame-spritecollide
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
sprite1 = pygame.sprite.Sprite()
sprite1.image = pygame.Surface((75, 75))
sprite1.image.fill((255, 0, 0))
sprite1.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
sprite2 = pygame.sprite.Sprite()
sprite2.image = pygame.Surface((75, 75))
sprite2.image.fill((0, 255, 0))
sprite2.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(75, 75)
all_group = pygame.sprite.Group([sprite2, sprite1])
test_group = pygame.sprite.Group(sprite2)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
sprite1.rect.center = pygame.mouse.get_pos()
collide = pygame.sprite.spritecollide(sprite1, test_group, False)
window.fill(0)
all_group.draw(window)
for s in collide:
pygame.draw.rect(window, (255, 255, 255), s.rect, 5, 1)
pygame.display.flip()
pygame.quit()
exit()
For a collision with masks, see How can I make a collision mask? or Pygame mask collision
See also Collision and Intersection
pygame.sprite.spritecollide() / collide_circle
repl.it/#Rabbid76/PyGame-spritecollidecollidecircle
import pygame
pygame.init()
window = pygame.display.set_mode((250, 250))
sprite1 = pygame.sprite.Sprite()
sprite1.image = pygame.Surface((80, 80), pygame.SRCALPHA)
pygame.draw.circle(sprite1.image, (255, 0, 0), (40, 40), 40)
sprite1.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(80, 80)
sprite1.radius = 40
sprite2 = pygame.sprite.Sprite()
sprite2.image = pygame.Surface((80, 89), pygame.SRCALPHA)
pygame.draw.circle(sprite2.image, (0, 255, 0), (40, 40), 40)
sprite2.rect = pygame.Rect(*window.get_rect().center, 0, 0).inflate(80, 80)
sprite2.radius = 40
all_group = pygame.sprite.Group([sprite2, sprite1])
test_group = pygame.sprite.Group(sprite2)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
sprite1.rect.center = pygame.mouse.get_pos()
collide = pygame.sprite.spritecollide(sprite1, test_group, False, pygame.sprite.collide_circle)
window.fill(0)
all_group.draw(window)
for s in collide:
pygame.draw.circle(window, (255, 255, 255), s.rect.center, s.rect.width // 2, 5)
pygame.display.flip()
pygame.quit()
exit()
What does this all mean for your code?
pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0) since a Surface object has no position. The position of the rectangle can be specified by a keyword argument. For example, the centre of the rectangle can be specified with the keyword argument center. These keyword arguments are applied to the attributes of the pygame.Rect before it is returned (see pygame.Rect for a list of the keyword arguments).
See *Why is my collision test always returning 'true' and why is the position of the rectangle of the image always wrong (0, 0)?
You do not need the x and y attributes of Sprite and Bullet at all. Use the position of the rect attribute instead:
#Define the sprite class
class Sprite:
def __init__(self, x, y, name):
self.image = pygame.image.load(name)
self.rect = self.image.get_rect(topleft = (x, y))
def render(self):
window.blit(self.image, self.rect)
# Define the bullet class to create bullets
class Bullet:
def __init__(self, x, y):
self.bullet = pygame.image.load("user_bullet.BMP")
self.rect = self.bullet.get_rect(topleft = (x + 23, y))
def render(self):
window.blit(self.bullet, self.rect)
Use pygame.Rect.colliderect() to detect collisions between instances of Sprite and Bullet.
See How to detect collisions between two rectangular objects or images in pygame:
my_sprite = Sprite(sx, sy, name)
my_bullet = Bullet(by, by)
while True:
# [...]
if my_sprite.rect.colliderect(my_bullet.rect):
printe("hit")

From what I understand of pygame you just need to check if the two rectangles overlap using the colliderect method. One way to do it is to have a method in your Bullet class that checks for collisions:
def is_collided_with(self, sprite):
return self.rect.colliderect(sprite.rect)
Then you can call it like:
sprite = Sprite(10, 10, 'my_sprite')
bullet = Bullet(20, 10)
if bullet.is_collided_with(sprite):
print('collision!')
bullet.kill()
sprite.kill()

There is a very simple method for what you are trying to do using built in methods.
here is an example.
import pygame
import sys
class Sprite(pygame.sprite.Sprite):
def __init__(self, pos):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([20, 20])
self.image.fill((255, 0, 0))
self.rect = self.image.get_rect()
self.rect.center = pos
def main():
pygame.init()
clock = pygame.time.Clock()
fps = 50
bg = [255, 255, 255]
size =[200, 200]
screen = pygame.display.set_mode(size)
player = Sprite([40, 50])
player.move = [pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN]
player.vx = 5
player.vy = 5
wall = Sprite([100, 60])
wall_group = pygame.sprite.Group()
wall_group.add(wall)
player_group = pygame.sprite.Group()
player_group.add(player)
# I added loop for a better exit from the game
loop = 1
while loop:
for event in pygame.event.get():
if event.type == pygame.QUIT:
loop = 0
key = pygame.key.get_pressed()
for i in range(2):
if key[player.move[i]]:
player.rect.x += player.vx * [-1, 1][i]
for i in range(2):
if key[player.move[2:4][i]]:
player.rect.y += player.vy * [-1, 1][i]
screen.fill(bg)
# first parameter takes a single sprite
# second parameter takes sprite groups
# third parameter is a do kill command if true
# all group objects colliding with the first parameter object will be
# destroyed. The first parameter could be bullets and the second one
# targets although the bullet is not destroyed but can be done with
# simple trick bellow
hit = pygame.sprite.spritecollide(player, wall_group, True)
if hit:
# if collision is detected call a function in your case destroy
# bullet
player.image.fill((255, 255, 255))
player_group.draw(screen)
wall_group.draw(screen)
pygame.display.update()
clock.tick(fps)
pygame.quit()
# sys.exit
if __name__ == '__main__':
main()

Make a group for the bullets, and then add the bullets to the group.
What I would do is this:
In the class for the player:
def collideWithBullet(self):
if pygame.sprite.spritecollideany(self, 'groupName'):
print("CollideWithBullet!!")
return True
And in the main loop somewhere:
def run(self):
if self.player.collideWithBullet():
print("Game Over")
Hopefully that works for you!!!

Inside the Sprite class, try adding a self.mask attribute with
self.mask = pygame.mask.from_surface(self.image)
and a collide_mask function inside of the Sprite class with this code:
def collide_mask(self, mask):
collided = False
mask_outline = mask.outline()
self.mask_outline = self.mask.outline()
for point in range(len(mask_outline)):
mask_outline[point] = list(mask_outline[point])
mask_outline[point][0] += bullet.x
mask_outline[point][1] += bullet.y
for point in range(len(self.mask_outline)):
self.mask_outline[point] = list(mask_outline[point])
self.mask_outline[point][0] += self.x
self.mask_outline[point][1] += self.y
for point in mask_outline:
for self_mask_point in self.mask_outline:
if point = self_mask_point:
collided = True
return collided

Related

Tile based lighting system 2d

I am looking for a tile based lighting system for my tile based game. I have not tried anything because I can't think of an effective way to do this. I have searched stack overflow and I found this but its not what I want. I am making a 2d version of Minecraft with pygame.
here is my tile class
class tile():
def __init__(self, block_category, block_type, x, y, world, win):
self.x, self.y, self.width, self.height = (x*64), (y*64), 64, 64
self.block_type = block_type
self.light_level = 1 # i want light level to range from 0-1
self._image = None
self.world = world
self.win = win
self.posx, self.posy = x, y
try:
self._image = self.world.block_textures[block_category][block_type]
except:
self._image = self.world.block_textures["missing"]["missing_texture"]
self.image = self._image
def draw(self):
#draw code here self.posx, self.win, self.world and self.posy are used here if you are wondering
def change_block(self, block_category, block_type):
try:
self._image = self.world.block_textures[block_category][block_type]
except:
self._image = self.world.block_textures["missing"]["missing_texture"]
self.image = self._image
and my world data looks like this
def generate_world(self):
for x in range(0, self.width):
self.tiles[x] = {}
for y in range(0, self.height):
self.tiles[x][y] = tile("terrain", "air", x, y, self, self.win)
for x in range(0, self.width):
for y in range(0, self.height):
if y == 0:
self.tiles[x][y].change_block("terrain", "bedrock")
elif y == 38:
self.tiles[x][y].change_block("terrain", "grass_block")
elif y < 38 and y > 34:
self.tiles[x][y].change_block("terrain", "dirt")
elif y < 35 and y > 0:
self.tiles[x][y].change_block("terrain", "stone")
if x == 0 or x == self.height - 1:
self.tiles[x][y].change_block("terrain", "bedrock")
return self.tiles
my game looks like this
For 2D games like you're making, how we could apply lighting - more like, shadowing - could go into 2 options:
Change screen color to shadow color & set transparency to objects, as OP suggested
Sandwich entire thing between screen and light layer
Let's start with problem of 1st option:
Problem of setting transparency
Here's demo code based on your idea:
"""
Demonstration of color overlapping
"""
import pygame as pg
class Player(pg.sprite.Sprite):
def __init__(self):
super(Player, self).__init__()
self.image = pg.Surface((50, 50))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect()
# setting alpha on player
self.image.set_alpha(125)
def update(self, *args, **kwargs):
x, y = pg.mouse.get_pos()
c_x, c_y = self.rect.center
self.rect.move_ip(x - c_x, y - c_y)
def mainloop():
player = Player()
screen = pg.display.set_mode((500, 500))
circle_colors = (255, 0, 0), (0, 255, 0), (0, 0, 255)
circle_coords = (150, 250), (250, 250), (350, 250)
# make surface, set alpha then draw circle
bg_surfaces = []
for (color, center) in zip(circle_colors, circle_coords):
surface = pg.Surface((500, 500), pg.SRCALPHA, 32)
surface.convert_alpha()
surface.set_alpha(125)
pg.draw.circle(surface, color, center, 75)
bg_surfaces.append(surface)
running = True
while running:
screen.fill((0, 0, 0))
# draw background
for surface in bg_surfaces:
screen.blit(surface, surface.get_rect())
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
player.update()
screen.blit(player.image, player.rect)
pg.display.flip()
if __name__ == '__main__':
pg.init()
mainloop()
pg.quit()
As you see, now the player (White square)'s color is Mixed with background circles.
It's basically just like what the drawing program does with layers.
Set layer transparency 50% and stack - everything mixes, producing undesirable effect which is far from lighting effect you wanted.
Unless you want Creeper or Steve to blend with the background and become a ghosty figure, it's better to go for sandwiched layout.
Sandwiched Layout
Following is demo code which uses mouse position as light source position.
Rendering order is Ground > Player > light overlay(shadow)
Demo code:
"""
Code demonstration for https://stackoverflow.com/q/72610504/10909029
Written on Python 3.10 (Using Match on input / event dispatching)
"""
import math
import random
import itertools
from typing import Dict, Tuple, Sequence
import pygame as pg
class Position:
"""Namespace for size and positions"""
tile_x = 20
tile_size = tile_x, tile_x
class SpriteGroup:
"""Namespace for sprite groups, with chain iterator keeping the order"""
ground = pg.sprite.Group()
entities = pg.sprite.Group()
light_overlay = pg.sprite.Group()
#classmethod
def all_sprites(cls):
return itertools.chain(cls.ground, cls.entities, cls.light_overlay)
class Player(pg.sprite.Sprite):
"""Player class, which is merely a rect following pointer in this example."""
def __init__(self):
super(Player, self).__init__()
self.image = pg.Surface((50, 50))
self.image.fill((255, 255, 255))
self.rect = self.image.get_rect()
SpriteGroup.entities.add(self)
self.rect.move_ip(225, 225)
def update(self, *args, **kwargs):
pass
# Intentionally disabling mouse following code
# x, y = pg.mouse.get_pos()
# c_x, c_y = self.rect.center
# self.rect.move_ip(x - c_x, y - c_y)
class TileLightOverlay(pg.sprite.Sprite):
"""
Light overlay tile. Using separate sprites, so we don't have to blit on
every object above ground that requires lighting.
"""
# light lowest boundary
lighting_lo = 255
# light effect radius
light_radius = Position.tile_x * 8
def __init__(self, x, y):
super(TileLightOverlay, self).__init__()
self.image = pg.Surface(Position.tile_size)
self.image.fill((0, 0, 0))
self.rect = self.image.get_rect()
self.rect.move_ip(x * Position.tile_x, y * Position.tile_x)
SpriteGroup.light_overlay.add(self)
def update(self, *args, **kwargs):
self.image.set_alpha(self.brightness)
#property
def brightness(self):
"""Calculate distance between mouse & apply light falloff accordingly"""
distance = math.dist(self.rect.center, pg.mouse.get_pos())
if distance > self.light_radius:
return self.lighting_lo
return (distance / self.light_radius) * self.lighting_lo
class TileGround(pg.sprite.Sprite):
"""Ground tile representation. Not much is going on here."""
def __init__(self, x, y, tile_color: Sequence[float]):
super(TileGround, self).__init__()
self.image = pg.Surface(Position.tile_size)
self.image.fill(tile_color)
self.rect = self.image.get_rect()
self.rect.move_ip(x * Position.tile_x, y * Position.tile_x)
SpriteGroup.ground.add(self)
# create and keep its pair light overlay tile.
self.light_tile = TileLightOverlay(x, y)
class World:
"""World storing ground tile data."""
# tile type storing color etc. for this example only have color.
tile_type: Dict[int, Tuple[float, float, float]] = {
0: (56, 135, 93),
1: (36, 135, 38),
2: (135, 128, 56)
}
def __init__(self):
# coord system : +x → / +y ↓
# generating random tile data
self.tile_data = [
[random.randint(0, 2) for _ in range(25)]
for _ in range(25)
]
# generated tiles
self.tiles = []
def generate(self):
"""Generate world tiles"""
for x, row in enumerate(self.tile_data):
tiles_row = [TileGround(x, y, self.tile_type[col]) for y, col in enumerate(row)]
self.tiles.append(tiles_row)
def process_input(event: pg.event.Event):
"""Process input, in case you need it"""
match event.key:
case pg.K_ESCAPE:
pg.event.post(pg.event.Event(pg.QUIT))
case pg.K_UP:
pass
# etc..
def display_fps_closure(screen: pg.Surface, clock: pg.time.Clock):
"""FPS display"""
font_name = pg.font.get_default_font()
font = pg.font.Font(font_name, 10)
color = (0, 255, 0)
def inner():
text = font.render(f"{int(clock.get_fps())} fps", True, color)
screen.blit(text, text.get_rect())
return inner
def mainloop():
# keeping reference of method/functions to reduce access overhead
fetch_events = pg.event.get
display = pg.display
# local variable setup
screen = display.set_mode((500, 500))
player = Player()
world = World()
world.generate()
clock = pg.time.Clock()
display_fps = display_fps_closure(screen, clock)
running = True
# main loop
while running:
screen.fill((0, 0, 0))
# process event
for event in fetch_events():
# event dispatch
match event.type:
case pg.QUIT:
running = False
case pg.KEYDOWN:
process_input(event)
# draw in ground > entities > light overlay order
for sprite in SpriteGroup.all_sprites():
sprite.update()
screen.blit(sprite.image, sprite.rect)
# draw fps - not related to question, was lazy to remove & looks fancy
clock.tick()
display_fps()
display.flip()
if __name__ == '__main__':
pg.init()
pg.font.init()
mainloop()
pg.quit()
You'll see it's blending properly with shadow without mixing color with ground tiles.
There could be much better approach or ways to implement this - as I never used pygame before, there would be bunch of good/better stuffs I didn't read on document.
But one thing for sure - always approach your goal with mindset that everything is related to your problem until you reach the goal! Comment you thought it wasn't going to be helpful gave me idea for this design.
One option is a black background, then I use set_alpha() to set how light or dark the tile is (how much the black background is seen through the tile) and no overlay is needed. Thanks to #jupiterbjy's original answer for inspiration.

First Dialogue Box

I'm in my early days of pygame coding, but here is the piece I am trying to figure out right now, which is to draw the dialogue box, then the text on top of it. When ran, I see the displayed dialogue box, but no text.
import pygame
from pygame.locals import *
import os
pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])
class Text(pygame.sprite.Sprite):
def __init__(self, text):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
os.chdir(r"<my directory to the dialogue box>.png")
self.surf = pygame.image.load("spikey_box.png").convert()
os.chdir(r"<my directory to my font>")
self.font = pygame.font.Font("final_fantasy_36_font.ttf", 12)
# set up dialogue box sprite
self.rect = self.surf.get_rect()
self.rect.center = (400, 500)
screen.blit(self.surf, self.rect)
# for text
self.textSurf = self.font.render(text, True, (255, 255, 255))
self.textRect = self.textSurf.get_rect()
self.textRect.center = (400, 500)
screen.blit(self.textSurf, self.textRect)
test_message = Text("Hello, world!")
running = True
clock = pygame.time.Clock()
while running:
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
elif event.type == QUIT:
running = False
pressed_keys = pygame.key.get_pressed()
screen.fill((255, 255, 255))
screen.blit(test_message.surf, test_message.rect)
pygame.display.flip()
clock.tick(30)
pygame.quit()
I have some sample code that I was able to adapt a little and make work for another piece I'm working on that I tried to use for inspiration to build the class:
def text_objects(text, font):
textSurface = font.render(text, True, (0, 0, 0))
return textSurface, textSurface.get_rect()
def message_display(text):
largeText = pygame.font.Font('freesansbold.ttf', 20)
TextSurf, TextRect = text_objects(text, largeText)
TextRect.center = ((SCREEN_WIDTH//2), (SCREEN_HEIGHT//2))
screen.blit(TextSurf, TextRect)
The set of two functions were defined outside of the game loop, and worked fine when called from within the game loop as {message_display(f-string)}. I respectfully request guidance to learn how to ask the right question to figure out how to make the class implementation work. I want to build on it to the point that I can call the dialogue window and allow the player to scroll back over dialogue already given in that instanced conversation.
Thank you in advance!
When you make a PyGame Sprite based on pygame.sprite.Sprite, you must define two variables as a minimum:
sprite.image - used to hold the sprite bitmap
sprite.rect - defines the location and size of the .image
Your Text Sprite does not appear to be creating the .image so it wont work as a normal sprite. But since you directly blit() the Text.surf to the display, you've dodged this issue for now.
The code is not writing the text image on top of the background dialogue. It's writing it directly to the screen in the sprite __init__(). Once the sprite is constructed, this screen update is lost.
Probably you need something like:
class Text(pygame.sprite.Sprite):
def __init__(self, text):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
os.chdir(r"<my directory to the dialogue box>.png")
self.image = pygame.image.load("spikey_box.png").convert()
os.chdir(r"<my directory to my font>")
# Draw the text, centred on the background
self.font = pygame.font.Font("final_fantasy_36_font.ttf", 12)
text = self.font.render(text, True, (255, 255, 255))
centre_x = ( self.rect.width - text.get_width() ) // 2
centre_y = ( self.rect.height - text.get_height() ) // 2
self.image.blit( text, ( centre_x, centre_y ) )
# Set the rect
self.rect = self.image.get_rect()
self.rect.center = (400, 500)
Which loads the dialogue background image, and then rendered the text centred into that box so there is just a single image.
Since surf has been renamed to image, the main loop needs a tweak:
...
screen.fill((255, 255, 255))
screen.blit(test_message.image, test_message.rect)
pygame.display.flip()
clock.tick(30)
pygame.quit()
But really the code should use the PyGame Sprite functions for drawing, rather than accessing the internal Surface directly.
Thank you #Kingsley for your input, and you're definitely right, and helped point me in the right direction. I found that the issue was how I was instantiating the class object, and performing an odd call in the game loop that didn't really make sense. I was trying to blit a blit. I restructured my class object, and it now works perfectly!
class Text(pygame.sprite.Sprite):
def __init__(self):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
os.chdir(r"C:\Users\mcbri\Documents\Python\master\Resources\pv1\pv1_objects")
self.surf = pygame.image.load("spikey_box.png").convert() #for box
os.chdir(r"C:\Users\mcbri\Documents\Python\master\Resources\fonts")
self.font = pygame.font.Font("final_fantasy_36_font.ttf", 12)
self.rect = self.surf.get_rect()
self.rect.center = (400, 500)
def pop_message(self, text):
screen.blit(self.surf, self.rect)
self.textSurf = self.font.render(text, True, (255, 255, 255))
self.textRect = self.textSurf.get_rect()
self.textRect.center = (400, 500)
screen.blit(self.textSurf, self.textRect)
...in loop:
elif event.type == QUIT:
running = False
pressed_keys = pygame.key.get_pressed()
screen.fill((255, 255, 255))
test_message.pop_message("Hello, world!")
pygame.display.flip()
clock.tick(30)
Thank you so much for the fast input!

Return a sprite to its original position?

So I have a very basic PyGame Python program which is a 'car' that the user controls and one which comes down he road towards it. How can I get the NPC car to return to its original position once it leaves the screen? My current code is shown below, thanks for any help you can provide!
import pygame, random
from car import Car
from car import AutoCar
pygame.init()
play = True
clock = pygame.time.Clock()
screen = pygame.display.set_mode((700, 750))
pygame.display.set_caption("Car Game")
# Define a bunch of colours
GREEN = (20, 255, 100)
GREY = (210, 210, 210)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
PURPLE = (255, 0, 255)
BLUE = (0, 0, 255)
all_sprites = pygame.sprite.Group() # Creates a list of all sprites in the game
playerCar = Car(RED, 20, 30) # Defines the car, its colour, and its
size
playerCar.rect.x = 200
playerCar.rect.y = 300
AutoCar1 = AutoCar(BLUE, 20, 30)
AutoCar1.rect.x = 200
AutoCar1.rect.y = 20
all_sprites.add(playerCar) # Adds the playerCar to the list of sprites
all_sprites.add(AutoCar1) # Adds an automated 'enemy' car which you must avoid
while play:
for event in pygame.event.get():
if event.type == pygame.QUIT: # If the detected event is clicking the exit button, stop the program.
play = False
elif event.type == pygame.KEYDOWN: # If a key is pressed and the key is 'x', quit the game.
if event.key == pygame.K_x:
play = False
keys = pygame.key.get_pressed() # Defines a variable keys that consists of whatever event is happening in the game
if keys[pygame.K_LEFT]:
playerCar.move_left(5)
if keys[pygame.K_RIGHT]:
playerCar.move_right(5)
if keys[pygame.K_DOWN]:
playerCar.move_backward(5)
if keys[pygame.K_UP]:
playerCar.move_forward(5)
all_sprites.update() # Draws in all sprites
AutoCar1.auto_move_forward(3) # Makes the 'enemy' move forward automatically.
# Create the background
screen.fill(GREEN)
# Create the road
pygame.draw.rect(screen, GREY, [100, 0, 500, 750])
# Draw the lines on the road
pygame.draw.line(screen, WHITE, [340, 0], [340, 750], 5)
# Draw in all the sprites
all_sprites.draw(screen)
pygame.display.flip()
clock.tick(60) # Cap limit at 60FPS
pygame.quit()
I also have a file defining the class Car and Autocar:
import pygame
WHITE = (255, 255, 255)
class Car(pygame.sprite.Sprite): # Creates a class representing a car, derived from the sprite class
def __init__(self, color, width, height):
# Call the parent class (i.e. Sprite) constructor
super().__init__()
self.image = pygame.Surface([width,height])
self.image.fill(WHITE)
self.image.set_colorkey(WHITE) # Sets white as transparent for this class
# alternatively load a proper car picture:
# self.image = pygame.image.load("car.png").convert_alpha()
pygame.draw.rect(self.image, color, [0, 0, width, height])
self.rect = self.image.get_rect() # Fetches the rectangle with the dimensions of the image
def move_right(self, pixels): # Adds to the x value of the car the desired amount of pixels
self.rect.x += pixels
def move_left(self, pixels): # Same as move_right()
self.rect.x -= pixels
def move_forward(self, pixels):
self.rect.y -= pixels
def move_backward(self, pixels):
self.rect.y += pixels
class AutoCar(pygame.sprite.Sprite):
def __init__(self, color, width, height):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
pygame.draw.rect(self.image, color, [0, 0, width, height])
self.rect = self.image.get_rect()
def auto_move_forward(self, pixels):
self.rect.y += pixels
def return_to_top(self):
Just store the start position as an attribute of the AutoCar, check in the update method if the sprite is below the screen and then set the self.rect.topleft (or one of the other rect attributes) to the self.start_position.
class AutoCar(pygame.sprite.Sprite):
def __init__(self, color, width, height):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(WHITE)
self.image.set_colorkey(WHITE)
pygame.draw.rect(self.image, color, [0, 0, width, height])
self.rect = self.image.get_rect()
self.start_position = (200, -50)
self.rect.topleft = self.start_position
def auto_move_forward(self, pixels):
self.rect.y += pixels
def return_to_top(self):
self.rect.topleft = self.start_position
def update(self):
if self.rect.top > 750: # Screen height.
self.rect.topleft = self.start_position

How do I pass a rect into pygame.display.update() to update a specific area of the window?

On the documentation page for pygame.display.update(), it says you can pass a rect into the method to update part of the screen. However, all of the examples I see just pass an existing rect from an image or shape in the program. How can I tell it to update an area on the screen directly? For example, when drawing a rectangle, I could use a rect argument of (100,200,30,40). This would draw a rectangle with a top at 200, a left at 100, a width of 30, and a height of 40. How can I pass a similar argument into pygame.display.update()? I tried pygame.display.update((100,200,30,40)), but this updates the entire window.
Just define a rect and pass it to pygame.display.update() to update only this specific region of the display. You can also pass a list of rects.
import random
import pygame as pg
from pygame.math import Vector2
# A simple sprite, just to have something moving on the screen.
class Ball(pg.sprite.Sprite):
def __init__(self, screen_rect):
super().__init__()
radius = random.randrange(5, 31)
self.image = pg.Surface((radius*2, radius*2), pg.SRCALPHA)
pg.draw.circle(self.image, pg.Color('dodgerblue1'), (radius, radius), radius)
pg.draw.circle(self.image, pg.Color('dodgerblue3'), (radius, radius), radius-2)
self.rect = self.image.get_rect(center=screen_rect.center)
self.vel = Vector2(random.uniform(-2, 2), random.uniform(-2, 2))
self.pos = Vector2(self.rect.center)
self.screen_rect = screen_rect
self.lifetime = 350
def update(self):
self.pos += self.vel
self.rect.center = self.pos
self.lifetime -= 1
if not self.screen_rect.contains(self.rect) or self.lifetime <= 0:
self.kill()
def main():
screen = pg.display.set_mode((800, 600))
screen.fill((20, 40, 70))
pg.display.update()
screen_rect = screen.get_rect()
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
# Pass this rect to `pg.display.update` to update only this area.
update_rect = pg.Rect(50, 50, 500, 400)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
all_sprites.add(Ball(screen_rect))
all_sprites.update()
screen.fill((20, 50, 90))
all_sprites.draw(screen)
# Update only the area that we specified with the `update_rect`.
pg.display.update(update_rect)
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()

line 475, in draw self.spritedict[spr] = surface_blit(spr.image, spr.rect)

import pygame
import random
WIDTH = 360
HEIGHT = 480
FPS = 30
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode([WIDTH, HEIGHT])
pygame.display.set_caption("My Game")
clock = pygame.time.Clock()
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((10, 10))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect = HEIGHT - 10
all_sprites = pygame.sprite.Group()
Player = Player()
all_sprites.add(Player)
running = True
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
all_sprites.update()
screen.fill(BLACK)
all_sprites.draw(screen)
pygame.display.flip()
pygame.quit()
Hello I am getting this error when i am trying to run this program File , line 54, in
all_sprites.draw(screen)
line 475, in draw
self.spritedict[spr] = surface_blit(spr.image, spr.rect)
TypeError: invalid destination position for blit. Plz help me.
Your rect attribute must be an Rect object but you're overriding it with an integer in the line self.rect = HEIGHT - 10. Since a single integer isn't a valid location to blit a sprite on you get "invalid destination position for blit".

Resources