2

I have 2 points in pygame and I want to move a sprite from point 1 to point 2. The approach I went with was that I get the vectors for both points (target - start), normalized that vector and then applied it to the sprite and multiplied the vector with a speed. This does move the sprite but the angle it is moving changes depending on the speed.

Here is the code:

import pygame, sys
from pygame.math import Vector2

class Icon(pygame.sprite.Sprite):
    def __init__(self,start,end):
        super().__init__()
        # setup
        self.image = pygame.Surface((40,40))
        self.image.fill('red')
        self.rect = self.image.get_rect(center = start)
        
        # positions
        self.direction = Vector2(end) - Vector2(start) 
        self.speed = 10 # changing this changes the direction the sprite moves
        self.moving = False

    def move(self):
        self.moving = True

    def update(self):
        if self.moving:
            self.rect.center += self.direction.normalize() * self.speed 

# drawing the points to show where things are supposed to be
def draw_path_points(p1,p2):
    pygame.draw.lines(screen,'grey',False,(p1,p2),4)
    pygame.draw.circle(screen,'white',p1,20)
    pygame.draw.circle(screen,'white',p2,20)


pygame.init()
screen = pygame.display.set_mode((600,600))
clock = pygame.time.Clock()
font = pygame.font.Font(None,40)

point_1 = (100,100)
point_2 = (400,520)
icon = pygame.sprite.GroupSingle(Icon(point_1,point_2))

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                icon.sprite.move()

    screen.fill((30,30,30))
    draw_path_points(point_1,point_2)
    icon.update()
    icon.draw(screen)


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

I am really confused by this, did I make a mistake somewhere or misunderstood vectors somewhere? Worst of all is that regardless of the speed the sprite never actually hits the second point and always misses it by a tiny bit...

Another_coder
  • 728
  • 1
  • 9
  • 23

1 Answers1

3

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 this is done every frame, the position error will accumulate over time.

If you want to store object positions with floating point accuracy, you have to store the location of the object in a separate variable respectively attribute and to synchronize the pygame.Rect object. round the coordinates and assign it to the location of the rectangle:

class Icon(pygame.sprite.Sprite):
    def __init__(self,start,end):
        # [...]

        self.pos = Vector2(start)
        self.rect = self.image.get_rect(center = start)
        
        # [...]

    def update(self):
        if self.moving:
            self.pos += self.direction.normalize() * self.speed 
            self.rect.center = (round(self.pos.x), round(self.pos.y))

Complete example:

import pygame, sys
from pygame.math import Vector2

class Icon(pygame.sprite.Sprite):
    def __init__(self,start,end):
        super().__init__()
        # setup
        self.image = pygame.Surface((40,40))
        self.image.fill('red')
        self.pos = Vector2(start)
        self.rect = self.image.get_rect(center = start)
        
        # positions
        self.direction = Vector2(end) - Vector2(start) 
        self.speed = 10 # changing this changes the direction the sprite moves
        self.moving = False

    def move(self):
        self.moving = True

    def update(self):
        if self.moving:
            self.pos += self.direction.normalize() * self.speed 
            self.rect.center = (round(self.pos.x), round(self.pos.y))

# drawing the points to show where things are supposed to be
def draw_path_points(p1,p2):
    pygame.draw.lines(screen,'grey',False,(p1,p2),4)
    pygame.draw.circle(screen,'white',p1,20)
    pygame.draw.circle(screen,'white',p2,20)


pygame.init()
screen = pygame.display.set_mode((600,600))
clock = pygame.time.Clock()
font = pygame.font.Font(None,40)

point_1 = (100,100)
point_2 = (400,520)
icon = pygame.sprite.GroupSingle(Icon(point_1,point_2))

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                icon.sprite.move()

    screen.fill((30,30,30))
    draw_path_points(point_1,point_2)
    icon.update()
    icon.draw(screen)


    pygame.display.update()
    clock.tick(60)
Rabbid76
  • 202,892
  • 27
  • 131
  • 174