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!
Related
I am currently making a fighting game and was wondering how command inputs could be added. I understand it is kind of irrelevant and many substitutes are possible, but it would be nice to use familiar fighting game inputs.
I currently have something like this:
keys = pygame.key.get_pressed()
if keys[pygame.K_DOWN]:
commandcount +=1
if commandcount > 0 and commandcount < 30 and keys[pygame.K_RIGHT] and keys[pygame.K_z]:
player1.projectile = True
The "commandcount" helps keep the window of action available until a certain amount of time.
The main problem with this is that you could still press the inputs in whatever order and the projectile would still come out.
Thanks
Try using the pygame.KEYDOWN events instead of pygame.key.get_pressed() so the order can be observed. Use a list to keep track of the order of these KEYDOWN events. When the order matches a specific combo then execute the move and reset the list. The list also gets reset after a certain amount of time with a combo. I made an example program with the combo down, right, z activating a fireball.
import pygame
# pygame setup
pygame.init()
# Open a window on the screen
width, height = 600, 600
screen = pygame.display.set_mode((width, height))
def main():
clock = pygame.time.Clock()
black = (0, 0, 0)
move_combo = []
frames_without_combo = 0
while True:
clock.tick(30) # number of loops per second
frames_without_combo += 1
if frames_without_combo > 30 or len(move_combo) > 2:
print("COMBO RESET")
frames_without_combo = 0
move_combo = []
screen.fill(black)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
move_combo.append(event.key) # Only keys for combos should be added
if move_combo == [pygame.K_DOWN, pygame.K_RIGHT, pygame.K_z]:
print("FIRE BALL")
frames_without_combo = 0
print(move_combo)
pygame.display.update()
main()
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
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
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)
I have written the following code that creates a simple game where when you click an arrow on the keyboard a box moves a unit over in the game.
I am trying to make it so that if i push any of the arrow buttons the box will continue to move in that direction until another arrow is pushed. So if i push the right arrow once instead of scooting +50 pixels it will move continuously across the screen untill a different arrow is clicked and then it will go that way
import pygame #importing the pygame library
# some initializations
pygame.init() # this line initializes pygame
window = pygame.display.set_mode( (800,600) ) # Create a window with width=800 and height=600
pygame.display.set_caption( 'Rectangle move' ) # Change the window's name we create to "Rectangle move"
clock = pygame.time.Clock() # Clocks are used to track and control the frame-rate of a game (how fast and how slow the pace of the game)
# This line creates and initializes a clock.
# color definitions, using RBG color model.
black = (0,0,0)
white = (255,255,255)
# initial center position for the square (bob)
x,y = 0,0
lastKey=0
game_loop=True
while game_loop:
for event in pygame.event.get(): # loop through all events
if event.type == pygame.QUIT:
game_loop = False # change the game_loop boolean to False to quit.
if event.type == pygame.KEYDOWN:
lastKey = event.key
#check last entered key
#lastKey equals "LEFT", "RIGHT", "UP", "DOWN" --> do the required stuff!
#set x coordinate minus 50 if left was pressed
if lastKey == pygame.K_LEFT:
x -= 50
if lastKey == pygame.K_RIGHT:
x += 50
if lastKey == pygame.K_UP:
y += 50
if lastKey == pygame.K_DOWN:
y -= 50
if event.key == pygame.K_LEFT:
x -= 50
if event.key == pygame.K_RIGHT:
x += 50
if event.key == pygame.K_UP:
y += 50
if event.key == pygame.K_DOWN:
y -= 50
# draw and update screen
window.fill( black ) # fill the screen with black overwriting even bob.
pygame.draw.rect( window, white, (x, y, 50, 50) ) # draw bob on the screen with new coordinates after its movement.
# the parameters are as follows: window: is the window object you want to draw on. white: the object color used to fill the rectangle
# (x,y,50,50) x is the x position of the left side of the rectangle. y is the y position of the upper side of the rectangle.
# In other words (x,y) is the coordinate of the top left point of the rectangle.
# 50 is the width, and 50 is the height
pygame.display.update() #updates the screen with the new drawing of the rectangle.
#fps stuff:
clock.tick(10) # this controls the speed of the game. low values makes the game slower, and large values makes the game faster.
pygame.quit()
any help would be much appreciated.
Try to save the entered key into a variable and check it after your Event-Loop.
Like this:
#...
lastKey = None
while game_loop:
for event in pygame.event.get(): # loop through all events
if event.type == pygame.QUIT:
game_loop = False # change the game_loop boolean to False to quit.
if event.type == pygame.KEYDOWN:
lastKey = event.key
#check last entered key
#lastKey equals "LEFT", "RIGHT", "UP", "DOWN" --> do the required stuff!
#set x coordinate minus 50 if left was pressed
if lastKey == pygame.K_LEFT
x -= 50
#<add the other statements here>
#(...)
I would recommend to not use that many if-statements. It could get a bit confusing after some time.
Check the following question out to keep your code brief:
Replacements for switch statement in Python?
You want to change the state of your application when you press a key. So you need a variable to keep track of that state (the state is: What direction should the box move?).
Here's a complete, minimal example that does what you're looking for. Note the comments.
import pygame, sys
pygame.init()
screen = pygame.display.set_mode((640, 480))
screen_r = screen.get_rect()
clock = pygame.time.Clock()
rect = pygame.rect.Rect(0, 0, 50, 50)
# let's start at the center of the screen
rect.center = screen_r.center
# a dict to map keys to a direction
movement = {pygame.K_UP: ( 0, -1),
pygame.K_DOWN: ( 0, 1),
pygame.K_LEFT: (-1, 0),
pygame.K_RIGHT: ( 1, 0)}
move = (0, 0)
# a simple helper function to apply some "speed" to your movement
def mul10(x):
return x * 10
while True:
for e in pygame.event.get():
if e.type == pygame.QUIT:
sys.exit()
# try getting a direction from our dict
# if the key is not found, we don't change 'move'
if e.type == pygame.KEYDOWN:
move = movement.get(e.key, move)
# move the rect by using the 'move_ip' function
# but first, we multiply each value in 'move' with 10
rect.move_ip(map(mul10, move))
# ensure that 'rect' is always inside the screen
rect.clamp_ip(screen_r)
screen.fill(pygame.color.Color('Black'))
pygame.draw.rect(screen, pygame.color.Color('White'), rect)
pygame.display.update()
clock.tick(60)
I use a Rect instead of keeping track of two coordinates x and y, since that allows to make use of the move_ip and clamp_ip functions to easily move the rect inside the screen.
Here are two versions, the first demonstrates how to utilize an event loop to get continuous movement (similar to Sloth's solution, but a bit simpler for beginners who don't know dictionaries yet), the second one shows how to achieve this with pygame.key.get_pressed().
Solution 1: Check which key was pressed in the event loop and change the x and y velocities to the desired values. Then add the velocities to the rect.x and rect.y positions in the while loop.
I'd actually recommend using vectors instead of the velocity_x and velocity_y variables and another one for the actual position of your sprite. pygame.Rects can't have floats as their coordinates and so a vector or separate variables for the position would be more accurate.
import pygame as pg
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
rect = pg.Rect(100, 200, 40, 60)
velocity_x = 0
velocity_y = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_d:
velocity_x = 4
elif event.key == pg.K_a:
velocity_x = -4
elif event.type == pg.KEYUP:
if event.key == pg.K_d and velocity_x > 0:
velocity_x = 0
elif event.key == pg.K_a and velocity_x < 0:
velocity_x = 0
rect.x += velocity_x
rect.y += velocity_y
screen.fill((40, 40, 40))
pg.draw.rect(screen, (150, 200, 20), rect)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
Solution 2: Call pygame.key.get_pressed to check which key is currently being held down. Check if the left, right, up or down keys are held and then adjust the position of the sprite each frame.
pygame.key.get_pressed has the disadvantage that you can't know the order of the key presses, but the code looks a bit simpler.
import pygame as pg
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
rect = pg.Rect(100, 200, 40, 60)
velocity = (0, 0)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
keys = pg.key.get_pressed()
if keys[pg.K_d]:
rect.x += 4
if keys[pg.K_a]:
rect.x -= 4
screen.fill((40, 40, 40))
pg.draw.rect(screen, (150, 200, 20), rect)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()