Can I have different pygame.set_repeat() values for different keys? - python

pygame.key.set_repeat(1, 1)
def game():
game_running = True
while game_running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_running = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
player.move_left(3)
if keys[pygame.K_RIGHT]:
player.move_right(3)
if keys[pygame.K_UP]:
player.move_up(3)
if keys[pygame.K_DOWN]:
player.move_down(3)
if keys[pygame.K_SPACE]:
create_bullet()
The pygame.key.set_repeat() value here allows me to move the player sprite smoothly, but also causes way more bullets than I'd want to be created if the player were to hold space. To prevent this, I'd want space key to have a set_repeat value of (1, 500). Would it be possible to accomplish this without creating a solution of my own?

I think you would be better served setting a timer on when a bullet was created, and not allow another bullet to be created for BULLET_COOLDOWN milliseconds. This is fairly easy to implement with the function pygame.time.get_ticks() which returns an ever-increasing number of milliseconds.
BULLET_COOLDOWN = 500 # milliseconds between bullets
bullet_time = 0 # time when bullet fired
...
if keys[pygame.K_SPACE]:
time_now = pygame.time.get_ticks() # what time is it?
if ( bullet_time + BULLET_COOLDOWN < time_now ): # has cooldown past?
create_bullet()
bullet_time = time_now # remember for next firing

Related

Throttle keypresses in pygame

I was recently coding a platformer game, when I faced into a problem: The user could spam space to jump infinitely.
Here is my code:
def __init__(self, pos):
super().__init__()
self.image = pygame.Surface((32, 64))
self.image.fill("green")
self.rect = self.image.get_rect(topleft = pos)
self.direction = pygame.math.Vector2(0, 0)
self.speed = speed
self.gravity = gravity
self.jump_height = jump_height
def get_input(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and keys[pygame.K_RIGHT]: pass
elif keys[pygame.K_LEFT]: self.direction.x = -1
elif keys[pygame.K_RIGHT]: self.direction.x = 1
else: self.direction.x = 0
if keys[pygame.K_SPACE]:
self.jump()
I tried several things, such as implementing a self.antispam boolean, set to True, at the __init__ method that turns into False when the space key is pressed, and then turns True again after the jump method, or turning the jump method into a coroutine to make an asyncio loop, but none of them worked.
To jump you have to use KEYDOWN instead of pygame.key.get_pressed(). Use pygame.key.get_pressed() to move but not to jump.
pygame.key.get_pressed() returns a sequence with the state of each key. If a key is held down, the state for the key is True, otherwise False. Use pygame.key.get_pressed() to evaluate the current state of a button and get continuous movement.
The keyboard events (see pygame.event module) occur only once when the state of a key changes. The KEYDOWN event occurs once every time a key is pressed. KEYUP occurs once every time a key is released. Use the keyboard events for a single action like jumping or spawning a bullet.
Make sure you only call pygame.get.event() once (see Faster version of 'pygame.event.get()'. Why are events being missed and why are the events delayed?):
def get_input(self, event_list):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and keys[pygame.K_RIGHT]: pass
elif keys[pygame.K_LEFT]: self.direction.x = -1
elif keys[pygame.K_RIGHT]: self.direction.x = 1
else: self.direction.x = 0
for event in event_list:
if event.type == pygame.KEYDONW and event.key == pygame.K_SPACE:
self.jump()
# application loop
while run:
# event loop
event_list = pygame.get.event()
for event in event_list:
if event.type == pygame.QUIT:
# [...]
player.get_input(event_list)
The other option is to state if SPACE was pressed in previous frames. So you can detect that the SPACE is hold down:
def __init__(self, pos):
super().__init__()
# [...]
self.space_was_pressed = False
def get_input(self):
keys = pygame.key.get_pressed()
# [...]
space_is_pressed = keys[pygame.K_SPACE]
if space_is_pressed and not self.space_was_pressed:
self.jump()
self.space_was_pressed = space_is_pressed
See also How to make a character jump in Pygame?

pygame - while loop makes pygame window freeze / how do I shoot the bullets out of the player?

so I was programming again this morning and I wanted to write that the player in my small game can shoot bullets. That worked fine but theres an issue: I wrote for the x and the y coordinates of the 'bullet spawner' player.x and player.y and I thought that the bullets would shoot from the player's position. but they don't. They shoot from the position, where the player was in the beginning of the game and the spawner doesn't move. So I tried to do this with a while loop and the bool isMoving, that only is True if the player moves:
...
isMoving = False
...
bullets = []
position = (player.x, player.y)
while isMoving:
position = (player.x, player.y)
...
if keys[pygame.K_d] or keys[pygame.K_a] or keys[pygame.K_w] or keys[pygame.K_s] or keys[pygame.K_UP] or keys[pygame.K_DOWN] or keys[pygame.K_LEFT] or keys[pygame.K_RIGHT]:
isMoving = True
else:
isMoving = False
But if I run pygame now, the window just freezes. If I remove the while loop again, it works but it shoots from the player's first position again.
Oh, and I get the error " while isMoving:
UnboundLocalError: local variable 'isMoving' referenced before assignment
" Any ideas how to fix that?
Pygame should run in a main while loop which has all the main actions inside it.
Try setting the position at the start, then inside the while loop check for pygame events that trigger the isMoving change. Nested while loops will cause issues with pygame. Use if functions inside the while loop instead of another while loop. For example,
position = (player.x, player.y) # initial position
while isRunning:
isMoving = False
# PyGame event interaction
for event in pygame.event.get():
# Exits loop
if event.type == pygame.QUIT:
isRunning = False
# Check if key is pressed
if event.type == pygame.KEYDOWN:
keys = [pygame.K_a, pygame.K_w, pygame.K_s, pygame.K_d, pygame.K_UP, pygame.K_DOWN, pygame.K_LEFT, pygame.K_RIGHT]
if event.key in keys:
isMoving = True
if isMoving:
position = (player.x, player.y)
# do other stuff

How to move object smoothly [duplicate]

This question already has answers here:
Framerate affect the speed of the game
(1 answer)
Pygame snake velocity too high when the fps above 15 [duplicate]
(1 answer)
Closed 2 years ago.
I've been searching for a solution but still can't get it to work. I don't know what's wrong. I want to move my object smoothly just like this:
https://youtu.be/vc1pJ8XdZa0?t=153
but mine is always "teleporting" when moving. Here's part of my code
#Player
playerImg = pygame.image.load('yin.png')
playerX = 0
playerY = 0
playerX_move = playerY_move = 0
playerMoveUnit = 5
def player(x,y):
screen.blit(playerImg,(x,y))
#Game Loop
running =True
while running:
screen.blit(background,(-100,-80))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
playerX_move-=playerMoveUnit
if event.key == pygame.K_RIGHT:
playerX_move+=playerMoveUnit
if event.key == pygame.K_DOWN:
playerY_move+=playerMoveUnit
if event.key == pygame.K_UP:
playerY_move-=playerMoveUnit
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
playerX_move+=playerMoveUnit
if event.key == pygame.K_RIGHT:
playerX_move-=playerMoveUnit
if event.key == pygame.K_DOWN:
playerY_move-=playerMoveUnit
if event.key == pygame.K_UP:
playerY_move+=playerMoveUnit
playerX+=playerX_move
playerY+=playerY_move
#Check boundary
if playerX <=-10:
playerX = -10
elif playerX >=(length-70):
playerX = length-70
if playerY <= -20:
playerY = -20
elif playerY >=(width-105):
playerY = width-105
player(playerX,playerY)
pygame.display.update()
just reduce your "playerMoveUnit"
Use pygame.time.Clock() and .tick() to control the flops per second. The framerate argument the .tick() will delay to keep the game running slower than the given ticks per second. For instance:
clock = pygame.time.Clock()
FPS = 60
running =True
while running:
clock.tick(FPS)
# [...]
Furthermore I recommend to use pygame.key.get_pressed() rather than the keyboard events for the movement of the player. pygame.key.get_pressed() returns a sequence of boolean values representing the state of every key.
Get and evaluate the states of the keys in every frame and move the player accordingly:
clock = pygame.time.Clock()
FPS = 60
running =True
while running:
clock.tick(FPS)
screen.blit(background,(-100,-80))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
playerX -= playerMoveUnit
if keys[pygame.K_RIGHT]:
playerX += playerMoveUnit
if keys[pygame.K_UP]:
playerY -= playerMoveUnit
if keys[pygame.K_DOWN]:
playerY += playerMoveUnit
#Check boundary
if playerX <=-10:
playerX = -10
elif playerX >=(length-70):
playerX = length-70
if playerY <= -20:
playerY = -20
elif playerY >=(width-105):
playerY = width-105
player(playerX,playerY)
pygame.display.update()
I notice that you do not have a clock.tick(...) in your code. That is needed to limit the frame rate.
You need to put it into your main while loop.
Somewhere at the top of the code you want to set a constant that controls your framerate. You'll also need a clock to call tick() on People commonly use a frame rate of 60, so you would want something like this near the top of your code:
FPS = 60
clock = pygame.time.Clock()
Then at the end of your while loop you need a clock.tick(FPS). I tend to put it just before the screen update like this:
while running:
....
clock.tick(FPS)
pygame.display.update()
FPS stands for Frames Per Second. The tick() call remembers the time that you last called it and does not return until an amount of time equal to 1/FPS has passed since the previous call. Putting it after the computation and just before the update() in the while loop ensures the screen updates occur as close as possible to those even 1/FPS time periods. You can find the docs here.
If your game logic per pass does not take very much time, without this limit your game will run very fast (as you discovered). A different issue but just as bad is, if the amount of computing taken each frame varies, then without imposing a constant frame rate the animation would not be smooth since each frame would display for different amounts of time.
Something like this was likely in the tutorial you were working with, but you may have missed it or not realized what it was for and left it out.
Edit:
After you said you had tried this and it still was not working, I looked at it a bit more. There are two other issues I see, though one may be intentional, I am not sure.
You have playerX_move, playerY_move and playerMoveUnit which together appear to be intended to control your speed. It appears that playerMoveUnit is supposed to be the absolute amount and that playerX_move and playerY_move are intended to be that applied to the X and Y directions. That would mean that you would assign playerMoveUnit to either playerX_move or playerY_move depending on the key press, like this:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
playerX_move = playerMoveUnit
However you are using += or -= instead. This means that each pass you will be increasing your speed by playerMoveUnit. That makes playerMoveUnit an acceleration not a speed and will result in your object moving very fast very quickly. This is likely the cause of the teleporting behaviour, it is just moving VERY fast.
The other issue I want to point out is that on the KEYDOWN you start the object moving in the desired direction, and on KEYUP you don't stop it by setting the speed to 0. Instead you start the speed going the other way. That is likely not the behaviour that you want. Your code will cause it to move one way when the key is pressed and then start moving in reverse when it is released and not stop till it hits the edge or another key is pressed. You likely want something like this instead:
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
playerX_move = 0
By the way this would be cleaner if you structured the as a class so you could group all the player related information together.
I just noticed that #JohnnyMopp had a comment on your question that pointed out the += -= thing. Though I had not noticed it, he added that before I edited my answer to include that fix.
You are not using sprites, but single image. You need to load multiple images in order to get smooth movement, in this case for player, if you are following this tutorial, you need 9 images for "left" and 9 for "right" movement. Loading can be done e.g. this way:
go_right = []
go_left = []
for i in range(1,10):
sprite_r = pg.image.load("R{}.png".format(i))
sprite_l = pg.image.load("L{}.png".format(i))
go_right.append(sprite_r)
go_left.append(sprite_l)
Then, in Player class define methods for left and right:
def go_left(self):
self.left = True
self.right = False
self.x -= self.v
def go_right(self):
self.right = True
self.left = False
self.x += self.v
Now, in redraw() or whatever you called it, set FPS to 27 for example(for divisibility reason):
clock.tick(27)
if player.right:
window.blit(go_right[player.walk_count//3], (player.x, player.y))
player.walk_count += 1
elif player.left:
window.blit(go_left[player.walk_count//3], (player.x, player.y))
player.walk_count += 1
else:
player.stand()
window.blit(char, (player.x, player.y))
This if's are responsible for displaying appropriate images from the list (loaded at the beginning)
And at the end, in the main_loop you need to handle the events (left and right arrows e.g.):
if keys[pg.K_RIGHT]:
if player.x < win_width - get_player_width: # or constant number
player.go_right()
elif keys[pg.K_LEFT]:
if player.x > 0:
player.go_left()
You will probably need to adjust some of the code but I made it following the given tutorial. I hope this will work for you
EDIT:
After additional info from the comments I managed to run your code, as others already suggested putting clock.tick(FPS) in main_loop solves the issue. Do not forget clock = pygame.time.Clock() line before the loop. Additionally, if you want smoother and animated movement apply the code I provided above

Pygame - Movement drift

I currently have an object that has code to rotate it around its center and move it around (bby altering its pos values).
However I want to make it so that if up arrow is pressed, it will accelerate in the direction its facing and when its released it will decelerate again back to 0. In the code I use a dt value for the change in time.
I tried clocking the time when a button is pressed and released and use that as the dt value, using this method the dT value can be negative. I also think this wouldn't work because then the rocket would receive let's say a dT value of 1 sec and update its velocity to go really fast instead of having a smooth acceleration/deacceleration.
class Rocket:
def __init__(self, image, pos, angle):
self.image = pygame.image.load(image)
self.imageDimensions = self.image.get_rect()
self.angle = angle
self.pos = pos
self.center = (self.pos[0], self.pos[1])
self.velocity = [0, 0]
self.acceleration = [0, 0]
self.angularVelocity = 0
self.angularAcceleration = 0
self.isAccelerating = False
self.thrust = 50
def draw(self, surface):
rotatedImg = pygame.transform.rotate(self.image, self.angle)
rotatedImgDimensions = rotatedImg.get_rect()
#display image
surface.blit(rotatedImg, (self.pos[0] - rotatedImgDimensions.center[0], self.pos[1] - rotatedImgDimensions.center[1]))
def update(self, dt):
#update angle
self.angularVelocity += self.angularAcceleration * dt
self.angle += self.angularVelocity * dt
#if accelerating update the acceleration
if self.isAccelerating:
self.acceleration[0] -= self.thrust * math.sin(math.radians(self.angle))
self.acceleration[1] -= self.thrust * math.sin(math.radians(self.angle))
#update velocity
self.velocity[0] += self.acceleration[0] * dt
self.velocity[1] += self.acceleration[1] * dt
#update position
self.pos[0] += self.velocity[0] * dt
self.pos[1] += self.velocity[1] * dt
So in short I expect the rocket to accelerate forward when I press the up arrow, deaccelerate to 0 when I press the down arroy and rotate left and right when pressing arrow left and right.
Please note that the above class is in a different file named Objects.py
Thank you!!
Here is the rest of the code:
import pygame
from pygame.locals import *
import math
import Objects
#colors
WHITE = (255,255,255)
BLACK = (0,0,0)
TRANSPARENT = (0,0,0,0)
#size window
Width, Height = (1280,720)
#Main menu
def game_intro():
intro = True
while intro:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
intro = False
screen.fill(BLACK)
FPS = 30
#initialise pygame
pygame.init()
fpsClock = pygame.time.Clock()
screen = pygame.display.set_mode((Width, Height))
pygame.display.set_caption("Rockets and Missiles")
#Add players
Rocket1 = Objects.Rocket("rocket.png", [100, 100], 0) #start at pos 50,50
#run main menu first
game_intro()
run = True
while run:
screen.fill(BLACK)
Rocket1.draw(screen)
#event handler
pressed = pygame.key.get_pressed() #list with all pressed keys
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.KEYDOWN:
Time0 = pygame.time.get_ticks()
if event.type == pygame.KEYUP:
Time1 = pygame.time.get_ticks()
if pressed[pygame.K_UP]:
dT = Time1 - Time0
print(dT)
Rocket1.update(dT)
pygame.display.update()
fpsClock.tick(FPS)
pygame.quit()
quit()
I don't think you have a very useful definition of dT. As far as I can tell you only call Rocket.update() when a key is pressed, the rockets need to update every frame with small dT if you want smooth motion. Without calling update() in your rocket class more consistently you will not get the nice motion you want.
I suggest something like this for your main loop:
dT = 1/FPS
while run:
screen.fill(BLACK)
Rocket1.draw(screen)
#event handler
pressed = pygame.key.get_pressed() #list with all pressed keys
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if pressed[pygame.K_UP]:
Rocket1.ChangeCurrentAcceleration()
Rocket1.update()
pygame.display.update()
fpsClock.tick(FPS)
For some new ChangeCurrentAcceleration() function that adds to the acceleration in your rocket, you can then change update() to assume that it already has the proper acceleration from thrust and calculates new velocity and position from there (maybe add a 0.95* multiplier to accel so it naturally slows down).
I do not think measuring the time the button is pressed is the correct approach. Each iteration of the main loop corresponds to a fixed amount of time, and to produce an animation you want to move the rocket by that fixed amount of time each iteration. So no need to calculate a dt the way you are doing. You already have it, and it's equal to 1/FPS.
What you want to do usually is to set some velocity / acceleration for you rocket. Now they are all 0, but you should set a fixed value different from zero: how much you want it to be faster, or how much faster do you want it to accelerate, when the key button is pressed.
And when the key button corresponding is pressed, call the update method to calculate the new position / angle based on that velocity / acceleration and then redraw the rocket, considering that the time passed is 1/FPS.
And you also need two method to update separately linear motion and rotation. The way is now, you cannot separate the movements based on different keys.
Thanks guys for helping out, i didnt realise this part :D
However i want to answer my own question for people visiting this page later on.
A perhaps better way of doing it is to get the time it took the software to go over each iteration and using that as dT. it would look like the following: BBefore the main loop:fpsClock = pygame.time.Clock()
The main loop:
while run:
screen.fill(BLACK)
Rocket1.draw(screen)
#draw missiles
for missile in Missiles:
missile.draw(screen)
#event handler
pressed = pygame.key.get_pressed() #list with all pressed keys
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if pressed[K_UP]:
Rocket1.acceleration = [0, -100]
if pressed[K_DOWN]:
Rocket1.acceleration = [0, 100]
if pressed[K_RCTRL]:
Missiles.append(Objects.Missile("missile.png", Rocket1.pos, Rocket1.angle))
dT = fpsClock.get_time()
Rocket1.update(dT/1000)
pygame.display.update()
fpsClock.tick(FPS)

pygame - on hold button down

I found a solution to make a sprite move when you hold a key down. The problem is that it forces writing ugly duplicated code. The current solution I found is:
for event in pygame.event.get():
if event.type == KEYDOWN:
keystate = pygame.key.get_pressed()
while keystate[K_RIGHT]:
screen.fill((255,255,255))
pygame.event.get()
for sprite in sprites:
rimage = sprite[1].getimage()
if sprite[2] is None:
x+=3
sprite[1].update(time)
screen.blit(rimage, (x,y))
if sprite[1].isfinished() == True:
sprite[1].reset()
last_dir = "right"
if x >= screen_width - rimage.get_width():
x = screen_width - rimage.get_width()
#update player sprite movement
#update player sprite animation
#update rest of game map
keystate = pygame.key.get_pressed()
time = pygame.time.get_ticks()
pygame.display.update()
The problem is that the while keystate block. It has to be repeated for each direction and the game world needs to be updated in each while block. That is five places where the same code needs to be duplicated....4 directions plus if a key is not pressed. I could wrap it in a function but I was wondering if there was a better way to handle holding down a button in pygame.
The usual way program in pygame is use the events to update the direction, then write the update position code outside events, that way you don't need replicated code.
clock = pygame.time.Clock()
direction = (0,0)
while True: # main loop
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_RIGHT:
direction = (3, 0)
elif event.key == K_LEFT:
direction = (-3, 0)
elif event.key == K_UP:
direction = (0, 3)
elif event.key == K_DOWN:
direction = (0, -3)
else:
print "Unrecognized key"
if event.type == KEYUP:
direction = (0, 0)
screen.fill((255,255,255))
for sprite in sprites:
rimage = sprite[1].getimage()
if sprite[2] is None:
# Check if new position is inside the screen
new_pos = x + direction[0], y + direction[1]
if new_pos[0] + rimage.get_width() < screen_width:
x = new_pos[0]
if new_pos[1] + rimage.get_height() < screen_height:
y = new_pos[1]
# Draw the sprite
sprite[1].update(time)
screen.blit(rimage, (x,y))
if sprite[1].isfinished() == True:
sprite[1].reset()
last_dir = direction
#update player sprite movement
#update player sprite animation
#update rest of game map
time = pygame.time.get_ticks()
pygame.display.update()
clock.tick(40) # Keep game running at 40 fps
If you want you can achieve the same with keystate, in such case you don't need to process key events.
Pygame suggests or implies the division of programs into 3 parts:
The event handling, updating and drawing.
As pmoleri already said, you simply change the direction of the movement.
In the update function, you should pass in a delta time parameter, to move all the sprites according to the time passed. It is quite important, since the other technique doesn't take into account the variable speed of the processor. Games in DOS have been made this way, so now we need emulators to artificially slow down the processor. The draw part simply draws all the sprites.
This way you have a clear division between these 3 distinc parts in games: player input, game logic(movement, collision, actions etc.) and drawing.
Additionally, pygame.key.get_pressed() can be used.
This returns a list of all the keys currently being held down.
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
# Move Up
if keys[pygame.K_a]:
# Move Down
... etc ...
This does not need to be in the event section, but probably where the player updates.
This can make the code less clunky and (possible) be more efficient
-Hope I helped!

Categories