How to move object smoothly [duplicate] - python

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

Related

PYGAME - create clock and do action at intervals

I'm making a game where a moving cube at the top of the screen will fire a cube down the screen at certain intervals. How would I do this. For example, I want it so that every 1 second the moving cube will fire a projectile down the screen towards the player icon and when it reaches past the screen, it will respawn where the moving cube is and be able to fire again.
This is what I have so far.
import pygame
pygame.init()
screen = pygame.display.set_mode((280, 800))
pygame.display.set_caption("Cube Run")
icon = pygame.image.load("cube.png")
pygame.display.set_icon(icon)
player_icon = pygame.image.load("cursor.png")
player_x = 128
player_y = 750
player_x_change = 0
cube_1 = pygame.image.load("rectangle.png")
cube1_x = 128
cube1_y = 0
cube1_x_change = 0.8
cube_fire = pygame.image.load("rectangle.png")
cube_fire_x = 0
cube_fire_y = 0
cube_y_change = 1.5
cube_fire_state = "ready"
def player(player_x, player_y):
screen.blit(player_icon, (player_x, player_y))
def cube(cube1_x, cube1_y):
screen.blit(cube_1, (cube1_x, cube1_y))
def cube_enemy(cube_fire_x, cube_fire_y):
screen.blit(cube_fire, (cube_fire_x, cube_fire_y))
running = True
while running:
screen.fill((255, 255, 255))
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
player_x_change += 0.7
if event.key == pygame.K_LEFT:
player_x_change -= 0.7
if event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT or pygame.K_LEFT:
player_x_change = 0
player_x += player_x_change
if player_x < 0:
player_x = 0
elif player_x > 280-32:
player_x = 280-32
cube1_x += cube1_x_change
if cube1_x > 248:
cube1_x_change = -1
cube1_x += cube1_x_change
elif cube1_x < 0:
cube1_x_change = 1
cube1_x += cube1_x_change
cube_fire_x += cube1_x
cube_enemy(cube_fire_x, cube_fire_y)
player(player_x, player_y)
cube(cube1_x, cube1_y)
pygame.display.update()
You can register events with pygame.time.set_timer. Create a new event and set how many milliseconds should pass before it's fired. This event will then appear at the set intervall.
FIRE_EVENT = pygame.USEREVENT + 1 # This is just a integer.
OTHER_EVENT = pygame.USEREVENT + 2 # This is how you define more events.
pygame.time.set_timer(FIRE_EVENT, 1000) # 1000 milliseconds is 1 seconds.
Then in your event loop, you check for this event and do whatever you want.
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == FIRE_EVENT: # Will appear once every second.
make_square_fire()
When you want to disable the event, just set the interval to 0.
pygame.time.set_timer(FIRE_EVENT, 0)
In your code, you don't include a time manager of any kind – which means your code will run as fast as possible, an you can't really control how fast that will be, it will really depend on the machine it is working and on the CPU load, among other things.
Basically, you want to purposely wait within your program just the right amount of time so you can dynamically adapt to the execution speed. You could implement this by yourself (it isn't that hard, and there are plenty of tutorials out there), but to take a first glance of it, you could use pygame.Clock:
first, create a clock with clock = pygame.Clock().
Then, within your main loop, call eta = clock.tick(FPS), where FPS represents the target frame rate you want your application to run (you can simply fix it to 60 at the start of your program if don't really know what value you want it to be), and the eta variable measures the time elapsed (in milliseconds) since last tick call.
Next, to have something happen, say, every second, just keep a counter:
counter = 1000 # in ms
clock = pygame.Clock()
while True:
# do what you want
eta = clock.tick(FPS)
counter -= eta
if counter < 0:
# trigger the event
counter += 1000
# don't set it directly like
# counter = 1000
# to keep track of margin

Where should I implement my check for collision method?

I am making a pokemon ripoff in pygame, and I have made a method to test whether the player is in the tall grass. However, I do not know where I should call the method. I have tried in the player move method, in the game loop method, and the update method. When I do it like this, it checks after you have moved out of the grass, which means the first time you move into grass it won't work, and when you first move out of grass it works (when it shouldn't).
in_grass(self) method:
def in_grass(self):
for g in self.game.grass:
if pygame.sprite.collide_rect(self, g):
print(random.randint(1, 10000))
if random.randint(1, 180) <= 25:
self.battle()
player move(self) method:
def move(self, x_change, y_change):
if x_change > 0:
self.dir = 'RIGHT'
if x_change < 0:
self.dir = 'LEFT'
if y_change > 0:
self.dir = 'DOWN'
if y_change < 0:
self.dir = 'UP'
if not self.collide(x_change, y_change):
self.x += x_change
self.y += y_change
image_list = None
if self.dir == 'UP':
image_list = self.image_up
elif self.dir == 'LEFT':
image_list = self.image_left
elif self.dir == 'RIGHT':
image_list = self.image_right
elif self.dir == 'DOWN':
image_list = self.image_down
if image_list:
if self.walkcount >= len(image_list):
self.walkcount = 0
self.image = image_list[self.walkcount]
self.walkcount += 1
self.image.set_colorkey(BLACK)
Main loop:
def events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_w:
self.player.move(0, -1)
if event.key == pygame.K_a:
self.player.move(-1, 0)
if event.key == pygame.K_s:
self.player.move(0, 1)
if event.key == pygame.K_d:
self.player.move(1, 0)
if event.type == pygame.KEYUP:
self.player.image = self.player.default_images[self.player.dir]
self.player.image.set_colorkey(BLACK)
EDIT: I finally got it working! I can't give myself the bounty, so I guess it will just sit there.
So here is some food for thought.
Little disclaimer,
I am in no way a game dev nor am i a very experienced developer.
In a larger game you might want to keep a good grasp on what is happening where. Now say we have a game full of entities (think of entities as things you see on screen that have some sort of impact on the game). We would wanna update all these entities every frame of the gameloop.
Entities might be rendered, moved, collission checked, affected by player input, animated or whatever functionality your game needs.
The order in which we do this is important for our game, suppose we have a list of entities
entities = []
We could then do something like this where we loop trough each enemy in our gameloop en execute their behaviour function. we pass in all the entities into the check_collision function to make this code a little more understandable.
Version 1
entities = []
def game_loop:
while(true):
for entity in entities:
entity.gather_input()
entity.move()
entity.check_collision(entities)
entity.animate()
entity.render()
Now take a look at this:
Version 2
entities = []
def game_loop:
while(true):
for entity in entities:
entity.gather_input()
for entity in entities:
entity.move()
for entity in entities:
entity.check_collision(entities)
for entity in entities:
entity.animate()
for entity in entities:
entity.render()
The order in which we execute the entities their behaviour has changed. We now have grouped behaviour, we could say we have defined the update fases of our gameloop. We now know all the movement of entities is updated before we check all of their collisions.
Whereas in Version 1 we do all the logic first for 1 entity and then do the next. We might lose relevant data in a single frame using Version 1 because 1 entity might move and check collision wheres it really should've waited till all entities have moved.
Hope this might spark some more research into the rabbithole that is game design/architecture.
Create a boolean in the player's __init__ method callled self.checked = False. Then, in the in_grass method, at the end, set self.checked = True. In the update method, if not self.checked: self.in_grass. Finally in the move method, at the very start, set self.checked = False
I finally got it working, yay!

Why does my character only move left when I use pygame.key.get_pressed()?

I'm new to Python but I decided to give it a try making a fighting game in Pygame.
My character will only move left and only when an update is forced.
I tried:
KEY DOWN
if event.key == pygame.K_d:
KEY UP
if event.key == pygame.K_d or event.key == pygame.K_a:
x_change = 0
And it works to an extent. It's just annoying when you need to suddenly change direction, but can't change direction.
I also changed the OR with AND, so it will stop when both are lifted, but that prevents movement from both directions.
Made it do so I can move while not fighting for testing reasons
fighting = False
while not fighting:
for event in pygame.event.get():
if event.type == pygame.QUIT:
fighting - False
keys = pygame.key.get_pressed()
if keys[pygame.K_a]:
x_change1 = -5
if keys[pygame.K_d]:
x_change1 = 5
if not keys[pygame.K_a] and keys[pygame.K_d]:
x_change1 = 0
x1 += x_change1
gameDisplay.fill(white)
fighter1(x1,y1)
pygame.display.update()
clock.tick(28)
My guy moves X pixels left only when I force an update such as pressing A again (left) lifting A or moving my mouse over the window. D (right) registers but doesn't move. Once A is pressed, it won't stop going even if the key is up.
I want to fix this so I can then more easily add jumping, attacks and a player 2.
You are missing a not operator here:
if not keys[pygame.K_a] and keys[pygame.K_d]:
x_change1 = 0
Add it like this:
if not keys[pygame.K_a] and not keys[pygame.K_d]:
x_change1 = 0

How to make a character move while key is held down

I'm trying to create a "player" - black square - which moves when you hold down WASD. I tried looking around here, on google and on youtube on how to make this work, but every solution I've tried has the same problem: instead of moving it while I hold down the key, I have to tap the key constantly to make it move in small bits. I've no clue what I'm doing wrong. Here's the code (using python 3.3 - pygame 1.9):
import pygame
from pygame.locals import *
from pygame.time import *
import sys
pygame.init()
velX = 0
velY = 0
running = True
clock = pygame.time.Clock()
def draw():
global velX
global velY
playerx = 20
playery = 20
screen = pygame.display.set_mode((700,300))
pygame.display.set_caption('something')
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((255,255,255))
screen.blit(background, (0,0))
playerx = playerx + velX
playery = playery + velY
player_filename = 'player.png'
player = pygame.image.load(player_filename)
screen.blit(player, (playerx,playery))
pygame.display.flip()
def main():
global velX
global velY
global running
while running:
keys_down = pygame.key.get_pressed()
pygame.key.set_repeat(1, 50)
time = 50/1000
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running=False
if keys_down[K_d]:
velX += 50*time
if keys_down[K_w]:
velY -= 50*time
if keys_down[K_s]:
velY += 50*time
if keys_down[K_a]:
velX -= 50*time
clock.tick(50)
draw()
if __name__ == '__main__':
main()
I already tried the set repeat command, but it didn't seem to do much anything. I also tried directly copying from a few solutions I found here on stackoverflow, but none of them worked. I suppose there is something else wrong in the code.
There are two problems with your code. First to your question. The reason the player never moves more than one step is that you reset the player position in every call to draw() when you do
playerx = 20
playery = 20
Instead, you should put that code above the draw()function and add
global playerx
global playery
at the top of draw(). Now, the player position is not reset every frame.
The second problem is that you create a new screen in every call to draw() when you do
screen = pygame.display.set_mode((700,300))
pygame.display.set_caption('something')
instead you should move those to lines above draw() and just use the same screen for every draw.
Also, like elyase points out, what you probably want is to set the velocities to fixed values, not increase them. Like so
velX = 0
velY = 0
if keys_down[K_d]:
velX = 10
if keys_down[K_w]:
velY = -10
if keys_down[K_s]:
velY = 10
if keys_down[K_a]:
velX = -10
This way the player will move around with a constant speed in the direction you steer it.
Hope that clear some stuff up. :)
You are resetting the position inside draw(). Also you should put the code that changes the direction, inside the event.type == pygame.KEYDOWN condition. Something like this:
if event.type == KEYDOWN:
if event.key == K_d:
velX += 50*time
elif event.key == K_w:
velY -= 50*time
...
if event.type == KEYUP:
# set velocity to 0

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