Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """
- SUPER DETAILED CAR GAME
- ------------------------
- A top-down car racing / dodging game built with Pygame.
- Features:
- - Smooth accelerating/braking/steering car physics
- - Scrolling multi-lane highway with lane markings
- - Randomly spawning traffic cars you must dodge
- - Collision detection with crash effect
- - Score based on distance survived + near-misses
- - Increasing difficulty (traffic speed & spawn rate) over time
- - Fuel/boost meter with a nitro boost (Shift key)
- - Particle effects for exhaust smoke and crashes
- - Start menu, pause menu, and game-over screen with high score tracking
- - On-screen HUD (speed, score, high score, fuel)
- Controls:
- LEFT / RIGHT or A / D - Steer
- UP / W - Accelerate
- DOWN / S - Brake / Reverse
- SHIFT - Nitro boost (uses fuel)
- P - Pause
- ENTER - Start / Restart
- ESC - Quit
- Run with: python car_game.py
- (Requires: pip install pygame)
- """
- import pygame
- import random
- import sys
- import math
- import json
- import os
- # --------------------------------------------------------------------------
- # CONFIG / CONSTANTS
- # --------------------------------------------------------------------------
- SCREEN_WIDTH = 800
- SCREEN_HEIGHT = 700
- FPS = 60
- ROAD_WIDTH = 480
- ROAD_LEFT = (SCREEN_WIDTH - ROAD_WIDTH) // 2
- ROAD_RIGHT = ROAD_LEFT + ROAD_WIDTH
- NUM_LANES = 4
- LANE_WIDTH = ROAD_WIDTH // NUM_LANES
- GRASS_COLOR = (34, 120, 45)
- ROAD_COLOR = (55, 55, 60)
- SHOULDER_COLOR = (90, 90, 95)
- LINE_COLOR = (230, 230, 60)
- WHITE = (255, 255, 255)
- BLACK = (10, 10, 10)
- RED = (220, 40, 40)
- DARK_RED = (150, 20, 20)
- YELLOW = (250, 210, 30)
- BLUE = (40, 110, 220)
- GREEN = (60, 200, 90)
- GRAY = (150, 150, 150)
- ORANGE = (245, 140, 30)
- HIGH_SCORE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "car_game_highscore.json")
- def load_high_score():
- try:
- with open(HIGH_SCORE_FILE, "r") as f:
- data = json.load(f)
- return int(data.get("high_score", 0))
- except (FileNotFoundError, json.JSONDecodeError, ValueError):
- return 0
- def save_high_score(score):
- try:
- with open(HIGH_SCORE_FILE, "w") as f:
- json.dump({"high_score": score}, f)
- except OSError:
- pass
- class Particle:
- def __init__(self, x, y, color, vel, life, radius, gravity=0.0, fade=True):
- self.x = x
- self.y = y
- self.color = color
- self.vx, self.vy = vel
- self.life = life
- self.max_life = life
- self.radius = radius
- self.gravity = gravity
- self.fade = fade
- def update(self):
- self.x += self.vx
- self.y += self.vy
- self.vy += self.gravity
- self.life -= 1
- def alive(self):
- return self.life > 0
- def draw(self, surface):
- if self.life <= 0:
- return
- t = self.life / self.max_life
- r = max(1, int(self.radius * t)) if self.fade else self.radius
- alpha_color = self.color
- s = pygame.Surface((r * 2, r * 2), pygame.SRCALPHA)
- pygame.draw.circle(s, (*alpha_color, int(255 * t) if self.fade else 255), (r, r), r)
- surface.blit(s, (int(self.x - r), int(self.y - r)))
- class ParticleSystem:
- def __init__(self):
- self.particles = []
- def emit_exhaust(self, x, y):
- for _ in range(1):
- vel = (random.uniform(-0.3, 0.3), random.uniform(1.5, 3.0))
- self.particles.append(
- Particle(x, y, GRAY, vel, life=25, radius=6, fade=True)
- )
- def emit_crash(self, x, y):
- for _ in range(40):
- angle = random.uniform(0, math.tau)
- speed = random.uniform(2, 8)
- vel = (math.cos(angle) * speed, math.sin(angle) * speed)
- color = random.choice([ORANGE, RED, YELLOW, GRAY])
- self.particles.append(
- Particle(x, y, color, vel, life=random.randint(20, 45),
- radius=random.randint(3, 7), gravity=0.15)
- )
- def update(self):
- self.particles = [p for p in self.particles if p.alive()]
- for p in self.particles:
- p.update()
- def draw(self, surface):
- for p in self.particles:
- p.draw(surface)
- def make_car_surface(width, height, body_color, window_color=(30, 30, 40)):
- """Draw a simple top-down car sprite onto a transparent surface."""
- surf = pygame.Surface((width, height), pygame.SRCALPHA)
- body_rect = pygame.Rect(0, 6, width, height - 12)
- pygame.draw.rect(surf, body_color, body_rect, border_radius=10)
- # windshield / rear window
- pygame.draw.rect(surf, window_color, (width * 0.18, height * 0.12, width * 0.64, height * 0.22), border_radius=4)
- pygame.draw.rect(surf, window_color, (width * 0.18, height * 0.66, width * 0.64, height * 0.22), border_radius=4)
- # headlights
- pygame.draw.circle(surf, (255, 250, 200), (int(width * 0.2), 8), 4)
- pygame.draw.circle(surf, (255, 250, 200), (int(width * 0.8), 8), 4)
- # taillights
- pygame.draw.circle(surf, (255, 60, 60), (int(width * 0.2), height - 8), 4)
- pygame.draw.circle(surf, (255, 60, 60), (int(width * 0.8), height - 8), 4)
- # side mirrors
- pygame.draw.rect(surf, body_color, (-3, height * 0.32, 6, 8))
- pygame.draw.rect(surf, body_color, (width - 3, height * 0.32, 6, 8))
- return surf
- class PlayerCar:
- WIDTH = 46
- HEIGHT = 84
- def __init__(self):
- self.x = SCREEN_WIDTH / 2
- self.y = SCREEN_HEIGHT - 140
- self.vx = 0.0
- self.speed = 0.0
- self.max_speed = 14.0
- self.min_speed = -3.0
- self.acceleration = 0.18
- self.brake_power = 0.35
- self.friction = 0.06
- self.steer_speed = 0.32
- self.max_steer_v = 6.5
- self.sprite = make_car_surface(self.WIDTH, self.HEIGHT, BLUE)
- self.fuel = 100.0
- self.boosting = False
- self.alive = True
- self.invincible_timer = 0
- @property
- def rect(self):
- w, h = self.WIDTH - 10, self.HEIGHT - 14
- return pygame.Rect(self.x - w / 2, self.y - h / 2, w, h)
- def handle_input(self, keys, dt_scale):
- # Steering
- if keys[pygame.K_LEFT] or keys[pygame.K_a]:
- self.vx -= self.steer_speed * dt_scale
- elif keys[pygame.K_RIGHT] or keys[pygame.K_d]:
- self.vx += self.steer_speed * dt_scale
- else:
- self.vx *= 0.85
- self.vx = max(-self.max_steer_v, min(self.max_steer_v, self.vx))
- if keys[pygame.K_UP] or keys[pygame.K_w]:
- self.speed += self.acceleration * dt_scale
- elif keys[pygame.K_DOWN] or keys[pygame.K_s]:
- self.speed -= self.brake_power * dt_scale
- else:
- if self.speed > 0:
- self.speed -= self.friction * dt_scale
- elif self.speed < 0:
- self.speed += self.friction * dt_scale
- self.boosting = False
- if (keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]) and self.fuel > 0 and self.speed > 0:
- self.speed += self.acceleration * 1.6 * dt_scale
- self.fuel -= 0.9 * dt_scale
- self.boosting = True
- else:
- self.fuel = min(100.0, self.fuel + 0.12 * dt_scale)
- top_speed = self.max_speed * (1.35 if self.boosting else 1.0)
- self.speed = max(self.min_speed, min(top_speed, self.speed))
- def update(self, dt_scale):
- self.x += self.vx * dt_scale
- margin = self.WIDTH / 2
- self.x = max(ROAD_LEFT - 20 + margin, min(ROAD_RIGHT + 20 - margin, self.x))
- def draw(self, surface, particles):
- if self.speed > 1:
- particles.emit_exhaust(self.x, self.y + self.HEIGHT / 2 - 4)
- tilt = max(-12, min(12, -self.vx * 2.2))
- rotated = pygame.transform.rotate(self.sprite, tilt)
- rect = rotated.get_rect(center=(self.x, self.y))
- surface.blit(rotated, rect)
- if self.boosting:
- flame_h = random.randint(14, 26)
- flame_rect = pygame.Rect(0, 0, 14, flame_h)
- flame_rect.midtop = (self.x, self.y + self.HEIGHT / 2 - 6)
- pygame.draw.polygon(surface, ORANGE, [
- (flame_rect.centerx - 7, flame_rect.top),
- (flame_rect.centerx + 7, flame_rect.top),
- (flame_rect.centerx, flame_rect.bottom),
- ])
- class TrafficCar:
- WIDTH = 46
- HEIGHT = 84
- COLORS = [RED, GREEN, YELLOW, (150, 60, 200), (240, 240, 240), ORANGE]
- def __init__(self, lane_x, y, speed):
- self.x = lane_x
- self.y = y
- self.speed = speed
- color = random.choice(self.COLORS)
- self.sprite = make_car_surface(self.WIDTH, self.HEIGHT, color)
- self.passed = False
- @property
- def rect(self):
- w, h = self.WIDTH - 10, self.HEIGHT - 14
- return pygame.Rect(self.x - w / 2, self.y - h / 2, w, h)
- def update(self, scroll_speed):
- self.y += scroll_speed - self.speed
- def draw(self, surface):
- surface.blit(self.sprite, self.sprite.get_rect(center=(self.x, self.y)))
- def off_screen(self):
- return self.y > SCREEN_HEIGHT + 120 or self.y < -120
- class Road:
- def __init__(self):
- self.dash_offset = 0
- def update(self, scroll_speed):
- self.dash_offset = (self.dash_offset + scroll_speed) % 60
- def lane_centers(self):
- return [ROAD_LEFT + LANE_WIDTH * (i + 0.5) for i in range(NUM_LANES)]
- def draw(self, surface):
- surface.fill(GRASS_COLOR)
- # shoulders
- pygame.draw.rect(surface, SHOULDER_COLOR, (ROAD_LEFT - 20, 0, ROAD_WIDTH + 40, SCREEN_HEIGHT))
- # road
- pygame.draw.rect(surface, ROAD_COLOR, (ROAD_LEFT, 0, ROAD_WIDTH, SCREEN_HEIGHT))
- # edge lines (solid white)
- pygame.draw.rect(surface, WHITE, (ROAD_LEFT, 0, 5, SCREEN_HEIGHT))
- pygame.draw.rect(surface, WHITE, (ROAD_RIGHT - 5, 0, 5, SCREEN_HEIGHT))
- # lane dashes
- for i in range(1, NUM_LANES):
- x = ROAD_LEFT + LANE_WIDTH * i
- y = -60 + self.dash_offset
- while y < SCREEN_HEIGHT:
- pygame.draw.rect(surface, LINE_COLOR, (x - 3, y, 6, 34))
- y += 60
- class Game:
- STATE_MENU = "menu"
- STATE_PLAYING = "playing"
- STATE_PAUSED = "paused"
- STATE_GAMEOVER = "gameover"
- def __init__(self):
- pygame.init()
- pygame.display.set_caption("Super Detailed Car Game")
- self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
- self.clock = pygame.time.Clock()
- self.font_big = pygame.font.SysFont("arial", 64, bold=True)
- self.font_med = pygame.font.SysFont("arial", 32, bold=True)
- self.font_small = pygame.font.SysFont("arial", 22)
- self.high_score = load_high_score()
- self.state = self.STATE_MENU
- self.reset()
- def reset(self):
- self.player = PlayerCar()
- self.road = Road()
- self.particles = ParticleSystem()
- self.traffic = []
- self.spawn_timer = 0
- self.spawn_interval = 70
- self.distance = 0.0
- self.score = 0
- self.difficulty_timer = 0
- self.base_traffic_speed = 3.0
- self.max_traffic_speed_variance = 2.0
- self.scroll_speed = 0.0
- self.crash_shake = 0
- def spawn_traffic(self):
- lanes = self.road.lane_centers()
- candidate_lanes = list(range(NUM_LANES))
- random.shuffle(candidate_lanes)
- for lane_i in candidate_lanes:
- lane_x = lanes[lane_i]
- too_close = any(
- abs(t.x - lane_x) < 5 and t.y < 160
- for t in self.traffic
- )
- if not too_close:
- speed = self.base_traffic_speed + random.uniform(
- -self.max_traffic_speed_variance * 0.3, self.max_traffic_speed_variance
- )
- self.traffic.append(TrafficCar(lane_x, -120, speed))
- break
- def update(self):
- if self.state != self.STATE_PLAYING:
- self.particles.update()
- return
- keys = pygame.key.get_pressed()
- self.player.handle_input(keys, dt_scale=1.0)
- self.player.update(dt_scale=1.0)
- self.scroll_speed = max(0.0, self.player.speed)
- self.distance += self.scroll_speed
- self.road.update(self.scroll_speed)
- # difficulty ramps up over time
- self.difficulty_timer += 1
- if self.difficulty_timer % 300 == 0:
- self.base_traffic_speed = min(9.0, self.base_traffic_speed + 0.4)
- self.spawn_interval = max(28, self.spawn_interval - 4)
- # spawn traffic
- self.spawn_timer += 1
- if self.spawn_timer >= self.spawn_interval:
- self.spawn_timer = 0
- self.spawn_traffic()
- # update traffic
- for t in self.traffic:
- t.update(self.scroll_speed)
- # near-miss / passed scoring
- for t in self.traffic:
- if not t.passed and t.y > self.player.y + 60:
- t.passed = True
- self.score += 15
- # collisions
- player_rect = self.player.rect
- for t in self.traffic:
- if player_rect.colliderect(t.rect):
- self.crash()
- break
- self.traffic = [t for t in self.traffic if not t.off_screen()]
- # score from distance
- self.score = self.score # (near-miss score already added)
- self.distance_score = int(self.distance / 8)
- self.particles.update()
- if self.crash_shake > 0:
- self.crash_shake -= 1
- def crash(self):
- self.particles.emit_crash(self.player.x, self.player.y)
- self.crash_shake = 18
- self.state = self.STATE_GAMEOVER
- final_score = self.score + int(self.distance / 8)
- if final_score > self.high_score:
- self.high_score = final_score
- save_high_score(self.high_score)
- self.final_score = final_score
- # ---------------- DRAW ----------------
- def draw(self):
- offset_x, offset_y = 0, 0
- if self.crash_shake > 0:
- offset_x = random.randint(-8, 8)
- offset_y = random.randint(-8, 8)
- canvas = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT))
- self.road.draw(canvas)
- for t in sorted(self.traffic, key=lambda c: c.y):
- t.draw(canvas)
- if self.state in (self.STATE_PLAYING, self.STATE_PAUSED):
- self.player.draw(canvas, self.particles)
- elif self.state == self.STATE_GAMEOVER:
- # still show wrecked player briefly
- self.player.draw(canvas, self.particles)
- self.particles.draw(canvas)
- self.screen.blit(canvas, (offset_x, offset_y))
- self.draw_hud()
- if self.state == self.STATE_MENU:
- self.draw_menu()
- elif self.state == self.STATE_PAUSED:
- self.draw_pause()
- elif self.state == self.STATE_GAMEOVER:
- self.draw_gameover()
- pygame.display.flip()
- def draw_text_center(self, text, font, color, y, outline=True):
- surf = font.render(text, True, color)
- rect = surf.get_rect(center=(SCREEN_WIDTH / 2, y))
- if outline:
- out = font.render(text, True, BLACK)
- for dx, dy in [(-2, 0), (2, 0), (0, -2), (0, 2)]:
- self.screen.blit(out, out.get_rect(center=(SCREEN_WIDTH / 2 + dx, y + dy)))
- self.screen.blit(surf, rect)
- def draw_hud(self):
- if self.state not in (self.STATE_PLAYING, self.STATE_PAUSED):
- return
- current_score = self.score + int(self.distance / 8)
- hud_lines = [
- f"Score: {current_score}",
- f"High Score: {self.high_score}",
- f"Speed: {int(self.player.speed * 12)} km/h",
- ]
- for i, line in enumerate(hud_lines):
- surf = self.font_small.render(line, True, WHITE)
- self.screen.blit(surf, (14, 12 + i * 26))
- # fuel bar
- bar_x, bar_y, bar_w, bar_h = SCREEN_WIDTH - 174, 14, 160, 20
- pygame.draw.rect(self.screen, (50, 50, 50), (bar_x, bar_y, bar_w, bar_h), border_radius=5)
- fill_w = int(bar_w * (self.player.fuel / 100))
- fuel_color = GREEN if self.player.fuel > 30 else RED
- pygame.draw.rect(self.screen, fuel_color, (bar_x, bar_y, fill_w, bar_h), border_radius=5)
- pygame.draw.rect(self.screen, WHITE, (bar_x, bar_y, bar_w, bar_h), 2, border_radius=5)
- label = self.font_small.render("NITRO", True, WHITE)
- self.screen.blit(label, (bar_x, bar_y + bar_h + 2))
- def draw_menu(self):
- overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
- overlay.fill((0, 0, 0, 140))
- self.screen.blit(overlay, (0, 0))
- self.draw_text_center("SUPER CAR GAME", self.font_big, YELLOW, 220)
- self.draw_text_center("Press ENTER to Start", self.font_med, WHITE, 320)
- self.draw_text_center(f"High Score: {self.high_score}", self.font_small, WHITE, 370)
- controls = [
- "Arrows / WASD: Steer & Accelerate",
- "SHIFT: Nitro Boost | P: Pause | ESC: Quit",
- ]
- for i, line in enumerate(controls):
- self.draw_text_center(line, self.font_small, GRAY, 430 + i * 28)
- def draw_pause(self):
- overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
- overlay.fill((0, 0, 0, 150))
- self.screen.blit(overlay, (0, 0))
- self.draw_text_center("PAUSED", self.font_big, WHITE, 300)
- self.draw_text_center("Press P to Resume", self.font_med, WHITE, 370)
- def draw_gameover(self):
- overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
- overlay.fill((0, 0, 0, 170))
- self.screen.blit(overlay, (0, 0))
- self.draw_text_center("CRASHED!", self.font_big, RED, 260)
- self.draw_text_center(f"Score: {self.final_score}", self.font_med, WHITE, 330)
- self.draw_text_center(f"High Score: {self.high_score}", self.font_med, YELLOW, 370)
- self.draw_text_center("Press ENTER to Restart", self.font_small, WHITE, 430)
- def run(self):
- running = True
- while running:
- for event in pygame.event.get():
- if event.type == pygame.QUIT:
- running = False
- elif event.type == pygame.KEYDOWN:
- if event.key == pygame.K_ESCAPE:
- running = False
- elif event.key == pygame.K_RETURN:
- if self.state in (self.STATE_MENU, self.STATE_GAMEOVER):
- self.reset()
- self.state = self.STATE_PLAYING
- elif event.key == pygame.K_p:
- if self.state == self.STATE_PLAYING:
- self.state = self.STATE_PAUSED
- elif self.state == self.STATE_PAUSED:
- self.state = self.STATE_PLAYING
- self.update()
- self.draw()
- self.clock.tick(FPS)
- pygame.quit()
- sys.exit()
- if __name__ == "__main__":
- game = Game()
- game.run()
Advertisement
Add Comment
Please, Sign In to add comment