Problem with rounding in pygame, using rotate_ip function

Viewed 32

i have made a simple car game in pygame, w for acceleration, a and d for steering. However, Ive noticed that after some rotational motion, my sprite is going sideways while the image displays as if its going straight.

By printing velocity vector I have noticed that after some rotation and translation, value is no longer 0, but e-13/14. Could this be the problem? And if yes, how do I round it up, so the image actually follows the vector?

My code so far:

import pygame
import os
import sys

pygame.init()

screen_height = 750
screen_width = 1500
screen = pygame.display.set_mode((screen_width,screen_height))

robot = pygame.Surface((100,81), pygame.SRCALPHA)
robot.fill((255, 0, 0))

class Robot(pygame.sprite.Sprite):

    def __init__(self):
        super().__init__()
        self.robot_image = robot
        self.image = self.robot_image
        self.rect = self.image.get_rect(center=(100,355))
        self.velocity = pygame.math.Vector2(1,0)
        self.angle = 0
        self.rotation = 5
        self.state = 0
        self.direction = 0

    def update(self):
        self.drive()
        self.rotate()

    def drive(self):
        if self.state == 1:
            self.rect.center += self.velocity * 6
        if self.state == -1:
            self.rect.center -= self.velocity * 6

    def rotate(self):
        if self.direction == 1:
            self.angle -= self.rotation
            self.velocity.rotate_ip(self.rotation)
        if self.direction == -1:
            self.angle += self.rotation
            self.velocity.rotate_ip(-self.rotation)
        self.rect = self.image.get_rect(center=self.rect.center)
        self.image = pygame.transform.rotozoom(self.robot_image,self.angle,1)


rob = pygame.sprite.GroupSingle(Robot())

def main():
    run = True
    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

        input = pygame.key.get_pressed()
        if sum(pygame.key.get_pressed()) <= 1:
            rob.sprite.state = 0
            rob.sprite.direction = 0

        if input[pygame.K_w]:
            rob.sprite.state = 1

        if input[pygame.K_s]:
            rob.sprite.state = -1

        if input[pygame.K_d]:
            rob.sprite.direction = 1

        if input[pygame.K_a]:
            rob.sprite.direction = -1

        screen.fill(0)
        rob.draw(screen)
        rob.update()
        pygame.display.update()

main()
1 Answers
  1. As you accumulate the rotation of the vector, a floating point precision error accumulates over time. I suggest calculating the velocity vector from the angle.

    self.velocity = pygame.math.Vector2(1,0)
    self.velocity.rotate_ip(-self.angle)
    
  2. Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data. The fraction part of the coordinates gets lost when the velocity vector is added to the position of the rectangle. If you want to store object positions with floating point accuracy, you have to store the location of the object in separate attribute and to synchronize the pygame.Rect object.

    if self.state == 1:
        self.position += self.velocity * 6
    if self.state == -1:
        self.position -= self.velocity * 6
    self.rect.center = self.position
    

Complete class Robot:

class Robot(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.robot_image = robot
        self.image = self.robot_image
        self.rect = self.image.get_rect(center=(100,355))
        self.position = self.rect.center
        self.velocity = pygame.math.Vector2(1,0)
        self.angle = 0
        self.rotation = 5
        self.state = 0
        self.direction = 0

    def update(self):
        self.drive()
        self.rotate()

    def drive(self):
        if self.state == 1:
            self.position += self.velocity * 6
        if self.state == -1:
            self.position -= self.velocity * 6
        self.rect.center = self.position

    def rotate(self):
        if self.direction == 1:
            self.angle -= self.rotation
        if self.direction == -1:
            self.angle += self.rotation
        
        self.velocity = pygame.math.Vector2(1,0)
        self.velocity.rotate_ip(-self.angle)

        self.rect = self.image.get_rect(center=self.rect.center)
        self.image = pygame.transform.rotozoom(self.robot_image,self.angle,1)
Related