How Could I Make A Basic Car Physics In Pygame?
Here is the improved code:
import pygame, math
pygame.init()
window = pygame.display.set_mode((600,600))
pygame.display.set_caption("car game")
img = pygame.image.load("1.png")
class Car:
def __init__(self, x, y, height, width, color):
self.x = x - width / 2
self.y = y - height / 2
self.height = height
self.width = width
self.color = color
self.rect = pygame.Rect(x, y, height, width)
self.surface = pygame.Surface((height, width)) # 1
self.surface.blit(img, (0, 0))
self.angle = 0
self.speed = 0 # 2
def draw(self): # 3
self.rect.topleft = (int(self.x), int(self.y))
rotated = pygame.transform.rotate(self.surface, self.angle)
surface_rect = self.surface.get_rect(topleft = self.rect.topleft)
new_rect = rotated.get_rect(center = surface_rect.center)
window.blit(rotated, new_rect.topleft)
white = (255, 255, 255)
car1 = Car(300, 300, 73, 73, white) # 4
clock = pygame.time.Clock()
runninggame = True
while runninggame:
for event in pygame.event.get():
if event.type == pygame.QUIT:
runninggame = False
pressed = pygame.key.get_pressed()
car1.speed *= 0.9 # 5
if pressed[pygame.K_UP]: car1.speed += 0.5 # 6
if pressed[pygame.K_DOWN]: car1.speed -= 0.5 # 6
if pressed[pygame.K_LEFT]: car1.angle += car1.speed / 2 # 7
if pressed[pygame.K_RIGHT]: car1.angle -= car1.speed / 2 # 7
car1.x -= car1.speed * math.sin(math.radians(car1.angle)) # 8
car1.y -= car1.speed * math.cos(math.radians(-car1.angle)) # 8
window.fill((0, 0, 0)) # 9
car1.draw()
pygame.display.flip()
clock.tick(60) # 10
pygame.quit()
Some things to notice:
- I created a new surface to use to draw the picture. This makes it easier to rotate it.
- I created a speed variable for the car, to store its speed. I use it later for momentum.
- The draw function rotates the image anticlockwise, because that's how Pygame works. Check out the code that I used.
- The car dimensions that I used are 73, 73. Make this the width and height of your picture, otherwise the car won't turn properly.
- I decrease the speed ever so slightly, so that when you don't press forward, the car goes on for a bit.
- When the car moves forward and backward, its maximum speed is 5 pixels per frame. (Because 5 * 0.9 + 0.5 = 5.)
- The angle that the car turns depends on the speed.
- Here is the trigonometry that I was trying to say earlier. Because math.sin and math.cos use radians, I have to convert from degrees to radians.
- I filled the screen with black so you wouldn't see the earlier frames.
- The
clock.tick
is used to keep it from going too fast, and it means "a maximum of 60 frames per second".
I hope you understand everything.
I wanted to add a PyGame Sprite based answer to this question. Implementing this sort of thing as a sprite makes it easier to use the PyGame collision functions. For example, any number of CarSprites could be made, but their collision checked against the player's CarSrpite in a single call to groupcollide()
.
This implementation uses PyGame.math.Vector2()
for velocity and position. This allows for a fairly simple turning and speed model utilising the Vector2's polar co-ordinate function. Initially this gave weird and confusing result... until I realised the Vector2.from_polar()
required the angle in degrees. (Not radians unlike just about every other programming language function that takes angles.)
When the sprite is initially created, the code will make a lot of pre-rotated images. This does the smoothest turning at around 1 per degree (360), but if memory-usage was a issue, it could also be much less.
Anyway, the code is fairly self-explanatory. It requires a car_128.png
image, and a background texture image road_texture.png
. Please comment any questions.
import pygame
import math
# Window size
WINDOW_WIDTH = 600
WINDOW_HEIGHT = 600
WINDOW_SURFACE = pygame.HWSURFACE|pygame.DOUBLEBUF|pygame.RESIZABLE
class CarSprite( pygame.sprite.Sprite ):
""" Car Sprite with basic acceleration, turning, braking and reverse """
def __init__( self, car_image, x, y, rotations=360 ):
""" A car Sprite which pre-rotates up to <rotations> lots of
angled versions of the image. Depending on the sprite's
heading-direction, the correctly angled image is chosen.
The base car-image should be pointing North/Up. """
pygame.sprite.Sprite.__init__(self)
# Pre-make all the rotated versions
# This assumes the start-image is pointing up-screen
# Operation must be done in degrees (not radians)
self.rot_img = []
self.min_angle = ( 360 / rotations )
for i in range( rotations ):
# This rotation has to match the angle in radians later
# So offet the angle (0 degrees = "north") by 90° to be angled 0-radians (so 0 rad is "east")
rotated_image = pygame.transform.rotozoom( car_image, 360-90-( i*self.min_angle ), 1 )
self.rot_img.append( rotated_image )
self.min_angle = math.radians( self.min_angle ) # don't need degrees anymore
# define the image used
self.image = self.rot_img[0]
self.rect = self.image.get_rect()
self.rect.center = ( x, y )
# movement
self.reversing = False
self.heading = 0 # pointing right (in radians)
self.speed = 0
self.velocity = pygame.math.Vector2( 0, 0 )
self.position = pygame.math.Vector2( x, y )
def turn( self, angle_degrees ):
""" Adjust the angle the car is heading, if this means using a
different car-image, select that here too """
### TODO: car shouldn't be able to turn while not moving
self.heading += math.radians( angle_degrees )
# Decide which is the correct image to display
image_index = int( self.heading / self.min_angle ) % len( self.rot_img )
# Only update the image if it's changed
if ( self.image != self.rot_img[ image_index ] ):
x,y = self.rect.center
self.image = self.rot_img[ image_index ]
self.rect = self.image.get_rect()
self.rect.center = (x,y)
def accelerate( self, amount ):
""" Increase the speed either forward or reverse """
if ( not self.reversing ):
self.speed += amount
else:
self.speed -= amount
def brake( self ):
""" Slow the car by half """
self.speed /= 2
if ( abs( self.speed ) < 0.1 ):
self.speed = 0
def reverse( self ):
""" Change forward/reverse, reset any speed to 0 """
self.speed = 0
self.reversing = not self.reversing
def update( self ):
""" Sprite update function, calcualtes any new position """
self.velocity.from_polar( ( self.speed, math.degrees( self.heading ) ) )
self.position += self.velocity
self.rect.center = ( round(self.position[0]), round(self.position[1] ) )
### initialisation
pygame.init()
pygame.mixer.init()
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
pygame.display.set_caption("Car Steering")
### Bitmaps
road_image = road_image = pygame.image.load( 'road_texture.png' )
background = pygame.transform.smoothscale( road_image, ( WINDOW_WIDTH, WINDOW_HEIGHT ) )
car_image = pygame.image.load( 'car_128.png' ).convert_alpha()
### Sprites
black_car = CarSprite( car_image, WINDOW_WIDTH//2, WINDOW_HEIGHT//2 )
car_sprites = pygame.sprite.Group() #Single()
car_sprites.add( black_car )
### Main Loop
clock = pygame.time.Clock()
done = False
while not done:
# Handle user-input
for event in pygame.event.get():
if ( event.type == pygame.QUIT ):
done = True
elif ( event.type == pygame.VIDEORESIZE ):
WINDOW_WIDTH = event.w
WINDOW_HEIGHT = event.h
window = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), WINDOW_SURFACE )
background = pygame.transform.smoothscale( road_image, ( WINDOW_WIDTH, WINDOW_HEIGHT ) )
elif ( event.type == pygame.MOUSEBUTTONUP ):
# On mouse-click
pass
elif ( event.type == pygame.KEYUP ):
if ( event.key == pygame.K_h ):
print( 'meep-meep' )
elif ( event.key == pygame.K_r ):
print( 'resersing' )
black_car.reverse()
elif ( event.key == pygame.K_UP ):
print( 'accelerate' )
black_car.accelerate( 0.5 )
elif ( event.key == pygame.K_DOWN ):
print( 'brake' )
black_car.brake( )
# Continuous Movement keys
keys = pygame.key.get_pressed()
if ( keys[pygame.K_LEFT] ):
black_car.turn( -1.8 ) # degrees
if ( keys[pygame.K_RIGHT] ):
black_car.turn( 1.8 )
# Update the car(s)
car_sprites.update()
# Update the window
window.blit( background, ( 0, 0 ) ) # backgorund
car_sprites.draw( window )
pygame.display.flip()
# Clamp FPS
clock.tick_busy_loop(60)
pygame.quit()
car_128.png (Source: https://openclipart.org )
road_texture.png