Pygame - Movement drift - python

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)

Related

I need a way to map two separate actions to mouse 1 in pygame

I am making a reaction time test (with a twist) in pygame.
I understand that the code in my event loop for mouse_clicks[0] (ie: left mouse button) can register whether or not you've clicked in the area for a small circle. However, I wish to have another action for left mouse button once I figure out how to code the event loop for when a big circle flashes on the screen. So, pretty much the same action for mouse_clicks[0] but with "if math.sqrt(sqx + sqy) < CIRCLE_RADIUS_LARGE". How could I achieve this using the pygame event loop. This is my first independent pygame so any help would be greatly appreciated. Much love, cLappy.
while running:
Clock.tick(FPS)
event_list = pygame.event.get()
mouse_clicks = pygame.mouse.get_pressed()
for event in event_list:
if mouse_clicks[2]:
pygame.display.update()
SCREEN.fill(BLACK)
circle(BLUE, CIRCLE_RADIUS_SMALL)
if mouse_clicks[0]:
x = pygame.mouse.get_pos()[0]
y = pygame.mouse.get_pos()[1]
sqx = (x - 640) ** 2
sqy = (y - 360) ** 2
if math.sqrt(sqx + sqy) < CIRCLE_RADIUS_SMALL:
reset_circle(BLUE, CIRCLE_RADIUS_SMALL)
pygame.display.update()
if event.type == pygame.QUIT:
running = False
You must draw the objects in the application loop, but not in the event loop. The typical PyGame application loop has to:
limit the frames per second to limit CPU usage with pygame.time.Clock.tick
handle the events by calling either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by calling either pygame.display.update() or pygame.display.flip()
Minimal example
import pygame, math
pygame.init()
SCREEN = pygame.display.set_mode((400, 400))
CIRCLE_RADIUS_SMALL = 20
circles = []
clock = pygame.time.Clock()
running = True
while running:
clock.tick(100)
event_list = pygame.event.get()
for event in event_list:
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
circles.append(event.pos)
if event.button == 3:
for center in circles[:]:
dx = center[0] - event.pos[0]
dy = center[1] - event.pos[1]
if math.sqrt(dx*dx + dy*dy) < CIRCLE_RADIUS_SMALL:
circles.remove(center)
SCREEN.fill("black")
for center in circles:
pygame.draw.circle(SCREEN, "blue", center, CIRCLE_RADIUS_SMALL)
pygame.display.update()

Im trying to solve this un-smooth and choppy animations and movements on pygame, i've been trying everything but nothing works

heres the video of the animation https://www.youtube.com/watch?v=uhPdN3v8vg0
other pygame codes dont act like this, only this specific one, so i'm pretty sure its not hardware problem
import sys
import time
import pygame
import os
from pygame import mixer
pygame.init()
pygame.mixer.init()
width,height=(900,500)
border= pygame.Rect(0,0,6,height)
WIN=pygame.display.set_mode((width,height))
bg= pygame.image.load(os.path.join('','pixel_bg.jpg'))
bg=pygame.transform.scale(bg,(width,height))
person1=pygame.image.load(os.path.join('','mario.png'))
p1_width, p1_height = (50,60)
person1=pygame.transform.scale(person1,(p1_width,p1_height))
black=(0,0,0)
p1_rect = pygame.Rect(50,340,p1_width,p1_height)
pygame.display.set_caption("rpg game")
Velocity= 4
jump_height = 50
FPS = 60
mixer.music.load("adventurebeat.mp3")
mixer.music.play(-1)
def draw():
WIN.blit(bg, (0, 0))
WIN.blit(person1, (p1_rect.x,p1_rect.y))
pygame.display.update()
def p1_movement(keys_pressed, p1_rect):
if keys_pressed[pygame.K_a] and p1_rect.x - Velocity > border.x + border.width: # left
p1_rect.x -= Velocity
if keys_pressed[pygame.K_d] and p1_rect.x + Velocity + p1_rect.width < width: # right
p1_rect.x += Velocity
if keys_pressed[pygame.K_w] and p1_rect.y - Velocity > 0: # up
p1_rect.y -= Velocity
if keys_pressed[pygame.K_SPACE] and p1_rect.y - Velocity > 0: # up
p1_rect.y -= jump_height
if keys_pressed[pygame.K_s] and p1_rect.y + Velocity + p1_rect.height < height: # down
p1_rect.y += Velocity
def main():
clock = pygame.time.Clock()
run = True
while run:
clock.tick_busy_loop(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
sys.exit()
draw()
keys_pressed = pygame.key.get_pressed()
p1_movement(keys_pressed,p1_rect)
main()
I've tried changing the clock.tick_busy_loop() to clock.tick() but it still doesnt work :/
You need to draw the object in the application loop instead of the event loop:
def main():
clock = pygame.time.Clock()
run = True
while run:
clock.tick_busy_loop(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
pygame.quit()
sys.exit()
# draw() <--- DELETE
keys_pressed = pygame.key.get_pressed()
p1_movement(keys_pressed,p1_rect)
draw() # <--- INSERT
The application loop is executed in every frame, but the event loop only is executed when an event occurs.

How to overwrite the y position of the bird in the game? [duplicate]

im trying to get my image (bird) to move up and down on the screen but i cant figure out how to do it here is what i tried im sure its way off but im trying to figure it out if anyone can help that would be great!
import pygame
import os
screen = pygame.display.set_mode((640, 400))
running = 1
while running:
event = pygame.event.poll()
if event.type == pygame.QUIT:
running = 0
screen.fill([255, 255, 255])
clock = pygame.time.Clock()
clock.tick(0.5)
pygame.display.flip()
bird = pygame.image.load(os.path.join('C:\Python27', 'player.png'))
screen.blit( bird, ( 0, 0 ) )
pygame.display.update()
class game(object):
def move(self, x, y):
self.player.center[0] += x
self.player.center[1] += y
if event.key == K_UP:
player.move(0,5)
if event.key == K_DOWN:
player.move(0,-5)
game()
im trying to get it to move down on the down button press and up on the UP key press
As stated by ecline6, bird is the least of your worries at this point.
Consider reading this book..
For now, First let's clean up your code...
import pygame
import os
# let's address the class a little later..
pygame.init()
screen = pygame.display.set_mode((640, 400))
# you only need to call the following once,so pull them out of the while loop.
bird = pygame.image.load(os.path.join('C:\Python27', 'player.png'))
clock = pygame.time.Clock()
running = True
while running:
event = pygame.event.poll()
if event.type == pygame.QUIT:
running = False
screen.fill((255, 255, 255)) # fill the screen
screen.blit(bird, (0, 0)) # then blit the bird
pygame.display.update() # Just do one thing, update/flip.
clock.tick(40) # This call will regulate your FPS (to be 40 or less)
Now the reason that your "bird" is not moving is:
When you blit the image, ie: screen.blit(bird, (0, 0)),
The (0,0) is constant, so it won't move.
Here's the final code, with the output you want (try it) and read the comments:
import pygame
import os
# it is better to have an extra variable, than an extremely long line.
img_path = os.path.join('C:\Python27', 'player.png')
class Bird(object): # represents the bird, not the game
def __init__(self):
""" The constructor of the class """
self.image = pygame.image.load(img_path)
# the bird's position
self.x = 0
self.y = 0
def handle_keys(self):
""" Handles Keys """
key = pygame.key.get_pressed()
dist = 1 # distance moved in 1 frame, try changing it to 5
if key[pygame.K_DOWN]: # down key
self.y += dist # move down
elif key[pygame.K_UP]: # up key
self.y -= dist # move up
if key[pygame.K_RIGHT]: # right key
self.x += dist # move right
elif key[pygame.K_LEFT]: # left key
self.x -= dist # move left
def draw(self, surface):
""" Draw on surface """
# blit yourself at your current position
surface.blit(self.image, (self.x, self.y))
pygame.init()
screen = pygame.display.set_mode((640, 400))
bird = Bird() # create an instance
clock = pygame.time.Clock()
running = True
while running:
# handle every event since the last frame.
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit() # quit the screen
running = False
bird.handle_keys() # handle the keys
screen.fill((255,255,255)) # fill the screen with white
bird.draw(screen) # draw the bird to the screen
pygame.display.update() # update the screen
clock.tick(40)
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 or a step-by-step movement.
If you want to achieve a continuously movement, you have to use pygame.key.get_pressed(). pygame.key.get_pressed() returns a list 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.
See also Key and Keyboard event and How can I make a sprite move when key is held down.
Minimal example:
import pygame
import os
class Bird(object):
def __init__(self):
self.image = pygame.image.load(os.path.join('C:\Python27', 'player.png'))
self.center = [100, 200]
def move(self, x, y):
self.center[0] += x
self.center[1] += y
def draw(self, surf):
surf.blit(self.image, self.center)
class game(object):
def __init__(self):
self.screen = pygame.display.set_mode((640, 400))
self.clock = pygame.time.Clock()
self.player = Bird()
def run(self):
running = 1
while running:
self.clock.tick(60)
event = pygame.event.poll()
if event.type == pygame.QUIT:
running = 0
keys = pygame.key.get_pressed()
move_x = keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]
move_y = keys[pygame.K_DOWN] - keys[pygame.K_UP]
self.player.move(move_x * 5, move_y * 5)
self.screen.fill([255, 255, 255])
self.player.draw(self.screen)
pygame.display.update()
g = game()
g.run()

Why doesn't my tank move when I press "a" and "d"?

Currently, I am stuck on trying to get my tank to move when the user presses "a" and "d". The lines involving pressing a key to move the tank seem correct and I believe should work. This is also my first time using one of these forums. Please provide feedback so I can improve. Thank you for your help.
I have asked my teacher and friends for help, but they are all wondering why the tank will not move. I also have searched over the internet and youtube for answers. A weird thing is that my friend and I directly copied a youtube video on user movement where the user can move a rectangle around. My friend can hold down "w","a","s",or "d" to move the rectangle, but I can not hold down "w","a","s",or "d" to move it but need to spam the button. What is weird is that when you move your mouse around, I can then hold down "w","a","s",or "d".
import pygame
from pygame.locals import *
import math
import random
width = 640
height = 480
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("2 Player Tanks")
def gameloop():
pygame.init()
time = pygame.time.get_ticks()
screen.fill(white)
tankx = 100
tanky = 100
tankwidth = 40
tankheight = 20
turretwidth = 5
wheelwidth = 5
tankmove = 5
def tank(x,y):
x = int(x)
y = int(y)
pygame.draw.circle(screen,black,(x,y),10)
pygame.draw.rect(screen,black,(x-tankheight,y,tankwidth,tankheight))
pygame.draw.line(screen,black,(x,y),(x-20,y-20), turretwidth)
startx = 15
for i in range(7):
pygame.draw.circle(screen,black,(x-startx,y+20),wheelwidth)
startx -= 5
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
keys= pygame.key.get_pressed()
if keys[pygame.K_a]:
tankx -= tankmove
if keys[pygame.K_d]:
tankx += tankmove
if keys[pygame.K_w]:
tanky -= tankmove
if keys[pygame.K_s]:
tanky += tankmove
tank(tankx,tanky)
pygame.display.update()
gameloop()
I want the player to be able to use "a" and "d" to move the tank horizontally.
The event loop is executed only when an event occurs. This means it is executed when a key is pressed or a key is released — however, when a key is held down, no event occurs and the event loop is not executed.
You've got to evaluate the key presses in the main loop (in scope of gameloop) rather than in the event loop:
e.g.
def gameloop():
# [...]
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
# <--
keys= pygame.key.get_pressed()
if keys[pygame.K_a]:
tankx -= tankmove
if keys[pygame.K_d]:
tankx += tankmove
if keys[pygame.K_w]:
tanky -= tankmove
if keys[pygame.K_s]:
tanky += tankmove
tank(tankx,tanky)
pygame.display.update()
Note: pygame.key.get_pressed() returns the current states of the keys and the states are evaluated and updated when pygame.event.get() is called.
The position of the tank is reset at the begin of the frame, because the variables tankx and tanky are set at the begin of gameloop:
def gameloop():
#[...]
tankx = 100
tanky = 100
Define the variables in global scope, and use the global statement to access them.
Decrease the speed of the tank, because it would move very rapidly (tankmove = 1).
The pygame.init() should be called once only, at the begin of application.
e.g.
def gameloop():
global tankx, tanky, tankmove
tankwidth = 40
tankheight = 20
turretwidth = 5
wheelwidth = 5
time = pygame.time.get_ticks()
screen.fill(white)
# [...]
pygame.init()
tankx = 100
tanky = 100
tankmove = 1
run = True
while run:
gameloop()
I can move the tank with a randomized background, but the program keeps on drawing a new tank. To fix this, I added a screen.fill(white). That fixes the drawing problem, but now I have no background.
Don't draw the random background to the window. Create a pygame.Surface and draw the random background to the surface.
.blit the background surface to the screen in every frame:
background_surface = pygame.Surface((widht, height))
# draw background to "background_surface" rather then "screen"
# [...]
def gameloop():
# [...]
# blit background instead of screen.fill(white)
screen.blit(background_surface, (0, 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