How to put a health bar over the sprite in pygame - python

By the title, I'm hoping that my the player would have a health bar attached to their head. If they move, the health bar also moves. Say sprite is my player. Hey sprite! He has a health bar on top of his head and yeah thats it. To be honest, I don't really know where to start, so help would be appreciated. Thanks!
P.S. A big thanks to Rabbid76 for his help! Also to Ann Zen!
Code:
import pygame
import os
import random
import math
import winsound
# winsound.PlaySound("explosion.wav", winsound.SND_ALIAS)
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (0, 30)
wn = pygame.display.set_mode((1920, 1020))
clock = pygame.time.Clock()
icon = pygame.image.load('Icon.png')
pygame.image.load('Sprite0.png')
pygame.image.load('Sprite0.png')
pygame.display.set_icon(icon)
pygame.display.set_caption('DeMass.io')
vel = 5
class Player(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
z = random.randint(0, 101)
if z <= 51:
self.original_image = pygame.image.load('Sprite0.png')
else:
self.original_image = pygame.image.load('Sprite3.png')
if z == 41:
self.original_image = pygame.image.load('Sprite5.png')
self.image = self.original_image
self.rect = self.image.get_rect(center=(x, y))
self.direction = pygame.math.Vector2((0, -1))
self.velocity = 5
self.position = pygame.math.Vector2(x, y)
self.x = pygame.math.Vector2(x)
self.y = pygame.math.Vector2(y)
self.health = 10
self.visible = True
def point_at(self, x, y):
self.direction = pygame.math.Vector2(x, y) - self.rect.center
if self.direction.length() > 0:
self.direction = self.direction.normalize()
angle = self.direction.angle_to((0, -1))
self.image = pygame.transform.rotate(self.original_image, angle)
self.rect = self.image.get_rect(center=self.rect.center)
def move(self, x, y):
self.position -= self.direction * y * self.velocity
self.position += pygame.math.Vector2(-self.direction.y, self.direction.x) * x * self.velocity
self.rect.center = round(self.position.x), round(self.position.y)
def reflect(self, NV):
self.direction = self.direction.reflect(pygame.math.Vector2(NV))
def update(self):
self.position += self.direction * self.velocity
self.rect.center = round(self.position.x), round(self.position.y)
def hit(self, player):
if self.health > 0:
self.health -= 1
else:
self.visible = False
distance = math.sqrt(math.pow(player.x - player.x(), 2) + math.pow(player.y - player.y(), 2))
if distance < 20:
return True
else:
return False
def move(self, x, y, clamp_rect):
self.position -= self.direction * y * self.velocity
self.position += pygame.math.Vector2(-self.direction.y, self.direction.x) * x * self.velocity
self.rect.center = round(self.position.x), round(self.position.y)
if self.rect.left < clamp_rect.left:
self.rect.left = clamp_rect.left
self.position.x = self.rect.centerx
if self.rect.right > clamp_rect.right:
self.rect.right = clamp_rect.right
self.position.x = self.rect.centerx
if self.rect.top < clamp_rect.top:
self.rect.top = clamp_rect.top
self.position.y = self.rect.centery
if self.rect.bottom > clamp_rect.bottom:
self.rect.bottom = clamp_rect.bottom
self.position.y = self.rect.centery
class Projectile(object):
def __init__(self, x, y, radius, color, facing):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.facing = facing
self.vel = 8 * facing
def draw(self, win):
pygame.draw.circle(win, self.color, (self.x, self.y), self.radius)
player = Player(200, 200)
all_sprites = pygame.sprite.Group(player)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.MOUSEMOTION:
player.point_at(*event.pos)
pygame.draw.rect(wn, (0, 0, 0), (50, 50, 10000, 100000))
keys = pygame.key.get_pressed()
if keys[pygame.K_w] or keys[pygame.K_UP]:
player.move(0, -1, wn.get_rect())
wn.fill((255, 255, 255))
all_sprites.draw(wn)
pygame.display.update()

See haw to draw a bar in How can I display a smooth loading bar in pygame?.
Create a function that draws a health bar:
def draw_health_bar(surf, pos, size, borderC, backC, healthC, progress):
pygame.draw.rect(surf, backC, (*pos, *size))
pygame.draw.rect(surf, borderC, (*pos, *size), 1)
innerPos = (pos[0]+1, pos[1]+1)
innerSize = ((size[0]-2) * progress, size[1]-2)
rect = (round(innerPos[0]), round(innerPos[1]), round(innerSize[0]), round(innerSize[1]))
pygame.draw.rect(surf, healthC, rect)
Add a method draw_health to the class Player and use the function draw_health_bar:
class Player(pygame.sprite.Sprite):
# [...]
def draw_health(self, surf):
health_rect = pygame.Rect(0, 0, self.original_image.get_width(), 7)
health_rect.midbottom = self.rect.centerx, self.rect.top
max_health = 10
draw_health_bar(surf, health_rect.topleft, health_rect.size,
(0, 0, 0), (255, 0, 0), (0, 255, 0), self.health/max_health)
Call the method in the main application loop:
while run:
# [...]
wn.fill((255, 255, 255))
all_sprites.draw(wn)
player.draw_health(wn) # <---
pygame.display.update()
See also Sprite.
Minimal example:
repl.it/#Rabbid76/PyGame-HealthBar
import pygame
import math
def draw_health_bar(surf, pos, size, borderC, backC, healthC, progress):
pygame.draw.rect(surf, backC, (*pos, *size))
pygame.draw.rect(surf, borderC, (*pos, *size), 1)
innerPos = (pos[0]+1, pos[1]+1)
innerSize = ((size[0]-2) * progress, size[1]-2)
rect = (round(innerPos[0]), round(innerPos[1]), round(innerSize[0]), round(innerSize[1]))
pygame.draw.rect(surf, healthC, rect)
class Player(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.original_image = pygame.image.load('CarBlue64.png')
self.original_image = pygame.transform.rotate(self.original_image, 90)
self.image = self.original_image
self.rect = self.image.get_rect(center=(x, y))
self.direction = pygame.math.Vector2((0, -1))
self.velocity = 5
self.position = pygame.math.Vector2(x, y)
self.health = 10
def point_at(self, x, y):
self.direction = pygame.math.Vector2(x, y) - self.rect.center
if self.direction.length() > 0:
self.direction = self.direction.normalize()
angle = self.direction.angle_to((0, -1))
self.image = pygame.transform.rotate(self.original_image, angle)
self.rect = self.image.get_rect(center=self.rect.center)
def move(self, x, y, clamp_rect):
self.position -= self.direction * y * self.velocity
self.position += pygame.math.Vector2(-self.direction.y, self.direction.x) * x * self.velocity
self.rect.center = round(self.position.x), round(self.position.y)
test_rect = self.rect.clamp(clamp_rect)
if test_rect.x != self.rect.x:
self.rect.x = test_rect.x
self.position.x = self.rect.centerx
self.health = max(0, self.health - 1)
if test_rect.y != self.rect.y:
self.rect.y = test_rect.y
self.position.y = self.rect.centery
self.health = max(0, self.health - 1)
def draw_health(self, surf):
health_rect = pygame.Rect(0, 0, self.original_image.get_width(), 7)
health_rect.midbottom = self.rect.centerx, self.rect.top
max_health = 10
draw_health_bar(surf, health_rect.topleft, health_rect.size,
(0, 0, 0), (255, 0, 0), (0, 255, 0), self.health/max_health)
window = pygame.display.set_mode((250, 250))
clock = pygame.time.Clock()
player = Player(200, 200)
all_sprites = pygame.sprite.Group(player)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.MOUSEMOTION:
player.point_at(*event.pos)
keys = pygame.key.get_pressed()
mouse_buttons = pygame.mouse.get_pressed()
if any(keys) or any(mouse_buttons):
player.move(0, -1, window.get_rect())
window.fill((127, 127, 127))
pygame.draw.rect(window, (255, 0, 0), window.get_rect(), 3)
all_sprites.draw(window)
player.draw_health(window)
pygame.display.update()
pygame.quit()
exit()

Rabbid's answer worked for me, however I am using Python 3.8.2 and continually got deprication errors as well as compilation errors when trying to output an .exe. The progress variable is a float and results in this error:
pygame.draw.rect(surf, healthC, (*innerPos, *innerSize))
DeprecationWarning: an integer is required (got type float). Implicit conversion to integers using __int__ is deprecated, and may be removed in a future version of Python.
Multiplication by the variable progress in the function draw_health_bar needs to be raised to an int to get away from that. It doesn't seem to affect the visual appearance of the health bar at all. This is the culprit line updated with the int.
innerSize = (int((size[0]-2) * progress), size[1]-2)
Full updated code below. I prefer complete names for things so as not to confuse myself what variables mean, so I've done that, but everything else is the same except for the int I added.
def draw_health_bar(surface, position, size, color_border, color_background, color_health, progress):
pygame.draw.rect(surface, color_background, (*position, *size))
pygame.draw.rect(surface, color_border, (*position, *size), 1)
innerPos = (position[0]+1, position[1]+1)
innerSize = (int((size[0]-2) * progress), size[1]-2)
pygame.draw.rect(surface, color_health, (*innerPos, *innerSize))
Sorry if this posting this breaks StackOverflow's rules. First time posting and I thought this was helpful info to newbs like me who try this code out.

Related

Creating HealthBar using pygame [duplicate]

By the title, I'm hoping that my the player would have a health bar attached to their head. If they move, the health bar also moves. Say sprite is my player. Hey sprite! He has a health bar on top of his head and yeah thats it. To be honest, I don't really know where to start, so help would be appreciated. Thanks!
P.S. A big thanks to Rabbid76 for his help! Also to Ann Zen!
Code:
import pygame
import os
import random
import math
import winsound
# winsound.PlaySound("explosion.wav", winsound.SND_ALIAS)
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (0, 30)
wn = pygame.display.set_mode((1920, 1020))
clock = pygame.time.Clock()
icon = pygame.image.load('Icon.png')
pygame.image.load('Sprite0.png')
pygame.image.load('Sprite0.png')
pygame.display.set_icon(icon)
pygame.display.set_caption('DeMass.io')
vel = 5
class Player(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
z = random.randint(0, 101)
if z <= 51:
self.original_image = pygame.image.load('Sprite0.png')
else:
self.original_image = pygame.image.load('Sprite3.png')
if z == 41:
self.original_image = pygame.image.load('Sprite5.png')
self.image = self.original_image
self.rect = self.image.get_rect(center=(x, y))
self.direction = pygame.math.Vector2((0, -1))
self.velocity = 5
self.position = pygame.math.Vector2(x, y)
self.x = pygame.math.Vector2(x)
self.y = pygame.math.Vector2(y)
self.health = 10
self.visible = True
def point_at(self, x, y):
self.direction = pygame.math.Vector2(x, y) - self.rect.center
if self.direction.length() > 0:
self.direction = self.direction.normalize()
angle = self.direction.angle_to((0, -1))
self.image = pygame.transform.rotate(self.original_image, angle)
self.rect = self.image.get_rect(center=self.rect.center)
def move(self, x, y):
self.position -= self.direction * y * self.velocity
self.position += pygame.math.Vector2(-self.direction.y, self.direction.x) * x * self.velocity
self.rect.center = round(self.position.x), round(self.position.y)
def reflect(self, NV):
self.direction = self.direction.reflect(pygame.math.Vector2(NV))
def update(self):
self.position += self.direction * self.velocity
self.rect.center = round(self.position.x), round(self.position.y)
def hit(self, player):
if self.health > 0:
self.health -= 1
else:
self.visible = False
distance = math.sqrt(math.pow(player.x - player.x(), 2) + math.pow(player.y - player.y(), 2))
if distance < 20:
return True
else:
return False
def move(self, x, y, clamp_rect):
self.position -= self.direction * y * self.velocity
self.position += pygame.math.Vector2(-self.direction.y, self.direction.x) * x * self.velocity
self.rect.center = round(self.position.x), round(self.position.y)
if self.rect.left < clamp_rect.left:
self.rect.left = clamp_rect.left
self.position.x = self.rect.centerx
if self.rect.right > clamp_rect.right:
self.rect.right = clamp_rect.right
self.position.x = self.rect.centerx
if self.rect.top < clamp_rect.top:
self.rect.top = clamp_rect.top
self.position.y = self.rect.centery
if self.rect.bottom > clamp_rect.bottom:
self.rect.bottom = clamp_rect.bottom
self.position.y = self.rect.centery
class Projectile(object):
def __init__(self, x, y, radius, color, facing):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.facing = facing
self.vel = 8 * facing
def draw(self, win):
pygame.draw.circle(win, self.color, (self.x, self.y), self.radius)
player = Player(200, 200)
all_sprites = pygame.sprite.Group(player)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.MOUSEMOTION:
player.point_at(*event.pos)
pygame.draw.rect(wn, (0, 0, 0), (50, 50, 10000, 100000))
keys = pygame.key.get_pressed()
if keys[pygame.K_w] or keys[pygame.K_UP]:
player.move(0, -1, wn.get_rect())
wn.fill((255, 255, 255))
all_sprites.draw(wn)
pygame.display.update()
See haw to draw a bar in How can I display a smooth loading bar in pygame?.
Create a function that draws a health bar:
def draw_health_bar(surf, pos, size, borderC, backC, healthC, progress):
pygame.draw.rect(surf, backC, (*pos, *size))
pygame.draw.rect(surf, borderC, (*pos, *size), 1)
innerPos = (pos[0]+1, pos[1]+1)
innerSize = ((size[0]-2) * progress, size[1]-2)
rect = (round(innerPos[0]), round(innerPos[1]), round(innerSize[0]), round(innerSize[1]))
pygame.draw.rect(surf, healthC, rect)
Add a method draw_health to the class Player and use the function draw_health_bar:
class Player(pygame.sprite.Sprite):
# [...]
def draw_health(self, surf):
health_rect = pygame.Rect(0, 0, self.original_image.get_width(), 7)
health_rect.midbottom = self.rect.centerx, self.rect.top
max_health = 10
draw_health_bar(surf, health_rect.topleft, health_rect.size,
(0, 0, 0), (255, 0, 0), (0, 255, 0), self.health/max_health)
Call the method in the main application loop:
while run:
# [...]
wn.fill((255, 255, 255))
all_sprites.draw(wn)
player.draw_health(wn) # <---
pygame.display.update()
See also Sprite.
Minimal example:
repl.it/#Rabbid76/PyGame-HealthBar
import pygame
import math
def draw_health_bar(surf, pos, size, borderC, backC, healthC, progress):
pygame.draw.rect(surf, backC, (*pos, *size))
pygame.draw.rect(surf, borderC, (*pos, *size), 1)
innerPos = (pos[0]+1, pos[1]+1)
innerSize = ((size[0]-2) * progress, size[1]-2)
rect = (round(innerPos[0]), round(innerPos[1]), round(innerSize[0]), round(innerSize[1]))
pygame.draw.rect(surf, healthC, rect)
class Player(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.original_image = pygame.image.load('CarBlue64.png')
self.original_image = pygame.transform.rotate(self.original_image, 90)
self.image = self.original_image
self.rect = self.image.get_rect(center=(x, y))
self.direction = pygame.math.Vector2((0, -1))
self.velocity = 5
self.position = pygame.math.Vector2(x, y)
self.health = 10
def point_at(self, x, y):
self.direction = pygame.math.Vector2(x, y) - self.rect.center
if self.direction.length() > 0:
self.direction = self.direction.normalize()
angle = self.direction.angle_to((0, -1))
self.image = pygame.transform.rotate(self.original_image, angle)
self.rect = self.image.get_rect(center=self.rect.center)
def move(self, x, y, clamp_rect):
self.position -= self.direction * y * self.velocity
self.position += pygame.math.Vector2(-self.direction.y, self.direction.x) * x * self.velocity
self.rect.center = round(self.position.x), round(self.position.y)
test_rect = self.rect.clamp(clamp_rect)
if test_rect.x != self.rect.x:
self.rect.x = test_rect.x
self.position.x = self.rect.centerx
self.health = max(0, self.health - 1)
if test_rect.y != self.rect.y:
self.rect.y = test_rect.y
self.position.y = self.rect.centery
self.health = max(0, self.health - 1)
def draw_health(self, surf):
health_rect = pygame.Rect(0, 0, self.original_image.get_width(), 7)
health_rect.midbottom = self.rect.centerx, self.rect.top
max_health = 10
draw_health_bar(surf, health_rect.topleft, health_rect.size,
(0, 0, 0), (255, 0, 0), (0, 255, 0), self.health/max_health)
window = pygame.display.set_mode((250, 250))
clock = pygame.time.Clock()
player = Player(200, 200)
all_sprites = pygame.sprite.Group(player)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif event.type == pygame.MOUSEMOTION:
player.point_at(*event.pos)
keys = pygame.key.get_pressed()
mouse_buttons = pygame.mouse.get_pressed()
if any(keys) or any(mouse_buttons):
player.move(0, -1, window.get_rect())
window.fill((127, 127, 127))
pygame.draw.rect(window, (255, 0, 0), window.get_rect(), 3)
all_sprites.draw(window)
player.draw_health(window)
pygame.display.update()
pygame.quit()
exit()
Rabbid's answer worked for me, however I am using Python 3.8.2 and continually got deprication errors as well as compilation errors when trying to output an .exe. The progress variable is a float and results in this error:
pygame.draw.rect(surf, healthC, (*innerPos, *innerSize))
DeprecationWarning: an integer is required (got type float). Implicit conversion to integers using __int__ is deprecated, and may be removed in a future version of Python.
Multiplication by the variable progress in the function draw_health_bar needs to be raised to an int to get away from that. It doesn't seem to affect the visual appearance of the health bar at all. This is the culprit line updated with the int.
innerSize = (int((size[0]-2) * progress), size[1]-2)
Full updated code below. I prefer complete names for things so as not to confuse myself what variables mean, so I've done that, but everything else is the same except for the int I added.
def draw_health_bar(surface, position, size, color_border, color_background, color_health, progress):
pygame.draw.rect(surface, color_background, (*position, *size))
pygame.draw.rect(surface, color_border, (*position, *size), 1)
innerPos = (position[0]+1, position[1]+1)
innerSize = (int((size[0]-2) * progress), size[1]-2)
pygame.draw.rect(surface, color_health, (*innerPos, *innerSize))
Sorry if this posting this breaks StackOverflow's rules. First time posting and I thought this was helpful info to newbs like me who try this code out.

Game Over image not showing

I am making a spaceship game where you control a spaceship and fire bullets at the enemies. I am now trying to make a game over background show when the player and any enemy collide, but when I ran the game, the game over background never showed!
This is my current code (Some parts omitted or replaced by --snip--):
class Spaceship(pygame.sprite.Sprite):
def __init__(self, s, x, y):
pygame.sprite.Sprite.__init__(self)
self.screen = s
self.x, self.y = x, y
self.image = pygame.image.load("spaceship.png")
self.image = pygame.transform.scale(self.image, (175, 175))
self.rect = self.image.get_rect()
self.rect.center = (self.x, self.y)
def update(self):
self.rect.center = (self.x, self.y)
class Bullet(pygame.sprite.Sprite):
def __init__(self, s, x, y):
pygame.sprite.Sprite.__init__(self)
self.screen = s
self.x, self.y = x, y
self.image = pygame.image.load("bullet.png")
self.image = pygame.transform.scale(self.image, (100, 100))
self.rect = self.image.get_rect()
self.rect.center = (self.x, self.y)
def update(self):
self.y -= 5
self.rect.center = (self.x, self.y)
if self.y < 0:
self.kill()
class Enemy(pygame.sprite.Sprite):
def __init__(self, s, x, y, t):
pygame.sprite.Sprite.__init__(self)
self.type = t
self.screen, self.x, self.y = s, x, y
self.image = pygame.image.load("enemy.png")
self.image = pygame.transform.scale(self.image, (235, 215))
self.rect = self.image.get_rect()
self.rect = self.image.get_rect()
self.rect.center = (self.x, self.y)
self.score_given = get_enemy_given_score()[self.type]
def update(self):
if self.y < 0:
self.kill()
self.y += 3
self.rect.center = (self.x, self.y)
class GameOverBackground(pygame.sprite.Sprite):
def __init__(self, s, x, y, size=(100, 100)):
pygame.sprite.Sprite.__init__(self)
self.screen, self.x, self.y = s, x, y
self.size = size
self.image = pygame.image.load("Game_Over.jpg")
self.image = pygame.transform.scale(self.image, self.size)
self.rect = self.image.get_rect()
def blitme(self):
self.screen.blit(self.image, self.rect)
bg = GameOverBackground(screen, 0, 0)
spaceship = Spaceship(screen, 400, 400)
bullets = pygame.sprite.Group()
enemies = pygame.sprite.Group()
clock = pygame.time.Clock()
enemy_interval = 2000 # It's in milliseconds
enemy_event = pygame.USEREVENT + 1
pygame.time.set_timer(enemy_event, enemy_interval)
score = 0
font = pygame.font.SysFont("Arial", 30)
textsurface = font.render("Score: {:,}".format(score), True, (0, 0, 0))
spaceship_collided = False
running = True
while running:
--snip--
screen.fill((255, 255, 255)) # DO NOT DRAW ANYTHING IN FRONT OF THIS LINE, I'M WARNING YOU
bullets.update()
key = pygame.key.get_pressed()
amount = 5
if key[pygame.K_a]:
spaceship.x -= amount
--snip--
spaceship.update()
if not spaceship_collided:
screen.blit(spaceship.image, spaceship.rect)
if spaceship_collided is False:
bullets.draw(screen)
enemies.draw(screen)
for i in enemies:
i.update()
if pygame.sprite.spritecollide(i, bullets, True):
score += i.score_given
i.kill()
if score >= 99999:
score = 99999
textsurface = font.render("Score: {:,}".format(score), True, (0, 0, 0))
screen.blit(textsurface, (590, 0))
if pygame.sprite.spritecollide(spaceship, enemies, dokill=True):
spaceship_collided = True
bg.blitme()
pygame.display.update()
clock.tick(60)
Can anybody help me?
The condition pygame.sprite.spritecollide(spaceship, enemies, dokill=True): os only fulfilled in a single frame. If you want to permanently display the game over screen, you need to draw it depending on the spaceship_collided :
running = True
while running:
# [...]
if pygame.sprite.spritecollide(spaceship, enemies, dokill=True):
spaceship_collided = True
if spaceship_collided:
bg.blitme()
pygame.display.update()
clock.tick(60)

Enemy not appearing

I am making a pygame game where you control a spaceship and fire bullets to hit the enemies. As of right now, I am trying to make an enemy appear on the screen. Not making it move yet. However, when I ran my following code, Nothing but the spaceship appeared. The spaceship also was able to move and fire bullets.
This is my current code:
import pygame
from pygame.locals import *
pygame.init()
screen = pygame.display.set_mode((800, 500))
screen.fill((255, 255, 255))
class Spaceship(pygame.sprite.Sprite):
def __init__(self, s, x, y):
pygame.sprite.Sprite.__init__(self)
self.screen = s
self.x, self.y = x, y
self.image = pygame.image.load("C:/eqodqfe/spaceship.png")
self.image = pygame.transform.scale(self.image, (175, 175))
self.rect = self.image.get_rect()
self.rect.center = (self.x, self.y)
def update(self):
self.rect.center = (self.x, self.y)
class Bullet(pygame.sprite.Sprite):
def __init__(self, s, x, y):
pygame.sprite.Sprite.__init__(self)
self.screen = s
self.x, self.y = x, y
self.image = pygame.image.load("C:/eqodqfe/bullet.png")
self.image = pygame.transform.scale(self.image, (100, 100))
self.rect = self.image.get_rect()
self.rect.center = (self.x, self.y)
def update(self):
self.y -= 1
self.rect.center = (self.x, self.y)
if self.y < 0:
self.kill()
class Enemy(Spaceship):
def __init__(self, s, x, y):
Spaceship.__init__(self, s, x, y)
self.image = pygame.image.load("C:/eqodqfe/enemy.png")
self.image = pygame.transform.scale(self.image, (175, 175))
self.rect = self.image.get_rect()
spaceship = Spaceship(screen, 400, 400)
enemy = Enemy(screen, 100, 100)
bullets = pygame.sprite.Group()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == MOUSEBUTTONDOWN:
bullet = Bullet(screen, spaceship.x, spaceship.y - 20)
bullets.add(bullet)
bullets.update()
key = pygame.key.get_pressed()
if key[pygame.K_a]:
spaceship.x -= 0.5
elif key[pygame.K_d]:
spaceship.x += 0.5
elif key[pygame.K_w]:
spaceship.y -= 0.5
elif key[pygame.K_s]:
spaceship.y += 0.5
spaceship.update()
screen.blit(enemy.image, enemy.rect)
enemy.update()
screen.fill((255, 255, 255))
screen.blit(spaceship.image, spaceship.rect)
bullets.draw(screen)
pygame.display.update()
What is wrong?
You have to draw the enemy after drawing the background. If you draw the enemy before the background, the background will hide the enemy:
running = True
while running:
# [...]
# screen.blit(enemy.image, enemy.rect) <-- DELETE
enemy.update()
screen.fill((255, 255, 255))
screen.blit(enemy.image, enemy.rect) # <-- INSERT
screen.blit(spaceship.image, spaceship.rect)
bullets.draw(screen)
pygame.display.update()

Check sprite collision only for one side

Hey I have a problem with my game code. Is there a way to check if, for example, the right side of my player sprite (hero) is colliding with the group obstacle_sprite?
I can only figure out how to check for collision in general but I want only a true output if the right side of the player is colliding wit the obstacle.
In my code I am using a invisible sprite as hitbox and draw the player inside this invisible sprite to check collisions but make it look like the obstacle denies moving of the player.
import pygame
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
PINK = (230, 77, 149)
FPS = 60
window_width = 900
window_height = 600
window_color = WHITE
window = pygame.display.set_mode((window_width, window_height))
window.fill(window_color)
pygame.display.set_caption("Bub - the game")
clock = pygame.time.Clock()
class Player(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, velocity, color):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((width + 2*velocity, height + 2*velocity))
self.rect = self.image.get_rect()
self.rect.topleft = (x - velocity, y - velocity)
self.velocity = velocity
self.is_jump = False
self.jump_count = 15
self.fall_count = 1
self.ground = self.rect.bottom
self.body = pygame.Rect(x, y, width, height)
pygame.draw.rect(window, color, self.body)
def move(self):
key = pygame.key.get_pressed()
if key[pygame.K_a] and self.rect.left >= 0:
self.rect.x -= self.velocity
if key[pygame.K_d] and self.rect.right <= window_width:
self.rect.x += self.velocity
if not self.is_jump:
if key[pygame.K_SPACE] and self.rect.bottom == self.ground:
self.is_jump = True
else:
if self.jump_count >= 0:
self.rect.y -= round((self.jump_count ** 2) * 0.1)
self.jump_count -= 1
else:
self.jump_count = 15
self.is_jump = False
def gravity(self):
if not self.is_jump and self.rect.bottom != self.ground:
self.rect.y += round((self.fall_count ** 2) * 0.1)
if self.fall_count <= 15:
self.fall_count += 1
else:
self.fall_count = 1
def update(self):
self.move()
self.gravity()
pygame.draw.rect(window, BLACK, self.body)
self.body.topleft = (self.rect.x + self.velocity, self.rect.y + self.velocity)
class Obstacle(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, color):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((width, height))
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.center = (x, y)
player_sprite = pygame.sprite.Group()
obstacle_sprite = pygame.sprite.Group()
hero = Player(0, 570, 30, 30, 5, BLACK)
player_sprite.add(hero)
obstacle_1 = Obstacle(100, 585, 120, 30, PINK)
obstacle_sprite.add(obstacle_1)
run = True
while run:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(window_color)
player_sprite.update()
player_sprite.draw(window)
obstacle_sprite.update()
obstacle_sprite.draw(window)
pygame.display.update()
pygame.quit()
It's easier if you keep track of your player's velocity; this will make handling jumping also less complex.
A common technique AFAIK is to do two collision detection runs: one for the x-axis and one for the y-axis.
Here's how I changed your code. As you can see, checking for the side of a collision is as easy as checking xvel < 0 or xvel > 0:
import pygame
pygame.init()
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
PINK = (230, 77, 149)
FPS = 60
window_width = 900
window_height = 600
window_color = WHITE
window = pygame.display.set_mode((window_width, window_height))
window.fill(window_color)
pygame.display.set_caption("Bub - the game")
clock = pygame.time.Clock()
class Toast(pygame.sprite.Sprite):
def __init__(self, *grps):
super().__init__(*grps)
self.image = pygame.Surface((900, 200))
self.image.fill(WHITE)
self.rect = self.image.get_rect(topleft=(10, 10))
self.font = pygame.font.SysFont(None, 32)
def shout(self, message):
self.text = self.font.render(message, True, BLACK)
self.image.fill(WHITE)
self.image.blit(self.text, (0, 0))
class Player(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, velocity, color, *grps):
super().__init__(*grps)
self.image = pygame.Surface((width + 2*velocity, height + 2*velocity))
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.topleft = (x - velocity, y - velocity)
self.velocity = velocity
self.on_ground = True
self.xvel = 0
self.yvel = 0
def collide(self, xvel, yvel, obstacle_sprites):
global toast
for p in pygame.sprite.spritecollide(self, obstacle_sprites, False):
if xvel > 0:
self.rect.right = p.rect.left
toast.shout('collide RIGHT')
if xvel < 0:
self.rect.left = p.rect.right
toast.shout('collide LEFT')
if yvel > 0:
self.rect.bottom = p.rect.top
self.on_ground = True
self.yvel = 0
toast.shout('collide BOTTOM')
if yvel < 0:
self.rect.top = p.rect.bottom
self.yvel = 0
toast.shout('collide TOP')
def update(self, obstacle_sprites):
global toast
key = pygame.key.get_pressed()
self.xvel = 0
if key[pygame.K_a]:
self.xvel =- self.velocity
if key[pygame.K_d]:
self.xvel = self.velocity
if self.on_ground:
if key[pygame.K_SPACE]:
toast.shout('JUMPING')
self.on_ground = False
self.yvel = -20
else:
self.yvel += 1
self.rect.left += self.xvel
self.collide(self.xvel, 0, obstacle_sprites)
self.rect.top += self.yvel
self.on_ground = False
self.collide(0, self.yvel, obstacle_sprites)
self.rect.clamp_ip(pygame.display.get_surface().get_rect())
if self.rect.bottom == pygame.display.get_surface().get_rect().bottom:
self.on_ground = True
class Obstacle(pygame.sprite.Sprite):
def __init__(self, x, y, width, height, color, *grps):
super().__init__(*grps)
self.image = pygame.Surface((width, height))
self.image.fill(color)
self.rect = self.image.get_rect(center=(x, y))
all_sprites = pygame.sprite.Group()
player_sprite = pygame.sprite.Group()
obstacle_sprites = pygame.sprite.Group()
toast = Toast(all_sprites)
hero = Player(0, 570, 30, 30, 5, GREEN, all_sprites, player_sprite)
for x in [
(100, 585, 120, 30),
(300, 535, 120, 30),
(500, 485, 120, 30)
]:
Obstacle(*x, PINK, all_sprites, obstacle_sprites)
run = True
while run:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.fill(window_color)
all_sprites.update(obstacle_sprites)
all_sprites.draw(window)
pygame.display.update()
pygame.quit()

Collision between masks in pygame

I have a problem with collisions in my game, spaceship's mask doesn't collide properly with a background and I believe that offset is a problem, however I'm not sure.
I've tried multiple collision techniques and checked a lot of answers to my problem, but none of them helped me.
import pygame as pg
import os
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (2, 29)
pg.init()
def gameLoop():
display_width = 1800
display_height = 1000
pg.display.set_caption("Kerbal Space Landing Simulator")
clock = pg.time.Clock()
display = pg.display.set_mode((display_width, display_height))
sprsp = pg.image.load('C:/Users/PC/PycharmProjects/untitled/sprspaceship.png').convert_alpha()
cosbg = pg.image.load('C:/Users/PC/PycharmProjects/untitled/cosmos bg.png').convert_alpha()
done = False
class Spaceship:
def __init__(self, x, y, mass):
self.x = x
self.y = y
self.width = 139
self.height = 106
self.velx = 0
self.vely = 0
self.mass = mass
self.color = (255, 255, 255)
self.spr = sprsp
self.fuel = 500
self.mask = pg.mask.from_surface(self.spr)
self.angle = 0
self.changerot = 0
def check_controls(self):
if keys[pg.K_SPACE] and self.fuel > 0:
if self.angle > 0:
self.vely += 0.005 * (self.angle - 90)
self.velx += -0.005 * self.angle
else:
self.vely += -0.005 * (self.angle + 90)
self.velx += -0.005 * self.angle
self.fuel += -3
if keys[pg.K_LEFT] and self.angle < 90:
self.angle += 2
if keys[pg.K_RIGHT] and self.angle > -90:
self.angle += -2
def update_pos(self):
self.vely += 0.01
self.x += self.velx
self.y += self.vely
self.mask = pg.mask.from_surface(self.spr)
def update_rotation(self):
self.rspr = pg.transform.rotate(self.spr, self.angle)
self.changerot -= self.angle
def draw(self):
if self.fuel > 0:
pg.draw.rect(display, (255, 255, 255), (display_width - 100, 100 + 500 - self.fuel, 10, self.fuel), 0)
display.blit(self.rspr, (int(self.x), int(self.y)))
self.changerot = 0
class Terrain(object):
def __init__(self, x, y):
self.x = x
self.y = y
self.mask = pg.mask.from_threshold(display, (160, 160, 160))
self.hitbox = pg.Rect(self.x, self.y, display_width, 500)
self.ox = display_width // 2 - self.x // 2
self.oy = display_height // 2 - self.y // 2
def draw(self):
pg.draw.rect(display, (160, 160, 160), (self.x, self.y, display_width, 500), 0)
spaceship = (Spaceship(500, 100, 1))
terrain = (Terrain(0, 800))
def redrawGameWindow():
display.blit(cosbg, (0, 0))
spaceship.draw()
terrain.draw()
pg.display.update()
def check_for_collisions():
offset = (int(spaceship.x - terrain.ox), int(spaceship.y - terrain.oy))
print(offset)
print(spaceship.mask.overlap(terrain.mask, offset))
return spaceship.mask.overlap(terrain.mask, offset)
# return spaceship.hitbox.colliderect(terrain.hitbox)
# return pg.sprite.spritecollide(spaceship.spr, terrain.mask, False, pg.sprite.collide_mask)
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
keys = pg.key.get_pressed()
mouse_pressed = pg.mouse.get_pressed()
x, y = pg.mouse.get_pos()
spaceship.check_controls()
spaceship.update_pos()
spaceship.update_rotation()
if check_for_collisions() is not None:
print('Hit! You\'ve hit the ground with the speed:', spaceship.vely)
exit()
redrawGameWindow()
clock.tick(60)
gameLoop()
Spaceship doesn't collide with the surface. I know how to fix it using simpler code, but in future I want to use randomly generated terrain. Could you help me with these collisions?
The mask for the Terrain is never set. Crate a proper Terrain mask:
class Terrain(object):
def __init__(self, x, y):
self.x = x
self.y = y
maskSurf = pg.Surface((display_width, display_height)).convert_alpha()
maskSurf.fill(0)
pg.draw.rect(maskSurf, (160, 160, 160), (self.x, self.y, display_width, 500), 0)
self.mask = pg.mask.from_surface(maskSurf)
print(self.mask.count())
# [...]
When using pygame.mask.Mask.overlap(), then you've to check the overlapping of the Spaceship and the Terrain, rather than the Terrain and the Spaceship.
Since the Terrain mask is a mask of the entire screen, the offset for the overlap() test is the position of the Spaceship:
def check_for_collisions():
offset = (int(spaceship.x), int(spaceship.y))
collide = terrain.mask.overlap(spaceship.mask, offset)
print(offset, collide)
return collide
Minimal example: repl.it/#Rabbid76/PyGame-SurfaceMaskIntersect
See also: Mask

Categories