Загрузка данных
import math
import random
import sys
import pygame
# =============================================================================
# НАСТРОЙКИ ЭКРАНА И МИРА
# =============================================================================
SCREEN_WIDTH = 1200
SCREEN_HEIGHT = 800
FPS = 60
COLOR_GRASS = (34, 139, 34)
COLOR_ROAD = (50, 50, 50)
COLOR_BORDER = (220, 220, 220)
COLOR_RUMBLE = (200, 30, 30)
COLOR_UI_BG = (20, 20, 20, 200)
# =============================================================================
# ВСПОМОГАТЕЛЬНЫЕ МАТЕМАТИЧЕСКИЕ КЛАССЫ
# =============================================================================
class Vec2:
__slots__ = ('x', 'y')
def __init__(self, x=0.0, y=0.0):
self.x = float(x)
self.y = float(y)
def __add__(self, o):
return Vec2(self.x + o.x, self.y + o.y)
def __sub__(self, o):
return Vec2(self.x - o.x, self.y - o.y)
def __mul__(self, s):
return Vec2(self.x * s, self.y * s)
def length(self):
return math.hypot(self.x, self.y)
def normalize(self):
l = self.length()
return Vec2(self.x / l, self.y / l) if l > 0.0001 else Vec2(0, 0)
def dot(self, o):
return self.x * o.x + self.y * o.y
# =============================================================================
# ЧАСТИЦЫ И СЛЕДЫ РЕЗИНЫ
# =============================================================================
class SmokeParticle:
def __init__(self, pos, vel):
self.pos = Vec2(pos.x, pos.y)
self.vel = Vec2(vel.x * 0.2 + random.uniform(-1, 1), vel.y * 0.2 + random.uniform(-1, 1))
self.size = random.uniform(4, 9)
self.alpha = 180.0
self.decay = random.uniform(3.0, 5.0)
def update(self, dt):
self.pos += self.vel * (dt * 60.0)
self.alpha -= self.decay * (dt * 60.0)
self.size += 0.2 * (dt * 60.0)
def draw(self, surface, cam):
if self.alpha <= 0:
return
sx = int(self.pos.x - cam.x + SCREEN_WIDTH // 2)
sy = int(self.pos.y - cam.y + SCREEN_HEIGHT // 2)
s = pygame.Surface((int(self.size * 2), int(self.size * 2)), pygame.SRCALPHA)
col = (200, 200, 200, int(max(0, min(255, self.alpha))))
pygame.draw.circle(s, col, (int(self.size), int(self.size)), int(self.size))
surface.blit(s, (sx - int(self.size), sy - int(self.size)))
class SkidMark:
def __init__(self, p1, p2, alpha=100):
self.p1 = p1
self.p2 = p2
self.alpha = alpha
# =============================================================================
# ТРАССА (ГЕНЕРАТОР КОЛЬЦА С ЧЕКПОИНТАМИ)
# =============================================================================
class Track:
def __init__(self):
self.width = 160.0
# Опорные контрольные точки замкнутого трека
self.control_points = [
Vec2(400, 300), Vec2(1000, 250), Vec2(1600, 400),
Vec2(2000, 800), Vec2(2100, 1400), Vec2(1800, 1900),
Vec2(1200, 2100), Vec2(800, 1700), Vec2(500, 2000),
Vec2(200, 1500), Vec2(150, 900), Vec2(250, 500)
]
self.centerline = self._interpolate_catmull_rom(self.control_points, 12)
self.checkpoints = self.centerline[::6]
def _interpolate_catmull_rom(self, pts, divisions):
result = []
n = len(pts)
for i in range(n):
p0 = pts[(i - 1) % n]
p1 = pts[i]
p2 = pts[(i + 1) % n]
p3 = pts[(i + 2) % n]
for step in range(divisions):
t = step / float(divisions)
t2 = t * t
t3 = t2 * t
x = 0.5 * ((2 * p1.x) + (-p0.x + p2.x) * t +
(2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 +
(-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3)
y = 0.5 * ((2 * p1.y) + (-p0.y + p2.y) * t +
(2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 +
(-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3)
result.append(Vec2(x, y))
return result
def is_on_road(self, pos):
min_dist_sq = float('inf')
for pt in self.centerline:
dx = pos.x - pt.x
dy = pos.y - pt.y
d_sq = dx * dx + dy * dy
if d_sq < min_dist_sq:
min_dist_sq = d_sq
return min_dist_sq <= ((self.width * 0.5) ** 2)
def get_closest_index(self, pos):
closest = 0
min_d = float('inf')
for idx, cp in enumerate(self.checkpoints):
d = (pos.x - cp.x)**2 + (pos.y - cp.y)**2
if d < min_d:
min_d = d
closest = idx
return closest
def draw(self, surface, cam, skids):
ox = SCREEN_WIDTH // 2 - cam.x
oy = SCREEN_HEIGHT // 2 - cam.y
n = len(self.centerline)
# Отрисовка полотна асфальта и бордюров
for i in range(n):
p_cur = self.centerline[i]
p_next = self.centerline[(i + 1) % n]
tangent = (p_next - p_cur).normalize()
normal = Vec2(-tangent.y, tangent.x)
# Точки полосы
p1_in = p_cur + normal * (self.width * 0.5)
p1_out = p_cur - normal * (self.width * 0.5)
p2_in = p_next + normal * (self.width * 0.5)
p2_out = p_next - normal * (self.width * 0.5)
# Бордюр
rumble_col = COLOR_RUMBLE if (i % 4 < 2) else COLOR_BORDER
pygame.draw.polygon(surface, rumble_col, [
(p1_in.x + ox, p1_in.y + oy),
(p1_in.x + normal.x * 12 + ox, p1_in.y + normal.y * 12 + oy),
(p2_in.x + normal.x * 12 + ox, p2_in.y + normal.y * 12 + oy),
(p2_in.x + ox, p2_in.y + oy)
])
pygame.draw.polygon(surface, rumble_col, [
(p1_out.x + ox, p1_out.y + oy),
(p1_out.x - normal.x * 12 + ox, p1_out.y - normal.y * 12 + oy),
(p2_out.x - normal.x * 12 + ox, p2_out.y - normal.y * 12 + oy),
(p2_out.x + ox, p2_out.y + oy)
])
# Асфальт
pygame.draw.polygon(surface, COLOR_ROAD, [
(p1_in.x + ox, p1_in.y + oy),
(p1_out.x + ox, p1_out.y + oy),
(p2_out.x + ox, p2_out.y + oy),
(p2_in.x + ox, p2_in.y + oy)
])
# Линия старт/финиш
p_start = self.centerline[0]
tan_start = (self.centerline[1] - p_start).normalize()
norm_start = Vec2(-tan_start.y, tan_start.x)
pygame.draw.line(surface, (255, 255, 255),
(p_start.x - norm_start.x * self.width * 0.5 + ox, p_start.y - norm_start.y * self.width * 0.5 + oy),
(p_start.x + norm_start.x * self.width * 0.5 + ox, p_start.y + norm_start.y * self.width * 0.5 + oy), 6)
# Следы торможения
for sk in skids:
pygame.draw.line(surface, (20, 20, 20),
(sk.p1.x + ox, sk.p1.y + oy),
(sk.p2.x + ox, sk.p2.y + oy), 4)
# =============================================================================
# ФИЗИЧЕСКАЯ МОДЕЛЬ МАШИНЫ (ДРИФТ, ИНЕРЦИЯ, ЗАЦЕП)
# =============================================================================
class Car:
def __init__(self, x, y, angle, color, is_ai=False):
self.pos = Vec2(x, y)
self.velocity = Vec2(0, 0)
self.angle = angle # Угол в градусах
self.angular_velocity = 0.0
self.color = color
self.is_ai = is_ai
# Физические коэффициенты
self.width = 46.0
self.height = 24.0
self.mass = 1200.0
self.engine_power = 2200.0
self.braking_power = 3000.0
self.drag = 0.985
self.cornering_grip = 0.88 # Сцепление поперечной оси
# Состояние гонки
self.cur_checkpoint = 0
self.lap = 0
self.lap_time = 0.0
self.best_lap = 0.0
self.drifting = False
self.prev_wheel_left = None
self.prev_wheel_right = None
def update(self, dt, track, throttle, steering, brake):
rad = math.radians(self.angle)
forward = Vec2(math.cos(rad), math.sin(rad))
right = Vec2(-math.sin(rad), math.cos(rad))
# Разложение вектора скорости
fwd_speed = self.velocity.dot(forward)
side_speed = self.velocity.dot(right)
# Проверка съезда на траву (падение сцепления и тяги)
on_road = track.is_on_road(self.pos)
grip = self.cornering_grip if on_road else 0.45
accel_factor = 1.0 if on_road else 0.4
# Тяга и торможение
force = 0.0
if throttle > 0:
force += self.engine_power * throttle * accel_factor
if brake > 0:
force -= self.braking_power * brake
# Поворот с учетом скорости движения
turn_mult = 1.0 if fwd_speed >= 0 else -1.0
speed_factor = min(abs(fwd_speed) / 400.0, 1.0)
self.angle += steering * 160.0 * speed_factor * turn_mult * dt
# Сила трения о дорогу и компенсация бокового сноса
side_friction = right * (-side_speed * grip * 45.0)
fwd_drive = forward * force
accel = (fwd_drive + side_friction) * (1.0 / self.mass)
self.velocity += accel * (dt * 60.0)
self.velocity = self.velocity * (self.drag if on_road else 0.93)
self.pos += self.velocity * dt
# Определение режима заноса
self.drifting = abs(side_speed) > 130.0 and abs(fwd_speed) > 150.0
# Чекпоинты круга
next_cp = (self.cur_checkpoint + 1) % len(track.checkpoints)
target_pt = track.checkpoints[next_cp]
dist_to_next = math.hypot(self.pos.x - target_pt.x, self.pos.y - target_pt.y)
if dist_to_next < track.width * 0.9:
self.cur_checkpoint = next_cp
if self.cur_checkpoint == 0:
self.lap += 1
if self.best_lap == 0.0 or self.lap_time < self.best_lap:
self.best_lap = self.lap_time
self.lap_time = 0.0
self.lap_time += dt
def get_wheel_positions(self):
rad = math.radians(self.angle)
fwd = Vec2(math.cos(rad), math.sin(rad))
rgt = Vec2(-math.sin(rad), math.cos(rad))
rear_hub = self.pos - fwd * (self.width * 0.35)
w_left = rear_hub - rgt * (self.height * 0.45)
w_right = rear_hub + rgt * (self.height * 0.45)
return w_left, w_right
def draw(self, surface, cam):
sx = self.pos.x - cam.x + SCREEN_WIDTH // 2
sy = self.pos.y - cam.y + SCREEN_HEIGHT // 2
car_surf = pygame.Surface((int(self.width), int(self.height)), pygame.SRCALPHA)
# Корпус
pygame.draw.rect(car_surf, self.color, (0, 0, int(self.width), int(self.height)), border_radius=6)
# Лобовое стекло
pygame.draw.rect(car_surf, (30, 30, 40), (int(self.width * 0.4), 2, int(self.width * 0.25), int(self.height - 4)), border_radius=3)
# Фары
pygame.draw.circle(car_surf, (255, 240, 150), (int(self.width - 3), 4), 3)
pygame.draw.circle(car_surf, (255, 240, 150), (int(self.width - 3), int(self.height - 4)), 3)
# Задние фонари
pygame.draw.circle(car_surf, (220, 20, 20), (2, 4), 3)
pygame.draw.circle(car_surf, (220, 20, 20), (2, int(self.height - 4)), 3)
# Поворот
rot_surf = pygame.transform.rotate(car_surf, -self.angle)
rect = rot_surf.get_rect(center=(int(sx), int(sy)))
surface.blit(rot_surf, rect.topleft)
# =============================================================================
# ИИ СОПЕРНИКА
# =============================================================================
class AICarController:
def __init__(self, car, target_offset=0.0):
self.car = car
self.target_offset = target_offset
self.node_idx = 0
def update(self, dt, track):
n = len(track.centerline)
target = track.centerline[(self.node_idx + 4) % n]
cur_node = track.centerline[self.node_idx]
# Достигли ли узла
if math.hypot(self.car.pos.x - cur_node.x, self.car.pos.y - cur_node.y) < 140.0:
self.node_idx = (self.node_idx + 1) % n
# Вектор на узел с учетом смещения полосы
to_target = target - self.car.pos
target_angle = math.degrees(math.atan2(to_target.y, to_target.x))
diff = (target_angle - self.car.angle + 180) % 360 - 180
steering = max(-1.0, min(1.0, diff / 25.0))
speed = self.car.velocity.length()
throttle = 1.0
brake = 0.0
# Замедление перед резкими дугами
if abs(diff) > 40.0 and speed > 350.0:
throttle = 0.0
brake = 0.6
self.car.update(dt, track, throttle, steering, brake)
# =============================================================================
# ГЛАВНЫЙ ИГРОВОЙ ЦИКЛ
# =============================================================================
class DriftRacingGame:
def __init__(self):
pygame.init()
pygame.font.init()
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("2D Pro Drift Grand Prix")
self.clock = pygame.time.Clock()
self.font_speed = pygame.font.SysFont("impact", 40)
self.font_ui = pygame.font.SysFont("monospace", 18, bold=True)
self.font_big = pygame.font.SysFont("impact", 68)
self.track = Track()
start_pt = self.track.centerline[0]
tan_pt = (self.track.centerline[1] - start_pt).normalize()
init_angle = math.degrees(math.atan2(tan_pt.y, tan_pt.x))
# Создаем игрока и ботов на стартовой решетке
self.player = Car(start_pt.x, start_pt.y, init_angle, (220, 20, 40))
self.opponents = [
Car(start_pt.x - 70, start_pt.y + 40, init_angle, (30, 144, 255), is_ai=True),
Car(start_pt.x - 140, start_pt.y - 40, init_angle, (255, 200, 0), is_ai=True),
Car(start_pt.x - 210, start_pt.y + 20, init_angle, (50, 205, 50), is_ai=True)
]
self.ai_controllers = [
AICarController(self.opponents[0], -25.0),
AICarController(self.opponents[1], 30.0),
AICarController(self.opponents[2], 0.0)
]
self.smoke_particles = []
self.skidmarks = []
def handle_collisions(self):
all_cars = [self.player] + self.opponents
n = len(all_cars)
for i in range(n):
for j in range(i + 1, n):
c1 = all_cars[i]
c2 = all_cars[j]
delta = c2.pos - c1.pos
dist = delta.length()
min_dist = (c1.height + c2.height) * 0.95
if dist < min_dist and dist > 0.001:
norm = delta * (1.0 / dist)
overlap = min_dist - dist
c1.pos -= norm * (overlap * 0.5)
c2.pos += norm * (overlap * 0.5)
# Обмен импульсами
v_rel = c1.velocity - c2.velocity
impulse = norm * v_rel.dot(norm) * 0.8
c1.velocity -= impulse
c2.velocity += impulse
def run(self):
running = True
while running:
dt = self.clock.tick(FPS) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_r:
self.__init__()
# Ввод игрока
keys = pygame.key.get_pressed()
throttle = 1.0 if (keys[pygame.K_UP] or keys[pygame.K_w]) else 0.0
brake = 1.0 if (keys[pygame.K_DOWN] or keys[pygame.K_s]) else 0.0
steering = 0.0
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
steering -= 1.0
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
steering += 1.0
# Физика
self.player.update(dt, self.track, throttle, steering, brake)
for ai in self.ai_controllers:
ai.update(dt, self.track)
self.handle_collisions()
# Следы шин и дым дрифта игрока
wl, wr = self.player.get_wheel_positions()
if self.player.drifting:
if self.player.prev_wheel_left and self.player.prev_wheel_right:
self.skidmarks.append(SkidMark(self.player.prev_wheel_left, wl))
self.skidmarks.append(SkidMark(self.player.prev_wheel_right, wr))
if len(self.skidmarks) > 400:
self.skidmarks.pop(0)
self.skidmarks.pop(0)
# Дым
self.smoke_particles.append(SmokeParticle(wl, self.player.velocity))
self.smoke_particles.append(SmokeParticle(wr, self.player.velocity))
self.player.prev_wheel_left = wl
self.player.prev_wheel_right = wr
for sm in self.smoke_particles[:]:
sm.update(dt)
if sm.alpha <= 0:
self.smoke_particles.remove(sm)
# Камера плавно следует за машиной
cam = self.player.pos
# ОТРИСОВКА
self.screen.fill(COLOR_GRASS)
self.track.draw(self.screen, cam, self.skidmarks)
for sm in self.smoke_particles:
sm.draw(self.screen, cam)
for ai_car in self.opponents:
ai_car.draw(self.screen, cam)
self.player.draw(self.screen, cam)
# Приборная панель (HUD)
spd = int(self.player.velocity.length() * 0.36)
hud_spd = self.font_speed.render(f"{spd} KM/H", True, (255, 230, 0))
hud_lap = self.font_ui.render(f"LAP: {self.player.lap} | TIME: {self.player.lap_time:.2f}s", True, (255, 255, 255))
hud_best = self.font_ui.render(f"BEST: {self.player.best_lap:.2f}s" if self.player.best_lap > 0 else "BEST: --.--", True, (180, 255, 180))
ui_box = pygame.Surface((260, 110), pygame.SRCALPHA)
ui_box.fill(COLOR_UI_BG)
self.screen.blit(ui_box, (20, 20))
self.screen.blit(hud_spd, (35, 25))
self.screen.blit(hud_lap, (35, 75))
self.screen.blit(hud_best, (35, 98))
if self.player.drifting:
drift_txt = self.font_speed.render("DRIFT!", True, (255, 120, 0))
self.screen.blit(drift_txt, (SCREEN_WIDTH - 160, 30))
pygame.display.flip()
pygame.quit()
sys.exit()
if __name__ == "__main__":
game = DriftRacingGame()
game.run()