1

I would like my player to move diagonally at the same speed as it does when only moving horizontally or vertically. This does work when moving in a line with a negative gradient however, when moving along a positive gradient, the player doesn't move at a perfect 45 degree angle.

This is my code for moving the player:

def move(self, speed):
    if self.direction.magnitude() != 0:
        self.direction = self.direction.normalize()
            
    self.rect.x += self.direction.x * speed
    self.rect.y += self.direction.y * speed

As I said earlier, all I want is for the player to move diagonally at the same speed as it does in only the x or y direction.

Here is the full player class in case it is needed:

class Player:
    def __init__(self, x, y):
        self.image = pygame.Surface((30, 30))
        self.image.fill((255, 255, 255))
        self.rect = self.image.get_rect(center = (x, y))
        self.direction = pygame.math.Vector2()
        self.speed = 5 

    def input(self):
        keys = pygame.key.get_pressed()

        if keys[K_w]:
            self.direction.y = -1
        elif keys[K_s]:
            self.direction.y = 1
        else:
            self.direction.y = 0
            
        if keys[K_a]:
            self.direction.x = -1
        elif keys[K_d]:
            self.direction.x = 1
        else:
            self.direction.x = 0

    def move(self, speed):
        if self.direction.magnitude() != 0:
            self.direction = self.direction.normalize()
            
        self.rect.x += self.direction.x * speed
        self.rect.y += self.direction.y * speed

    def update(self):
        self.input()
        self.move(self.speed)
            
    def draw(self, screen):
        screen.blit(self.image, self.rect.center)

1 Answers1

2

Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.

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

The fraction part of the coordinates gets lost when the new position of the object is assigned to the Rect object. If you want to move the object with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location of the rectangle:

class Player:
   def __init__(self, x, y):
       self.image = pygame.Surface((30, 30))
       self.image.fill((255, 255, 255))
       self.rect = self.image.get_rect(center = (x, y))
       self.direction = pygame.math.Vector2()
       self.speed = 5 

       self.position = pygame.math.Vector2(x, y)

   def input(self):
       keys = pygame.key.get_pressed()
       dx = keys[K_d] - keys[K_a]
       dy = keys[K_s] - keys[K_w]
       self.direction = pygame.math.Vector2(dx, dy)
       if dx != 0 and dy != 0:
           self.direction /= 1.41421

   def move(self, speed):    
       self.position += self.direction * speed
       self.rect.x = round(self.position.x)
       self.rect.y = round(self.position.y)
       
   def update(self):
       self.input()
       self.move(self.speed)
           
   def draw(self, screen):
       screen.blit(self.image, self.rect.center)

See also Pygame doesn't let me use float for rect.move, but I need it and moving with a normalized vector in pygame inconsistent?.


Note, that pygame.math.Vector2.magnitude and math.Vector2.normalize are expensive for performance. Try to avoid this operations. Normalization of the vector is only required when moving along both axes. Since the absolute value of the vector is √2 in this case, the normalization can be replaced by dividing by √2.

dx = keys[K_d] - keys[K_a]                   # dx is -1, 0 or 1
dy = keys[K_s] - keys[K_w]                   # dy is -1, 0 or 1
self.direction = pygame.math.Vector2(dx, dy)
if dx != 0 and dy != 0:  
    self.direction /= 1.41421                # sqrt(2) is ~1.41421
Rabbid76
  • 202,892
  • 27
  • 131
  • 174