0

I have this simple script for movement.

if x > 0:
    if key[pygame.K_a] or key[pygame.K_LEFT]:
        rect_player.move_ip(-1 * speed, 0)
if x < SCREEN_WIDTH - 110:
    if key[pygame.K_d] or key[pygame.K_RIGHT]:
        rect_player.move_ip(speed, 0)
if y > 0:
    if key[pygame.K_w] or key[pygame.K_UP]:
        rect_player.move_ip(0, -1 * speed)
if y < SCREEN_HEIGHT - 110:
    if key[pygame.K_s] or key[pygame.K_DOWN]:
        rect_player.move_ip(0, speed)

When the player travels in one direction, everything goes fine. But when they travel diagonally, it goes faster. Is there any way to fix this?

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Coder Gautam YT
  • 1,044
  • 7
  • 20
  • 1
    For the diagonal you have to multiply the speed with `1/math.sqrt(2)` (~0.707). However, [`pygame.Rect`](https://www.pygame.org/docs/ref/rect.html) objects can just store integral data. So it's not that easy. – Rabbid76 Sep 19 '21 at 19:57

2 Answers2

3

Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data (the decimal places are lost when you assign a floating point number).

The coordinates for Rect objects are all integers. [...]

I recommend to use pygame.math.Vector2 for the problem. Store the position of the object in a pygame.math.Vector2 object:

pos = pygame.math.Vector2(start_x, start_y)

Set a direction vector depending on the keys. Scale the vector to the length of speed with scale_to_length. Move the object and update rect_player:

key = pygame.key.get_pressed()
up = key[pygame.K_w] or key[pygame.K_UP]
down = key[pygame.K_s] or key[pygame.K_DOWN]
left = key[pygame.K_a] or key[pygame.K_LEFT]
right = key[pygame.K_d] or key[pygame.K_RIGHT]

move = pygame.math.Vector2(right - left, down - up)
if move.length_squared() > 0:
    move.scale_to_length(speed)
    pos += move  
    rect_player.topleft = round(pos.x), round(pos.y)   

If you don't care about the floating point accuracy just move the rectangle:

key = pygame.key.get_pressed()
up = key[pygame.K_w] or key[pygame.K_UP]
down = key[pygame.K_s] or key[pygame.K_DOWN]
left = key[pygame.K_a] or key[pygame.K_LEFT]
right = key[pygame.K_d] or key[pygame.K_RIGHT]

move = pygame.math.Vector2(right - left, down - up)
if move.length_squared() > 0:
    move.scale_to_length(speed)
    rect_player.move_ip(round(move.x), round(move.y))        

Minimal example:

import pygame

pygame.init()
window = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()

rect_player = pygame.Rect(0, 0, 20, 20)
rect_player.center = window.get_rect().center
speed = 5

run = True
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False      

    key = pygame.key.get_pressed()
    up = key[pygame.K_w] or key[pygame.K_UP]
    down = key[pygame.K_s] or key[pygame.K_DOWN]
    left = key[pygame.K_a] or key[pygame.K_LEFT]
    right = key[pygame.K_d] or key[pygame.K_RIGHT]

    move = pygame.math.Vector2(right - left, down - up)
    if move.length_squared() > 0:
        move.scale_to_length(speed)
        rect_player.move_ip(round(move.x), round(move.y))       

    window.fill(0)
    pygame.draw.rect(window, (255, 0, 0), rect_player)
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
exit()

If you derive the velocity by math.sqrt(2) but continue to use a pygame.Rect, it still does not lead to an accurate movement. pygame.Rect objects (rect_player) can only store integral values (the decimal places are lost when you assign a floating point number). Therefore this works only approximately and fails completely if seed is a small value (e.g. 1). The only value for which the solution works really well is for speed=10 because 10/math.sqrt(2), is 7.071 (~7).

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
0

Thanks to @Rabbid76 for helping me figure it out, but this worked out for me:

count  = 0
if x > 0:
    if key[pygame.K_a] or key[pygame.K_LEFT]:
        count += 1
if x < SCREEN_WIDTH - 110:
    if key[pygame.K_d] or key[pygame.K_RIGHT]:
        count += 1
if y > 0:
    if key[pygame.K_w] or key[pygame.K_UP]:
        count += 1
if y < SCREEN_HEIGHT - 110:
    if key[pygame.K_s] or key[pygame.K_DOWN]:
        count += 1

if count >= 2:
    speed = speed/math.sqrt(2)

if x > 0:
    if key[pygame.K_a] or key[pygame.K_LEFT]:
        rect_player.move_ip(-1 * speed, 0)
if x < SCREEN_WIDTH - 110:
    if key[pygame.K_d] or key[pygame.K_RIGHT]:
        rect_player.move_ip(speed, 0)
if y > 0:
    if key[pygame.K_w] or key[pygame.K_UP]:
        rect_player.move_ip(0, -1 * speed)
if y < SCREEN_HEIGHT - 110:
    if key[pygame.K_s] or key[pygame.K_DOWN]:
        rect_player.move_ip(0, speed)
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
Coder Gautam YT
  • 1,044
  • 7
  • 20
  • Your solution still does not lead to an accurate movement. `pygame.Rect` objects (`rect_player`) can only store integral values (the decimal places are lost when you assign a floating point number). Therefore your solution works only approximately and fails completely if `seed` is a small value (e.g. 1). The only value for which the solution works really well is for `speed=10` because `10/math.sqrt(2)`, is 7.07 (~7). – Rabbid76 Nov 06 '22 at 13:12