Finn Formica
Finn Formica

Reputation: 138

Implementing smooth motion in 2d space in Pygame

I want to implement smooth motion that accelerates up to max speed and then decelerates slowly if no keys are pressed, similar to the motion of the space ship in asteroid. Here is my current code for the movement:

import pygame

pygame.init()

display = pygame.display.set_mode((640, 480))

clock = pygame.time.Clock()

GRAY = pygame.Color('gray12')

display_width, display_height = display.get_size()

x = display_width * 0.45
y = display_height * 0.8

x_change = 0
y_change = 0
accel_x = 0
accel_y = 0
max_speed = 6

sign = lambda a: (a > 0) - (a < 0)

crashed = False
while not crashed:
    keys = pygame.key.get_pressed()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            crashed = True
        if event.type == pygame.KEYDOWN:
            # Set the acceleration value.
            if event.key == pygame.K_LEFT:
                print('left')
                accel_x = -.2
            if event.key == pygame.K_RIGHT:
                print('right')
                accel_x = .2
            if event.key == pygame.K_DOWN:
                print('down')
                accel_y = .2
            if event.key == pygame.K_UP:
                print('up')
                accel_y = -.2
        if event.type == pygame.KEYUP:
            if event.key in (pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN):
                print('key up')
                accel_x = 0
                accel_y = 0

    x_change += accel_x  # Accelerate.
    y_change += accel_y

    if abs(x_change) >= max_speed:  # If max_speed is exceeded.
        # Normalize the x_change and multiply it with the max_speed.
        x_change = sign(x_change) * max_speed

    if abs(y_change) >= max_speed:  # If max_speed is exceeded.
        # Normalize the y_change and multiply it with the max_speed.
        y_change = sign(y_change) * max_speed

    # Decelerate if no key is pressed.
    if accel_x == 0:
        x_change *= 0.98

    if accel_y == 0:
        y_change *= 0.98

    x += x_change  # Move the object.
    y += y_change

    display.fill(GRAY)
    pygame.draw.rect(display, (0, 120, 250), (x, y, 20, 40))

    pygame.display.update()
    clock.tick(60)
pygame.quit()

When pressing the arrow keys individually the motion works perfectly, however if pressing 2 keys in rapid succession, the second key is registered but there is no motion from the object. I've tried making a list of keys using pygame.key.get_pressed() and comparing the current motion with the key pressed in the list, however this didn't solve the problem.

Upvotes: 2

Views: 822

Answers (1)

Rabbid76
Rabbid76

Reputation: 210909

The major issue in your code is, that when you press and LEFT, then press and hold RIGHT and finally release LEFT, the resulting states of accel_x and accel_y are both 0, because the last event that happend was pygame.KEYUP.

I recommend to evaluate the states of pygame.key.get_pressed() rather than to use the key events:

  • If UP is pressed and DOWN is not pressed, the increment y_change up to its maximum (y_change = max(y_change-0.2, -max_speed)).
  • If UP is not pressed and DOWN is pressed, the decrement y_change down to its minimum (y_change = min(y_change+0.2, max_speed)).
  • If both keys are pressed simultaneously or no one of them is pressed, then decrease the speed (y_change *= 0.98).

Apply the same principle to x_change, when the keys LEFT and/or RIGHT are pressed:

crashed = False
while not crashed:

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            crashed = True

    keys = pygame.key.get_pressed()

    # handle left and right movement
    if keys[pygame.K_LEFT] and not keys[pygame.K_RIGHT]:
        x_change = max(x_change-0.2, -max_speed)
    elif keys[pygame.K_RIGHT] and not keys[pygame.K_LEFT]:
        x_change = min(x_change+0.2, max_speed)
    else:
        x_change *= 0.98

    # handle up and down movement
    if keys[pygame.K_UP] and not keys[pygame.K_DOWN]:
        y_change = max(y_change-0.2, -max_speed)
    elif keys[pygame.K_DOWN] and not keys[pygame.K_UP]:
        y_change = min(y_change+0.2, max_speed)
    else:
        y_change *= 0.98

    x += x_change  # Move the object.
    y += y_change

    display.fill(GRAY)
    pygame.draw.rect(display, (0, 120, 250), (x, y, 20, 40))

    pygame.display.update()
    clock.tick(60)

Upvotes: 1

Related Questions