2

I'm creating a game in pygame and I want to decrease the player's HP slowly yet in real time. For this, I know that I can't use time.sleep() because it freezes the whole game. I've tried using threading.Thread on pygame.MOUSEBUTTONDOWN to do an amount of damage to the player, but it did not work. Keep in mind that it is my first time using threads ever.

My code:

#-*- coding-utf8 -*-
import pygame
import threading
import time
import os

os.environ['SDL_VIDEO_CENTERED'] = '1'
pygame.init()

SIZE = (1200, 600)
screen = pygame.display.set_mode(SIZE)
clock = pygame.time.Clock()

class Player:
    def __init__(self):
        self.HP = 100
        self.HP_BAR = pygame.Rect(10, 10, self.HP * 3, 10)

        self.X = 100
        self.Y = 100
        self.WIDTH = 50
        self.HEIGHT = 100
        self.COLOR = (255,255,0)
        self.PLAYER = pygame.Rect(self.X, self.Y, self.WIDTH, self.HEIGHT)

    def move(self):
        pressed = pygame.key.get_pressed()
        if pressed[pygame.K_w]: self.Y -= 1
        if pressed[pygame.K_s]: self.Y += 1
        if pressed[pygame.K_a]: self.X -= 1
        if pressed[pygame.K_d]: self.X += 1

        self.PLAYER = pygame.Rect(self.X, self.Y, self.WIDTH, self.HEIGHT)

    def draw(self):
        # clear screen
        screen.fill((0,0,0))

        # draw HP bar
        self.HP_BAR = pygame.Rect(10, 10, self.HP * 3, 10)
        pygame.draw.rect(screen, (50,0,0), self.HP_BAR)

        # draw player
        pygame.draw.rect(screen, self.COLOR, self.PLAYER)

        pygame.display.flip()

    def remove_HP(self, value):
        for x in range(value):
            self.HP -= 1
            self.draw()
            # time.sleep(0.1)

p1 = Player()

while True:  

    for event in pygame.event.get():
        if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
            exit()

        if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            td2 = threading.Thread(target = p1.remove_HP(20), name = "td2")
            td2.daemon = True
            td2.start()

    p1.move()
    p1.draw()

    clock.tick(60)

Am I using threads wrong? I want to execute the p1.remove_HP() and p1.move() at the same time, but what the program does is:

user clicks while pressing [A]

1º: p1.move()

2º: p1 stops moving

3º: p1.remove_HP()

4º: p1 is moving again

Sorry if it is stupid but I'm really trying and studying. Thanks in advance!

Pedro Gonçalves
  • 119
  • 1
  • 1
  • 10

1 Answers1

1

You don't need threads at all. Don't make your life more complicated than it has to be.

You just need a different thinking about how your game runs. It runs in a loop; in your case it runs at 60 fps.

So if you want to do anything over time, you have different options:

1) Make a variable, increase it every frame, and once it hits 60, you know 1 second passed (easy, but not reliable since you depend on a constant frame rate)

2) Use events. In pygame, you can create custom events that you can trigger manually or periodically, and handle them in your main loop like pygame's build in events (works good for simple use cases, but the amount of events you can create is limited)

3) Make a variable and store a time stamp in it. A time stamp can be the system time or something else, like the amount of ticks since your game started (using e.g. pygame's pygame.time.get_ticks. Then, every frame compare it to the current system time or ticks to calculate how much time has passed

I updated your code a little bit. Note how the I seperated the different concerns into different parts of the code:

  • The player class only cares about itself (position, movement, hp), not how the entire screen is managed
  • The GUI class only cares about drawing the GUI
  • The main loop doesn't care what's on the screen

I just decrease the hp a tiny bit each frame (which is basically a form of option 1 I listed above); it's a good-enough approach to your problem.

#-*- coding-utf8 -*-
import pygame
import threading
import time
import os

os.environ['SDL_VIDEO_CENTERED'] = '1'
pygame.init()

SIZE = (1200, 600)
screen = pygame.display.set_mode(SIZE)
clock = pygame.time.Clock()

class GUI(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((1200, 600))
        self.image.set_colorkey((12,34,56))
        self.image.fill((12,34,56))
        self.rect = self.image.get_rect()
        self.player = None

    def update(self):
        self.image.fill((12,34,56))
        if self.player:
            pygame.draw.rect(self.image, (50, 0, 0), pygame.Rect(10, 10, self.player.hp * 3, 10))

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()

        self.image = pygame.Surface((50, 100))
        self.image.fill((255,255,0))
        self.rect = self.image.get_rect(topleft=(100, 100))

        self.hp = 100

    def update(self):
        pressed = pygame.key.get_pressed()
        if pressed[pygame.K_w]: self.rect.move_ip( 0, -1)
        if pressed[pygame.K_s]: self.rect.move_ip( 0,  1)
        if pressed[pygame.K_a]: self.rect.move_ip(-1,  0)
        if pressed[pygame.K_d]: self.rect.move_ip( 1,  0)

        self.hp -= 0.05

p1 = Player()
gui = GUI()
gui.player = p1
sprites = pygame.sprite.Group(p1, gui)

while True:  

    for event in pygame.event.get():
        if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
            exit()

    screen.fill((0,0,0))
    sprites.update()
    sprites.draw(screen)
    pygame.display.flip()
    clock.tick(60)
sloth
  • 99,095
  • 21
  • 171
  • 219
  • Thanks! I'm trying to not use the first option because I heard and saw that is not viable doing things over time in pygame. I'll organize my classes better from now on tho :) Also I'll take a look at the events that you mentioned – Pedro Gonçalves Dec 03 '18 at 12:49