How to make Pygame respond different keys constantly with different speeds? - python

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.

Related

How do i add a cooldown to enemy attacks in pygame? [duplicate]

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]

How do I schedule an audio file to play automatically in pygame after the first song ends? [duplicate]

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()

Pygame responds incorrectly to button clicks

I'm having an issue with pygame. I've set up a window that randomly places circles across the screen very quickly, just for testing purposes. There are also three buttons: play/pause (switches back and forth, stops circles from appearing) and an increase speed and decrease speed button. I'm not very experienced with python or pygame, but I've come up with this function to create a clickable button on the screen:
def makeButton(rect, color, hovercolor, text, textsize, textcolor):
clicked = False
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
clicked = True
mouse = pygame.mouse.get_pos()
rect = pygame.Rect(rect)
displaycolor = color
if rect.collidepoint(mouse):
displaycolor = hovercolor
buttonSurface = pygame.draw.rect(gameDisplay, displaycolor, rect, 0)
font = pygame.font.Font('freesansbold.ttf',textsize)
TextSurf = font.render(text, True, textcolor)
TextRect = TextSurf.get_rect()
TextRect.center = rect.center
gameDisplay.blit(TextSurf, TextRect)
if clicked:
return True
else:
return False
This function can definitely be shortened and simplified, but it has worked for me, up until now. I took out a big chunk of code that I realized was useless (having a completely different block of code to render the button when hovered, instead of just changing the display color). Now, whenever I click any of the three previously-mentioned buttons, it seems to pick a random one and return True, messing up the rest of the program. For example, the play button will increase the speed one time, pressing decrease speed will pause, etc. Sometimes it does do what it is supposed to, but it seems to be random.
Some extra info, if it's useful:
-This function is called three times every tick. It's inside a loop, and if it returns true, its corresponding actions are supposed to be performed (pause or play the game, increase/decrease speed)
-The play/pause button is one button that toggles between green with an 'play' arrow, and red with a pause symbol. They are two separate buttons and functions, and only one of them is executed at a time.
-I have almost zero experience with classes, so they may be way better at handling this situation.
-The only explanation I can think of for this problem is that the returned booleans are getting mixed up between the different places this function is used. I'm pretty sure the problem is within this chunk of code, but ask me and I will post the places it is called too.
"pygame.event.get()" takes one event at a time, and *clears it** from the list of events that need to be processed.
So, more specifically, pygame.event.get() returns each event only once.
Take a look at the following code:
clicked = False
for event in pygame.event.get():
if event.type == pygame.MOUSEBUTTONDOWN:
clicked = True
After this is called, all of the events are removed. Here is an analysis of the code. Assume that there are currently two events that haven't been processed, the first being a key pressed down and the other being a mouse button that's been pressed down.
The first event, event.KEYDOWN, is put into the variable "event".
The program checks whether "event" (currently equal to event.KEYDOWN) is equal to event.MOUSEBUTTONDOWN. They are obviously not the same thing, so the next line is skipped.
The second event, event.MOUSEBUTTONDOWN, is put into variable "event". This removes what was previously in the variable "event", removing the first event from existence.
The program checks whether "event" (currently equal to event.MOUSEBUTTONDOWN) is equal to event.MOUSEBUTTONDOWN. It is, so it proceeds to the next line...
"clicked" is set to True, and the for loop exits, because there are no event remaining.
You should now have a better understanding of how Pygame processes events.
There are also many problems with the function you gave (makeButton). You should find a python tutorial to learn the rest. I suggest a book called "Hello World", by Carter and Warren Sande. The book is kind of out of date (teaches Python 2.5), but its code still works with Python 2.7, and it is one of the few decent Python books I've been able to find.
I have included the code to do what you are trying to do. I don't use Rect objects, but if you want them you can change the code to include them. I also didn't include the text, because I am short on time. Instead of placing random circles, this prints text (to the shell) when buttons are clicked.
import pygame, sys
pygame.init()
screen = pygame.display.set_mode([640,480])
clock = pygame.time.Clock()
buttons = []
#buttons = [[rect, color, hovercolor, hovering, clicked, msg]]
def makeButton(rect, color, hovercolor, text):
global buttons
buttons.append([rect, color, hovercolor, False, False, text])
makeButton([0,0,50,50], [0,127,0], [0,255,0], "Clicked Green")
makeButton([50,0,50,50], [190,190,0], [255,255,0], "Clicked Yellow")
makeButton([100,0,50,50], [0,0,127], [0,0,255], "Clicked Blue")
while 1:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.MOUSEMOTION:
mousepos = event.pos
for a in range(len(buttons)):
if mousepos[0] >= buttons[a][0][0] and mousepos[0] <= buttons[a][0][0]+buttons[a][0][2] and mousepos[1] >= buttons[a][0][1] and mousepos[1] <= buttons[a][0][1]+buttons[a][0][3]:
buttons[3] = True
else:
buttons[3] = False
if event.type == pygame.MOUSEBUTTONDOWN:
mousepos = event.pos
for a in range(len(buttons)):
if mousepos[0] >= buttons[a][0][0] and mousepos[0] <= buttons[a][0][0]+buttons[a][0][2] and mousepos[1] >= buttons[a][0][1] and mousepos[1] <= buttons[a][0][1]+buttons[a][0][3]:
buttons[4] = True
else:
buttons[4] = False
for a in range(len(buttons)):
if buttons[3] == 0:
pygame.draw.rect(screen, buttons[1], buttons[0])
else:
pygame.draw.rect(screen, buttons[2], buttons[0])
if buttons[4] == 1:
buttons[4] = 0
print buttons[5]
pygame.display.flip()
I haven't had the opportunity to test out the code I just typed (using school computer), but it should work. If there are any problems with the code, just leave a comment and I'll fix it.
Also leave a comment if you don't understand something. Don't give up, you can do it!

list index is out of range for bullets in pygame

So i'm rather new to pygame, and the project i have been working with is making the old alien invasion arcade game. I know i need to clean up my pictures and display and dimensions and stuff so don't worry about that, but the problem i'm struggling with now is firing my bullets, i'm storing them in a list and then deleting them. but i come up with the error "list index out of range". this error shows up on line 50 of this code. Oh and this error only shows up while i have 2 bullets active at a time. for instance i can shoot 1 every second or two just fine, but if i rapid fire a little bit and two are moving on screen at once then i get the error the things to really look at are on lines 42-52, 88-90,and the last 3. also any suggestions on improving the efficiency of my code is greatly appreciated
import pygame
pygame.init()
#keystate variables
keystate={'left':False,'right':False,'up':False,'down':False}
red=(255,0,0)
black=(0,0,0)
green=(0,255,0)
shipX=0
shipY=445
bulletsX=[]
bulletsY=[]
ship=pygame.image.load("ship.png")
ship=pygame.transform.scale(ship,(35,35))
bullet_pic=pygame.image.load("bullet.png")
bullet_pic=pygame.transform.scale(bullet_pic,(25,25))
backdrop=pygame.image.load("backdrop.png")
backdrop=pygame.transform.scale(backdrop,(640,400))
clock=pygame.time.Clock()
screen=pygame.display.set_mode((640,480))
screen.blit(backdrop,(0,0))
# ship movement functions
def moveship_Xneg():
global shipX
if shipX>0:
shipX-=1
def moveship_Xpos():
global shipX
if shipX<605:
shipX+=1
def moveship_Yneg():
global shipY
if shipY<445:
shipY+=1
def moveship_Ypos():
global shipY
if shipY>400:
shipY-=1
#gunfire definitions
def move_bullet():
for bullet in range(len(bulletsX)):
bulletsY[bullet]-=2
screen.blit(bullet_pic,(bulletsX[bullet],bulletsY[bullet]))
pygame.display.update()
def del_bullet():
for bullet in range(len(bulletsX)):
#below this is line 50, realized it didn't show numbers, my bad
if bulletsY[bullet]<=-10:
bulletsY.remove(bulletsY[bullet])
bulletsX.remove(bulletsX[bullet])
# ship movement changes
def start_ship(): #draws the starting position of the ship
screen.blit(ship,(0,445))
pygame.display.update()
def draw_newship(): #draws the new ship and updates the screen
screen.blit(ship,(shipX,shipY))
#screen.blit(backdrop,(shipX
#print(shipX,shipY)
pygame.display.update()
#def del_oldship(): #deletes the old ship
start_ship()
#Main Loop
running=True
while running:
clock.tick(350)
#checks keystroke events
for event in pygame.event.get():
#quits the program
if event.type==pygame.QUIT:
running=False
pygame.quit()
#KEYDOWN CHECKS
if event.type==pygame.KEYDOWN:
#Movement variable changes
if event.key==pygame.K_LEFT:
keystate['left']=True
if event.key==pygame.K_RIGHT:
keystate['right']=True
if event.key==pygame.K_DOWN:
keystate['down']=True
if event.key==pygame.K_UP:
keystate['up']=True
#Action per event
if event.key==pygame.K_SPACE:
bulletsX.append(shipX+17.5)
bulletsY.append(shipY)
#KEYUP CHECKS
if event.type==pygame.KEYUP:
#movement variable changes
if event.key==pygame.K_LEFT:
keystate['left']=False
if event.key==pygame.K_RIGHT:
keystate['right']=False
if event.key==pygame.K_DOWN:
keystate['down']=False
if event.key==pygame.K_UP:
keystate['up']=False
# pygame event processing ends
if running==True:
#performs an action per each loop dependant on keystate variables
if keystate['left']==True:
#del_oldship()
moveship_Xneg()
draw_newship()
if keystate['right']==True:
#del_oldship()
moveship_Xpos()
draw_newship()
if keystate['down']==True:
#del_oldship()
moveship_Yneg()
draw_newship()
if keystate['up']==True:
#del_oldship()
moveship_Ypos()
draw_newship()
if bulletsX!=[]:
del_bullet()
move_bullet()
#for coord in range(len(bulletsX)):
#print(bulletsX[coord],bulletsY[coord])
Actually you can find error in the error message. List index out of range
For example
a = [1]
a.remove(1)
>>>a[0]
`IndexError: list index out of range`
In your code,
if bulletsY[bullet] <= -10
This time may be no elements in bulletsY in the position bullet or list bulletsY may be empty.
So you can try like this
if bulletsY and bulletsY[bullet] <= -10
Hope this helps
Updated
Try this
def del_bullet():
for bullet in range(len(bulletsX)):
#below this is line 50, realized it didn't show numbers, my bad
try:
if bulletsY[bullet]<=-10:
bulletsY.remove(bulletsY[bullet])
bulletsX.remove(bulletsX[bullet])
except:pass

pygame.key.set_repeat for Joystick

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.

Categories