This question already has answers here:
How do I continuously trigger an action at certain time intervals? Enemy shoots constant beam instead of bullets in pygame [duplicate]
(1 answer)
How can one continuously generate and track several random objects with a time delay in pygame? [duplicate]
(1 answer)
Closed 2 years ago.
I've tried using threading.timer to solve the issue, but can't seem to get it to work for what i want to do. No error messages. Either it doesn't even subtract from the players health, or it just drains the health instantly defeating the whole point, and time.sleep just freezes the whole program.
Here is the code i can't get to work properly
from threading import Timer
import pygame
playerhealth = ['<3', '<3', '<3', '<3', '<3'] # just a list of player health symbolized by hearts
running = True
while running:
def removehealth():
playerhealth.__delitem__(-1) # deletes the last item in the list of hearts
t = Timer(1, removehealth)
t.start()
# id display the hearts on screen down here
The way to do that using pygame, is to usee pygame.time.set_timer() to repeatedly create an USEREVENT. e.g.:
milliseconds_delay = 1000 # 1 seconds
timer_event = pygame.USEREVENT + 1
pygame.time.set_timer(timer_event, milliseconds_delay)
In pygame customer events can be defined. Each event needs a unique id. The ids for the user events have to be between pygame.USEREVENT (24) and pygame.NUMEVENTS (32). In this case pygame.USEREVENT+1 is the event id for the timer event, which drains the health.
Remove a heart when the event occurs in the event loop:
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == timer_event:
del playerhealth[-1]
A timer event can be stopped by passing 0 to the time parameter (pygame.time.set_timer(timer_event, 0)).
You can use the module time and wait for a certain amount of seconds.
import time
start = time.time() # gets current time
while running:
if time.time() - start > 1: # if its been 1 second
removehealth()
start = time.time()
Also to delete the last item in a list, you can do del playerhealth[-1]
Related
I am making a shooting game containing a "player" who can shoot "bullets". Press "WASD" can control the movement of the player and press "Space" can make the "player" shoot. Now I hope that Pygame can respond the long-pressed keys with different speeds. For example, respond "WASD" for every 10 ms and respond "Space" for every 1000ms. What should I do?
I have tried pygame.key.set_repeat() and every key will be responded with the same speed.
pygame.key.set_repeat() sets the delay for the whole keyboard, it cannot discriminate between keys. You need to do it in your program.
A possible solution is to set the delay between repeated events with set_repeat to the shortest interval you want to have in your game. For keys which require a longer interval, you need to check by yourself if enough time is passed to "accept" the event and allow the corresponding action to be performed.
This sample code should give you an idea of what I mean.
import sys
import pygame
#the action you want to perform when the key is pressed. Here simply print the event key
#in a real game situation, this action would differ according to the key value
def onkeypress(event):
print(event.key)
#dict of key constants for which you want a longer delay and their tracking time
#in ms, initialized to 0. Here only the spacebar
repeat1000 = {pygame.K_SPACE : 0}
pygame.init()
screen = pygame.display.set_mode((500, 500))
#sets repeat interval to 10 milliseconds for all keys
pygame.key.set_repeat(10)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == pygame.KEYDOWN:
current_time = pygame.time.get_ticks()
if event.key in repeat1000.keys():
if current_time - repeat1000[event.key] > 1000:
repeat1000[event.key] = current_time
onkeypress(event)
elif event.key in [pygame.K_w, pygame.K_a, pygame.K_s, pygame.K_d]:
onkeypress(event)
If you try the above code, you'll see that the key of the spacebar (32 on my system) is printed each second if you keep pressed the spacebar. Buf if you press one of W A S D the corresponding keys (119, 97, 115, 100 on my system) are printed each 0.01 seconds.
I'm trying to recreate new object each time user press c key so that I can load game once again from start point. But I'm not able to find a way to do so.
This is what I've tried so far:
def initializeGame(theGame):
while run:
theGame.clock.tick(FPS)
# This function consists code for Events
theGame.events()
# This function consists code from enemy hit events
theGame.hit_or_not()
# This function consists code for bullet hit events
theGame.bulletHit_or_not()
# This function consists code for player movements
theGame.movements()
# This function consists code for drawing the sprites over the screen
theGame.redrawGameWindow()
def startGame(run):
first_game = Game()
while run:
initializeGame(first_game)
keys = pygame.key.get_pressed()
if keys[pygame.K_ESCAPE]:
run = False
if keys[pygame.K_c]:
new_game = Game()
initializeGame(new_game)
startGame(True)
All I want to do is when I press 'c' key the game must restart from starting point and for that I have to recreate new 'Game()' class object and initialize the game
Game Class Code - https://pastebin.com/abAiey34
Avoid a game loop in the game loop, this means remove the loop from initializeGame.
The name initializeGame is misleading, call it runGame:
def runGame(theGame):
# This function consists code for Events
theGame.events()
# This function consists code from enemy hit events
theGame.hit_or_not()
# This function consists code for bullet hit events
theGame.bulletHit_or_not()
# This function consists code for player movements
theGame.movements()
# This function consists code for drawing the sprites over the screen
theGame.redrawGameWindow()
So it is sufficient to have a single game, which is just "reset" ba creating an ew game object.
In the one and only game loop runGame has to be called:
def startGame(run):
game = Game()
while run:
theGame.clock.tick(FPS)
# run the game
runGame(game)
# get keys
keys = pygame.key.get_pressed()
# handle keys
if keys[pygame.K_ESCAPE]:
run = False
if keys[pygame.K_c]:
game = Game()
startGame(True)
Note, startGame() already has a loop, so there is no necessity for a further game loop in any function. runGame() does all the things which have be done in on frame of the game. runGame() is continuously called in the game loop in startGame().
If a new game has to be started, it is sufficient to create a new Game object.
Note, the states which are returned by pygame.key.get_pressed() are evaluated, when the pygame.events are handled by either pygame.event.get() or pygame.event.pump().
Call pygame.key.get_pressed() after the event loop.
I would prefere a slightly different designe. Get the events in the main loop (pygame.event.get()) and pass them to runGame() and further to Game.events():
class Game:
# [...]
def events(self, eventlist):
for event in eventlist:
# handle events
# [...]
def runGame(theGame, eventlist):
# This function consists code for Events
theGame.events(eventlist)
# [...]
def startGame(run):
game = Game()
while run:
theGame.clock.tick(FPS)
# run the game
eventlist = pygame.event.get()
runGame(game, eventlist)
# get keys
keys = pygame.key.get_pressed()
# handle keys
if keys[pygame.K_ESCAPE]:
run = False
if keys[pygame.K_c]:
game = Game()
This question already has an answer here:
Pygame Playlist without while True loop
(1 answer)
Closed 1 year ago.
I tried using the queue function, but
pygame.mixer.music.queue(filename)
doesn't seem to be working.
Here is the code I use to run my mp3 file:
def playmusic(self):
pygame.mixer.init()
pygame.mixer.music.load(self.music_link+self.files[self.file_index])
pygame.mixer.music.play()
self.pausedmusic = 0
self.file_index = self.fileindex + 1
pygame.mixer.music.queue(self.music_link+self.files[self.file_index])
I tried to use events but got no solution from it either.
And if I use this code,
while(pygame.mixer.music.get_busy()):
continue
self.playmusic()
the Tkinter GUI is unresponsive but the song keeps playing and it plays the next song automatically, too, keeping my player unresponsive till all songs are played.
I'm using Python 3.6.
Put your music files (paths) into a list, define a custom userevent and call pygame.mixer.music.set_endevent(YOUR_USEREVENT). Then pygame will add this event to the event queue when a song is finished and you can execute some code to change the index of the current song. In the example below you can either increment the index by pressing the right arrow key or wait until a song is finished (the SONG_FINISHED event is emitted) and the program will choose a random song (index).
import random
import pygame as pg
pg.mixer.pre_init(44100, -16, 2, 2048)
pg.init()
screen = pg.display.set_mode((640, 480))
# A list of the music file paths.
SONGS = ['file1.ogg', 'file2.ogg', 'file3.ogg']
# Here we create a custom event type (it's just an int).
SONG_FINISHED = pg.USEREVENT + 1
# When a song is finished, pygame will add the
# SONG_FINISHED event to the event queue.
pg.mixer.music.set_endevent(SONG_FINISHED)
# Load and play the first song.
pg.mixer.music.load('file1.ogg')
pg.mixer.music.play(0)
def main():
clock = pg.time.Clock()
song_idx = 0 # The index of the current song.
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.KEYDOWN:
# Press right arrow key to increment the
# song index. Modulo is needed to keep
# the index in the correct range.
if event.key == pg.K_RIGHT:
print('Next song.')
song_idx += 1
song_idx %= len(SONGS)
pg.mixer.music.load(SONGS[song_idx])
pg.mixer.music.play(0)
# When a song ends the SONG_FINISHED event is emitted.
# Then just pick a random song and play it.
elif event.type == SONG_FINISHED:
print('Song finished. Playing random song.')
pg.mixer.music.load(random.choice(SONGS))
pg.mixer.music.play(0)
screen.fill((30, 60, 80))
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()
Since importing the module "time" and using time.sleep(seconds) freezes the whole screen. How to freeze a sprite for 3, 4 or 5 seconds? I Tried doing this but it freezes the whole pygame for 3 seconds!
elif event.key == pygame.K_q:
time.sleep(3)
In Pygame you are responsible for the "main loop", and therefore for all the time management of your game.
This is great for simple drawing examples, and great to "keep in control" of things - but that means that if are the sole responsble for everything that moves and updates on the screen.
When you call pygame.time.delay or time.sleep you pause the whole program - the only way of pausing a certain object and not others, and nt stopping responding events at all, is to build mechanisms in your code that allow for individual object pausing.
For simple code, it can be a simple "pause" attribute and an if verification in your sprite code - for example, given a "frame rate" of 30 updates per second - soemthng along this on your sprite class's update method, if you use that method to actual update the position and "take actions" with your object would be:
class MySprite(BaseSprite):
def __init__(self, ...):
self.pause = 0...
def update(self):
if self.pause:
self.pause -= 1
return
def main():
FRAMERATE = 30
myobject = MySprite(...)
while True:
if ...:
...
elif event.key == pygame.K_q:
myobject.pause = 3 * FRAMERATE
for obj in all_my_objs:
obj.update()
I'm building a menu using pygame and I want to make it navigable using a specific gamepad. Ideally I want to be able to press and hold *down" on the D-pad repeatedly, or get something like on a keyboard where the first button press has a delay before repeatedly entering the same character (seemingly).
I'm trying to emulate the pygame.key.set_repeat(...) function for a Joystick. my approach so far has been
pygame.time.set_timer(pygame.USEREVENT, 10)
DELAY_TIME = 0.250 #ms
y_delay = True
while not done:
for event in pygame.event.get():
y_axis = gamepad.get_axis(1)
if y_axis > 0.5: # pushing down
main_menu.move_down()
redraw() #redraw everything on the surface before sleeping
if y_delay:
time.sleep(DELAY_TIME)
y_delay = False #don't delay the next time the y axis is used
elif y_axis < -0.5: #pushing up
# repetitive I know, but I'm still working on it
main_menu.move_up()
redraw()
if y_delay:
time.sleep(DELAY_TIME)
y_delay = False
else:
y_delay = True # delay the next time
my issue is if someone taps up or down faster than DELAY_TIME they are limited to the DELAY_TIME before they can move again. Also if someone releases and depresses the up/down button within the time.sleep interval, python never sees that it was released at all and doesn't allow for a delay.
Maybe there's a way to do this using events or mapping the joystick to keys somehow? qjoypad doesn't cut it for me, and joy2keys is trash. I would need to do the mapping within the python program.
Sleep causes the program to halt execution, so it's not a viable option. You can also do this without using set_timer and events. I did it using a couple of flags and pygame.time's get_ticks.
import pygame
from pygame.locals import *
def main():
pygame.init()
pygame.display.set_mode((480, 360))
gamepad = pygame.joystick.Joystick(0)
gamepad.init()
delay = 1000
neutral = True
pressed = 0
last_update = pygame.time.get_ticks()
while True:
for event in pygame.event.get():
if event.type == QUIT:
return
move = False
if gamepad.get_axis(1) == 0:
neutral = True
pressed = 0
else:
if neutral:
move = True
neutral = False
else:
pressed += pygame.time.get_ticks() - last_update
if pressed > delay:
move = True
pressed -= delay
if move:
print "move"
last_update = pygame.time.get_ticks()
if __name__ == "__main__":
main()
pygame.quit()
When get_axis indicates no motion, the neutral flag is set, and the pressed timer is reset, causing the move flag to remain unset. When the neutral flag is unset, if it's newly set, the move flag is set. If it's not newly set, the pressed timer increases, and move is set only if the pressed timer is greater than delay.